ISR Never Gets Called

I’m working on creating a two wheeled robot, utilizing wheel encoders for rotation and distance measurement. When using attachInterrupt(), however, the ISR (function that activates when the pin is interrupted) is never called. Despite this, using digitalRead()on the same pin in the loop()clearly shows that the state is changing, and it has been my alternative for now. My full code is below. Any help is appreciated!

// Pin assignments for motor control
const byte RForward = 12;
const byte RBackward = 11;
const byte LForward = 10;
const byte LBackward = 9;

// Pins for speed control
const byte RSpeed = 3;
const byte LSpeed = 6;

// Pins for wheel encoders
const byte RIntterupt = 7;
const byte LInterrupt = 2;

// Variables to store encoder counts
volatile int leftInterruptCount = 0;
volatile int rightInterruptCount = 0;

// Constants for encoder and robot measurements
const int interruptsPerRotation = 23; // Number of encoder ticks per wheel revolution
const float wheelDiameter = 65.0; 
const float wheelBase = 150.0; // Distance between the two wheels
const float wheelCircumference = PI * wheelDiameter;

// How many encoder counts before stopping the robot
const int motorStopValue = 100;

byte originalValue = 0;

void setup() {
  // Set motor control pins as outputs
  pinMode(RForward, OUTPUT);
  pinMode(RBackward, OUTPUT);
  pinMode(LForward, OUTPUT);
  pinMode(LBackward, OUTPUT);

  // Attach interrupts for the encoders
  // Each time a falling/rising edge is detected, the count function will run
  pinMode(LInterrupt, INPUT);
  attachInterrupt(digitalPinToInterrupt(LInterrupt), countLeft, CHANGE);

  // Start serial communication for debugging
  Serial.begin(9600);
  Serial.println("Started");

  originalValue = digitalRead(LInterrupt);
}

// Interrupt service routine for left wheel encoder
void countLeft() {
  Serial.flush();
  Serial.println("Counted left");
  leftInterruptCount++;
}

// Interrupt service routine for right wheel encoder
void countRight() {
  rightInterruptCount++;
}

// Function to calculate and print motion data
// This determines how many wheel revolutions have occurred,
// how far each wheel has traveled, and how much the robot has turned.
void calculateMotion() {
  // Calculate number of revolutions for each wheel
  float leftRevs = (float)leftInterruptCount / interruptsPerRotation;
  float rightRevs = (float)rightInterruptCount / interruptsPerRotation;

  // Calculate distance traveled by each wheel
  float leftDistance = leftRevs * wheelCircumference;
  float rightDistance = rightRevs * wheelCircumference;

  // Calculate the difference in distance traveled between the two wheels
  float distanceDiff = rightDistance - leftDistance;

  // Calculate how much the robot has turned, in degrees
  float turnDegrees = (distanceDiff / wheelBase) * (180.0 / PI);

  // Print all the calculated information to the serial monitor
  Serial.println("----- Motion Data -----");
  Serial.print("Left revolutions: ");
  Serial.println(leftRevs, 3);
  Serial.print("Right revolutions: ");
  Serial.println(rightRevs, 3);
  Serial.print("Left distance (mm): ");
  Serial.println(leftDistance, 2);
  Serial.print("Right distance (mm): ");
  Serial.println(rightDistance, 2);
  Serial.print("Estimated turn (degrees): ");
  Serial.println(turnDegrees, 2);
  Serial.println("-----------------------");
}

void loop() {  
  // byte value = digitalRead(LInterrupt);
  // if (value != originalValue) {
  //   originalValue = value;
  //   leftInterruptCount++;
  //   Serial.flush();
  //   Serial.println("Changed");
  // }
  
  // Run the motors forward until the left wheel reaches a set number of encoder counts
  if (leftInterruptCount >= motorStopValue) {
    // Stop both motors
    digitalWrite(RForward, LOW);
    digitalWrite(RBackward, LOW);
    digitalWrite(LForward, LOW);
    digitalWrite(LBackward, LOW);
    Serial.println("Stopped");

    // Once stopped, calculate and display motion data
    calculateMotion();

    // Stop the program from looping further
    while (true);
  } else {
    // Move both motors forward
    digitalWrite(RForward, HIGH);
    digitalWrite(RBackward, LOW);
    digitalWrite(LForward, HIGH);
    digitalWrite(LBackward, LOW);

    // analogWrite(RSpeed, 100);
    analogWrite(LSpeed, 255);
  }
}

Try leaving the Serial.flush() out of the interrupt routine. Interrupts are disabled inside the interrupt routine, unless you re-enable them, so the interrupt to load the next character from the Serial transmit buffer would never be processed.

as general rule you can't use a Serial inside an interrupt handler code .

Thanks for the feedback! The Serial is not the problem, though; I added it after observing that the function did not get called. Removing it does not fix the problem of the count never incrementing.

What is your arduino board?

State which MCU you have. On many, only a few pins support interrupts, or specifically on CHANGE.

Also, check the wiring.

I’m using an Arduino Uno Rev3.

On my Arduino Uno R3, I believe pins 2 and 3 support interrupts (in my code I am testing 2). I believe the wiring works fine because my digitalRead() on the pin shows the signal changing on my optical wheel encoder, and the LED changes in accordance.

On the Uno R3 pin 2 should be working as you expect, although as mentioned, remove the calls to Serial from the ISR.

After doing that, test with the minimum code that just tests the interrupt functionality (for example, by printing the encoder counts in the main loop), and post again, showing the revised code and results.

Note: when accessing multbyte volatile variables in the main loop, you MUST protect them from being corrupted by the interrupt, as follows:

void loop() {
...
noInterrupts();
int left_count_copy = leftInterruptCount;
interrupts();
Serial.println(left_count_copy);
...

Also, increase the serial baud rate to something reasonable, like 115200.

Finally, many encoders require pullup resistors. Enable that on pin 2 by calling

  pinMode(LInterrupt, INPUT_PULLUP);

Is it just a blind rule or there are technical reasons for not incuding print() method inside an ISR?

In case the print uses interrupts itself the rule became trivial.

Major concern is that the transmit buffer may become full and the ISR will not exit until sufficient characters have been sent to free up space in the buffer.

Originally this was a problem, but now the write() function (which is called for all Serial output, write(), print(), and println()) directly checks the status registers when interrupts are disabled, allowing the process to continue inside an ISR.

Works great! Thank you so much. Just one more question: is there any advantage to doing it this way over polling?

Polling is preferred and actually faster than using interrupts, however, If the MCU has other things to do while polling, encoder transitions might be missed.

For what reson polling is faster than interrupt to render service to the peripheral?

It takes many machine cycles to acknowledge an interrupt, save critical registers, call the ISR, restore critical registers, then resume normal, non-interrupt processing. Look up "interrupt latency" for the MCU of your choice.

This code takes one machine cycle on an Arduino Uno R3:
result = PINB&1; //check bit PB0

Can you show calculation in favor of your claim as my calculation shows 4 cock (machine) cycles assumng the destination is RAM?

Moreover, machine cycle is confusing for AVR; rather, it is a term used for CISC machines like 8085, 8086, 8051, etc.

Incorrect assumption.

PINB is a register on AVR processors, and the compiler uses a bit test instruction.

You can avoid wasting time by looking at the compiled machine code.

The variable result will hold the outcome of
PINB&1, which could be a register or a RAM location. I have decided to take a RAM location.

Let us say that the destination is a register, then how many cycles would be needed to execute result = PINB&1?

Rather than get distracted by details irrelevant to the topic, do you see how polling can be much faster than an ISR?

If not, consider the following construction, versus interrupt-triggered execution of an ISR.

if (PINB&1) do_something(); //if pin PB0 HIGH, execute routine