2

Task: I'm trying to create a fan-control with the arduino mega. I'm generating a PWM signal on pin 6 and 7 and want to use interrups to measure the rpm of the fans.

Problem: On pin 2 and 3 everything wors fine, but I cannot get it working on any of pins 18-21. The numbers are jumping around wildly. BUT: The numbers are correct on 0% and 100% duty!

What I've aleady tried:

  • Changed my timer from 1 to 4 (currently it's 4)
  • Replaced the display to get rid of U8g2lib.
  • Changing connection order of the signal cables (and thus the connected fan).
  • Check the signal with an digital oscilloscope (looks fine on all ports)
  • Set the global ISR variables to volatile and byte to prevent update hazards.
  • Bang my head on the table.

Nothing solved my problem. What am I missing?

    #include <Arduino.h>
    #include <U8g2lib.h>
    #include <Bounce2.h>
    
    #ifdef U8X8_HAVE_HW_SPI
    #include <SPI.h>
    #endif
    #ifdef U8X8_HAVE_HW_I2C
    #include <Wire.h>
    #endif
    
    U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/U8X8_PIN_NONE);
    
    #define PIN_CONTROL_S 6
    #define PIN_CONTROL_L 7
    #define PIN_SENSE_1 18
    #define PIN_SENSE_2 19
    #define PIN_SENSE_3 3
    #define PIN_SENSE_4 2
    #define PIN_BUTTON 22
    
    #define UPDATE_CYCLE 1000 // Refresh interval of the display/measurement.
    #define SIGNAL_PER_RND 2  // 2 Signals per revolution
    #define DUTY_INC 10       // Increate of the duty in % per button press
    #define PWM_MAX 320       // PWM mode counts up 320 then down 320 counts (25kHz)
    #define lineOffset 10     // Offset (y) per line on display
    
    word duty = 0;  // 0-100 duty cycle
    word dutyS = 0; // 0-320 = 0-100% duty cycle for small fans
    word dutyL = 0; // 0-320 = 0-100% duty cycle for large fans
    
    volatile byte counterFan1 = 0;
    volatile byte counterFan2 = 0;
    volatile byte counterFan3 = 0;
    volatile byte counterFan4 = 0;
    
    unsigned long lastDraw = 0;
    
    Bounce btnBouncer = Bounce(PIN_BUTTON, 50);
    
    void setupPwm() {
        // Steuerung
        pinMode(PIN_CONTROL_S, OUTPUT);
        pinMode(PIN_CONTROL_L, OUTPUT);
    
        // Sensoren
        pinMode(PIN_SENSE_1, INPUT_PULLUP);
        pinMode(PIN_SENSE_2, INPUT_PULLUP);
        pinMode(PIN_SENSE_3, INPUT_PULLUP);
        pinMode(PIN_SENSE_4, INPUT_PULLUP);
    
        // Clear timer register
        TCCR4A = 0;
        TCCR4B = 0;
        TCCR4C = 0;
        TCNT4 = 0;
    
        TCCR4A |= _BV(WGM41);  // Set PWM mode, phase correct. TOP is ICR1.  (Mode 10)
        TCCR4B |= _BV(WGM43);  // Start BOTTOM, TOP from OCR1x to OCR1x.
    
        TCCR4B |= _BV(CS40);   // Prescaler 1:1
    
        TCCR4A |= _BV(COM4A1); // High output OC1A on compareMatch when up-counting / low when down-counting
        TCCR4A |= _BV(COM4B1); // High output OC1B on compareMatch when up-counting / low when down-counting
    
        OCR4A = dutyS;         // set initial duty
        OCR4B = dutyL;         // set initial duty
    
        ICR4 = PWM_MAX; // TOP for TCNTx. 320 => @16Mhz CPU -> 25kHz PWM
    }
    
    void setupDisplay() {
        u8g2.begin();
        u8g2.setFont(u8g2_font_6x10_tf);
        u8g2.setFontRefHeightExtendedText();
        u8g2.setDrawColor(1);
        u8g2.setFontPosTop();
        u8g2.setFontDirection(0);
    }
    
    void attachInterrups() {
        attachInterrupt(digitalPinToInterrupt(PIN_SENSE_1), isrFan1, RISING);
        attachInterrupt(digitalPinToInterrupt(PIN_SENSE_2), isrFan2, RISING);
        attachInterrupt(digitalPinToInterrupt(PIN_SENSE_3), isrFan3, RISING);
        attachInterrupt(digitalPinToInterrupt(PIN_SENSE_4), isrFan4, RISING);
    }
    
    void detachInterrups() {
        detachInterrupt(digitalPinToInterrupt(PIN_SENSE_1));
        detachInterrupt(digitalPinToInterrupt(PIN_SENSE_2));
        detachInterrupt(digitalPinToInterrupt(PIN_SENSE_3));
        detachInterrupt(digitalPinToInterrupt(PIN_SENSE_4));
    }
    
    void setup() {
        setupDisplay();
        setupPwm();
        attachInterrups();
    
        pinMode(PIN_BUTTON, INPUT_PULLUP);
    }
    
    void draw(void) {
        char output[22]; // One line of the display can show 21 characters.
        float uPerSekS1 = counterFan1; // * 60000 / measureDuration / SIGNAL_PER_RND;
        float uPerSekS2 = counterFan2; // * 60000 / measureDuration / SIGNAL_PER_RND;
        float uPerSekL1 = counterFan3; // * 60000 / measureDuration / SIGNAL_PER_RND;
        float uPerSekL2 = counterFan4; // * 60000 / measureDuration / SIGNAL_PER_RND;
        int y = 0;
    
        sprintf(output, "Duty    :    %3d", duty);
        u8g2.drawStr(0, y, output);
    
        y += lineOffset;
        sprintf(output, "U/sec S1: %6d", (int) uPerSekS1);
        u8g2.drawStr(0, y, output);
    
        y += lineOffset;
        sprintf(output, "U/sec S2: %6d", (int) uPerSekS2);
        u8g2.drawStr(0, y, output);
    
        y += lineOffset;
        sprintf(output, "U/sec L1: %6d", (int) uPerSekL1);
        u8g2.drawStr(0, y, output);
    
        y += lineOffset;
        sprintf(output, "U/sec L2: %6d", (int) uPerSekL2);
        u8g2.drawStr(0, y, output);
    }
    
    void measure() {
        unsigned long measureDuration = millis() - lastDraw;
        if (measureDuration >= UPDATE_CYCLE) {
            // Deactivate interrupt while we are calculating
            detachInterrups();
    
            u8g2.clearBuffer();
            draw();
            u8g2.sendBuffer();
    
            // reset state
            counterFan1 = 0;
            counterFan2 = 0;
            counterFan3 = 0;
            counterFan4 = 0;
            lastDraw = millis();
    
            // Reactivate interrupt
            attachInterrups();
        }
    }
    
    void loop() {
        measure();
        btnBouncer.update();
        if (btnBouncer.fell()) {
            // Button was pressed. Increase duty.
            if (duty > 90) {
                duty = 0;
            } else {
                duty += DUTY_INC;
            }
    
            dutyS = duty * PWM_MAX / 100;
            dutyL = duty * PWM_MAX / 100;
    
            OCR4A = dutyS;
            OCR4B = dutyL;
        }
    }
    
    void isrFan1() {
        counterFan1++;
    }
    
    void isrFan2() {
        counterFan2++;
    }
    
    void isrFan3() {
        counterFan3++;
    }
    
    void isrFan4() {
        counterFan4++;
    }

Edit: I've tried my best to create a readable circuit diagram. The 4-pin connectors represent the fans.

Circuit diagram of 4 4-pin fan control

5
  • Ok, maybe it is an electronical issue? I just double checked my statements and connected the oscilloscope simultaneously to the arduino and it looked like the osci is stabilizint the results... I'm using a 5KΩ pull up resistance. Is that ok?
    – Tarkil
    Jun 19 '20 at 16:20
  • I can't understand what the duty cycle has to do with reading the pulses. What happens if you unplug 6 and 7 and turn the fans with your hand? Are you sure the 8 bit counters do not overflow when the fan turns fast? Jun 19 '20 at 16:20
  • @EdgarBonet If i unplugg 6/7 the fans will go up to 100%. I've forgotten to mention I'm using 4pin PC fans. They are connected to 12V and get regulated by the 25KHz PWM I'm generatingion the third pin. The fourth pin is the signal pin with two signals per round.
    – Tarkil
    Jun 19 '20 at 16:25
  • @EdgarBonet ...and 8 bit are not overflowing. The measurement interval is currently 1 sec, and I get up to 50 signals per measurement cycle.
    – Tarkil
    Jun 19 '20 at 16:31
  • what is wired to interrupt pins and how?
    – Juraj
    Jun 19 '20 at 19:03
1

Ok, I did not solve the problem regarding pin 18-21, but I found an other solution: using PinChangeInterrupts and the PinChangeInterrupt libary

I did not change anything about my hardware setup, excep moving the wires from pin 18/19 to A8/A9. I've changed my code to use PinChangeInterrups for two of the fans, maybe I'll change all 4 fans.

Here is the new code:

#include <Arduino.h>
#include <U8g2lib.h>
#include <Bounce2.h>
#include <PinChangeInterrupt.h>

#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif

U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/U8X8_PIN_NONE);

#define PIN_CONTROL_S 6
#define PIN_CONTROL_L 7

//#define PIN_SENSE_1 18
//#define PIN_SENSE_2 19
#define PIN_SENSE_1 A8
#define PIN_SENSE_2 A9

#define PIN_SENSE_3 3
#define PIN_SENSE_4 2
#define PIN_BUTTON 22

#define UPDATE_CYCLE 1000 // Refresh interval of the display/measurement.
#define SIGNAL_PER_RND 2  // 2 Signals per revolution
#define DUTY_INC 10       // Increate of the duty in % per button press
#define PWM_MAX 320       // PWM mode counts up 320 then down 320 counts (25kHz)
#define lineOffset 10     // Offset (y) per line on display

word duty = 0;  // 0-100 duty cycle
word dutyS = 0; // 0-320 = 0-100% duty cycle for small fans
word dutyL = 0; // 0-320 = 0-100% duty cycle for large fans

volatile byte counterFan1 = 0;
volatile byte counterFan2 = 0;
volatile byte counterFan3 = 0;
volatile byte counterFan4 = 0;

unsigned long measureDuration;
unsigned long lastDraw = 0;

Bounce btnBouncer = Bounce(PIN_BUTTON, 50);

void setupPwm() {
    // Steuerung
    pinMode(PIN_CONTROL_S, OUTPUT);
    pinMode(PIN_CONTROL_L, OUTPUT);

    // Sensoren
    pinMode(PIN_SENSE_1, INPUT_PULLUP);
    pinMode(PIN_SENSE_2, INPUT_PULLUP);
    pinMode(PIN_SENSE_3, INPUT_PULLUP);
    pinMode(PIN_SENSE_4, INPUT_PULLUP);

    // Clear timer register
    TCCR4A = 0;
    TCCR4B = 0;
    TCCR4C = 0;
    TCNT4 = 0;

    TCCR4A |= _BV(WGM41);  // Set PWM mode, phase correct. TOP is ICR1.  (Mode 10)
    TCCR4B |= _BV(WGM43);  // Start BOTTOM, TOP from OCR1x to OCR1x.

    TCCR4B |= _BV(CS40);   // Prescaler 1:1

    TCCR4A |= _BV(COM4A1); // High output OC1A on compareMatch when up-counting / low when down-counting
    TCCR4A |= _BV(COM4B1); // High output OC1B on compareMatch when up-counting / low when down-counting

    OCR4A = dutyS;         // set initial duty
    OCR4B = dutyL;         // set initial duty

    ICR4 = PWM_MAX; // TOP for TCNTx. 320 => @16Mhz CPU -> 25kHz PWM
}

void setupDisplay() {
    u8g2.begin();
    u8g2.setFont(u8g2_font_6x10_tf);
    u8g2.setFontRefHeightExtendedText();
    u8g2.setDrawColor(1);
    u8g2.setFontPosTop();
    u8g2.setFontDirection(0);
}

void attachInterrups() {
//    attachInterrupt(digitalPinToInterrupt(PIN_SENSE_1), isrFan1, RISING);
    attachPCINT(digitalPinToPCINT(PIN_SENSE_1), isrFan1, RISING);

//    attachInterrupt(digitalPinToInterrupt(PIN_SENSE_2), isrFan2, RISING);
    attachPCINT(digitalPinToPCINT(PIN_SENSE_2), isrFan2, RISING);

    attachInterrupt(digitalPinToInterrupt(PIN_SENSE_3), isrFan3, RISING);
    attachInterrupt(digitalPinToInterrupt(PIN_SENSE_4), isrFan4, RISING);
}

void detachInterrups() {
//    detachInterrupt(digitalPinToInterrupt(PIN_SENSE_1));
    detachPinChangeInterrupt(digitalPinToPCINT(PIN_SENSE_1));

//    detachInterrupt(digitalPinToInterrupt(PIN_SENSE_2));
    detachPinChangeInterrupt(digitalPinToPCINT(PIN_SENSE_2));

    detachInterrupt(digitalPinToInterrupt(PIN_SENSE_3));
    detachInterrupt(digitalPinToInterrupt(PIN_SENSE_4));
}

void setup() {
//    cli();
    setupDisplay();
    setupPwm();
    attachInterrups();

    pinMode(PIN_BUTTON, INPUT_PULLUP);
//    sei();
}

void draw() {
    char output[22]; // One line of the display can show 21 characters.
    float uPerSekS1 = counterFan1 * 60000 / measureDuration / SIGNAL_PER_RND;
    float uPerSekS2 = counterFan2 * 60000 / measureDuration / SIGNAL_PER_RND;
    float uPerSekL1 = counterFan3 * 60000 / measureDuration / SIGNAL_PER_RND;
    float uPerSekL2 = counterFan4 * 60000 / measureDuration / SIGNAL_PER_RND;
    int y = 0;

    sprintf(output, "Duty    :    %3d", duty);
    u8g2.drawStr(0, y, output);

    y += lineOffset;
    sprintf(output, "U/sec S1: %6d", (int) uPerSekS1);
    u8g2.drawStr(0, y, output);

    y += lineOffset;
    sprintf(output, "U/sec S2: %6d", (int) uPerSekS2);
    u8g2.drawStr(0, y, output);

    y += lineOffset;
    sprintf(output, "U/sec L1: %6d", (int) uPerSekL1);
    u8g2.drawStr(0, y, output);

    y += lineOffset;
    sprintf(output, "U/sec L2: %6d", (int) uPerSekL2);
    u8g2.drawStr(0, y, output);
}

void measure() {
    measureDuration = millis() - lastDraw;
    if (measureDuration >= UPDATE_CYCLE) {
        // Deactivate interrupt while we are calculating
        detachInterrups();

        u8g2.clearBuffer();
        draw();
        u8g2.sendBuffer();

        // reset state
        counterFan1 = 0;
        counterFan2 = 0;
        counterFan3 = 0;
        counterFan4 = 0;
        lastDraw = millis();

        // Reactivate interrupt
        attachInterrups();
    }
}

void loop() {
    measure();
    btnBouncer.update();
    if (btnBouncer.fell()) {
        // Button was pressed. Increase duty.
        if (duty > 90) {
            duty = 0;
        } else {
            duty += DUTY_INC;
        }

        dutyS = duty * PWM_MAX / 100;
        dutyL = duty * PWM_MAX / 100;

        OCR4A = dutyS;
        OCR4B = dutyL;
    }
}

void isrFan1() {
    counterFan1++;
}

void isrFan2() {
    counterFan2++;
}

void isrFan3() {
    counterFan3++;
}

void isrFan4() {
    counterFan4++;
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.