Interaction between external interrupt pin and serial communication

Hello!

I have been trying to solve this problem for some hours and I still can't find a solution I find convincing. I am using an Arduino UNO REV 3 and Arduino ISP 1.6.7.

I am doing a project for which I am going to need some stopwatches. The way I was trying to implement the stopwatches is by connecting each stopwatch button to an external interrupt pin, because the main loop is planned to get a little bit intricate and I need a good consistency with the time measurements.

The buttons are connected to a pull-up resistor and have a debouncing circuit consisting of a 1st order low-pass RC filter (like this one), so that, when a button is pressed, the interrupt pin receives a LOW signal.

I made a simple version of the code I am using which contains my problem:

#define intpin 3                //pin where the button is connected

unsigned long time_i;           //starting time of the stopwatch
unsigned long time_f;           //finishing time of the stopwatch
int time_diff;                  //time interval between the first and the second press of the stopwatch button
bool measuring = false;         //is the arduino measuring the time interval?
volatile bool cicle = false;    //has the arduino finished measuring a time interval?

void setup() {
  Serial.begin(9600);
  pinMode(intpin, INPUT);
  attachInterrupt(digitalPinToInterrupt(intpin), stopwatch_ISR, FALLING);

}

void loop() {
  // put your main code here, to run repeatedly:
  if(cicle==true){              //if a time interval was measured, send the time interval
    cicle = false;
    time_diff = time_f - time_i;
    Serial.print("Time: ");
    Serial.print(time_diff);
    Serial.println(" ms.");
  }
}

void stopwatch_ISR(){           //if the stopwatch button was pressed, check wether the time interval was being measured or not and proceed accordingly
    if(measuring==false){       //if the time interval was not being measured, start measuring it
      time_i = millis();
      measuring = true;
    }
    else{                       //if the time interval was being measured, finish measuring it and set interval measured flag
      time_f = millis();
      measuring = false;
      cicle = true;
    }
}

And this is what I get in my Serial monitor after pressing and releasing the button just once:

Does anyone have any clues to what could be causing this behaviour?

Thanks a lot for your time!

Considering that the interruption is carried out on a falling edge, there should not be two interruption events with just one press of the button.

There must be some bad contact on the button, or sensor that is used. There are special switches, which have an internal spring, which makes the contact open and close very quickly, perhaps helping to avoid this type of problem.

This type of switch is widely used in gaming machines.

I don't think the problem is the button, because a friend of mine lent me his Leonardo board and this same code and circuit work perfectly. I should try it out with another UNO board, just to check the problem is not my board, but I have no easy access to one more of those right now.

Anyways, the Leonardo board starts showing this same problem if, in the main loop, I add an instruction to toggle any pin. There's no way I can measure time if the ISR is being called by the arduino board toggling a pin.

BTW, there are not going to be any buttons in the final application but proximity sensors, because I want to measure the speed something has.

Please don't post images of text!!!! GRRRR!!!

I compiled your code, connected a button to pin 3 and ground. I made pin 3 INPUT_PULLUP to save having an external resistor. I didn't put any filtering on. A singe press produced:

17:28:48.084 -> Time: 2 ms.
17:28:48.118 -> Time: 0 ms.
17:28:48.118 -> Time: 0 ms.
17:28:48.457 -> Time: 0 ms.

Two consecutive presses produced:

17:29:45.262 -> Time: 1 ms.
17:29:45.296 -> Time: 0 ms.
17:29:45.296 -> Time: 0 ms.
17:29:45.296 -> Time: 0 ms.
17:29:46.724 -> Time: 1470 ms.
17:29:46.758 -> Time: 0 ms.
17:29:46.758 -> Time: 0 ms.
17:29:46.894 -> Time: 0 ms.

This is what I expected, or close enough. Single press triggers the interrupt several times because there's no debouncing, hence the multiple prints. This press starts the measurement. Second press stops the measurement, prints the result plus some more because of the bouncing.

The way I was trying to implement the stopwatches is by connecting each stopwatch button to an external interrupt pin, because the main loop is planned to get a little bit intricate and I need a good consistency with the time measurements.

There's no point, you can't get better than the bounce time of the button anyway.

Don't use interrupts for detecting button presses!

But I have a debouncing circuit and it works perfectly with the Leonardo board.

aimapepinillo: But I have a debouncing circuit and it works perfectly with the Leonardo board.

I conclude that your debouncing circuit isn't working as you expect.

I didn't use one, the result I got was pretty much as I expected having read your code. The button I used was fairly crap, I actually got fewer bounces than I expected.

Have I mentioned that you should not be using interrupts for buttons?

++Karma; // For posting your code correctly on your first post.

Actually, I never used a button but the proximity sensor which is going to be used in the final project (I just said button because I thought it would be easier to explain, but I see it's causing more trouble than good).

This sensor emits a low voltage when it detects an obstacle and high when it doesn't detect.

As I said, if I use this same code in the Leonardo board, it works perfectly until I add any instructions in the main loop that toggle any other pin.

BTW, the interrupts are needed because, as I said, I need the timing to be measured in a reliable way, and the main loop is going to get a little bit intricate.

OK, well, I have nothing more to add. Maybe someone else will think of something.

Enjoy :)

UPDATE

I have been trying it with the Leonardo board I have and this code and circuit work perfectly. It measures time wihtout any problems. Well, except for when I add these two lines to the main loop:

  digitalWrite(13, HIGH);
  digitalWrite(13, LOW);

Then, I start getting these random messages in the serial monitor:

Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 1 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 1 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 1 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.
Time: 0 ms.

That was from just one button press...

I can't try your code on a Leonardo as I don't have one, sorry. I wouldn't expect anything much different. I still suspect your debounce circuit but obviously can't prove that. Do you have an oscilloscope? That would show you if the debounce circuit is working.

If you post a full schematic of the input circuit you are using, including component values, then I'll see if I build and try it.

Here's the full schematic of my circuit. I'm using a 10 kOhm resistor, a 10 uF capacitor and the proximity sensor is a vma326, which is based on TCRT5000.

I could access an oscilloscope yesterday to check the circuit and it worked as intended.

With the first code I posted, it works perfectly with the Leonardo board, but when I change the main loop to this

void loop() {
  // put your main code here, to run repeatedly:
  if(cicle==true){              //if a time interval was measured, send the time interval
    cicle = false;
    time_diff = time_f - time_i;
    Serial.print("Time: ");
    Serial.print(time_diff);
    Serial.println(" ms.");
  }
  digitalWrite(13, HIGH);
  digitalWrite(13, LOW);
  
}

it starts behaving in a strange manner again.

Thanks a lot for the time and patience you spent trying to help me :slight_smile:

The RC filter is going to take a while to transition between logic states, if you are turning an LED on and off you may be affecting the power supply voltage just enough to cause problems in the undefined region between HIGH and LOW on the input port. Do you have any idea what the absolute minimum amount of time between valid transitions from the sensor would be? With that RC filter I don't really see a problem with having the ISR ignore any interrupts that occur within a few milliseconds of each other.

You need to declare all variables used by both the ISR and the remainder of your code as volatile. You need to disable interrupts when accessing time_f and time_i, and to be safe I would copy cicle to another variable for use in loop() instead of accessing it in two places in the code.

I created your circuit as close as I can. I don't have a Leonardo so I used a Uno, I don't have a vma326 so I used a push button between the 10k resistor and ground. I also used INPUT_PULLUP for pin 3.

The output I get it now clean, for example:

14:19:13.982 -> Time: 6810 ms.
14:19:50.684 -> Time: 2485 ms.

Then I modified the code as follows:

#define intpin 3                //pin where the button is connected
#define monitorPin 4

unsigned long time_i;           //starting time of the stopwatch
unsigned long time_f;           //finishing time of the stopwatch
int time_diff;                  //time interval between the first and the second press of the stopwatch button
bool measuring = false;         //is the arduino measuring the time interval?
volatile bool cicle = false;    //has the arduino finished measuring a time interval?

void setup() {
  Serial.begin(9600);
  pinMode(intpin, INPUT_PULLUP);
  pinMode(monitorPin, OUTPUT);
  attachInterrupt(digitalPinToInterrupt(intpin), stopwatch_ISR, FALLING);

}

void loop() {
  // put your main code here, to run repeatedly:
  if(cicle==true){              //if a time interval was measured, send the time interval
    cicle = false;
    time_diff = time_f - time_i;
    Serial.print("Time: ");
    Serial.print(time_diff);
    Serial.println(" ms.");
  }
}

void stopwatch_ISR(){           //if the stopwatch button was pressed, check whether the time interval was being measured or not and proceed accordingly
    digitalWrite(monitorPin, !digitalRead(monitorPin)); //Change state every time the interrupt is triggered
    if(measuring==false){       //if the time interval was not being measured, start measuring it
      time_i = millis();
      measuring = true;
    }
    else{                       //if the time interval was being measured, finish measuring it and set interval measured flag
      time_f = millis();
      measuring = false;
      cicle = true;
    }
}

Note pin 4, monitorPin. The code changes the state of this pin when the interrupt is triggered. By monitoring this pin on one channel of my oscilloscope and the button on the other I measured the time delay between pressing the button and the Uno seeing the interrupt, the delay is 102ms.

You said more than once:

BTW, the interrupts are needed because, as I said, I need the timing to be measured in a reliable way, and the main loop is going to get a little bit intricate.

If you write your code well, by which mean none blocking and no delays, then I would expect loop to complete many, many times in 102ms, and any sensible debounce code to deliver a debounced input within maybe 20ms. I stand by what I have already said, which is you should not be using an interrupt for your input. From what I have seen of your ability to write code I think you are capable of writing good, non-blocking code.

I don't know if your vma326 delivers a clean input, which won't need to be debounced, or if it is dirty. You probably ought to find out.

@PerryBebbington

I don't have a Leonardo so I used a Uno

Well, then... I suppose my UNO board might be a little bit broken, because if these exact circuit and code worked for you and not for me...

the delay is 102ms

Actually, there is so much delay because, when I started having problems, my first thought was the debouncing circuit was not working properly, so I decided to use the 10 uF capacitor and the 10 kOhm resistor to give it a huge time constant. My initial plan was to use a 1 kOhm resistor and a 1 uF capacitor, so that would reduce the delay to roughly 1 ms. In any case, whether there is more or less delay, that is irrelevant to the calculations I have to do.

I stand by what I have already said, which is you should not be using an interrupt for your input

The thing is I actually need pretty good time measures... I might want my Arduino to multitask a little too much (?), because I need it to: implement a PID control to control the speed of a motor, show some data on a screen (I was thinking of a bunch of 7-segment displays), read some potentiometers and buttons and calculate the time it takes a little car to move between two positions (so that I can calculate its velocity). I did most of these back during my university years as separate projects... I just lost the codes I wrote, but I remember using a lot of interrupts for those, so that's why my approach this time was using interrupts as well...

I don't know if your vma326 delivers a clean input, which won't need to be debounced, or if it is dirty. You probably ought to find out.

Yes, I should find out.

Could you try to change the main loop to this:

void loop() {
  if(cicle==true){              //if a time interval was measured, send the time interval
    cicle = false;
    time_diff = time_f - time_i;
    Serial.print("Time: ");
    Serial.print(time_diff);
    Serial.println(" ms.");
  }
  digitalWrite(13, HIGH);
  digitalWrite(13, LOW);
 }

(adding the setup to use pin 13 as output) and tell me if it still gives you a clean output? Maybe my friend's Leonardo board is also broken even though it's new.

Thanks a lot for your reply.

@david_2018

The RC filter is going to take a while to transition between logic states

I know it's a pretty slow RC filter. I just used it for debugging my cricuit. My plans are to use a faster one, as I said up in this post.

if you are turning an LED on and off you may be affecting the power supply voltage just enough to cause problems in the undefined region between HIGH and LOW on the input port

What do you mean? There are no LEDs at the moment in the circuit, but there is a built-in LED in the sensor I am using. Could that be affecting the Arduino ability to correctly read input pins?

Do you have any idea what the absolute minimum amount of time between valid transitions from the sensor would be?

No. I have no idea.

With that RC filter I don't really see a problem with having the ISR ignore any interrupts that occur within a few milliseconds of each other

Do you mean I should try to code the ISR so that unless the interrupts occur slow enough, it ignores them?

You need to declare all variables used by both the ISR and the remainder of your code as volatile

The only variables which are lacking the volatile definition are time_f and time_i, right?

You need to disable interrupts when accessing time_f and time_i

I have tried disabling and reenabling interrupts everywhere in the code without success.

I would copy cicle to another variable for use in loop() instead of accessing it in two places in the code

Wouldn't that make the other variable be accessed in two places in the code instead of the cicle variable?

Thanks a lot for your time!

After I posted my last reply I did some more button presses and wasn't always getting clean responses. Then I went off to read my book / watch TV, whatever.

I've just come back and I have changed my code as you asked. I am getting multiple interrupts about 15μ apart.

I then removed the extra lines for pin 13 and I get the same.

I can't get consistent responses from your code.

Here's where I am with helping you: Nothing you have told me has convinced me that you need to use interrupts for your timing. Everything I have tried at your request has made me even more sure that your approach is wrong and my advice remains the same, only stronger. I hear your concerns about the speed of your code but I think you won't have a problem, or if you do then it will be due to untidy code elsewhere. From what I've seen of you I think you will be able to write responsive, non-blocking code, maybe not first time, but you will be able to do it.

You keep expressing concern about timings, so here are a few things to consider: You mentioned a 1ms RC delay for debuncing. 1ms isn't enough; I see a lot of questions on here about debuncing and I did some experiments, I found I could get 20ms bounce from a button, although ~5ms was more reasonable. A 1ms debounce timer will not be reliable.

You are timing with millis(), so the best you can hope for is 1ms resolution.

You've not told us how accurate you want the timing, so we are in the dark about that.

If you do run into timing issues with well written code then a more powerful processor will fix that at not much money.

I've done all I can for you, my advice is as it was; don't use interrupts for button presses. You can take my advice or ignore it, that's your decision.

I wish you well with your project.

Oh, please buy an oscilloscope, they make this kind of thing so easy!

OK, you convinced me: I will stop trying to use the interrupts and write the most efficient code I can think of to take care of the different time measurements.

From my calculations, I will need to measure time intervals of ~50 ms.

BTW, I wish I had an oscilloscope, they made my life so easy back in university, but I don't do this kind of projects often enough as to buy one :(

Thanks a lot for your time an patience!

“I might want my Arduino to multitask a little too much (?), because I need it to: implement a PID control to control the speed of a motor, show some data on a screen (I was thinking of a bunch of 7-segment displays), read some potentiometers and buttons and calculate the time it takes a little car to move between two positions (so that I can calculate its velocity). I did most of these back during my university years as separate projects… I just lost the codes I wrote, but I remember using a lot of interrupts for those, so that’s why my approach this time was using interrupts as well…”

In fact, this is a strong argument against using interrupts, because you will run out of interrupt vectors and/or timers for all of those processes. The fact that only one timer is required for millis(), and it can be used for virtually unlimited timing processes, is a compelling reason to use it whenever interrupts are not strictly necessary.

Measuring the time between 2 button presses

From my calculations, I will need to measure time intervals of ~50 ms.

Easy!