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

Interrupt doesn't work with rotary encoder #12

Closed scottkelso closed 4 years ago

scottkelso commented 4 years ago

Description

I am trying to build a low power Arduino circuit with a rotary encoder as input. I need the system to go into deep sleep as much as possible to save battery power, apart from 2x conditions:

  1. A timed task needs to be executed
  2. User wakes up the system via the rotary encoder

Problem

I cannot get my attached interrupt to work when using DeepSleepScheduler. I have made sure that it works without:

// Rotary Encoder
#define CLK_PIN 2   // Generating interrupts using CLK signal
#define DT_PIN 3    // Reading DT signal
#define SW_PIN 4    // Reading Push Button switch

volatile boolean TurnDetected;  // need volatile for Interrupts
volatile boolean rotationdirection;  // CW or CCW rotation

// 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;
}

void setup() {
  Serial.begin(9600);

  // rotary encoder
  pinMode(CLK_PIN, INPUT);
  pinMode(DT_PIN, INPUT);  
  pinMode(SW_PIN, INPUT_PULLUP);

  attachInterrupt(digitalPinToInterrupt(CLK_PIN), isr, FALLING);
}

void loop() {
  if (TurnDetected)  {
    Serial.println("Turn detected!");
    TurnDetected = false;
  }
}

But when I do it with DeepSleepScheduler, the interrupt never activates. I have verified that the timed schedule works. Just not the interrupt.

#define AWAKE_INDICATION_PIN LED_BUILTIN
#define TASK_TIME 5000

// Rotary Encoder
#define CLK_PIN 2   // Generating interrupts using CLK signal
#define DT_PIN 3    // Reading DT signal
#define SW_PIN 4    // Reading Push Button switch

#include <DeepSleepScheduler.h>

volatile boolean TurnDetected;  // need volatile for Interrupts
volatile boolean rotationdirection;  // CW or CCW rotation

void ledOn() {
  digitalWrite(LED_BUILTIN, HIGH);
  Serial.println("LED On");
  scheduler.scheduleDelayed(ledOff, 2000);
}

void ledOff() {
  digitalWrite(LED_BUILTIN, LOW);
  Serial.println("LED Off");
  attachInterrupt(digitalPinToInterrupt(CLK_PIN), isrInterruptPin, FALLING);
}

// Interrupt routine runs if CLK goes from HIGH to LOW
void measureRotation ()  {
  if (digitalRead(CLK_PIN))
    rotationdirection = digitalRead(DT_PIN);
  else
    rotationdirection = !digitalRead(DT_PIN);
  TurnDetected = true;
  Serial.println("Turn detected!");
  scheduler.schedule(ledOn);
}

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

/*
 * Work should be done under only one of two conditions
 *   1) Rotary Encoder movement sends interrupt signal
 *   2) Task is previously set for a time
 */
void setup() {
  Serial.begin(9600);

  pinMode(LED_BUILTIN, OUTPUT);

  // rotary encoder
  pinMode(CLK_PIN, INPUT);
  pinMode(DT_PIN, INPUT);  
  pinMode(SW_PIN, INPUT_PULLUP);

  // 1) Rotary Encoder movement sends interrupt signal
  attachInterrupt(digitalPinToInterrupt(CLK_PIN), isrInterruptPin, FALLING);

  // 2) Task is previously set for a time
  scheduler.scheduleAt(ledOn, TASK_TIME);
}

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

If this is a misuse as appose to a bug, I apologies in advance. But any help would be much appreciated.

PRosenb commented 4 years ago

Hi @scottkelso, nice are you using my library.

Are you running it on Arduino Uno?

I think it has to do with the sleep mode the CPU is put by DeepSleepScheduler. You can configure what sleep mode is entered when the CPU is idle by defining the constant SLEEP_MODE before you include DeepSleepScheduler.h.

Please see documentation for details.

The default SLEEP_MODE is POWER_DOWN where not all interrupts are active. Please see documentation of the CPU on page 48.

You can quickly check if it's caused by that by setting SLEEP_MODE to SLEEP_MODE_IDLE: #define SLEEP_MODE SLEEP_MODE_IDLE

How did you go?

scottkelso commented 4 years ago

Thanks for the quick reply @PRosenb !

I'm running an Elegoo Mega 2560 and have since checked its datasheet.

Setting #define SLEEP_MODE SLEEP_MODE_IDLE as you suggested has allowed the interrupt to execute, which seems interesting as the datasheet p50 would suggest that the SLEEP_MODE_PWR_DOWN should still allow wake-up sources from INT7:0 and Pin Change, as I was doing with digitalPinToInterrupt(2).

In this mode, the external Oscillator is stopped, while the external interrupts, the 2-wire Serial Interface, and the Watchdog continue operating (if enabled). Only an External Reset, a Watchdog Reset, a Brown-out Reset, 2-wire Serial Interface address match, an external level interrupt on INT7:4, an external interrupt on INT3:0, or a pin change interrupt can wake up the MCU.

Is there any way to extend the library to apply other sleep modes allowed by ATMEGA2560?

PRosenb commented 4 years ago

There is no extension needed, you can set any sleep mode with #define SLEEP_MODE <any supported sleep mode by the CPU>

I remember that I also had some troubles with interrupts not firing and think it helped to use this library. https://github.com/GreyGnome/EnableInterrupt

scottkelso commented 4 years ago

Oh very cool. I will try some more things out this weekend and post anything I find out here. Thanks for the help!

PRosenb commented 4 years ago

Great, thanks mate. Have a nice weekend. Cheers

scottkelso commented 4 years ago

I have since had a chance to try some other sleep modes, however IDLE still seems to be the only mode which is allowing an interrupt on digitalPinToInterrupt(2)

#define SLEEP_MODE SLEEP_MODE_IDLE              // Interrupt works
#define SLEEP_MODE SLEEP_MODE_PWR_SAVE          // Interrupt doesn't works
#define SLEEP_MODE SLEEP_MODE_PWR_DOWN          // Interrupt doesn't works

As a less important side note: I am unsure where these SLEEP_MODE options are defined. I know based on my board's datasheet that it also supports modes like standby and ADCNRM, but I can't find any documentation on the wording of the variable. E.g. SLEEP_MODE_EXTENDED_STANDBY? Or SLEEP_MODE_EXT_STANDBY? The same way SLEEP_MODE_PWR_SAVE is not SLEEP_MODE_POWER_SAVE.

I have also tried using EnableInterrupt with no change. E.g. change attachInterrupt to enableInterrupt, and then remove detachInterrupt's according to EnableInterrupt's Debounce example. Interrupt still only fires on SLEEP_MODE_IDLE.

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.scheduleOnce(measure_rotation);
  //  // detach interrupt to prevent executing it multiple
  //  // times when touching more than once.
  //  detachInterrupt(digitalPinToInterrupt(CLK_PIN));
}

void setup() {
  Serial.begin(9600);
  ...
  pinMode(CLK_PIN,INPUT);
  pinMode(DT_PIN,INPUT);  
  pinMode(SW_PIN,INPUT_PULLUP);
  enableInterrupt(CLK_PIN, isr, FALLING);
  // attachInterrupt(CLK_PIN, isr, FALLING);
  ...
}

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

The only other thing I can think of to try is other pins other than 2 (which I will do soon).

PRosenb commented 4 years ago

Mmmm.. The example ScheduleFromInterrupt uses SLEEP_MODE_PWR_DOWN and works on my Arduino Uno. Could you try that one?