PRosenb / DeepSleepScheduler

DeepSleepScheduler is a lightweight, cooperative task scheduler library with configurable sleep and task supervision.
Apache License 2.0
83 stars 6 forks source link

Debounce Example #13

Closed scottkelso closed 3 years ago

scottkelso commented 4 years ago

Description

I would find it very helpful if you provided a debounce example and how that works with DeepSleepScheduler. I have tried with my own intuition and have been so far unsuccessful.

Problem

I am trying to fire a single interrupt on pin 2 of my Elegoo Mega2560 using the CLK pin of a rotary encoder. My aim is to wake up my system from sleep with a turn of the rotary encoder, turning on an LCD display eta. When I attempt to implement this myself using DeepSleepScheduler, instead of the expected one interrupt on rotation of the rotary encoder, I get 2 (I also see sometimes 1 and 3, although less frequently).

Steps to Reproduce

I suspect this is due to debounce and my bad implementation of it in your library. Below I have the non-DeepSleepScheduler version which works, followed by non-working versions which use DeepSleepScheduler.

// Interrupt routine runs if CLK goes from HIGH to LOW
void isr ()  {
  delay(4);  // delay for Debouncing
  if (digitalRead(CLK_PIN))
    rotationdirection = digitalRead(DT_PIN);
  else
    rotationdirection = !digitalRead(DT_PIN);
  TurnDetected = true;
}

This is a working example that doesn't use the DeepSleepScheduler library and works correctly - firing only once for each turn of the rotary encoder.

void measure_rotation ()  {
  if (digitalRead(CLK_PIN))
    rotationdirection = digitalRead(DT_PIN);
  else
    rotationdirection = !digitalRead(DT_PIN);
  TurnDetected = true;
  attachInterrupt(CLK_PIN, isr, FALLING);
}

void isr() {
  scheduler.scheduleDelayed(measure_rotation, 4);
  // detach interrupt to prevent executing it multiple
  // times when touching more than once.
  detachInterrupt(digitalPinToInterrupt(CLK_PIN));
}

This is what I assumed the same code might look like using DeepSleepScheduler. Notice the scheduleDelayed with the same 4 milliseconds for debouncing. Increasing the value here made no difference. I also tried delayed re-attaching of the interrupt, and combination of scheduleOnce and scheduleDelayed to make sure only one measure_rotation call was ever in the job queue. However, nearly every time there was a second delayed measure_rotation call.

// Interrupt routine runs if CLK goes from HIGH to LOW
void measure_rotation ()  {
  uint32_t interrupt_time = scheduler.getMillis();

  if (interrupt_time - last_interrupt_time > DEBOUNCE_DELAY) {
    //  delay(4);
    if (digitalRead(CLK_PIN))
      rotationdirection = digitalRead(DT_PIN);
    else
      rotationdirection = !digitalRead(DT_PIN);
    TurnDetected = true;
    Serial.println("Turn detected!");
    check_inputs();
  }

  last_interrupt_time = interrupt_time;

}

void isr() {
  scheduler.scheduleOnce(measure_rotation);
//  // detach interrupt to prevent executing it multiple
//  // times when touching more than once.
//  detachInterrupt(digitalPinToInterrupt(CLK_PIN));
}

This example was an attempt inspired by EnableInterrupt's Debounce example. This, unfortunately, did not allow any interrupt calls.

Any advice or guidance would be much appreciated. Thanks!

PRosenb commented 4 years ago

Hi @scottkelso, The library DeepSleepScheduler does not support debouncing as such. I only added the possibility to schedule a task once until it is actually executed. It does this by first removing all scheduled tasks of the same task and then add it again. This means that as soon as the tasks executes, it can be added again if the interrupt still triggers an execution. That's why you see multiple executions of your task measure_rotation(). I suggest to still follow the recommendations of the EnableInterrupt's Debounce example and call scheduleOnce() instead of digitalWrite() in that example. Do not use delay() in an ISR as it must run as short as possible and as I remember does delay() do nothing inside an ISR.

I suggest something like the following.

Cheers, Pete

#include "DeepSleepScheduler.h"

#define DEBOUNCE_DELAY_MS 100
#define INTERRUPT_PIN 2
#define LED_PIN LED_BUILTIN

unsigned long lastInterruptTimeMs = 0;
unsigned long isrCount = 0;

void task() {
  Serial.print("isrCount: ");
  Serial.println(isrCount);
  digitalWrite(LED_PIN, !digitalRead(LED_PIN));
}

void isrInterruptPin() {
  isrCount++;
  unsigned long interruptTimeMs = millis();

  if (interruptTimeMs - lastInterruptTimeMs > DEBOUNCE_DELAY_MS) {
    scheduler.scheduleOnce(task);
  }

  lastInterruptTimeMs = interruptTimeMs;
}

void setup() {
  Serial.begin(115200);
  delay(150);
  Serial.println("======================= start");

  pinMode(INTERRUPT_PIN, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), isrInterruptPin, FALLING);
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, HIGH);

  scheduler.acquireNoSleepLock();
}

void loop() {
  scheduler.execute();
}