Author Topic: Detecting long button presses using timers  (Read 3728 times)

0 Members and 1 Guest are viewing this topic.

Offline MikeK

  • Super Contributor
  • ***
  • Posts: 1316
  • Country: us
Re: Detecting long button presses using timers
« Reply #25 on: October 08, 2023, 05:17:42 pm »
Regarding debugging...Rest assured that most people don't need a debugger.  Plenty of us older programmers get by very well by just turning on an LED at a strategic point or sending a value out the UART.  I've done a lot of microcontroller development and only a couple of times have I used the built-in debugger to step through code.  Overwhelmingly, I find it easier and faster to do the LED or UART method.
 

Offline MikeK

  • Super Contributor
  • ***
  • Posts: 1316
  • Country: us
Re: Detecting long button presses using timers
« Reply #26 on: October 08, 2023, 05:22:53 pm »
For keys I always use interrupts activated by edge not level, AND a counter loop inside the interrupt that polls the key state so I can know how long the key was pressed for, thus allowing for 2 functions on one key: long press and short press.

If you're looping inside an ISR, waiting for a long keypress, you've developed bad programming habits.  That is not what an ISR is for.
 

Online PartialDischarge

  • Super Contributor
  • ***
  • Posts: 1625
  • Country: 00
Re: Detecting long button presses using timers
« Reply #27 on: October 08, 2023, 05:33:51 pm »
[If you're looping inside an ISR, waiting for a long keypress, you've developed bad programming habits.  That is not what an ISR is for.

0.6s max, and for the applications that I do is perfectly fine, the CPU has nothing else to do.
*If* the CPU had something else to do, you could simply exit the ISR and run this code outside via some flag, but in my case it's not necessary
« Last Edit: October 08, 2023, 05:40:18 pm by PartialDischarge »
 

Offline snarkysparky

  • Frequent Contributor
  • **
  • Posts: 418
  • Country: us
Re: Detecting long button presses using timers
« Reply #28 on: October 08, 2023, 05:38:05 pm »
Create some tools first

where you are polling the key create

1   debounce using the history of the input.    Last 4 polled values is what i use

2  rising edge detection acting on the debounced signal

3  falling edge detection acting on the debounced signal

Now after you have debugged these use them like so


rising edge of keypress
{   
    zero and start the timer

}


falling edge of keypress
{

   stop timer
}


timer exceeding preset level
{
     do stuff you want if this happens
}
 

Offline DavidAlfa

  • Super Contributor
  • ***
  • Posts: 6273
  • Country: es
Re: Detecting long button presses using timers
« Reply #29 on: October 08, 2023, 05:43:46 pm »
0.6s max, and for the applications that I do is perfectly fine, the CPU has nothing else to do.
I know it might work but it's a bad idea anyways :D.
That's why timer interrupts are usually used. You only need to read the button state, increase some counters, set few flags when required.
Though I've used ISR to run some heavy code (No delays, but plenty of processing) in a sort of higher priority task, while the main did the graphics or other stuff which had no priority at all.
Still, it was under 5ms execution, but I consider that a lot for a ISR! But this was in a STM32, with plenty of IRQ priorities, so if done well you won't stall anything else.
600ms ISR is almost terrorism! The programmer army will find you in the night! :-DD
« Last Edit: October 08, 2023, 05:46:06 pm by DavidAlfa »
Hantek DSO2x1x            Drive        FAQ          DON'T BUY HANTEK! (Aka HALF-MADE)
Stm32 Soldering FW      Forum      Github      Donate
 

Online PartialDischarge

  • Super Contributor
  • ***
  • Posts: 1625
  • Country: 00
Re: Detecting long button presses using timers
« Reply #30 on: October 08, 2023, 05:52:53 pm »
while the main did the graphics or other stuff which had no priority at all.
Graphics? lol, my CPU does nothing until a key gets pressed

600ms ISR is almost terrorism! The programmer army will find you in the night! :-DD
I know, I'm already regretting posting, an avalanche of purists and ANSI bible programmers will start to appear
 

Offline nctnico

  • Super Contributor
  • ***
  • Posts: 28101
  • Country: nl
    • NCT Developments
Re: Detecting long button presses using timers
« Reply #31 on: October 08, 2023, 05:57:33 pm »
[If you're looping inside an ISR, waiting for a long keypress, you've developed bad programming habits.  That is not what an ISR is for.

0.6s max, and for the applications that I do is perfectly fine, the CPU has nothing else to do.
*If* the CPU had something else to do, you could simply exit the ISR and run this code outside via some flag, but in my case it's not necessary
Or use nested interrupts which interrupt the long interrupt. Nothing wrong with that. It is a whole lot cleaner compared to trying to mimic an OS with flags that may, or may not be set and all the associated process synchronisation that would be involved.
« Last Edit: October 08, 2023, 05:59:05 pm by nctnico »
There are small lies, big lies and then there is what is on the screen of your oscilloscope.
 

Offline newtekuserTopic starter

  • Frequent Contributor
  • **
  • Posts: 433
  • Country: us
Re: Detecting long button presses using timers
« Reply #32 on: October 08, 2023, 07:07:26 pm »
Pretty much. I do have a pickit4, but I gave up on using the mplab debugger as I can't make it do the basic things I'm expecting from it. i.e.: stepping over functions throws a "Stepover() failed. No breakpoint is available for the Step Over function" error and in other cases I get "no hardware breakpoints available". Not sure if these are a limitation of the PIC I'm using or simply not understanding the flow of debugging on MCUs. I am not a noob at using debuggers though as I've been using them for almost 20 years, but only recently with HW,
Yeah, you can set only a few breakpoints. Can't remember how many, but definitely not more than 3. Over this you'll get the "No breakpoints available" error.
So you must remove the old ones and set the new ones all the time.
Always halt the PIC when doing so, they can't do so on the fly, but MplabX should warn you about this if you try.

The 16F877 should debug fine, however there're some limitations. Not being able to step the code inside an interrupt is a bummer.
https://microchipdeveloper.com/debug-limits:8bit-devices-pic16f887

The 16F183xx series work really well with debuggers! But I gave up on Microchip 8-bitters, they were getting expensive for their value.
Having 32-bit ARM MCUs for less than 30 cents, once you get into them the PIC16/18 will look like toys!
Microchip kept trying 8-bit but industry is moving!

Just curious, what 32-bit MCUs can be had at that price? I didn't look too extensively, but the cheapest I found were north of $1.40.
 

Offline DavidAlfa

  • Super Contributor
  • ***
  • Posts: 6273
  • Country: es
Re: Detecting long button presses using timers
« Reply #33 on: October 08, 2023, 07:15:03 pm »
PY32F002A - 11 cents
HK32F030M - 24 cents
STM32G030 - 35 cents
Hantek DSO2x1x            Drive        FAQ          DON'T BUY HANTEK! (Aka HALF-MADE)
Stm32 Soldering FW      Forum      Github      Donate
 
The following users thanked this post: newtekuser

Offline pqass

  • Frequent Contributor
  • **
  • Posts: 920
  • Country: ca
Re: Detecting long button presses using timers
« Reply #34 on: October 08, 2023, 08:27:56 pm »
My brain hurts looking at that ISR.  Ya gotta keep it simple.

See attached for an updated version of my debounce function from here.
This updated version hasn't been tested, although, it does compile at least.
It's for AVR but I'm sure you can port it fairly easily; the register setup is localized or is straight-forward.

The gist of it is...
a. setup a free-running 5ms timer interrupt.
b. in the timer ISR, call debouncePin(current pin value, struct var *) for every pin that needs debouncing. Increment a tick counter var.
   debouncePin() keeps track of the last 8 pin states. If the last 8 are HIGH, the debounced var.state is set to HIGH. Same for LOW.
   Also, while in the LOW state, var.time is incremented to contain the number of timer interval counts it spent in the LOW state.
c. your main-line code just checks if var.state==HIGH and if var.time==0 or some other value.
   if it's the latter, do whatever you need doing then set var.time=0 to stop if from re-executing until another keypress.
 

Offline DavidAlfa

  • Super Contributor
  • ***
  • Posts: 6273
  • Country: es
Re: Detecting long button presses using timers
« Reply #35 on: October 08, 2023, 08:33:14 pm »
Sure, do you thing using magic Arduino crap "debouncePin((PINA>>(PA7)), &attn_key);" is simpler?  :)
It's just you're not seeing what it's doing behind the walls.
keeps track of the last 8 pin states? Why? More bloated functions.
« Last Edit: October 08, 2023, 08:36:34 pm by DavidAlfa »
Hantek DSO2x1x            Drive        FAQ          DON'T BUY HANTEK! (Aka HALF-MADE)
Stm32 Soldering FW      Forum      Github      Donate
 

Offline pqass

  • Frequent Contributor
  • **
  • Posts: 920
  • Country: ca
Re: Detecting long button presses using timers
« Reply #36 on: October 08, 2023, 08:38:31 pm »
It's not "magic Arduino crap".  I define the function!    Look at the code.
In fact, there is no Arduino library calls at all; except setup() and loop().
 

Offline DavidAlfa

  • Super Contributor
  • ***
  • Posts: 6273
  • Country: es
Re: Detecting long button presses using timers
« Reply #37 on: October 08, 2023, 08:47:48 pm »
My bad, I didn't see it so I assumed the usual!
Anyways, I kept the programming simple when people struggle with it. Like avoiding structures, pointers...
Also PIC architecture is pretty bad, not efficient at addressing so I avoid pointers there when possible.
Hantek DSO2x1x            Drive        FAQ          DON'T BUY HANTEK! (Aka HALF-MADE)
Stm32 Soldering FW      Forum      Github      Donate
 

Offline pqass

  • Frequent Contributor
  • **
  • Posts: 920
  • Country: ca
Re: Detecting long button presses using timers
« Reply #38 on: October 08, 2023, 09:13:13 pm »
Anyways, I kept the programming simple when people struggle with it. Like avoiding structures, pointers...
Also PIC architecture is pretty bad, not efficient at addressing so I avoid pointers there when possible.

Honestly, structures help to keep the data compartmentalized.
If you don't want to do pointers, then you can repackage as an array of structures or even array of arrays.  And just pass indexes around.

Why 8?  Because it represents 40ms of time; long enough for a bounce to settle.
If in that time the pin didn't change states (bounce), then you can say it has settled and change the debounced state flag to that value.
Also, I use the .tmp structure element which happens to be a uint8_t and every timer tick it is shifted left by 1 OR'd with the current pin state.
 

Offline tridac

  • Regular Contributor
  • *
  • Posts: 125
  • Country: gb
Re: Detecting long button presses using timers
« Reply #39 on: October 08, 2023, 09:37:43 pm »
Same method here, 10mS or so tick timer, isr as a little state machine that validates a keypress if a key has been seen for two or more ticks, 20-30Ms is usually enough for contact debounce. Isr just a reads a port, x & y, 4 bits each to handle 16 possible keys. Then use a simple lookup table to translate that into a key value. Table has all the valid single key values only, with anything else ignored, to reject multi key presses. Just a few lines of code...
Test gear restoration, hardware and software projects...
 

Offline DavidAlfa

  • Super Contributor
  • ***
  • Posts: 6273
  • Country: es
Re: Detecting long button presses using timers
« Reply #40 on: October 09, 2023, 01:25:18 pm »
As you see adding more buttons will need more code, eventually getting larger and making development messier.

To make it dynamic, at first it needs more complex code, but then it'll be a lot better.
Using pointers is the usual way when the architecture allows it, but PIC16 are not meant for this, neither their ecosystem/header definitions.
The terrible memory bank switching messes everything up, so manually making a pointer to 0x05 could be PORTA, TRISA, WDTCON or  SRCON depending on the selected bank.
Ports are declared as RAM volatile chars, so adding pointer to them will cause a "operand not constant" error by the compiler.
The only solution I found was to hardcode the PORT addresses, manually set the bank and use indirect addressing through FSR / INDF.
I recycled the debouncing code from here, but barely resembles it now!

Now this code is way better! Plus you can use getTick() to read the system time for other timing you need.
Definitely 10ms is better suited here, a 32bit timer will overflow after 49 days at 1KHz, 497 days at 100Hz.

Keep in mind I made this code for buttons to be active-low!
If you're using them active-high, you need to invert the definitions in button_physical_state_t.
When a short/long press was detected, the code won't update the button anymore until the software reads it using readButtonLogicState, this will reset the button state and resume the sampling.
This will give the software the time it needs to process the events. Of course one button won't affect the rest.

Code: [Select]
#include <xc.h>

#pragma config FOSC = INTRC_NOCLKOUT// Oscillator Selection bits (INTOSCIO oscillator: I/O function on RA6/OSC2/CLKOUT pin, I/O function on RA7/OSC1/CLKIN)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled and can be enabled by SWDTEN bit of the WDTCON register)
#pragma config PWRTE = OFF      // Power-up Timer Enable bit (PWRT disabled)
#pragma config MCLRE = OFF      // RE3/MCLR pin function select bit (RE3/MCLR pin function is digital input, MCLR internally tied to VDD)
#pragma config CP = OFF         // Code Protection bit (Program memory code protection is disabled)
#pragma config CPD = OFF        // Data Code Protection bit (Data memory code protection is disabled)
#pragma config BOREN = OFF      // Brown Out Reset Selection bits (BOR disabled)
#pragma config IESO = ON        // Internal External Switchover bit (Internal/External Switchover mode is enabled)
#pragma config FCMEN = ON       // Fail-Safe Clock Monitor Enabled bit (Fail-Safe Clock Monitor is enabled)
#pragma config LVP = OFF        // Low Voltage Programming Enable bit (RB3 pin has digital I/O, HV on MCLR must be used for programming)

// CONFIG2
#pragma config BOR4V = BOR40V   // Brown-out Reset Selection bit (Brown-out Reset set to 4.0V)
#pragma config WRT = OFF        // Flash Program Memory Self Write Enable bits (Write protection off)


#define TIMEBASE 10                                                             // Timebase in ms (100Hz timer)
#define PR1 ((const unsigned int)(0xFFFFU - (8000000U/(4UL*8*(1000/TIMEBASE))) + 4))// +4 for compensating interrupt latency and reload delay

typedef enum { button_pressed=0, button_released=1 }button_physical_state_t;    // Physical states. Adjust as required if inverted.
typedef enum { button_idle, button_short, button_long, button_wait_release }button_logic_state_t;    // Logical states
typedef enum {                                                                  // Have to do this because PIC addressing/bank switching and/or compiler sucks
            PORT_A=0x5, PORT_B=0x6, PORT_C=0x7,
            PORT_D=0x8, PORT_E=0x9, PORT_F=0xA
}port_addr_t;

typedef enum { button_RB1=0, button_RB2=1 }button_t;                            // Button index definition, just to make it easier

typedef struct {                                                                // Input filter structure
    union{
        volatile uint8_t d;
        struct{
            unsigned pin         :3;                                            // Pins: 0-7
            unsigned logic_state :2;                                            // idle, short, long, wait_release
            unsigned last        :1;                                            // last reading
            unsigned state       :1;                                            // current stable state
            unsigned stable      :1;                                            // stable flag
        };
    };
    uint8_t  port;                                                              // input port (port_addr_t))
    uint32_t timer;                                                             // Time store for debouncing, events
}inputFilter_t;

volatile inputFilter_t buttons[] = {                                            // Button structure initialization
  { .port = PORT_B, .pin = 1, .state = button_released },                       // RB1
  { .port = PORT_B, .pin = 2, .state = button_released },                       // RB2
};

volatile uint32_t tick;                                                         // System tick

uint32_t GetTick(void);
unsigned readButtonValue(button_t b);
uint8_t readButtonLogicState(button_t b, button_logic_state_t state);

void main(void)
{
    OSCCON =  0x70;     
    ANSEL = 0b0000000;
    ANSELH = 0b0000000;
     
    TRISA = 0x0;
    TRISC = 0x0;   
    PORTA = 0x0;
    PORTC = 0x0;
   
    TRISBbits.TRISB0 = 1;
    TRISBbits.TRISB1 = 1;
    TRISBbits.TRISB2 = 1;
    TRISCbits.TRISC0 = 0;

    TMR1H = (PR1 >> 8) & 0xFF;
    TMR1L = PR1 & 0xFF;
    T1CONbits.TMR1CS = 0;   
    T1CONbits.T1CKPS = 0b11;
    T1CONbits.TMR1ON = 1;   
    PIE1bits.TMR1IE = 1;
   
    INTCONbits.GIE = 1;         
    INTCONbits.PEIE = 1;

    while(1)
    {
        if(readButtonLogicState(button_RB1, button_short) {
            Lcd_Goto(1,1);
            Lcd_Print("Button RB1 pressed");
        }
        if(readButtonLogicState(button_RB1, button_long) {
            Lcd_Goto(1,1);
            Lcd_Print("Button RB1 L_pressed");
        }
        if(readButtonLogicState(button_RB2, button_short) {
            Lcd_Goto(1,1);
            Lcd_Print("Button RB2 pressed");
        }
        if(readButtonLogicState(button_RB2, button_long) {
            Lcd_Goto(1,1);
            Lcd_Print("Button RB2 L_pressed");
        }
    }
}


uint32_t GetTick(void){                                                         // returns system time in tens of ms (1=10ms, 100=1000ms)
    return tick;
}


uint8_t readButtonValue(button_t b) {                                           // Read filtered button state
  return (buttons[b].state == button_pressed);                                  // Return true if pressed
}

uint8_t readButtonLogicState(button_t b, button_logic_state_t state) {          // Read logical button states
    button_logic_state_t s = buttons[b].logic_state;
    if(s==button_wait_release){                                                 // treat button_wait_release as idle
        s=button_idle;
    }
    if(s==state) {                                                              // If state matching request
        if(s==button_short || s==button_long) {                                 // If we have a pending state to be read
            buttons[b].logic_state =  button_wait_release;                      // Set flag to wait for release
        }
        return 1;                                                               // Return true
    }
    return 0;                                                                   // Return false
}

#define BUTTON_COUNT (sizeof(buttons)/sizeof(inputFilter_t))
#define BUTTON_DEBOUNCE (20/TIMEBASE)                                           // Debounce time
#define BUTTON_LONG_MIN (1000/TIMEBASE)                                         // Min long press time

void updateButtons(void) {                                                      // To be called periodically by main or some interrupt every few ms
  uint32_t now = GetTick();
  for(uint8_t b=0; b<BUTTON_COUNT; b++) {                                       // Scan buttons
    STATUSbits.IRP = 0;                                                         // Select lower memory bank (For PORT registers) - PIC addressing is ancient!
    FSR = buttons[b].port;                                                      // Use indirect addressing, load port address into FSR
    uint8_t current = (INDF >> buttons[b].pin) & 1;                             // Read value, convert to boolean
   
    if(buttons[b].last != current){                                             // If button changed
      buttons[b].stable = 0;                                                    // Changed, not stable
      buttons[b].last = current;                                                // Update last state
      buttons[b].timer = now;                                                   // Restart timer
    }
    else if(!buttons[b].stable){                                                // If not stable
      if((now-buttons[b].timer)>BUTTON_DEBOUNCE ) {                             // Check if button was stable for the specified time
        if(buttons[b].state != current) {                                       // New state
            if(     current==button_released &&                                 // If button released,
                    buttons[b].logic_state == button_idle &&                    // logic state was idle
                    buttons[b].state == button_pressed) {                       // and previous button state was pressed
                buttons[b].logic_state = button_short;                          // Short press
            }           
            buttons[b].state = current;                                         // Update state
        }
        buttons[b].stable = 1;                                                  // Set stable condition
      }
    }
    else {                                                                      // Button stable
        if(current==button_pressed) {                                           // If pressed
            if(buttons[b].logic_state == button_idle) {                         // And logic state idle
                if(now - buttons[b].timer > BUTTON_LONG_MIN) {                  // If enough time passed
                    buttons[b].logic_state = button_long;                       // Long press
                }
            }
        }
        else if(buttons[b].logic_state == button_wait_release) {                // If released and logic button state was waiting
            buttons[b].logic_state = button_idle;                               // Set idle
        }
    }
  }
}

void __interrupt() isr(void)
{
  if(PIR1bits.TMR1IF) {
    T1CONbits.TMR1ON = 0;
    TMR1H = PR1 >> 8;                                                           // Preload timer
    TMR1L = PR1 & 0xFF;
    T1CONbits.TMR1ON = 1;
    PIR1bits.TMR1IF = 0;                                                        // Reset flag
   
    tick++;   
    updateButtons();
  }
}
« Last Edit: October 09, 2023, 02:06:13 pm by DavidAlfa »
Hantek DSO2x1x            Drive        FAQ          DON'T BUY HANTEK! (Aka HALF-MADE)
Stm32 Soldering FW      Forum      Github      Donate
 
The following users thanked this post: PlainName, newtekuser


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf