Одна кнопка для сна и пробуждения

NM
NM аватар
Offline
Зарегистрирован: 17.02.2018

Всем привет! Не могу заставить Дуину засыпать и просыпаться по прерыванию на кнопке.

На просторах вашего Интернета тема сна Ардуино достаточно распространена. Многие используют прерывания 0 и 1 на пинах 2 и 3 для того, чтобы выводить микроконтроллер Atmega328P из сна, и, реже, вводить его в сон.

Тем не менее, мне не посчастливилось отыскать даже намека на код, который использует единственную кнопку одновременно для ввода в сон и для пробуждения. Более того, мои любительские эксперименты с кодом привели к результату, что МК с легкостью выполняет одно из двух: либо засыпает по прерыванию, либо пробуждается. Но заставить выполнять оба действия разом у меня не получилось:

Код, который уводит в сон по нажатию (сенсорной) кнопки. За сигнал нажатия принимается логическая единица. Стягивающий резистор имеется.


#include <avr/sleep.h>

int TOUCH = 3;
int LED = 13;

void setup(){
  pinMode(TOUCH, INPUT);
  pinMode(LED, OUTPUT);
  delay(3000);
}

void Interrupt(void){
  sleep_disable();
  detachInterrupt(1);
}

void Sleep(void){
  digitalWrite(LED, LOW);  // (3) Светодиод гаснет при нажатии кнопки.
  delay(250);
  sleep_enable();
  attachInterrupt(1, Interrupt, HIGH);
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  sleep_mode();   // (4) С этого момента спим.
  
  sleep_disable();   // (5) С этого момента пробуждаемся. Должны вернуться в loop(), но светодиод не загорается.
  delay(250);
}

void loop(){
  digitalWrite(LED, HIGH);  // (1) Светодиод горит
  if (digitalRead(TOUCH)){  // (2) Если кнопка нажата, то вызываем фнкцию засыпания.
    Sleep();
  }
}

 

Код, который выводит из сна по нажатию (сенсорной) кнопки. За сигнал нажатия принимается логическая единица.Стягивающий резистор имеется.


#include <avr/sleep.h>

int TOUCH = 3;
int LED = 13;

void setup(){
  pinMode(TOUCH, INPUT);
  pinMode(LED, OUTPUT);
  digitalWrite(LED, HIGH);  // (1) Светодиод горит.
  delay(3000);
  Sleep();  // (2) Уходим в сон.
}

void Interrupt(void){
  sleep_disable();
  detachInterrupt(1);
}

void Sleep(void){
  digitalWrite(LED, LOW);  // (3) Светодиод гаснет.
  delay(250);
  sleep_enable();
  attachInterrupt(1, Interrupt, HIGH);
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  sleep_mode();   // (4) С этого моента спим.
  
  sleep_disable();   // (5) С этого момента пробуждаемся при нажатой кнопке.
  delay(250);
}

void loop(){
  digitalWrite(LED, HIGH);    // (6) Пробудились, светодиод загорелся.
  delay(3000);
  Sleep(); // (7) Ушли в сон, но пробуждение по кнопке сработало только один раз.
}

Учитывая, насколько распространенной может являться задача, меня раздирает любопытство: 1) почему на первых трех страницах выдачи "arduino sleep button"крупнейших поисковиков нет тем про одновременное использование кнопки для ввода и вывода из сна; 2) возможно ли это в принципе, и если да, то что я упустил.

Буду благодарен за ответы!

andycat
andycat аватар
Offline
Зарегистрирован: 07.09.2017

Я может не понимаю, а общий скетч где который не работает?

andycat
andycat аватар
Offline
Зарегистрирован: 07.09.2017

Скорее проблема в дребезге контактов, в коде засыпания поставить паузу минимум на секунду, после которой уже спать

NM
NM аватар
Offline
Зарегистрирован: 17.02.2018

Привет! В обоих скетчах есть то, что работает, и то что не работает (см. комментарии):

1) МК уходит в сон по нажатию, но не выходит из него по нажатию.

2) МК выходит из сна по нажатию, но не входит в него по нажатию.

 

Кнопка сенсорная, должна срабатывать без дребезга. Я попробовал задержку 500 и 1000 против дребезга. Результат тот же. 

Kakmyc
Offline
Зарегистрирован: 15.01.2018

Напиши функцию , которая будет запускаться при FALLING или RISING(в зависимости от того как кнопку прицепил). В теле проверка предыдущего состояния с его изменением на противоположное. И в зависимости от текущего, либо спать, либо вставать.

Kakmyc
Offline
Зарегистрирован: 15.01.2018

В твоих скетчах, ты сначала вызываешь функцию sleep_enable(), а потом включаешь прерывание.
Не знаю, что там у тебя в библиотеке, но похоже что прерывание уже просто не проверяется. Может не стоит его выключать в 16ой строке ?

Kakmyc
Offline
Зарегистрирован: 15.01.2018

И по мне прерывание внутри функции, это как то неправильно. Должно быть наоборот. Логичнее функцию вызывать по прерыванию.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

NM, для начала прочитайте п.п. 10.6 и 13.1 даташита - в режиме power_down прерывание надо настраивать на низкий уровен, асинхронно только он распознаётеся. Поэтому, чтобы надёжно пробуждалась, подтягивайте к питанию, а прерывание на LOW. Ну и дребезг надо уирать или программно, или аппаратно.

NM
NM аватар
Offline
Зарегистрирован: 17.02.2018

Всем привет! Заставил Дуину засыпать и просыпаться по прерыванию на кнопке.

Проблема была в малом: открепление прерывания detachInterrupt(1); не срабатывало из функции-обработчика прерываний Interrupt().

Вот проверенный скетч, который надежно уводит МК в сон и выводит из сна по нажатию на единственную кнопку. Также, код использует функцию отключения переферии и Brown Out Detector-а, благодаря чему потребелние в спящем режиме настолько мало, что измерить его моим простеньким амперметром не удалось.

// Код, который уводит в сон по нажатию (сенсорной) кнопки и выводит из сна по той-же кнопке. Стягивающий резистор имеется.

#include <avr/sleep.h>
#include <avr/power.h>     


int TOUCH = 3;
int LED = 13;

void setup(){
  pinMode(TOUCH, INPUT);
  pinMode(LED, OUTPUT);
}

void Interrupt(void){
}

void Sleep(void){
  digitalWrite(LED, LOW);  // (3) Светодиод гаснет при нажатии кнопки.
  delay(250);
  sleep_enable();
  attachInterrupt(1, Interrupt, HIGH);
  power_all_disable(); // (4) Отключаем переферию.               
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  MCUCR |= (1<<BODS) | (1<<BODSE);
  MCUCR &= ~(1<<BODSE); // (5) Отключаем Brown Out Detector для режима сна.
  sleep_mode();   // (6) С этого момента спим.
  
  sleep_disable();   // (7) С этого момента пробуждаемся.
  detachInterrupt(1); 
  power_all_enable(); // (8) Включаем переферию обратно.
  delay(5);
  
  while(digitalRead(TOUCH)){ // (9) Светодиод загорается сразу при нажатии. Как только кнопка будет отпущена, возвращаемся в loop().
    digitalWrite(LED, HIGH);
  }
  delay(250);
}

void loop(){
  digitalWrite(LED, HIGH);  // (1) Светодиод горит
  if (digitalRead(TOUCH)){  // (2) Если кнопка нажата, то вызываем фнкцию засыпания.
    Sleep();
  }
}

Всем спасибо!

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

В строке 22 правильнее писать не HIGH, а FALLING. 

NM
NM аватар
Offline
Зарегистрирован: 17.02.2018

Спасибо за комментарий! Думаю, там можно и RISING написать? В любом случае, данный скетч работает :)

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

RISING вряд-ли. Вроде в даташите написано, что в режимах экономии (кроме IDLE) срабатывает только FALLING. Хотя попробовать никто не мешает. Если надо могу найти раздел даташита. Кстати, Ваша HIGH (1) соответсвует CHANGE, но CHANGE предполагает FALLING - потому и работает.

NM
NM аватар
Offline
Зарегистрирован: 17.02.2018

Интересно. Честно признаюсь, даташит не читал, но вам верю на слово :) Непонятно только, почему тогда происходит прерывание при нажатии на кнопку в режиме сна? 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

А кнопка при нажатии HIGH даёт на пин или LOW (она типа к земле идёт или к питанию)? Если к питанию, то второй вопрос, а просыпается точно по нажатию? Может по отпусканию (в т.ч. и по дребезгу!)

NM
NM аватар
Offline
Зарегистрирован: 17.02.2018

А что гадать, я проверил. C RISING работает также. Кнопка дает HIGH  при детектировании нажатия.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Ну, значит либо в даташите перестраховались и это недокументированная возможность, либ ещё чего.