Arduino Leonardo - how to meausure time how long button is pushed

Hi!

I im working on small project that uses one button, and i im using input capture interrupt ICR on Arduino Leonardo to capture when button is pressed.

So, i create code to write in serial output how many ms is button pressed, but i im getting all the time same value, so code does not work as expected.

Question is: how can i get time in ms on how long button is pressed?
This information i need so that i can then make switch case in witch i decide if button is short pressed, long pressed or double click pressed.

Here is my code, so any help is welcome.

`const byte SWpin  = 4;

void setup() {
  pinMode(SWpin, INPUT_PULLUP);           // set SW pin as INPUT and enable pullup resistor
  
  // put your setup code here, to run once:
  cli();                                  // disable global interrupts

  // initialize TIMER1 input capture interrupt
  TCCR1A = 0;                             // set entire TCCR1A register to 0
  TCCR1B = (1 << ICES1)|(1 << CS10);      // trigger a capture event on a rising edge and start timer
  TIMSK1 = (1 << ICIE1);                  // enable timer input capture interrupt

  sei();                                  // enable global interrupts

  Serial.begin(9600);
}

void loop() {
  // put your main code here, to run repeatedly:

}

// pin capture interrupt for rotary encoder SW button
ISR(TIMER1_CAPT_vect) {
  // pin change SW interrupt
  int tSTART, tPULSE;
  if (TCCR1B & (1 << ICES1)) {                    // if rising edge
    TCCR1B = (0 << ICES1);                        // trigger a capture event on a falling edge
    tSTART = ICR1;
    Serial.print(tSTART);
    Serial.println("Rising Edge...");
  } else {
    TCCR1B = (1 << ICES1);                        // trigger a capture event on a rising edge
    tPULSE = ICR1 - tSTART;
    Serial.print(tPULSE);
    Serial.println("Falling Edge...");
  }
}`

You should not be using Serial in an interrupt.

I im using serial to see time how many ms is button pressed....in final code it will be removed serial.

When i press button for short or long time i get this:

So, time is always 301...and this is not correct...so where i im doing it wrong?

For one thing, you have tSTART declared as a local variable. The value will not be saved between interrupt calls.

1 Like

If you are just looking for the time a button is pressed, on a microcontroller you can - but you do not need to use an interrupt.

If you insist on using the interrupt, you should avoid time consuming functions in there because they may stopp the controller from doing other (probably useful) things.

This sketch does it without interrupts:

#define buttonPin 7

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  while (!Serial);
  pinMode(buttonPin, INPUT_PULLUP);
}

int lastState = HIGH;
unsigned long lastButtonPressed = 0;
unsigned long ButtonPressTime = 0;

void CheckButtonPressTime(){
  int State = digitalRead(buttonPin);
  if (State != lastState){
     lastState = State;
     if (State == LOW){lastButtonPressed = millis(); delay(50);}  // simple debouncing by delay ...
     if (State == HIGH) {
        ButtonPressTime = millis()-lastButtonPressed;
        Serial.println(ButtonPressTime/1000.0);
        delay(50);
     }
  } 
}

void loop() {
  CheckButtonPressTime();
}

``
1 Like

This stops the clock by clearing the prescaler's CS10:2 bits.

Maybe you want TCCR1B &= ~(1 << ICES1); or bitClear(TCCR1B,ICES1); ?

I try using this code, and declare global variables:

volatile int tSTART, tPULSE;

But code is not working, it reports same ms duration of short press and long press...

I didn't check your register setting code, your logic, or your resisters, I just noticed that when your code calls TCCR1B = in your ISR, it looks like a typo that clears the prescaler bits and turns off the counting. After the first interrupt, the clock gets stopped, at 301, for example, and on the rising edge reports "301", and on the falling edge reports "301 - 0" because tStart is reinitialized to zero each time the ISR is invoked. It would report the same stopped-clock value until the timer is re-started the next time setup() is called.

You also need to use @ToddL1962's suggestion:

Did you write this code yourself? Or is it an adapted copy from somewhere?

I'd use global variables like:

volatile int tSTART,tEND;

...and move the math and reporting into loop(); You could report conditionally when tEND changes.

I need to use it in interrupt code to get pressed time of button, so it all must do in interrupt service, i don't want to use loop().

Could you please write example code how to archieve this? I posted code that i try in #1 post.

I'd probably use @ec2021's code, with they nice encapsulated CheckButtonPresssTime() function. Or if using your code, I'd follow my advice on the bitSet(TCCR1B,ICES1)/bitClear(TCCR1B,ICES1) bit munging combined with the advice in both @ToddL1962's posts.

Did you take your ISR code and fix both TCCR1B bit-munging lines and move the int tSTART, tPULSE; to be globals? If so, in what way did it fail with your circuit/button?

If they were volatile unsigned int tSTART, tPULSE; globals they could measure twice the duration without going negative and could be useful outside the ISR.

You might be getting ~13100us of debouncing from the 9600 baud Serial.print() statements in your ISR.

Here is code that works very well, but i don't have idea how to do if then for double click button...i try now in serial console and all button pressed is correct and very accurate. So i got single press...double press button correct. So what will be code to detect double click button?

const byte SWpin  = 4;

void setup() {
  pinMode(SWpin, INPUT_PULLUP);           // set SW pin as INPUT and enable pullup resistor
  
  // put your setup code here, to run once:
  cli();                                  // disable global interrupts

  // initialize TIMER1 input capture interrupt
  TCCR1A = 0;                             // set entire TCCR1A register to 0
  TCCR1B = (0 << ICES1);                  // trigger a capture event on a falling edge and start timer
  TIMSK1 = (1 << ICIE1);                  // enable timer input capture interrupt

  sei();                                  // enable global interrupts

  Serial.begin(9600);
}

void loop() {
  // put your main code here, to run repeatedly:

}

// pin capture interrupt for rotary encoder SW button
ISR(TIMER1_CAPT_vect) {
  // pin change SW interrupt
  if (TCCR1B & (1 << ICES1)) {                    // if rising edge
    TCCR1B = (0 << ICES1)|(0 << CS10)|(0 << CS12);            // trigger a capture event on a falling edge and stop timer1
    //Serial.print("Rising Edge...Stop Timer1...");
    //Serial.println(TCNT1);

    if (ICR1 > 2000 && ICR1 < 3000) { Serial.println("Single Click..."); }
    if (ICR1 > 20000 && ICR1 < 40000 ) { Serial.println("Long Press Click..."); }
    
  } else {
    TCNT1 = 0;
    TCCR1B = (1 << ICES1)|(1 << CS10)|(1 << CS12);            // trigger a capture event on a rising edge and start timer1
    //Serial.print("Falling Edge...Start Timer1...");
    //Serial.println(TCNT1);
  }
}

Most important part is that i run Timer1 with prescaler of 1024 to slow down Timer1 counting when button is pressed (i im using button that when pressed it gets LOW and when released it gets HIGH state, so this is why i first capture falling edge and then rising edge).

I run 16MHz and using prescaler of 1024 so for 16 bit timer i got 0,000064.

Short press i calculated using 0,000064 x 3000ticks = 0,192sec
Long press i want to be 3sec so using 0,000064 x 40000 = 2,56sec

I im using serial so delay is not 3 sec and is around 2,56sec. And it works.

As you can see when button is clicked capture interrupt activates on falling edge, start1 timer...and when i release button capture interrupt is activated again but on rising edge....timer1 is stopped and ICR1 value is read from register (this is time that button is pressed).

This is very nice because Timer1 is running only when button is pressed, so MCU is light on usage and can do other things. Bad idea will be if Timer1 is running and scanning button press, or pooling button press in loop()...so i like all that runs in interrupts to free hardware resources for other things....

So question is how to detect double click...and code example?

Thanks.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.