0

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();
}
asked Feb 18, 2019 at 8:36
2
  • 1
    You need to change to the NONE state on button release, instead of press. Of have the goToSleep function wait till the button is released (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. Commented Feb 18, 2019 at 16:50
  • 1
    Thank you. That one line (delay) fixed the whole thing. Thanks. I'll keep the PRE-SLEEP state in mind for future projects. Commented Feb 24, 2019 at 15:51

1 Answer 1

-1

couple of thoughts: a fritzing Diagram of your Connections may help. as far as i can tell you connect the (?data?) lines of the Encoder to the Interrupt lines? do you want to trigger an Interrupt on Input value Change? like from 4 minutes to 5minutes? did you Thing of debouncing your switches? (just saw you do it in Software... consider a simple RC lowpass instead. Maybe the debouncing uses a timer and then collides with the remaining logic...) do you really Need to now the previous Button state? another Thing i see is you just have an awful lot of Code. consider taking out a piece of paper and draft a state Diagram for the statemachine your About to realise. and then share it here (makes life a lot easier!)

answered Feb 18, 2019 at 15:50
1
  • Thank you for your time looking into this. To reply to your questions in order: 1) Trigger an interrupt on input value change? Yes. 2) No. Only during sleep mode. 3) yes but this solution seems easier to implement. 4) Yes, I really need to know. state diagram: I'll do it next time it is necessary (I hope not). Thank you. Commented Feb 24, 2019 at 15:53

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.