Value of Global Variable Calculated in Interrupt Function Not Reflected Outside

Hi,

I made a code for the Arduino to control a PC Fan via PWM pin and get the RPM from its Tac pin. This was the first time I had to implement an external interrupt feature. The code works fine except for a small detail. The global variable holding the RPM value becomes zero the moment it’s called outside the interrupt function.

The calculation of the RPM from the sensor readings is done within the Interrupt function. The logic inside the function is that it counts each pulse/revolution until it hits its 10th revolutions. It takes the time it took to reach the 10th revolution to calculate the RPM and resets for the next calculation.

When I serial print the RPM value within the interrupt function, it prints out the correct value. But if I serial print it in the main loop or other functions, it’s zero.

What’s going on? Is there a way to pull that value outside of the interrupt function? The code is provided below.

#define potPin A0
#define motorPWM 9
#define tacPin 7

uint16_t potVal;
uint8_t pwmVal;

unsigned long lastMillisSerial;

unsigned int pulsePerRev = 2; //number of pulse per revolution of the fan
unsigned int revPerCalc = 10; //let 10 revolutions go by before calculating the RPM
unsigned int pulsePerCalc = pulsePerRev*revPerCalc;
unsigned int pulseCount = 0;

unsigned long lastMillisTac;
unsigned long MillisDiffTac;
int motorRPM;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  pinMode(potPin, INPUT);
  pinMode(tacPin, INPUT_PULLUP);
  pinMode(motorPWM, OUTPUT);
  attachInterrupt(digitalPinToInterrupt(tacPin), calcRPM, FALLING);
}

void loop() {
  // put your main code here, to run repeatedly:
  
  potVal = analogRead(potPin);
  pwmVal = map(potVal, 0, 1023, 0, 255);
  analogWrite(motorPWM, pwmVal);
  motorRPM = (revPerCalc/MillisDiffTac)*(60L/1000L);

  if(millis()-lastMillisSerial > 1000){
    printSerial();
    //Serial.print("motor RPM = "); Serial.println(motorRPM);
    lastMillisSerial = millis();
  }
  
}

void printSerial(){
  Serial.print("pwmVal = "); Serial.println(pwmVal);
  //Serial.print("motor RPM = "); Serial.println(motorRPM);
}

void calcRPM(){
  pulseCount++;
  if (pulseCount >= pulsePerCalc){
    MillisDiffTac = millis() - lastMillisTac;
    motorRPM = (revPerCalc*60L*1000L)/MillisDiffTac;
    Serial.print("motor RPM = "); Serial.println(motorRPM);
    pulseCount = 0;
    lastMillisTac = millis();
    return motorRPM;
  }
}

Declare all variables used in an ISR and accessed outside of it as volatile

Serial.print("motor RPM = "); Serial.println(motorRPM);and do not use Serial.print() within an ISR.

UKHeliBob:
Declare all variables used in an ISR and accessed outside of it as volatile

So I changed two variables used in the ISR to volatile:

volatile unsigned int pulseCount = 0;
volatile unsigned int motorRPM;

I noticed the variable pulseCount does return the right value to rest of the code but motorRPM still returns zero which is really frustrating.

Deva_Rishi:
and do not use Serial.print() within an ISR.

why is it bad idea to serial print within the ISR?

I figured out the cause of the problem. There was another line of code in the main loop that was also calculating the motor RPM and spitting out a zero as the answer.

@kuansterful, you didn't mention which processor you're running this on. If it's an 8-bit AVR (Uno, Mega, etc), then you have a major problem that nobody has mentioned yet.

Accesses to 32-bit variables such as 'revPerCalc' are non-atomic on these processors. So, when you do such an access in non-ISR code, there's a chance it will get clobbered by the ISR mid-access giving incorrect results. You need to protect these accesses with a Critical Section using a noInterrupts() / interrupts() sequence.

gfvalvo:
Accesses to 32-bit variables...

There are enough other reasons to include critical sections that they should always be used.

kuansterful:
why is it bad idea to serial print within the ISR?

Two reasons (on ATmega processors at least):

Firstly these functions may lock the processor if called when interrupts are disabled - when you enter
an ISR interrupts are automatically disabled until you return - or re-enable them explicitly, which has its
own caveats(*).

Secondly an ISR should not hog the processor - Serial functions are potentially going to wait for a
buffer to drain, so they can block. Never call a blocking or waiting function in an ISR.

(*) If a particular interrupt just re-enables interrupts and hangs around it can be interrupted by itself,
so the code would need to be re-entrant, and there's a risk of chewing up all of the stack if this repeats
continually.