RTOS and scanning buttons

On an ESP32 I have 6 push buttons connected to digital inputs that need to be checked every 10 milliseconds.
For each one I keep a history of the last 3 values and only register a button press or release if there are 3 consecutive 0 or 1 values over that 30 millisecond period. This debounces and rejects accidental brief presses.

Is it considered OK to run this type of scanning process in its own RTOS task, or would it be better to use a timer interrupt to give consistent 10 millisecond sample intervals?

Yes. An ESP32 running freeRTOS would do this.

Also possible.

Curious: In this scenario which seems like what tasks are for, what makes the scan happen (the task run) at 100 Hz your preferred rate of looking at the buttons?

a7

Set up a simple 10ms millis TIMER; when expired, scan all your switches.

If there is a state change, increment a counter.

If there is no change in a switch state, reset that switch counter.

If that switch counter reaches a value of your choosing, service the switch change, reset the switch counter.

FreeRtos has a software timer built in that you can use.
They are very simple to use also. When the timer times out it can run your function.
See FreeRtos_Real_Time_Kernal.pdf from the FreeRTOS site. It is a downloadable pdf that
describes with examples how to use FreeRTOS. Chapter 5 describes how to use the timer
with examples.

There are several ways to trigger a task to run.

This task is triggered by freeRTOS to run after a time out,

void fmqttWatchDog( void * paramater )
{
  int UpdateImeTrigger = 86400; //seconds in a day
  int UpdateTimeInterval = 85000; // get another reading when = UpdateTimeTrigger
  int maxNonMQTTresponse = 12;
  TickType_t xLastWakeTime = xTaskGetTickCount();
  const TickType_t xFrequency = 5000; //delay for mS
  for (;;)
  {
    xLastWakeTime = xTaskGetTickCount();
    vTaskDelayUntil( &xLastWakeTime, xFrequency );
    xSemaphoreTake( sema_mqttOK, portMAX_DELAY ); // update mqttOK
    mqttOK++;
    xSemaphoreGive( sema_mqttOK );
    if ( mqttOK >= maxNonMQTTresponse )
    {
      ESP.restart();
    }
    UpdateTimeInterval++; // trigger new time get
    if ( UpdateTimeInterval >= UpdateImeTrigger )
    {
      TimeSet = false; // sets doneTime to false to get an updated time after a days count of seconds
      UpdateTimeInterval = 0;
    }
  }
  vTaskDelete( NULL );
} //void fmqttWatchDog( void * paramater )

xFrequency sets the task timing frequency.

1 Like

@LarryD does that do what @mikb55 wants ? If the switch does not bounce then there will only be a single state change no matter how long you hold down the key hence the counter will not increment

Another way to trigger tasks is through the use of events.

In this case an event group is created.

#define evtAnemometer  ( 1 << 0 )
#define evtRainFall    ( 1 << 1 )
#define evtParseMQTT   ( 1 << 2 )
EventGroupHandle_t eg;
#define OneMinuteGroup ( evtAnemometer | evtRainFall )

hw_timer_t * timer = NULL;

The OneMinuteGroup when triggered will trigger 2 tasks
A hardware timer is declared to trigger the event group.

void IRAM_ATTR onTimer()
{
  BaseType_t xHigherPriorityTaskWoken;
  xEventGroupSetBitsFromISR(eg, OneMinuteGroup, &xHigherPriorityTaskWoken);
} // void IRAM_ATTR onTimer()

The timer callback to trigger the event group.

void setup()
{
  eg = xEventGroupCreate(); // get an event group handle
  // hardware timer 4 set for one minute alarm
  timer = timerBegin( 3, 80, true );
  timerAttachInterrupt( timer, &onTimer, true );
  timerAlarmWrite(timer, 60000000, true);
  timerAlarmEnable(timer);
  /* Initialize PCNT's counter */

The event groups is given a handle and the timer is setup and started.

void fRainFall( void *pvParemeters )
{
  int16_t click = 0; //count tipping bucket clicks
  pcnt_counter_pause( PCNT_UNIT_1 );
  pcnt_counter_clear( PCNT_UNIT_1 );
  pcnt_counter_resume( PCNT_UNIT_1 );
  for  (;;)
  {
    xEventGroupWaitBits (eg, evtRainFall, pdTRUE, pdTRUE, portMAX_DELAY);

The task set to wait for the event trigger to occur.

1 Like

Let’s say when the counter reaches 3 you service the switch change.

The 1st 10ms timeout, when there is a change in switch state, the counter will be incremented (1) (the lastSwitchState is not updated yet).

The 2nd 10ms timeout, if the switch is still in that state, the counter is again incremented (2) (the lastSwitchState is not updated yet).

The 3rd 10ms timeout, if the switch is still in that state, the counter is again incremented (3).

When the counter is 3, the lastSwitchState is updated, then counter is reset to 0, we then check to see if the switch was closed or opened etc.


If during the 2nd 10ms interval a bounce on the switch is back to where things are considered steady state, the counter is reset to 0 and the change in the switch is rejected.

These are the 2 statements that I cannot reconcile

I was under the impression that this is a cooperative timer, and therefore the running of a particular scheduled function will be delayed if another scheduled function is still running.

If using the software timer is an issue then use one of the hardware timers to trigger the tasks; post#8.

Am I missing something, it wont be the first time :woozy_face:

This was what I was trying to explain, things have been slowed down to show things in the serial monitor but you should get the idea.

//********************************************^************************************************
//  FileName.ino
//  LarryD
//
//  Version   YY/MM/DD     Comments
//  =======   ========     ========================================================
//  1.00      YY/MM/DD     Running code
//
//
//
//********************************************^************************************************

#define LEDon                              HIGH
#define LEDoff                             LOW

#define ENABLED                            true
#define DISABLED                           false

#define PUSHED                             LOW
#define RELEASED                           HIGH


//********************************************^************************************************

const byte startSwitch                     = 2;

const byte testLED                         = 12;
const byte heartbeatLED                    = 13;

const byte triggerAt                       = 10;

byte switch2Counter                        = 0;
byte lastStartSwitch                       = RELEASED;

//Timing stuff
unsigned long heartbeatMillis;
unsigned long switchMillis;

const unsigned long heartBeatInterval      = 500ul;      //1/2 second
const unsigned long sampleInterval         = 100ul;      //100 milliseconds


//                                       s e t u p ( )
//********************************************^************************************************
//
void setup()
{
  Serial.begin(115200);

  pinMode(testLED , OUTPUT);
  pinMode(heartbeatLED , OUTPUT);

  pinMode(startSwitch , INPUT_PULLUP);

} //END of   setup()


//                                       l o o p ( )
//********************************************^************************************************
//
void loop()
{
  //*********************************
  //is it time to toggle the heartbeatLED ?
  if (millis() - heartbeatMillis >= heartBeatInterval)
  {
    //restart this TIMER
    heartbeatMillis = millis();

    //toggle the heartbeatLED
    digitalWrite(heartbeatLED, !digitalRead(heartbeatLED));

  }

  //*********************************
  //is it time to check the switches ?
  if (millis() - switchMillis >= sampleInterval)
  {
    //restart this TIMER
    switchMillis = millis();

    checkSwitches();

  }


  //*********************************
  //other non blocking code
  //*********************************

} //END of   loop()


//                              c h e c k S w i t c h e s ( )
//********************************************^************************************************
//
void checkSwitches()
{
  byte currentState;

  //*********************************                               s t a r t S w i t c h
  currentState = digitalRead(startSwitch);

  //has this switch changed state ?
  if (lastStartSwitch != currentState)
  {
    switch2Counter++;
    Serial.println(switch2Counter);

    //have we had 10 consecutive reads at this state ?
    if (switch2Counter >= triggerAt)
    {
      //we have had a valid change, reset for the next one
      switch2Counter = 0;

      //update to the new switch state
      lastStartSwitch = currentState;

      //******************
      //has the switch been pushed ?
      if (currentState == PUSHED)
      {
        //toggle testLED
        digitalWrite(testLED, !digitalRead(testLED));

      }

      //******************
      //switch must have been released
      else
      {
        //do somthing

      }
    }

  } //END of     if (lastStartSwitch != currentState)

  else
  {
    //there was no valid chnge in state
    switch2Counter = 0;
  }

  //*********************************
  //future switches go here
  //*********************************

} //END of   checkSwitches()


//********************************************^************************************************
//                            E N D   o f   s k e t c h   c o d e
//********************************************^************************************************

I don't know what else you have going on in your application. Just giving you some options, bro.
From what I read in the kernel documentation, the timer task priority can be set in the FreeRTOSConfig.h file. I don't know how the espressif deals with timer task priority though as they have modified vanilla FreeRTOS.

@LarryD thanks for posting the code. I see what you mean but it is only clear after seeing the code

Sorry, I turned 70 :person_white_hair:

No need to apologise, although it is nice that one of you youngsters respects an elder member :grinning:

I don't use RTOS but my fast debounce works like yours, it looks for a stable pin after a first change detect. My way looks at an 8 read history over 4ms where you look at 3 reads over 10ms each.

I keep pin reads in the bits of a pinStateHistory byte, the pin read twice per ms for a 3500 us stable period debounce. I read every ms, 7 ms stable.

Each read, 1st the history bits are shifted left 1, same as a x 2. Bit 7 is lost, bit 0 = 0.
And the the read us added and that's it except for timing the reads. Bit 10 of millis() toggles twice a ms, I read on that change. My timer is the millis clock.

Time enough to switch to hexadecimal or Celsius when speaking of age.

46 (hex) or even better 21 (Celsius).

Never look back.

a7

1 Like

Whatever you do, don't use Octal or you will feel very old indeed

1 Like