Button click missed when sensor data being read, use interrupts?

I am using the clickButton library to read several buttons… works great; however, now that I’ve added a temperature and light sensor, I notice that during the sensor reading (each can take about 300-400ms), the code sometimes misses the click events (as they are just checked in the loop, not via interrupts).

I run the button.Update() between sensor readings (meaning, only read one sensor, then check buttons, then read the buttons again) which helps.

Sensors are read every 10 seconds. (i could make this longer, but like this interval and still won’t solve my issue).

I have tried using interrupts to read the clicks, but then have issues with debounce and lose the library features of multiple clicks, and worse, interferes with with library.

is there a click library that uses interrupts? or suggest some other solution?

Thanks!

@marcusone, the clickButton library is not designed to run in an interrupt service routing. However, it might run withing a Software Timer which is something I need to test. There is also this library which might also be able to run in a Software Timer:

I’ll need to do some experimentation and publish my findings.

@marcusone Common problem is you want to catch the button press or release but read from the application loop. Here’s a model you can try - I have just shown 1 button but values can be arrays:

volatile uint32_t msLastPress = 0;
volatile int isrKey = LOW;
void button1Pressed(void); 

In setup()

attachInterrupt(button1, button1Pressed, CHANGE);      //use CHANGE and check if HIGH or LOW in ISR

In loop()
result = checkButtons();   //returns the type of press or release and which button if many

ISR
void button1Pressed(void)
{
    isrKey = pinReadFast(button1Pin);
    msLastPress = millis();
}
// scan if any of the buttons or combinations has been pressed or released
// return 0 = no keys pressed, 1-4 = one of 4 keys pressed or 5 = key 1 and key 4 pressed together
// return 11-14 = one of 4 keys pressed and held for at least longPress mS
int checkButtons()
{
    int _key = 0;
    int key1 = isrKey; 
    // button 1
    if (key1 == HIGH && !pressed[0])
    {
        pressed[0] = true;
        _key = 1;
    }
    else if (key1 == LOW && pressed[0])
    {
        pressed[0] = false;
        _key = 0;
    }
}

thanks @armor that looks promising! didn’t think to store the state of the button in the ISR; that keeps it really quick as it should be in ISR, as not to mess with anything else going on.

I know the photon has a system thread… any chance the ISR or other routines can be elevated to the system thread? That would solve my issue as well. :slight_smile:

@peekay123 the issue with software timers is the same as ISR… can’t block inside them or things like MQTT or sensor readings start to fail (at least in my experience).
wish there was a way to use threads … ?

@marcusone, as long as code is non-blocking, then a Software Timer or ISR can be used. I will be exploring the blackketter/Switch library to do this.

UPDATE: I modified the clickButton example and put button1.update() along with setting the function variable in a 1ms Software Timer. I then put the print actions in a separate function which I call before and after delay(400) calls to emulate your sensor reading delays. Though there is a 400ms delay to the response to the button action, the button is sampled correctly with the correct corresponding function value. Here is the code I tested with. I tested with DeviceOS 1.2.1-rc.2 using the Web IDE. Don’t forget to attach the clickButton library.

/* ClickButton library demo

  OUtput a message on Serial according to different clicks on one button.
  
  Short clicks:

    Single click - 
    Double click - 
    Triple click - 
    
  Long clicks (hold button for one second or longer on last click):
    
    Single-click - 
    Double-click - 
    Triple-click - 

  2010, 2013 raron
 
 GNU GPLv3 license
*/
#include <clickButton.h>

// the Button
const int buttonPin1 = D4;
ClickButton button1(buttonPin1, LOW, CLICKBTN_PULLUP);

// Button results 
volatile int function = 0;

void buttonPoll() {
    button1.Update();
    
    if(button1.clicks != 0) function = button1.clicks;
}

void buttonAction() {

  if(function == 1) Serial.println("SINGLE click");

  if(function == 2) Serial.println("DOUBLE click");

  if(function == 3) Serial.println("TRIPLE click");

  if(function == -1) Serial.println("SINGLE LONG click");

  if(function == -2) Serial.println("DOUBLE LONG click");

  if(function == -3) Serial.println("TRIPLE LONG click");
  
  function = 0;
}

Timer timer(1, buttonPoll);

void setup()
{
  Serial.begin(9600);
  pinMode(D4, INPUT_PULLUP);

  // Setup button timers (all in milliseconds / ms)
  // (These are default if not set, but changeable for convenience)
  button1.debounceTime   = 20;   // Debounce timer in ms
  button1.multiclickTime = 250;  // Time limit for multi clicks
  button1.longClickTime  = 1000; // time until "held-down clicks" register
  
  timer.start();
}


void loop()
{
  buttonAction();
  delay(400);
  buttonAction();
  delay(400);
}

1 Like

Great, thank you! i’ll try to give this a shot soon and see if it works with the rest of the code (doing MQTT calls, receiving them, etc).

ISRs are by default higher priority than any FreeRTOS thread - so no need to "elevate" them.

more, I wonder if it is (or will be) possible to use the second thread for user code, but i can make a new post for that perhaps after I do more searching :slight_smile:

The system thread should be reserved for system code - otherwise it wouldn't deserve the name :wink:
However, you can spin up more user threads than only the main application thread.

1 Like