I have written a program for a kitchen timer. The timer uses just one single rotary encoder with a push button on its axis. It has modes for counting up and for counting down. After counting down it switches into alarm state. When I then push the button the alarm stops and the device goes into sleep mode. To wake it up I need to either push the button again or twist the encoder.
The problem: When I push the button during alarm the device does go into sleep mode but since that very same button is used to wake it up again, the timer switches into the wrong state and freezes in sleep mode. At least that is my interpretation of the problem:
Pushing the button during alarm triggers sleep mode ( goToSleep() ). During goToSleep I attach to that very button the interrupt that wakes the device.
The solution must be simple, I just can't see it. I just need to ignore any button input beween calling goToSleep() and sleepcpi();. Right?
How can I make this work?
#include <avr/sleep.h>
#include <Rotary.h>
#include <Bounce2.h>
unsigned long timerSeconds = 0; // Remaining countdown time in seconds. An unsigned int would be enough for more than 18 HOURS!
unsigned long alarmTime = 0; // The absolute point in time of the alarm
// Variables for updates
unsigned long currentMillis = 0;
unsigned long previousMillis = 0;
unsigned long currCountMillis = 0;
unsigned long prevCountMillis = 0;
// Setup Pins & Variables
Rotary rotary = Rotary(3, 4); // Needs to be pin 2 or 3 in order to use as interrupt
const byte encBtnPin = 2; // Needs to be pin 2 or 3 in order to use as interrupt
const byte beepPin = 5;
byte encBtnState;
byte prevEncBtnState;
bool encBtnPushed = false;
Bounce debouncer = Bounce();
// Setup state machine
typedef enum { NONE, SETTING, COUNTING_DOWN, COUNTING_UP, WAS_COUNTING, ALARM } states;
states state = NONE;
void setup() {
Serial.begin(9600); //remove after testing
pinMode(encBtnPin, INPUT_PULLUP);
debouncer.attach(encBtnPin);
debouncer.interval(10);
pinMode(beepPin, OUTPUT);
// Turn off ADC
ADCSRA = 0;
}
void loop() {
Serial.println("Top of loop"); // Remove after testing
Serial.print("State: "); // Remove after testing
Serial.println(state); // Remove after testing
switch (state) {
case NONE:
goToSleep();
break;
case SETTING:
setCountdown();
break;
case COUNTING_DOWN:
startCountdown();
break;
case COUNTING_UP:
startCounter();
break;
case ALARM:
alarm();
break;
}
}
void readEncButton() {
encBtnState = digitalRead(encBtnPin); // Check if button state has changed
if (encBtnState != prevEncBtnState) {
if (encBtnState == LOW) {
encBtnPushed = true;
beep();
}
}
prevEncBtnState = encBtnState;
if (encBtnPushed) { // If Button has been pushed ...
encBtnPushed = false; // ... reset button and do one of the following ...
switch (state) { // Core state machine (state changes)
case NONE:
break;
case SETTING:
if (timerSeconds != 0) { // Start countdown only if timer is not = 0 ...
state = COUNTING_DOWN;
Serial.print("State: "); // Remove after testing
Serial.println(state); // Remove after testing
}
else { // If timer = 0, go to sleep
state = NONE;
Serial.print("State: "); // Remove after testing
Serial.println(state); // Remove after testing
}
break;
case COUNTING_DOWN:
timerSeconds = (timerSeconds / 10) * 10; // Set last digit to zero ...
state = SETTING; // ... then go back to setting time
break;
case COUNTING_UP:
timerSeconds = 0;
state = SETTING;
break;
case ALARM:
state = NONE;
break;
}
}
}
void setCountdown() {
// Read rotary encoder input
unsigned long timeOut = millis();
while (state == SETTING) {
unsigned char result = rotary.process();
if (result == DIR_CW) {
timerSeconds += 10L;
timeOut = millis();
writeDisplay();
}
else if (result == DIR_CCW) {
timerSeconds -= 10L;
timeOut = millis();
writeDisplay();
}
if ( millis() >= timeOut + 30000L ) { // Go to sleep after 30 seconds idle
state = NONE;
}
// Read rotary encoder button
readEncButton();
}
}
void startCountdown() {
alarmTime = millis() + (timerSeconds * 1000L); // Determine absolute alarm point in time in milliseconds (only once on countdown start)
while (state == COUNTING_DOWN) {
updateDisplay();
timerSeconds = ( alarmTime - millis() ) / 1000L; // Calculate new remaining number-of-seconds
readEncButton();
}
}
void startCounter() {
while (state == COUNTING_UP) {
currCountMillis = millis();
updateDisplay();
if (currCountMillis - prevCountMillis > 999) { // Update counter once per second
prevCountMillis = currCountMillis;
timerSeconds ++;
}
readEncButton();
}
}
void updateDisplay () {
currentMillis = millis();
// Only once every 1000ms ...
if (currentMillis - previousMillis > 999) {
previousMillis = currentMillis; // Reset the timer for display updates
writeDisplay();
}
}
void writeDisplay() {
int cHours = timerSeconds / 3600L; // Update variables to display
int cMinutes = timerSeconds % 3600L / 60;
int cSeconds = timerSeconds % 3600L % 60;
// Format and print display data
if (cHours == 0 || cHours < 10) {Serial.print("0");} // Add leading "0" if necessary
Serial.print(cHours);
Serial.print(":");
if (cHours == 0 || cMinutes < 10) {Serial.print("0");} // Add leading "0" if necessary
Serial.print(cMinutes);
Serial.print(":");
if (cSeconds == 0 || cSeconds < 10) {Serial.print("0");} // Add leading "0" if necessary
Serial.println(cSeconds);
// Alarm trigger follows here since alarm is supposed to sound AFTER the nillth second has been displayed
if (state == COUNTING_DOWN && timerSeconds == 0) {
state = ALARM;
}
}
void beep() {
tone(beepPin, 1000, 120);
}
void alarm() {
int alarmRep = 0;
attachInterrupt(digitalPinToInterrupt(encBtnPin), endAlarm, LOW);
while (state == ALARM && alarmRep < 30) {
for (int i = 0; i < 4; i ++) {
tone(beepPin, 2000, 100);
delay(120);
}
delay(500);
alarmRep ++;
}
goToSleep(); // Go to sleep after alarm (state is changed to NONE in sleepmode-setup)
//state = NONE; // Need condition to prevent state = NONE in case previous while-loop was left due to state change
}
void endAlarm() {
detachInterrupt(digitalPinToInterrupt(encBtnPin));
state = NONE;
}
/* Sleep mode and stuff *
************************/
void wakeToSet() {
sleep_disable();
detachInterrupt(digitalPinToInterrupt(3)); // Hard-coded to pin 3
detachInterrupt(digitalPinToInterrupt(encBtnPin)); // Encoder button can no longer wake up CPU
state = SETTING;
}
void wakeToCount() {
sleep_disable();
Serial.println("Detaching interrupts ..."); // Remove after testing
detachInterrupt(digitalPinToInterrupt(3)); // Hard-coded to pin 3
detachInterrupt(digitalPinToInterrupt(encBtnPin)); // Encoder button can no longer wake up CPU
beep();
Serial.println("Waking up to count ..."); // Remove after testing
state = COUNTING_UP;
}
void goToSleep() {
Serial.println("Starting goToSleep()"); // Remove after testing
timerSeconds = 0; // Reset everything
alarmTime = 0;
state = NONE;
sleep_enable();
attachInterrupt(digitalPinToInterrupt(3), wakeToSet, LOW); // Hard-coded to pin 3
attachInterrupt(digitalPinToInterrupt(encBtnPin), wakeToCount, LOW); // Encoder button can wake up machine
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
Serial.print("State: "); // Remove after testing
Serial.println(state); // Remove after testing
Serial.println("Going to sleep now..."); // Remove after testing
delay(250);
sleep_cpu();
}
while(dititalRead(encBtnPin)==LOW){delay(10);}
). I used a PRE-SLEEP state in one of my projects. Then when the button is released and you are in the PRE-SLEEP state, move to the SLEEP (NONE) state.