Interrupt and serial.print and delay(), a phenomenon I saw in my code

Hi I am trying to find out the solution of bouncing when using with interrupt. I used the rc circuit

to debouncing my rotary encoder(as shown in attachment) and tested the following program:

int interrupt_pin = 2;
int pumpstep = 0;
void setup() {
  //pinMode (interrupt_pin,INPUT);
  Serial.begin(9600);
  Serial.println("Basic NoInterrupts Test:");
  attachInterrupt(digitalPinToInterrupt(2),print_step, RISING);
}
void print_step(){
  pumpstep++;
  }
void loop() {
   Serial.println(pumpstep); 
}

I found out that if I click the encoer, it the value printed out still jumps between 1 and 2. But if add another line of code 'delay(50) into the loop() function, then I found the value is much stable and will increase with an increament of 1 if I click the rotary encoder:

int interrupt_pin = 2;
int pumpstep = 0;
void setup() {
  //pinMode (interrupt_pin,INPUT);
  Serial.begin(9600);
  Serial.println("Basic NoInterrupts Test:");
  attachInterrupt(digitalPinToInterrupt(2),print_step, RISING);
}
void print_step(){
  pumpstep++;
  }
void loop() {
   Serial.println(pumpstep); // put your main code here, to run repeatedly
   delay(50);
}

Could anyone tell me what is the problem and how can I solve the problem without using the delay() function. Thanks!

circuit.png

It's contact bounce - when a physical switch is opened or closed, the contacts "bounce" for a short period of time.

Those encoders have two output pins, which are out of phase with eachother. If you use both (and PCINTs to read them) you can handle both debounce and direction detection at once. There are lots of tutorials for doing this - google it.

Hi DrAzzy, I actually used the circuit shown in the attached png files to debounce the signal and I also checked the signal with an ossiloscope. The signal looks good and should be debounced with the debouncing circuit. I just don’t know if I don’t use the delay() function in the loop, how can I eliminate the 1 additional increment.

The code you posted is not very good for a rotary encoder (as noted by @DrAzzy). If you use a state machine approach as described HERE, you don't need an RC circuit or delays to deal with contact bounce.

Double counting can happen if your code is expecting one complete quadrature cycle for every two detents and your encoder provides a complete cycle with every detent. Both types of encoders exist.

Here's a library that implements the state machine approach and handles both types of encoders: GitHub - gfvalvo/NewEncoder: Rotary Encoder Library. It does require 2 external interrupts which is all you have on an Uno (pins 2 and 3). It does not support PCINTs at this time.

Hi gfvalvo, Thank you for your reply. It seems that I didn't descibed my problems clearly. What I used here is a rotary encoder with click function, i.e. I used the rotary encoder's push button which in essense is a push button switch. I want to fullfil a purpose that each click of the button will increase pump_count by 1. However, even I used debouncing circuit as suggested by many tutorial. I still have increment of 2 most of the time unless I add an additional code of 'delay(50)' in the loop() function and I don't know what happened. I also checked the signal generated from each click with an ossiloscope, and it showed a square wave and if I zoom in I found it looks like a slope without significant bouncing at all.

Adding a delay() statement in the loop really should not affect the interrupt routine, although it would decrease the amount of serial data you are sending.

Variables you use in an interrupt routine and also elsewhere in your code should be declared as volatile.
You also need to be careful when using variables that are changed in an interrupt routine, a multi-byte variable such as an integer can take multiple instructions to do arithmetic, comparison, or simply copy into another variable, leaving you open to the possibility of an interrupt occurring in the middle of an operation.

This page has an extensive discussion of interrupts:
http://www.gammon.com.au/interrupts

How often do the interrupts occur? You could build a debounce time into the interrupt routine, where you record the time of the interrupt, and ignore any additional interrupts that occur within the debounce period.

int interrupt_pin = 2;
volatile int pumpstep = 0;
unsigned long currentInterrupt; //time of current interrupt
unsigned long previousInterrupt; //time of previous interrupt
const unsigned int delayTime = 50U; //50 millisecond debounce period

void setup() {
  //pinMode (interrupt_pin,INPUT);
  Serial.begin(9600);
  Serial.println("Basic NoInterrupts Test:");
  previousInterrupt = millis();
  delay(delayTime); //make sure debounce has timed out for first interrupt
  attachInterrupt(digitalPinToInterrupt(2), print_step, RISING);
}

void print_step() {
  currentInterrupt = millis(); //get current time
  //any interrupts that occur within (delayTime) of the previous interrupt
  // get ignored, this should function as a switch debounce
  if ((currentInterrupt - previousInterrupt) > delayTime) {
    pumpstep++;
  }
  previousInterrupt = currentInterrupt;
}

void loop() {
  Serial.println(pumpStep);
}

In reference to your last post, I really don't understand why you are using an interrupt for a push-button, that would normally be done in the main code with a normal debounce routine.

Hi David, thank you very much for your explanation. I have a micromanipulator project that I want to use a button to shuffle the the stepper motor between one site and another. In order to do so, I introduced a variable in the interrupt funtion meaning each time the interrupt function is called the variable will increase by 1 and when the variable goes to 4 it will be changed back to 1 again. So it seems that I don' t need the int type for this purpose, as you suggested it will take 4 bytes and will probably cause problem, so can I use a int_8t instead of int?

Hi David, I modified the code as suggested by you as the following but still saw the problem, which I have been dealing with for almost a day. don't know what is the problem. So, could you elaborate more on how could I build debounce time into the interrupt routine Thank you very much!

int interrupt_pin = 2;
volatile int8_t pumpstep = 0;
void setup() {
  Serial.begin(9600);
  Serial.println("Basic NoInterrupts Test:");
  attachInterrupt(digitalPinToInterrupt(2),print_step, RISING);
}
void print_step(){
  pumpstep++;
  }
void loop() {
   Serial.println(pumpstep); // put your main code here, to run repeatedly
   //delay(50); delay(50) will solve the problem.
}

david_2018:
Adding a delay() statement in the loop really should not affect the interrupt routine, although it would decrease the amount of serial data you are sending.

Variables you use in an interrupt routine and also elsewhere in your code should be declared as volatile.
You also need to be careful when using variables that are changed in an interrupt routine, a multi-byte variable such as an integer can take multiple instructions to do arithmetic, comparison, or simply copy into another variable, leaving you open to the possibility of an interrupt occurring in the middle of an operation.

This page has an extensive discussion of interrupts:
Gammon Forum : Electronics : Microprocessors : Interrupts

How often do the interrupts occur? You could build a debounce time into the interrupt routine, where you record the time of the interrupt, and ignore any additional interrupts that occur within the debounce period.

int interrupt_pin = 2;

volatile int pumpstep = 0;
unsigned long currentInterrupt; //time of current interrupt
unsigned long previousInterrupt; //time of previous interrupt
const unsigned int delayTime = 50U; //50 millisecond debounce period

void setup() {
 //pinMode (interrupt_pin,INPUT);
 Serial.begin(9600);
 Serial.println("Basic NoInterrupts Test:");
 previousInterrupt = millis();
 delay(delayTime); //make sure debounce has timed out for first interrupt
 attachInterrupt(digitalPinToInterrupt(2), print_step, RISING);
}

void print_step() {
 currentInterrupt = millis(); //get current time
 //any interrupts that occur within (delayTime) of the previous interrupt
 // get ignored, this should function as a switch debounce
 if ((currentInterrupt - previousInterrupt) > delayTime) {
   pumpstep++;
 }
 previousInterrupt = currentInterrupt;
}

void loop() {
 Serial.println(pumpStep);
}





In reference to your last post, I really don't understand why you are using an interrupt for a push-button, that would normally be done in the main code with a normal debounce routine.

Hi David, I just saw your code and will have a try. Thank you!

You don't need an interrupt at all to handle a push button (or debounce it). If you code requires it, you should re-think its structure. It can all be handled in the main program loop. Install the Bounce2 library from the IDE's library manager and try the examples.

Hi David, I tried your code and the problem remains.

david_2018:
Adding a delay() statement in the loop really should not affect the interrupt routine, although it would decrease the amount of serial data you are sending.

Variables you use in an interrupt routine and also elsewhere in your code should be declared as volatile.
You also need to be careful when using variables that are changed in an interrupt routine, a multi-byte variable such as an integer can take multiple instructions to do arithmetic, comparison, or simply copy into another variable, leaving you open to the possibility of an interrupt occurring in the middle of an operation.

This page has an extensive discussion of interrupts:
Gammon Forum : Electronics : Microprocessors : Interrupts

How often do the interrupts occur? You could build a debounce time into the interrupt routine, where you record the time of the interrupt, and ignore any additional interrupts that occur within the debounce period.

int interrupt_pin = 2;

volatile int pumpstep = 0;
unsigned long currentInterrupt; //time of current interrupt
unsigned long previousInterrupt; //time of previous interrupt
const unsigned int delayTime = 50U; //50 millisecond debounce period

void setup() {
 //pinMode (interrupt_pin,INPUT);
 Serial.begin(9600);
 Serial.println("Basic NoInterrupts Test:");
 previousInterrupt = millis();
 delay(delayTime); //make sure debounce has timed out for first interrupt
 attachInterrupt(digitalPinToInterrupt(2), print_step, RISING);
}

void print_step() {
 currentInterrupt = millis(); //get current time
 //any interrupts that occur within (delayTime) of the previous interrupt
 // get ignored, this should function as a switch debounce
 if ((currentInterrupt - previousInterrupt) > delayTime) {
   pumpstep++;
 }
 previousInterrupt = currentInterrupt;
}

void loop() {
 Serial.println(pumpStep);
}





In reference to your last post, I really don't understand why you are using an interrupt for a push-button, that would normally be done in the main code with a normal debounce routine.

Hi gfvalvo, I will have a try. Thank you very much!

gfvalvo:
You don't need an interrupt at all to handle a push button (or debounce it). If you code requires it, you should re-think its structure. It can all be handled in the main program loop. Install the Bounce2 library from the IDE's library manager and try the examples.

Since the delay() in the loop seems to affect the problem, maybe it is being caused at least partially by all the data you are saturating the serial port with. Have you tried printing pumpstep only when its value changes?

int interrupt_pin = 2;
int pumpstep = 0;
int prevpumpstep = 0;
void setup() {
  //pinMode (interrupt_pin,INPUT);
  Serial.begin(9600);
  Serial.println("Basic NoInterrupts Test:");
  attachInterrupt(digitalPinToInterrupt(2), print_step, RISING);
}
void print_step() {
  pumpstep++;
}
void loop() {
  if (pumpstep != prevpumpstep) {
    Serial.println(pumpstep);
    prevpumpstep = pumpstep;
  }
}

Hi David, This code works. Thanks a lot! So do you know what happened if we don't use the delay() or the if condtioning. I just cound not reproduce such result step by step without the computer.

david_2018:
Since the delay() in the loop seems to affect the problem, maybe it is being caused at least partially by all the data you are saturating the serial port with. Have you tried printing pumpstep only when its value changes?

int interrupt_pin = 2;

int pumpstep = 0;
int prevpumpstep = 0;
void setup() {
  //pinMode (interrupt_pin,INPUT);
  Serial.begin(9600);
  Serial.println("Basic NoInterrupts Test:");
  attachInterrupt(digitalPinToInterrupt(2), print_step, RISING);
}
void print_step() {
  pumpstep++;
}
void loop() {
  if (pumpstep != prevpumpstep) {
    Serial.println(pumpstep);
    prevpumpstep = pumpstep;
  }
}

Without the delay() or IF statement, you were trying to send much more data through the serial port than it was capable of handling. At 9600 baud, you only have time for about 960 characters per second, and the loop with nothing but a serial.println statement runs much too fast for that. The serial port has a 32-byte buffer that holds the characters waiting to be sent out, I'm not exactly sure what happens when that fills up, most discussion on buffer overrun concerns the input side of the serial, but I did find a discussion where someone claimed the serial.print would wait for the buffer to empty enough to hold the characters it was trying to send. If that was what was happening, there was probably never a problem with the interrupt or counter, but the serial output was taking so long to send data that you were not able to see the count correctly.

Another possible solution would be to increase the baud rate of the serial port. At 115200 baud you can send 120 times more characters per second as you can at 9600 baud.

You really shouldn't need the interrupt routine anyway, unless you have something in your code that is causing a considerable delay that would prevent checking the input from the switch at a decent interval.