Как одной кнопкой с прерыванием включать и отключать спящий режим?
Я написал программу для кухонного таймера. Таймер использует только один поворотный энкодер с кнопкой на его оси. Имеет режимы прямого и обратного счета. После обратного отсчета он переходит в состояние тревоги. Когда я затем нажимаю кнопку, будильник выключается и устройство переходит в спящий режим. Чтобы его разбудить, мне нужно либо нажать кнопку еще раз, либо покрутить энкодер.
Проблема: когда я нажимаю кнопку во время будильника, устройство переходит в спящий режим, но поскольку эта же кнопка используется для повторного пробуждения, таймер переключается в неправильное состояние и зависает в спящем режиме. По крайней мере, такова моя интерпретация проблемы:
Нажатие кнопки во время будильника запускает спящий режим ( goToSleep() ). Во время goToSleep я прикрепляю к этой самой кнопке прерывание, которое будит устройство.
Решение должно быть простым, я просто его не вижу. Мне просто нужно игнорировать любой ввод кнопок между вызовами goToSleep() и SleepCPI();. Верно?
Как мне это сделать?
#include <avr/sleep.h>
#include <Rotary.h>
#include <Bounce2.h>
unsigned long timerSeconds = 0; // Оставшееся время обратного отсчета в секундах. Беззнакового int хватило бы более чем на 18 ЧАСОВ!
unsigned long alarmTime = 0; // Абсолютный момент времени тревоги
// Переменные для обновлений
unsigned long currentMillis = 0;
unsigned long previousMillis = 0;
unsigned long currCountMillis = 0;
unsigned long prevCountMillis = 0;
// Настройка контактов & Переменные
Rotary rotary = Rotary(3, 4); // Должен быть контактом 2 или 3, чтобы использовать его в качестве прерывания
const byte encBtnPin = 2; // Должен быть контактом 2 или 3, чтобы использовать его в качестве прерывания
const byte beepPin = 5;
byte encBtnState;
byte prevEncBtnState;
bool encBtnPushed = false;
Bounce debouncer = Bounce();
// Настройка конечного автомата
typedef enum { NONE, SETTING, COUNTING_DOWN, COUNTING_UP, WAS_COUNTING, ALARM } states;
states state = NONE;
void setup() {
Serial.begin(9600); //удаляем после тестирования
pinMode(encBtnPin, INPUT_PULLUP);
debouncer.attach(encBtnPin);
debouncer.interval(10);
pinMode(beepPin, OUTPUT);
// выключаем АЦП
ADCSRA = 0;
}
void loop() {
Serial.println("Top of loop"); // Удалить после тестирования
Serial.print("State: "); // Удалить после тестирования
Serial.println(state); // Удалить после тестирования
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); // Проверяем, изменилось ли состояние кнопки
if (encBtnState != prevEncBtnState) {
if (encBtnState == LOW) {
encBtnPushed = true;
beep();
}
}
prevEncBtnState = encBtnState;
if (encBtnPushed) { // Если кнопка была нажата ...
encBtnPushed = false; // ... кнопку сброса и делаем одно из следующих действий...
switch (state) { // Основной конечный автомат (изменения состояния)
case NONE:
break;
case SETTING:
if (timerSeconds != 0) { // Начинаем обратный отсчет, только если таймер не = 0 ...
state = COUNTING_DOWN;
Serial.print("State: "); // Удалить после тестирования
Serial.println(state); // Удалить после тестирования
}
else { // Если таймер = 0, переходим в режим сна
state = NONE;
Serial.print("State: "); // Удалить после тестирования
Serial.println(state); // Удалить после тестирования
}
break;
case COUNTING_DOWN:
timerSeconds = (timerSeconds / 10) * 10; // Устанавливаем последнюю цифру в ноль...
state = SETTING; // ... затем возвращаемся к настройке времени
break;
case COUNTING_UP:
timerSeconds = 0;
state = SETTING;
break;
case ALARM:
state = NONE;
break;
}
}
}
void setCountdown() {
// Чтение входных данных поворотного энкодера
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 ) { // Переход в режим сна после 30 секунд простоя
state = NONE;
}
// Чтение кнопки поворотного энкодера
readEncButton();
}
}
void startCountdown() {
alarmTime = millis() + (timerSeconds * 1000L); // Определить абсолютную точку тревоги во времени в миллисекундах (только один раз при начале обратного отсчета)
while (state == COUNTING_DOWN) {
updateDisplay();
timerSeconds = ( alarmTime - millis() ) / 1000L; // Вычисляем новое оставшееся количество секунд
readEncButton();
}
}
void startCounter() {
while (state == COUNTING_UP) {
currCountMillis = millis();
updateDisplay();
if (currCountMillis - prevCountMillis > 999) { // Обновляем счетчик раз в секунду
prevCountMillis = currCountMillis;
timerSeconds ++;
}
readEncButton();
}
}
void updateDisplay () {
currentMillis = millis();
// Только раз в 1000 мс...
if (currentMillis - previousMillis > 999) {
previousMillis = currentMillis; // Сбрасываем таймер обновлений дисплея
writeDisplay();
}
}
void writeDisplay() {
int cHours = timerSeconds / 3600L; // Обновляем переменные для отображения
int cMinutes = timerSeconds % 3600L / 60;
int cSeconds = timerSeconds % 3600L % 60;
// Форматируем и печатаем отображаемые данные
if (cHours == 0 || cHours < 10) {Serial.print("0");} // При необходимости добавляем начальный "0"
Serial.print(cHours);
Serial.print(":");
if (cHours == 0 || cMinutes < 10) {Serial.print("0");} // При необходимости добавляем начальный "0"
Serial.print(cMinutes);
Serial.print(":");
if (cSeconds == 0 || cSeconds < 10) {Serial.print("0");} // При необходимости добавляем начальный "0"
Serial.println(cSeconds);
// Здесь следует триггер тревоги, поскольку сигнал тревоги должен звучать ПОСЛЕ отображения нулевой секунды
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(); // Переход в режим сна после тревоги (состояние меняется на NONE в настройке режима сна)
//состояние = НЕТ; // Требуется условие для предотвращения состояния = NONE в случае, если предыдущий цикл while был оставлен из-за изменения состояния
}
void endAlarm() {
detachInterrupt(digitalPinToInterrupt(encBtnPin));
state = NONE;
}
/* Sleep mode and stuff *
************************/
void wakeToSet() {
sleep_disable();
detachInterrupt(digitalPinToInterrupt(3)); // Жестко запрограммировано на контакте 3
detachInterrupt(digitalPinToInterrupt(encBtnPin)); // Кнопка кодировщика больше не может разбудить процессор
state = SETTING;
}
void wakeToCount() {
sleep_disable();
Serial.println("Detaching interrupts ..."); // Удалить после тестирования
detachInterrupt(digitalPinToInterrupt(3)); // Жестко запрограммировано на контакте 3
detachInterrupt(digitalPinToInterrupt(encBtnPin)); // Кнопка кодировщика больше не может разбудить процессор
beep();
Serial.println("Waking up to count ..."); // Удалить после тестирования
state = COUNTING_UP;
}
void goToSleep() {
Serial.println("Starting goToSleep()"); // Удалить после тестирования
timerSeconds = 0; // Сбрасываем все
alarmTime = 0;
state = NONE;
sleep_enable();
attachInterrupt(digitalPinToInterrupt(3), wakeToSet, LOW); // Жестко запрограммировано на контакте 3
attachInterrupt(digitalPinToInterrupt(encBtnPin), wakeToCount, LOW); // Кнопка кодировщика может разбудить машину
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
Serial.print("State: "); // Удалить после тестирования
Serial.println(state); // Удалить после тестирования
Serial.println("Going to sleep now..."); // Удалить после тестирования
delay(250);
sleep_cpu();
}
@fertchen, 👍0
Обсуждение1 ответ
Пара мыслей: может помочь ужасная диаграмма ваших связей. насколько я могу судить, вы подключаете линии (? данных?) кодировщика к линиям прерывания? Вы хотите вызвать прерывание при изменении входного значения? например, от 4 минут до 5 минут? ты решил убрать дребезжание переключателей? (только что видел, как вы это делали в Программном обеспечении... вместо этого рассмотрите простой RC-фильтр нижних частот. Возможно, устранение дребезга использует таймер, а затем конфликтует с остальной логикой...) Вам действительно нужно теперь предыдущее состояние кнопки? Еще одна вещь, которую я вижу, это то, что у вас очень много кода. Возьмите лист бумаги и нарисуйте диаграмму состояний конечного автомата, который вы собираетесь реализовать. а затем поделитесь этим здесь (это делает жизнь намного проще!)
Спасибо, что уделили время этому вопросу. Чтобы ответить на ваши вопросы по порядку: 1) Запустить прерывание при изменении входного значения? Да. 2) Нет. Только в спящем режиме. 3) да, но это решение кажется более простым в реализации. 4) Да, мне действительно нужно знать. диаграмма состояний: сделаю это в следующий раз, когда будет необходимо (надеюсь, что нет). Спасибо., @fertchen
- Как сгенерировать аппаратное прерывание в mpu6050 для пробуждения Arduino из режима SLEEP_MODE_PWR_DOWN?
- Датчик PIR и сон (прерывание) на Mega2560
- POWER_MODE_IDLE пробуждается при любом изменении ввода?
- Как разбудить Arduino с помощью rtc?
- attiny85 сбрасывает себя вместо процедуры пробуждения
- ATtiny85 со сном и последовательным портом
- Порог сигнала пробуждения
- Arudino получает команду прерывания ДО перехода в спящий режим, из-за чего он не получает никаких команд прерывания для пробуждения.
Вам необходимо перейти в состояние NONE при отпускании кнопки, а не при ее нажатии. Возможно, функция goToSleep подождет, пока кнопка не будет отпущена (
while(dititalRead(encBtnPin)==LOW){delay(10);}
). В одном из своих проектов я использовал состояние ПРЕДСОН. Затем, когда кнопка будет отпущена и вы находитесь в состоянии PRE-SLEEP, перейдите в состояние SLEEP (NONE)., @GerbenСпасибо. Эта одна строка (задержка) все исправила. Спасибо. Я буду помнить о состоянии ПРЕДСОН для будущих проектов., @fertchen