Снотворное для Arduino

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

Спящие режимы могут быть очень полезными, если микроконтроллер долго бездействует. У разных микроконтроллеров имеются разные режимы, советую почитать раздел «Power Management and Sleep Mode» в документации микроконтроллера.

Режимы микроконтроллера Atmega168:

  • Idle Mode. Режим ожидания (общий для всех моделей). В этом режим останавливается ЦПУ, а периферия — SPI, USART, Аналоговый компаратор, ADC, TWI, таймеры/счетчики, сторожевой таймер и система прерываний продолжает работать.
  • ADC Noise Reduction Mode. В этом режиме останавливается процессор, но АЦП, внешние прерывания, TWI, таймер/счетчик2, сторожевой таймер (если включен) продолжают работать.
  • Power-down Mode. Общий для всех микроконтроллеров AVR. В этом режиме останавливается все что есть в микроконтроллере, кроме сторожевого таймера, внешних прерываний и TWI. Самый экономный режим.
  • Power-save Mode. Этот режим похож на Power-down mode. Отличается он тем, что если Таймер/счетчик2 работает асинхронно, то он продолжит свою работу и во время сна. Это может пригодиться при реализации часов реального времени на микроконтроллере.
  • StandbyMode. Этот режим также похож на режим Power-down mode. Но в этом режиме тактовый генератор продолжает работать (если установлен внешний кварц). Из этого режима МК просыпается за 6 тактов.

Чтобы было понятно как можно выйти из того или иного режима — вот таблица:

 

У микроконтроллера atmega8 — все то же самое, что и у atmega168 кроме StandbyMode.
У микроконтроллера atmega2560 прибавляется еще один режим:

 

 

В Arduino IDE спящие режимы реализовываются — так:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <avr/sleep.h>
#include <avr/power.h>
 
int pin = 2;
 
void wakeUp()
{
  Serial.print("WakeUp"); //Проснулись
  detachInterrupt(0); //Отключаем прерывания 
  while(1); //Бесконечный цикл
}
 
void EnterSleep()
{
 
  attachInterrupt(0, wakeUp, LOW); //Если на 0-вом прерываниии - ноль, то просыпаемся.
  delay(100);
 
  sleep_enable(); //Разрешаем спящий режим
  sleep_mode(); //Спим (Прерывания продолжают работать.) Программа останавливается.
  sleep_disable(); //Запрещаем спящий режим
}
 
void setup()
{
  Serial.begin(9600);
  set_sleep_mode(SLEEP_MODE_PWR_DOWN); //Определяем режим сна
  pinMode(pin, INPUT);
 
}
 
void loop()
{
 Serial.print("Hello World");
 delay(5000)
 Serial.print("Sleep");
 EnterSleep(); //Пора спать
 
}

В данном примере используется PWR-Down Mode. Чтобы вывести микроконтроллер из сна, нужно — подать ноль на прерывание INT0 (2пин). Другие режимы определяются в программе так:

SLEEP_MODE_IDLE
SLEEP_MODE_ADC
SLEEP_MODE_PWR_SAVE
SLEEP_MODE_STANDBY
SLEEP_MODE_PWR_DOWN
SLEEP_MODE_EXT_STANDBY

Также можно выйти из спящего режима с помощью UART(Serial), нужно соеденить вывод RX(0пин) через 220Ом-ный резистор со внешним прерыванием INT0(2пин). Когда Ардуина — заснет, то просто пульните что нибуль в com порт, и она проснется.

PS: О других возможностях вывода из того или иного спящего режима, напишу возможно позже.

Источники:
Arduino and Zigbee
Electronics Blog

0

About Кирилл Васин

Прохожий из шапки сайта

14 Comments

  1. Очень полезная инфа. Хотелось бы больше прояснить по командам
    Не совсем ясно
    sleep_enable(); //Разрешаем спящий режим
    sleep_mode(); //Спим (Прерывания продолжают работать.) Программа останавливается.
    sleep_disable(); //Запрещаем спящий режим
    Зачем сначала разрешать, и сразу запрещать, и режим раньше прописали
    set_sleep_mode(SLEEP_MODE_PWR_DOWN);

    1. Зачем разрешать? — функция sleep_enable() фактически разрешает вызывать функцию sleep_mode(), если первое не было выполнено, то контроллер просто не «уснет».
      sleep_disable() — запрещает выполнение функции sleep_mode. В данном примере сделано так, что когда контроллер уснул и проснулся по прерыванию, то в первую очередь выполняется подпрограмма wakeUp, а потом программа начинает продолжать с места остановки(то есть после функции sleep_mode()). И тут, по идее, мы должны запретить функцию sleep_mode, но нам не даст этого сделать пустой цикл в подпрограмме wakeUp. Поэтому sleep_disable() в данном примере не нужен.

      А set_sleep_mode(SLEEP_MODE_PWR_DOWN) — просто определяет режим сна.(тк их несколько)

      Таково устройство библиотеки sleep/power

  2. как сделать так чтоб только после выхода из сна выполнялась какая-то функция и только один раз?

    1. Есть много способов. В принципе, функция void wakeUp() (в примере) выполняется по прерыванию 1 раз. Единственное, уберите оттуда while(1);.

    1. Попробуйте так:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      
      #include <avr/sleep.h>
      #include <avr/power.h>
       
      int times = 0;
       
      ISR(TIMER1_OVF_vect)
      {
       
      }
       
      void EnterSleep(void)
      {
        set_sleep_mode(SLEEP_MODE_IDLE);
       
        sleep_enable();
       
        power_adc_disable();
        power_spi_disable();
        power_timer0_disable();
        power_timer2_disable();
        power_twi_disable();  
       
        /* 14 раз по 4.2 сек */
        for (times = 0; times < 14; ++times) {
      	sleep_mode();
      	TCNT1=0x0000;
        }
       
        sleep_disable(); 
       
       
        power_all_enable();
      }
       
      void setup()
      {
        Serial.begin(9600);
       
        TCCR1A = 0x00; 
        TCNT1=0x0000; 
        TCCR1B = 0x05;
        TIMSK1=0x01;
      }
       
      void loop() {
      	EnterSleep();
       
      	//Smth...
      }
      1. не работает. если делать
        for (times = 0; times < 14; ++times) {
        sleep_mode();
        то в сон не уходит, а если делать больше 14, например 65, то из сна не выходит…

        1. Иван, а TCNT1=0x0000; стоит в цикле?
          Да, и попробуйте выражение перенести в обработчик прерываний.

  3. А, что лучше для МК, крутиться в цикле while проверяя состояние нужной ноги, или сон, если вопрос о энергосбережении не стоит?
    Второй вопрос. Сколько лет может проработать МК (например atmega 328), не выключаясь?
    И третий. Может ли МК зависнуть в режиме сна? Как предусматривать watch dog в этом режиме.

    1. При «кручении» цикла while процессор постоянно выполняет инструкции, поэтому правильнее будет его усыпить, а также воспользоваться возможностями контроллера прерываний. А общем случае — никакой разницы.

      Сам проверял такое лишь в течение года 🙂 Atmega32 управляла освещением в помещении без сбоев.

      Шанс зависнуть в режиме сна значительно снижается, особенно в «Power Down». Watch Dog работает независимо от состояния МК. Например, можно просыпаться каждые 5 сек и сбрасывать таймер, тогда это обеспечит отличную защиту от зависаний.
      Однако Arduino (или иные платы с их загрузчиком) не работают с этим таймером из-за ошибок в коде загрузчика, как ее исправить можно почитать здесь: https://geektimes.ru/post/255800/ или подобные.
      Если у вас МК без загрузчика, то управлять WDT очень просто:

      1
      2
      3
      4
      5
      6
      7
      
      #include <avr/wdt.h>
      ..................................................
      wdt_enable(WDTO_4S); // Запускаем таймер один раз.
      while(2+2 != 5) {
         //Делаем что-то
         wdt_reset(); //Сбрасываете каждый раз
      }
  4. Админ,здравствуйте!
    Ваш код прекрасно работает(таймер на 60 сек),но с режимом SLEEP_MODE_IDLE.Можно ли реализовать его с SLEEP_MODE_PWR_DOWN?

      1. по-идее в режиме SLEEP_MODE_PWR_SAVE таймер отключается, поэтому прерывание по таймеру в этом режиме работать не будет.

  5. Добрый день.
    Скажите, Ардуино может просыпаться в режими, например, SLEEP_MODE_PWR_SAVE или SLEEP_MODE_STANDBY от watchdog таймера? В таблице, вроде бы такая возможность указана, а реализацию нигде не могу найти.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *