Arudino получает команду прерывания ДО перехода в спящий режим, из-за чего он не получает никаких команд прерывания для пробуждения.

Я написал код для Arduino Nano, который при нажатии кнопки отправляет ИК-команду. Он настроен таким образом, что если в течение 10 секунд не будет получено ни одного нажатия кнопки, он перейдет в спящий режим до тех пор, пока не будет получено нажатие кнопки. Кнопка для отправки ИК-команды — это та же кнопка, которая используется для отправки прерывания пробуждения (кнопка, подключенная к контакту D2). При тестировании кода Arduino может переходить в спящий режим и просыпаться от прерывания 1-3 раза, прежде чем он внезапно получит сигнал прерывания непосредственно перед переходом в спящий режим. Когда это происходит, он застревает в спящем режиме и больше не будет принимать прерывание пробуждения. В настоящее время я использую выходы Serial Monitor и встроенный светодиод, чтобы определить, находится ли Arduino в спящем режиме или нет.

#include <IRLib.h>
#include <avr/sleep.h>
#define interruptPin 2

IRsend My_Sender;

const int button = 2;
int buttonState = 0;
int timeSinceCommand = 0;
const unsigned long eventInterval = 1000;
unsigned long previousTime = 0;

void setup() {
  Serial.begin(9600);
  pinMode(button, INPUT);
  pinMode(LED_BUILTIN, OUTPUT);         //удалить, как только код будет завершен и заработает
  pinMode(interruptPin, INPUT_PULLUP);
  digitalWrite(LED_BUILTIN, HIGH);      //удалить, как только код будет завершен и заработает
  Serial.println("---------------------------New Run---------------------------");
}

void loop() {
  while (timeSinceCommand < 10) {
    unsigned long currentTime = millis();
    buttonState = digitalRead(button);
    if (buttonState == HIGH) {
      My_Sender.send(SONY, 0xC90, 12);
      timeSinceCommand = 0;
      previousTime = currentTime;
    }
    if (currentTime - previousTime >= eventInterval) {
      timeSinceCommand++;
      previousTime = currentTime;
    }
  }
  if (timeSinceCommand >= 10) {
    Going_To_Sleep();
    timeSinceCommand = 0;
  }
}

void Going_To_Sleep() {
  sleep_enable();
  attachInterrupt(0, wakeUp, RISING);
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  digitalWrite(LED_BUILTIN, LOW);             //удалить, как только код будет завершен и заработает
  Serial.println("Arudino going to sleep!");  //удалить, как только код будет завершен и заработает
  delay(1000);                                //удалить, как только код будет завершен и заработает
  sleep_cpu();
  Serial.println("Arudino has waken up!");    //удалить, как только код будет завершен и заработает
  digitalWrite(LED_BUILTIN, HIGH);            //удалить, как только код будет завершен и заработает
}

void wakeUp() {
  Serial.println("Interrupt received!");      //удалить, как только код будет завершен и заработает
  sleep_disable();
  detachInterrupt(0);
}

Вот вывод Serial Monitor из нескольких тестов:

---------------------------New Run---------------------------
Arudino going to sleep!
Interrupt received!
Arudino has waken up!
Arudino going to sleep!
Interrupt received!
Arudino has waken up!
Interrupt received!
Arudino going to sleep!
---------------------------New Run---------------------------
Arudino going to sleep!
Interrupt received!
Arudino has waken up!
Interrupt received!
Arudino going to sleep!

, 👍2

Обсуждение

Попробуйте очистить флаг прерывания перед выполнением attachInterrupt(). Для внешнего прерывания контакта 2 на плате класса Uno это будет что-то вроде: EIFR = бит (INTF0);, @6v6gt

Я скопировал эту строку кода и поместил ее прямо перед attachInterrupt(), и, похоже, это решило мою проблему. Теперь все работает правильно. Спасибо!, @99natmar99


2 ответа


2

Здесь происходит несколько неприятных моментов... Во-первых, вы должны обратите внимание на несколько фактов:

  1. Кнопки подвержены механическому дребезгу. Вы нажимаете кнопку один раз, и довольно часто микроконтроллер обнаруживает множественные переходы на входной контакт.

  2. Если INT0 настроен на обнаружение нарастающих фронтов, любой нарастающий фронт на pin установит флаг прерывания INTF0, независимо от того, является ли прерывание включено или нет. Как только флаг установлен, он вызовет прерывание запрос, как только прерывание разрешено.

  3. Когда ЦП получает запрос на прерывание, он завершает выполнение текущая инструкция перед обслуживанием запроса.

Имея это в виду, вот объяснение того, что происходит:

Когда вы активируете Arduino с помощью кнопки, она часто срабатывает несколько переходов сигналов. Первый нарастающий фронт пробуждает Arduino и вызывает прерывание. Второй нарастающий фронт устанавливает только INTF0. флаг прерывания: прерывание «на удержании», потому что прерывания не сами по себе прерываемы. Однако перед выходом wakeUp() отключает обслуживание этого прерывания, что означает, что флаги просто остаются установленными. Это своего рода ожидающее прерывание.

Некоторое время спустя снова вызывается Going_To_Sleep(). attachInterrupt() разрешает прерывание и, поскольку флаг все еще установлен, (ожидание) немедленно срабатывает прерывание. Вот когда вы видите сообщение «Прерывание полученный!" печатается прямо перед тем, как Arduino заснет. Проблема заключается в том, что прерывание отключено с помощью wakeUp(). Через секунду, sleep_cpu() переводит процессор в спящий режим, и теперь наш источник пробуждения отключен, так что Arduino просто заперта в своих мечтах.

Есть еще одна проблема с этим сценарием. Обработчик прерывания якобы отключил сон, так почему же Arduino заснул в любом случае? Ответ заключается в том, что сон был случайно повторно включен set_sleep_mode() из-за гонки данных. вот такая разборка set_sleep_mode():

in   r24, 0x33 ; read SMCR (Sleep Mode Control Register) into r24
andi r24, 0xF1 ; clear the Sleep Mode Select bits
ori  r24, 0x04 ; select the Power Down mode
out  0x33, r24 ; write back SMCR

Первая инструкция в этой последовательности идет сразу после инструкции в attachInterrupt(), который включает прерывание INT0. Когда это инструкция генерирует запрос на прерывание (поскольку флаг прерывания уже был установлен), запрос на прерывание достигает ЦП, пока он уже выполняет указанную выше инструкцию in, и эта инструкция завершается до обслуживания запроса на прерывание. Это означает, что мы иметь теперь в регистре r24 копию регистра управления спящим режимом из до того, как прерывание будет обслужено. В этой копии бит SE (Sleep Включить) установлен. Несмотря на то, что wakeUp() очищает этот бит в фактическом SMCR, он остается установленным в нашей копии в r24. Затем out инструкция снова включает сон. Я экспериментировал с одним nop инструкции прямо перед set_sleep_mode(), и этого было достаточно, чтобы предотвратить гонку данных.

Итак, вот вам объяснение наблюдаемого поведения. я могу прийти с некоторыми предложениями по устранению проблемы, но это необходимо перед этим немного поспать (не беспокойтесь, я позабочусь о том, чтобы включить один источник прерывания перед выполнением sleep).

,

Хорошее объяснение, особенно о риске гонки данных, о котором я не думал. Когда я использую спящий режим с пробуждением либо по внешнему прерыванию, либо по прерыванию смены контакта, я обычно, если это совместимо с приложением, (а) имею пустую процедуру обслуживания прерывания и (б) не отсоединяю прерывание, которое все равно подключено в настройке. (). Команда sleep_disable() следует сразу за командой sleep_cpu(), а затем за любым другим кодом, отвечающим на пробуждение., @6v6gt


1

Я пользуюсь этой возможностью, чтобы включить простой базовый скетч сна, который устраняет возможный риск состояния гонки, который Эдгар Боне определил в стандартном подходе к использованию спящего режима для микроконтроллеров класса AVR. См. ссылку в скетче для дальнейшей оптимизации, если требуется, чтобы отключить АЦП, обнаружение снижения напряжения и т. д.

/*
Демонстрация спящего режима (Uno/Nano ATmega328P)
на основе https://www.gammon.com.au/power Sketch J
но урезан для демонстрационных целей, чтобы исключить некоторую оптимизацию (BOD и ADC и т. д.)
См. также: https://www.nongnu.org/avr-libc/user-manual/group__avr__sleep.html

Подключите кнопочный переключатель между контактом 2 и землей.
Нажать на кнопку. Он просыпается, мигает светодиодом, а затем спит.

*/

#include <avr/sleep.h>

const byte LED = 13;  // встроенный светодиод

void wake()
{
  // здесь ничего. Все сделано в цикле
}

void setup()
{
  Serial.begin( 115200 ) ;
  pinMode( LED, OUTPUT );
  pinMode( 2, INPUT_PULLUP ) ; 
  attachInterrupt ( digitalPinToInterrupt(2), wake, FALLING);
  set_sleep_mode (SLEEP_MODE_PWR_DOWN);
}

void loop()
{
  Serial.println( "waking . . . ") ;

  // вместо этого здесь мог бы быть ваш собственный код

  for ( int i = 0; i < 4; i++ ) {
    digitalWrite (LED, HIGH);
    delay (50);
    digitalWrite (LED, LOW);
    delay (50);
  }
  delay(3000) ;
  

  Serial.println( "Sleeping. . . " ) ;
  Serial.flush() ;
  delay(20) ; // разрешить запись сообщения

  sleep_mode();
  // *** просыпаемся здесь ***
}
,

Обратите внимание, что вызовы sleep_enable(), sleep_cpu() и sleep_disable() могут быть заменены одним вызовом sleep_mode(). Три функции, которые вы использовали, наиболее полезны, когда вы хотите атомарно проверить какое-то условие перед тем, как заснуть., @Edgar Bonet

@ЭдгарБонет. Хорошая точка зрения. Я обновил, чтобы включить их, потому что цель состояла в том, чтобы предоставить «как можно более простой», но работающий пример., @6v6gt