Режимы сна и энергосбережение
Введение
Рано или поздно, в любом из проектов изобретатель сталкивается с проблемой автономности. Не всегда хочется держать подключенным к розетке устройство, которое большую часть времени работы ничего не делает. Например, для умной теплицы нужно время от времени получать данные о температуре, влажности и включать системы автополива. Но ведь абсолютно не обязательно получать измерения каждую секунду, вполне достаточно измерений раз в несколько минут, потому как погода (температура и влажность) и другие подобные показатели изменяются не быстро. И получается что больше 95% времени плата потребляет электричество впустую.
Режимы энергопотребления
Для подобных и аналогичных случаев существуют специальные режимы энергопотребления контроллера, которые позволяют значительно снизить энергопотребление платой во время простоя. Эти режимы называются режимами сна, и их несколько - названия и отличия приведены ниже:
- IDLE (режим ожидания) - в данном режиме приостанавливается работа процессора, но другие компоненты остаются работать (такие как сторожевой таймер, таймеры и счётчик millis(), прерывания, интерфейсы SPI, UART, I2C. Это самый «неэкономный» режим сна. Его основное преимущество - он сохраняет самую большую функциональность.
- ADC (режим подавления шумов АЦП) - данный режим доступен только для микроконтроллеров, имеющих АЦП
- PWR_SAVE (режим сохранения энергии) - в данном режиме продолжают работу сторожевой таймер, сравнение адреса I2C устройств, внешние прерывания и таймер.
- STANDBY (режим ожидания) - один из самых экономичных режимов энергопотребления. Рекомендуется использовать, только если имеется внешний резонатор.
- PWR_DOWN (режим пониженного энергопотребления) - самый экономичный режим потребления энергии. в данном режиме продолжают работу сторожевой таймер, сравнение адреса I2C устройств и внешние прерывания.
Примеры работы с Arduino UNO
Как писать скетчи проще
Синтаксис описания перевода контроллера в сон и подключения прерываний довольно сложный, и зачастую вызывает появление ошибок и нежелание работать с этими режимами. Очень хорошо, что была написана дополнительная библиотека для ардуино, упрощающая подключение режимов и прерываний. Скачать библиотеку Sleep_n0m1 можно на ресурсе гитхаб или по ссылке ниже:
Пример 1. Самый простой ввод в сон
В данном примере демонстрируется введение контроллера в режима сна PWR_DOWN на заданное количество времени.
Работает скетч следующим образом. В основном цикле выполняются команды (в частности вывод надписей в сериал-монитор). Вы можете добавить свои команды, например, измерение температуры.
Для данного примера нам потребуется только контроллер. Результат работы мы сможем наблюдать в мониторе Serial-порта.
#include <Sleep_n0m1.h> //подключение библиотеки
Sleep sleep; //объект для работы с режимами сна
unsigned long sleepTime; //переменная для задания времени сна
void setup(){
Serial.begin(9600); //инициализация монитора Serial-порта
sleepTime = 10000; //время сна в милисекундах, максимальное время сна 49.7 дней
}
void loop() {
delay(100); //задержка для того чтобы успеть вывести информацию в сериал порт
Serial.println("Execute your code here"); //вывод надписи "Ваш код запускается здесь"
Serial.print("Sleeping for "); //вывод надписи "сон на ..."
Serial.println(sleepTime); //вывод количества времени сна
delay(100); //задержка для того чтобы успеть вывести информацию в сериал порт
sleep.pwrDownMode(); //установка режима сна PWR_DOWN
sleep.sleepDelay(sleepTime); //заснуть на указанное время
}
Пример 2. Просыпаться по прерыванию
В данном примере контроллер будет введёт в сон, пока не возникнет внешнее прерывание. Простейший способ вызвать прерывание, это послать любые данные в монитор сериал-порта. Выводы прерывания на контроллере Smart UNO - это цифровые выводы 2 и 4. Поэтому нужно взять один провод папа-папа и соединить цифровые выводы 0 и 2 на контроллере.
Загрузите скетч на контроллер:
#include <Sleep_n0m1.h> //подключение библиотеки для режимов сна
Sleep sleep; //объект для ввода контроллера в сон
#define intPin 2 //пин прерывания
void setup(){
Serial.begin(9600); //Инициализация монитора порта
}
void loop(){
delay(100); //задержка для того чтобы успеть вывести информацию в сериал порт
Serial.println("Execute your code here"); //вывод надписи "Ваш код запускается здесь"
Serial.print("Sleeping for "); //вывод надписи "сон на ..."
Serial.println("Sleeping Till Interrupt"); //вывод надписи "Сон до прерывания"
delay(100); //задержка для того чтобы успеть вывести информацию в сериал порт
sleep.pwrDownMode(); //установка режима сна PWR_DOWN
//Сон будет продолжаться пока на выводе intPin не поменяется уровень сигнала
//В данном случае "LOW" - это состояние 0.
sleep.sleepPinInterrupt(intPin, LOW); //(номер вывода прерывания, состояние прерывания)
}
После того как контроллер уйдёт в сон (информация об этом появится в мониторе Serial-порта), вывести его из сна можно только с помощью внешнего прерывания. Чтобы создать его искусственно, нужно передать в сериал-порт любую информацию, например, передать единицу:
После этого цикл loop запустится заново.
Пример 3. Просыпаться по будильнику часов
В данном примере формируется минипроект - часы с будильником. Часы показывают время на черырёхразрядном индикаторе. У устройства имеется кнопка - которая переводит контроллер в режим сна до следующей минуты, устанавливая на часах будильник на следующую за текущей минуту. Индикацией установки будильника служит появление на индикаторе надписи «ALAR» - от английского alarm (будильник). Для демонстрации того, что устройство находится в режиме сна, на дисплей выводится информация в виде четырёх тире (- - - -). По пробуждению, дисплей снова отображает время.
Библиотеки
Помимо библиотеки для работы с режимами сна, понадобятся библиотеки для часов и модуля индикатора. Их можно загрузить со страниц в Базе Знаний: Модуль часов PCF8563 и Модуль 4 - разрядного индикатора
Что нужно | Кол-во, шт |
---|---|
Контроллер Smart UNO | 1 |
Sensor Shield | 1 |
Модуль часов PCF8563 | 1 |
Модуль 4-разрядного индикатора | 1 |
Модуль кнопки | 1 |
Шлейф "мама-мама" х4 | 1 |
Шлейф "мама-мама" х3 | 1 |
Провода мама-мама | 5 |
Скетч для загрузки
#include "SevenSegmentTM1637.h" //подключение библиотеки для работы с дисплеем
#include "SevenSegmentExtended.h" //подключение расширенных функций дисплея
#include <Wire.h> //библиотека для работы с I2C
#include <Rtc_Pcf8563.h> //библиотека для работы с часами
#include <Sleep_n0m1.h> //подключение библиотеки для режимов сна
const int wakeUpPin = 3; //вывод прерывания
const int button = 6; //вывод подключения кнопки
Rtc_Pcf8563 rtc; //инициализация часов
SevenSegmentExtended display(13, 12); //создание объекта дисплея
Sleep sleep; //объект для ввода контроллера в сон
void setup() {
display.begin(); //инициализация дисплея
display.setBacklight(100); //установка яркости в 100%
display.print("INIT"); //отображение надписи INIT (инициализация) на дисплее
delay(1000); //задержка в 1 секунду
pinMode(button, INPUT); //режим вывода кнопки
}
void loop() {
rtc.getTime(); //получение времени с часов
byte hour = rtc.getHour(); //запись в переменную количество часов
byte minute = rtc.getMinute(); //запись в переменную количество минут
display.printTime(hour, minute, false); //вывод времени
if (digitalRead(button)) { //была нажата кнопка
set_alarm(minute); //установить будильник
display.print("----"); //вывести четыре тире, означающие что контроллер спит
sleep.pwrDownMode(); //установка режима сна PWR_DOWN
//сон будет продолжаться пока на выводе wakeUpPin не поменяется уровень сигнала
//в данном случае с "HIGH" на "LOW"
sleep.sleepPinInterrupt(wakeUpPin, FALLING); //(номер вывода прерывания, состояние прерывания)
//очистка будильника
clr_alarm();
}
}
/* Процедура очищает регистры будильника на часах.
* Требуется для корректной работы модуля часов.
*/
void clr_alarm() {
detachInterrupt(1); //отключить прерывание номер 1 (вывод контроллера - 3)
rtc.clearAlarm(); //очистить будильник
delay(1000); //секундная задержка
}
/* Процедура устанавливает будильник на часах на одну минуту больше.
* Здесь можно устанавливать будильник на любое время методом setAlarm(),
* который принимает четыре параметра:
* - минуты будильника
* - часы будильника
* - день будильника
* - день недели будильника
* Если любое из значений равно 99 - значит берется значение текущего времени.
*/
void set_alarm(const int& minute) {
rtc.setAlarm(minute + 1, 99, 99, 99); //установка будильника на одну минуту
display.clear(); //очистить дисплей
display.print("ALAR"); //вывести надпись ALAR (ALARM)
delay(1000); //подождать секунду
display.clear(); //очистить дисплей
}