Использование режимов энергосбережения.
Все микроконтроллеры AVR на которых основаны большинство плат Arduino поддерживают различные режимы энергосбережения. Рассмотрим такие режимы для микроконтроллера ATmega328P, на котором основаны платы Arduino UNO, Arduino Nano, Arduino Pro Mini и некоторые другие:
IDLE mode (режим ожидания)
В данном режиме приостанавливается только работа процессора, в то время как остальная периферия (интерфейсы ввода-вывода, таймеры, счетчики, компараторы, система прерываний) продолжает работать. Данный режим обеспечивает самое низкое снижение потребления энергии, но его преимущество в очень быстрой реакции на события, приводящие к пробуждению микроконтроллера. Выход из режима IDLE возможен как по внешнему, так и по внутреннему прерыванию.
Power-Down mode (режим глубокого сна)
Этот режим обеспечивает максимальное энергосбережение за счет отключения тактирования всех узлов микроконтроллера, работающих в синхронном режиме. В рабочем состоянии остаются только сторожевой таймер, система обработки внешних прерываний и блок сравнения адреса модуля TWI. Пробуждение из данного режима возможно в результате возникновения следующих прерываний: от сторожевого таймера, по совпадению адреса от интерфейса TWI, прерывание изменения уровня, или внешнего прерывания INT0 или INT1.
Power Save mode (режим энергосбережения)
Отличается от режима Power-Down тем, что таймер/счетчик 2 продолжает свою работу как в синхронном, так и в асинхронном режиме. Пробуждение из этого режима возможно теми же прерываниями что и из режима Power-Down, а также прерыванием от таймера/счетчика 2.
Standby mode (режим ожидания)
Этот режим идентичен режиму работы Power-Down, за исключением того, что продолжает работать тактовый генератор. За счет этого пробуждение микроконтроллера происходит гораздо быстрее.
Для того чтобы начать использовать данные режимы энергосбережения, необходимо подключить библиотеку sleep.h:
Код: Выделить всё
#include <avr/sleep.h>
После этого нам станут доступны две простые функции –
set_sleep_mode();
и sleep_mode();
.С помощью функции
set_sleep_mode();
происходит выбор необходимого режима энергосбережения. Соответственно есть 4 интересующих нас аргумента этой функции для каждого из рассмотренных режимов работы:Код: Выделить всё
set_sleep_mode(SLEEP_MODE_IDLE);
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
set_sleep_mode(SLEEP_MODE_PWR_SAVE);
set_sleep_mode(SLEEP_MODE_STANDBY);
После того как был задан необходимый режим энергосбережения, мы можем воспользоваться функцией sleep_mode(); для перевода микроконтроллера в этот режим.
Как видно, ввести микроконтроллер в режим энергосбережения совсем несложно, но помимо этого его необходимо еще и выводить из этого режима для совершения полезной работы. Рассмотрим вариант использования прерывания от сторожевого таймера для этих целей. Для работы со сторожевым таймером необходимо подключить соответствующую библиотеку wdt.h:
Код: Выделить всё
#include <avr/wdt.h>
После этого в теле программы необходимо объявить функцию обработчика прерывания от сторожевого таймера:
Код: Выделить всё
ISR (WDT_vect) {
}
Для работы со сторожевым таймером понадобятся две функции –
wdt_enable();
и wdt_disable();
.Функция
wdt_enable();
имеет один аргумент, устанавливающий интервал срабатывания сторожевого таймера. Для этого доступны 10 констант:Код: Выделить всё
wdt_enable(WDTO_15MS);
wdt_enable(WDTO_30MS);
wdt_enable(WDTO_60MS);
wdt_enable(WDTO_120MS);
wdt_enable(WDTO_250MS);
wdt_enable(WDTO_500MS);
wdt_enable(WDTO_1S);
wdt_enable(WDTO_2S);
wdt_enable(WDTO_4S);
wdt_enable(WDTO_8S);
Кроме этого, необходимо разрешить прерывание от сторожевого таймера. Это можно сделать с помощью установки бита WDIE регистра
WDTCSR: WDTCSR |= (1 << WDIE);
.Рассмотрим пример программы, в котором будем просто моргать встроенным светодиодом с высокой энергоэффективностью:
Код: Выделить всё
#include <avr/sleep.h>
#include <avr/wdt.h>
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
set_sleep_mode(SLEEP_MODE_PWR_DOWN); //выбираем в качестве режима энергосбережения Power-Down mode
}
void loop() {
digitalWrite(LED_BUILTIN, LOW); //гасим светодиод
wdt_enable(WDTO_1S); //устанавливаем таймер на 1 секунду
WDTCSR |= (1 << WDIE); //разрешаем прерывание
sleep_mode(); //переходим в режим сна, через секунду попадаем в функцию обработчика прерывания ISR (WDT_vect)
digitalWrite(LED_BUILTIN, HIGH); //зажигаем светодиод
wdt_enable(WDTO_120MS); //устанавливаем таймер на 120 мс
WDTCSR |= (1 << WDIE); //разрешаем прерывание
sleep_mode(); //переходим в режим сна, через 120 мс попадаем в функцию обработчика прерывания ISR (WDT_vect)
}
ISR (WDT_vect) {
wdt_disable(); //прерывание сработало, отключаем таймер, после чего продолжается выполнение основной программы
}
Как видно, при каждом входе в режим сна, необходимо выполнять по 4 действия – включать таймер и устанавливать время его срабатывания, разрешать прерывание, входить в режим сна, и после выхода из него – отключать таймер. Кроме того, нет возможности устанавливать свои интервалы срабатывания таймера. Оба этих недостатка можно устранить если использовать библиотеку Narcoleptic.
Использование библиотеки Narcoleptic
Данную библиотеку создал Питер Кнайт, скачать ее можно по адресу https://code.google.com/p/narcoleptic/.
Эта библиотека позволяет вводить микроконтроллер в режим сна на определенное время с помощью одной функции –
Narcoleptic.delay();
. Аргументом данной функции является время в миллисекундах – используется точно так же как и стандартная функция delay();
.Рассмотрим ту же программу что и ранее, но с использованием данной библиотеки:
Код: Выделить всё
#include <Narcoleptic.h>
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
}
void loop() {
digitalWrite(LED_BUILTIN, LOW);
Narcoleptic.delay(1000);
digitalWrite(LED_BUILTIN, HIGH);
Narcoleptic.delay(120);
}
Как видно, код стал значительно проще, и в случае, когда нужны простые паузы между полезными действиями – эта библиотека является самым простым и удобным решением.
Выключение компонентов микроконтроллера
Этот метод подойдет в случаях, когда микроконтроллер длительное время должен выполнять ряд определенных действий с одной и той же периферией.
Любой микроконтроллер представляет из себя набор различных модулей, и для всех модулей предусмотрена возможность включения и отключения питания.
Для того чтобы воспользоваться данным методом необходимо подключить библиотеку power.h:
Код: Выделить всё
#include <avr/power.h>
После этого нам будет доступен ряд функций для включения и отключения отдельных модулей периферии микроконтроллера:
Код: Выделить всё
Функция выключения Функция включения Описание модуля
power_aca_disable() power_aca_enable() Аналоговый компаратор порта А.
power_adc_disable() power_adc_enable() АЦП.
power_adca_disable() power_adca_enable() АЦП порта А.
power_evsys_disable() power_evsys_enable() Модуль EVSYS
power_hiresc_disable() power_hiresc_enable() Модуль HIRES порта C.
power_lcd_disable() power_lcd_enable() Модуль LCD.
power_pga_disable() power_pga_enable() Усилитель с программируемым коэффициентом усиления.
power_pscr_disable() power_pscr_enable() Контроллер пониженной мощности.
power_psc0_disable() power_psc0_enable() 0 Контроллер уровня мощности.
power_psc1_disable() power_psc1_enable() 1 Контроллер уровня мощности.
power_psc2_disable() power_psc2_enable() 2 Контроллер уровня мощности.
power_ram0_disable() power_ram0_enable() SRAM блок 0.
power_ram1_disable() power_ram1_enable() SRAM блок 1.
power_ram2_disable() power_ram2_enable() SRAM блок 2.
power_ram3_disable() power_ram3_enable() SRAM блок 3.
power_rtc_disable() power_rtc_enable() Модуль часов реального времени.
power_spi_disable() power_spi_enable() Интерфейс SPI
power_spic_disable() power_spic_enable() Интерфейс SPI порта C
power_spid_disable() power_spid_enable() Интерфейс SPI порта D
power_tc0c_disable() power_tc0c_enable() Таймер/счетчик 0 порта C
power_tc0d_disable() power_tc0d_enable() Таймер/счетчик 0 порта D
power_tc0e_disable() power_tc0e_enable() Таймер/счетчик 0 порта E
power_tc0f_disable() power_tc0f_enable() Таймер/счетчик 0 порта F
power_tc1c_disable() power_tc1c_enable() Таймер/счетчик 1 порта C
power_twic_disable() power_twic_enable() Интерфейс I2C порта C
power_twie_disable() power_twie_enable() Интерфейс I2C порта E
power_timer0_disable() power_timer0_enable() Таймер 0
power_timer1_disable() power_timer1_enable() Таймер 1
power_timer2_disable() power_timer2_enable() Таймер 2
power_timer3_disable() power_timer3_enable() Таймер 3
power_timer4_disable() power_timer4_enable() Таймер 4
power_timer5_disable() power_timer5_enable() Таймер 5
power_twi_disable() power_twi_enable() Интерфейс I2C
power_usart_disable() power_usart_enable() Интерфейс USART
power_usart0_disable() power_usart0_enable() Интерфейс USART 0
power_usart1_disable() power_usart1_enable() Интерфейс USART 1
power_usart2_disable() power_usart2_enable() Интерфейс USART 2
power_usart3_disable() power_usart3_enable() Интерфейс USART 3
power_usartc0_disable() power_usartc0_enable() Интерфейс USART 0 порта C
power_usartd0_disable() power_usartd0_enable() Интерфейс USART 0 порта D
power_usarte0_disable() power_usarte0_enable() Интерфейс USART 0 порта E
power_usartf0_disable() power_usartf0_enable() Интерфейс USART 0 порта F
power_usb_disable() power_usb_enable() Интерфейс USB
power_usi_disable() power_usi_enable() Интерфейс USI
power_vadc_disable() power_vadc_enable() Модуль напряжения АЦП
power_all_disable() power_all_enable() Все модули
Доступность данных функций будет определяться типом используемого микроконтроллера, и тем какая периферия в нем присутствует. Для того чтобы не изучать документацию на каждый конкретный контроллер, можно отключать при запуске всю периферию контроллера с помощью функции power_all_disable(), а затем отдельно включать необходимые модули.
Для примера давайте добавим в нашу первую программу отправку данных через Serial порт, а всю остальную периферию микроконтроллера отключим:
Код: Выделить всё
#include <avr/sleep.h>
#include <avr/wdt.h>
#include <avr/power.h>
long int i = 0;
void setup() {
power_all_disable(); //отключаем всю периферию
power_usart0_enable(); //включаем USART0 для Arduino Mega
power_timer0_enable(); //включаем таймер 0 (он необходим для нормальной работы USART)
Serial.begin(9600); //устанавливаем скорость Serial в 9600 бод
pinMode(LED_BUILTIN, OUTPUT);
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
}
void loop() {
i++;
Serial.println(i); //Отправляем данные в Serial порт
delay(10); //Ждем пока завершится отправка данных через USART, иначе контроллер перейдет в режим сна до того как данные успеют отправиться полностью
digitalWrite(LED_BUILTIN, LOW);
wdt_enable(WDTO_1S);
WDTCSR |= (1 << WDIE);
sleep_mode();
digitalWrite(LED_BUILTIN, HIGH);
wdt_enable(WDTO_120MS);
WDTCSR |= (1 << WDIE);
sleep_mode();
}
ISR (WDT_vect) {
wdt_disable();
}
Снижение тактовой частоты.
Потребление любого микроконтроллера сильно зависит от частоты его тактирования, и снижая ее, мы можем добиться значительного снижения энергопотребления. В микроконтроллерах AVR имеется возможность программного изменения предделителя частоты тактирования. А для простоты работы с ним мы будем использовать специальную библиотеку Prescaler.h, скачать которую можно по адресу https://github.com/fschaefer/Prescaler:
Код: Выделить всё
#include “Prescaler.h”
Изменение предделителя тактирования производится с помощью функции
setClockPrescaler();
имеющей один аргумент, отвечающий за величину предделителя. Существует 9 констант в качестве аргументов для данной функции:Код: Выделить всё
setClockPrescaler(CLOCK_PRESCALER_1);
setClockPrescaler(CLOCK_PRESCALER_2);
setClockPrescaler(CLOCK_PRESCALER_4);
setClockPrescaler(CLOCK_PRESCALER_8);
setClockPrescaler(CLOCK_PRESCALER_16);
setClockPrescaler(CLOCK_PRESCALER_32);
setClockPrescaler(CLOCK_PRESCALER_64);
setClockPrescaler(CLOCK_PRESCALER_128);
setClockPrescaler(CLOCK_PRESCALER_256);
В зависимости от аргумента, данная функция снижает тактовую частоту в несколько раз (CLOCK_PRESCALER_16 означает что базовая тактовая частота микроконтроллера будет снижена в 16 раз).
Перед использованием данной функции, необходимо отметить, что правильность работы всей периферии сильно зависит от тактовой частоты, и при ее снижении – большинство функций микроконтроллера, завязанные на времени, будут работать неправильно (таймеры, PWM, USART, I2C и т.д.). Кроме того неправильно будут работать стандартные функции
millis();
и delay();
. Но данная библиотека предоставляет замену этим функциям в виде функций trueMillis();
и trueDelay();
.Рассмотрим вариант применения снижения тактовой частоты на примере работы с Serial интерфейсом:
Код: Выделить всё
#include "prescaler.h"
int i = 0;
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
Serial.begin(9600);
setClockPrescaler(CLOCK_PRESCALER_256); //Понижаем тактовую частоту в 256 раз
}
void loop() {
digitalWrite(LED_BUILTIN, HIGH); //включаем светодиод
trueDelay(200); //ждем 200 мс с учетом пониженной частоты
digitalWrite(LED_BUILTIN, LOW); //выключаем светодиод
trueDelay(1000); //ждем 1 секунду с учетом пониженной частоты
i++;
if (i == 5) { //каждый 5 цикл отправляем данные в Serial порт
setClockPrescaler(CLOCK_PRESCALER_1); //Повышаем частоту тактирования до стандартной
Serial.println("I'm alive!"); //Отправляем данные
delay(15); //Ждем пока данные отправятся
setClockPrescaler(CLOCK_PRESCALER_256); //Обратно снижаем частоту тактирования
i = 0;
}
}
Как видно, перед тем как использовать Serial порт, необходимо сначала повысить частоту тактирования до стандартной, и только потом отправлять данные. То же самое касается и приема данных – необходимо позаботиться о возвращении стандартной частоты тактирования еще до того, как данные будут отправлены на наш микроконтроллер, иначе они будут приняты неверно. То же касается и остальной периферии, завязанной на временных промежутках.