Как одной кнопкой с прерыванием включать и отключать спящий режим?

Я написал программу для кухонного таймера. Таймер использует только один поворотный энкодер с кнопкой на его оси. Имеет режимы прямого и обратного счета. После обратного отсчета он переходит в состояние тревоги. Когда я затем нажимаю кнопку, будильник выключается и устройство переходит в спящий режим. Чтобы его разбудить, мне нужно либо нажать кнопку еще раз, либо покрутить энкодер.

Проблема: когда я нажимаю кнопку во время будильника, устройство переходит в спящий режим, но поскольку эта же кнопка используется для повторного пробуждения, таймер переключается в неправильное состояние и зависает в спящем режиме. По крайней мере, такова моя интерпретация проблемы:

Нажатие кнопки во время будильника запускает спящий режим ( 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();
}

, 👍0

Обсуждение

Вам необходимо перейти в состояние NONE при отпускании кнопки, а не при ее нажатии. Возможно, функция goToSleep подождет, пока кнопка не будет отпущена ( while(dititalRead(encBtnPin)==LOW){delay(10);}). В одном из своих проектов я использовал состояние ПРЕДСОН. Затем, когда кнопка будет отпущена и вы находитесь в состоянии PRE-SLEEP, перейдите в состояние SLEEP (NONE)., @Gerben

Спасибо. Эта одна строка (задержка) все исправила. Спасибо. Я буду помнить о состоянии ПРЕДСОН для будущих проектов., @fertchen


1 ответ


-1

Пара мыслей: может помочь ужасная диаграмма ваших связей. насколько я могу судить, вы подключаете линии (? данных?) кодировщика к линиям прерывания? Вы хотите вызвать прерывание при изменении входного значения? например, от 4 минут до 5 минут? ты решил убрать дребезжание переключателей? (только что видел, как вы это делали в Программном обеспечении... вместо этого рассмотрите простой RC-фильтр нижних частот. Возможно, устранение дребезга использует таймер, а затем конфликтует с остальной логикой...) Вам действительно нужно теперь предыдущее состояние кнопки? Еще одна вещь, которую я вижу, это то, что у вас очень много кода. Возьмите лист бумаги и нарисуйте диаграмму состояний конечного автомата, который вы собираетесь реализовать. а затем поделитесь этим здесь (это делает жизнь намного проще!)

,

Спасибо, что уделили время этому вопросу. Чтобы ответить на ваши вопросы по порядку: 1) Запустить прерывание при изменении входного значения? Да. 2) Нет. Только в спящем режиме. 3) да, но это решение кажется более простым в реализации. 4) Да, мне действительно нужно знать. диаграмма состояний: сделаю это в следующий раз, когда будет необходимо (надеюсь, что нет). Спасибо., @fertchen