GyverPower – библиотека энергосбережения v1.7

ОБНОВЛЕНИЯ


  • v1.5 – совместимость с аттини
  • v1.6 – ещё совместимость с аттини
  • v1.7 – оптимизация, совместимость с ATtiny13

ТЕОРИЯ


Энергопотребление электронного устройства складывается из нескольких факторов:

  • Режим работы МК
  • Частота тактирования МК
  • Напряжение питания МК
  • Состояние периферии МК (вкл/выкл)
  • Состояние/потребление внешних устройств

Если управление внешними устройствами ложится на плечи разработчика железа (транзисторы по питанию и проч.), то управление энергопотреблением МК осуществляется прямо из программы!

На сайте есть отдельный подробный урок по энергосбережению.

БИБЛИОТЕКА


GyverPower – библиотека для расширенного управления энергопотреблением микроконтроллера

  • Управление системным клоком
  • Включение/выключение периферии:
    • BOD
    • Таймеры
    • I2C/UART/SPI
    • USB
    • ADC
  • Сон в разных режимах
  • Сон на любой период
    • Калибровка таймера для точных периодов сна
    • Коррекция millis() на время сна
  • Разработано by Egor ‘Nich1con’ Zaharov и AlexGyver

Поддерживаемые платформы: платы на основе ATmega2560/32u4/328 и ATtiny85/84/167

Версия 1.1 от 24.05.2020

  • Добавлена функция wakeUp() для просыпания по прерыванию из sleepDelay() (см. пример interruptWakesSleepDelay)
  • Библиотека обёрнута в класс (несовместимо с версией 1.0!!! Нужно добавить везде power. )
  • Убраны дефайны (для безопасности)
  • Добавлена коррекция миллис на время сна
  • Добавлены примеры и расширенное описание
  • Улучшена стабильность калибровки (не зависает)

Совместимость: Atmega2560/32u4/328, Attiny85/84/167

УСТАНОВКА


  • Библиотеку можно найти и установить через менеджер библиотек по названию GyverPower в:
    • Arduino IDE (Инструменты/Управлять библиотеками)
    • Arduino IDE v2 (вкладка “Library Manager”)
    • PlatformIO (PIO Home, вкладка “Libraries”)
  • Про ручную установку читай здесь

ДОКУМЕНТАЦИЯ


Сон


Режимы сна

Режим сна устанавливается при помощи power.setSleepMode(mode), где mode:

  • IDLE_SLEEP - Легкий сон, отключается только клок CPU и Flash, пробуждается мгновенно от любых прерываний
  • ADC_SLEEP - Легкий сон, отключается CPU и system clock, АЦП начинает преобразование при уходе в сон (см. пример ADCinSleep)
  • POWERDOWN_SLEEP - Наиболее глубокий сон, отключается всё кроме WDT и внешних прерываний, просыпается от аппаратных (обычных + PCINT) или WDT, пробуждение за 1000 тактов (62 мкс)
  • STANDBY_SLEEP - Глубокий сон, идентичен POWERDOWN_SLEEP + system clock активен, пробуждение за 6 тактов (0.4 мкс)
  • POWERSAVE_SLEEP - Глубокий сон, идентичен POWERDOWN_SLEEP + timer 2 активен (+ можно проснуться от его прерываний), можно использовать для счета времени (см. пример powersaveMillis)
  • EXTSTANDBY_SLEEP - Глубокий сон, идентичен POWERSAVE_SLEEP + system clock активен, пробуждение за 6 тактов (0.4 мкс)

Примечание: соответствующая режиму сна периферия автоматически отключается при входе в сон и включается при пробуждении. Если некоторая периферия была отключена вручную (см. ниже), то при выходе из сна она сама не включается. 

Установленный режим активируется при уходе в сон (см. пример power_mode).

Во всех режимах сна кроме IDLE и ADC_SLEEP принудительно отключается весь блок АЦП, что позволяет сильно снизить энергопотребление. Также потребление дополнительно снижается за счёт отключения BOD, на время сна он по умолчанию отключен. Если нужно включить - используйте power.bodInSleep(true);

Уход в сон  через sleep()

power.sleep(time) активирует выбранный режим сна на установленный промежуток времени time, через который МК будет разбужен сторожевым таймером:

  • SLEEP_16MS
  • SLEEP_32MS
  • SLEEP_64MS
  • SLEEP_128MS
  • SLEEP_256MS
  • SLEEP_512MS
  • SLEEP_1024MS
  • SLEEP_2048MS
  • SLEEP_4096MS
  • SLEEP_8192MS
  • SLEEP_FOREVER - спать вечно (без таймера)

Это периоды сторожевого таймера. Сам таймер очень простой и "с завода" имеет некоторый уход по времени, у всех МК он разный и также зависит от температуры корпуса, поэтому реальный период сна будет отличаться от установленной константы (8 секунд обычно превращаются в 9-10). Для точных периодов сна используйте power.sleepDelay().

Из сна, запущенного power.sleep(), можно проснуться аппаратным прерыванием (в том числе pin change interrupt). Никаких дополнительных действий делать не нужно, после обработки прерывания программа вернётся к выполнению кода сразу после вызова sleep(). (см. пример interruptWakeSleep). Пробудить может аппаратное прерывание любого типа (RISING/FALLING).

Во время глубокого сна Таймер 0 не работает, поэтому функция millis() останавливается, и её значение после сна не меняется. Используйте sleepDelay(), в него встроена автоматическая коррекция millis()!

Уход в сон  через sleepDelay()

power.sleepDelay(time) - спит указанное время (миллисекунды), time принимает uint32_t (до ‭4 294 967 296‬), что позволяет спать  до ~50 суток.

Механизм sleepDelay() использует сон по периодам сторожевого таймера (см. выше), подбирая их таким образом, чтобы поспать всё указанное время, таким образом точность сна кратна 16 мс и  минимальный период сна соответственно тоже 16 мс. Если у функции останется несколько миллисекунд сна (не больше 16) - она не будет спать новый период 16мс и просто вернёт остаток времени, может пригодиться для коррекции каких-то своих таймеров. Сторожевой таймер имеет крайне низкую точность, поэтому в библиотеке предусмотрена калибровка периодов сторожевого таймера (читай ниже).

Из сна, запущенного power.sleepDelay(), можно проснуться аппаратным прерыванием (в том числе pin change interrupt). Проблема в том, что мы просыпаемся внутри циклического сна внутри библиотеки и сразу засыпаем обратно на период 0-8 секунд. Чтобы этого избежать, нужно вызвать в будящем прерывании функцию power.wakeUp() (см. пример interruptWakeSleepDelay). Пробудить может аппаратное прерывание любого типа (RISING/FALLING).

Во время глубокого сна Таймер 0 не работает, поэтому функция millis() останавливается, но sleepDelay() автоматически корректирует millis() после выхода из сна (как-будто миллис продолжал свой отсчёт во время сна), что позволяет например спать с периодом в 1 секунду и выполнять периодические действия на таймере с millis() по любым периодам, кратным (или не менее) секунды! См. пример sleepDelayMillis. Для отключения коррекции millis() (если это вдруг нужно) используйте power.correctMillis(false);

Корректировка millis() работает только для ATmega328p/168/2560

Калибровка WDT


В библиотеке реализовано три способа калибровки сторожевого таймера, т.е. периодов сна (пример WDT_calibration):

Способ 1:

Есть функция power.getMaxTimeout(), которая выполняется ~8 секунд (блокирующая) и возвращает максимальный период в мс (~8000). Данный период мы можем вывести через Serial, и далее жёстко записать его в код или сохранить в EEPROM (например сделать калибровку только при первом запуске устройства, дальше значение будет браться из EEPROM). Полученное значение нам нужно скормить функции power.calibrate(); Делать это нужно один раз при запуске программы (в блоке setup()).

Способ 2:

Можно сразу передавать результат калибрующей функции, вот так:

power.calibrate(power.getMaxTimeout());

Данная конструкцию будет выполняться ~8 секунд. Делать это нужно один раз при запуске программы (в блоке setup()), т.е. получаем 8с задержку при запуске программы.

Способ 3:

Чуть менее точная автоматическая калибровка power.autoCalibrate(); выполняется 2 секунды, что не так критично воткнуть в setup() и пусть себе работает =)

Управление системной частотой


При помощи power.setSystemPrescaler(presc) можно установить предделитель системной частоты, где presc это:

  • PRESCALER_1 - по умолчанию
  • PRESCALER_2
  • PRESCALER_4
  • PRESCALER_8
  • PRESCALER_16
  • PRESCALER_32
  • PRESCALER_64
  • PRESCALER_128
  • PRESCALER_256

Системная частота влияет на все блоки микроконтроллера, т.е. Serial на указанной скорости уже не будет работать, функции времени будут считать неправильно, скорость всех интерфейсов и таймеров изменится пропорционально установленному делителю. По коррекции Serial смотри пример lowClock, по коррекции встроенных функций времени смотри пример lowClockTimeCorrect.

Зачем менять системную частоту? Работа на пониженной частоте снижает энергопотребление, а также позволяет микроконтроллеру более стабильно работать на пониженном напряжении. Мы можем это сделать без перепайки кварца и перепрошивки фьюзов.

 

Примечание: изначально с завода большинство микроконтроллеров настроены (фьюзами) на тактирование от внутреннего источника 8 МГц и имеют прошитый фьюз CLKDIV8, который устанавливает делитель системной частоты на 8 при запуске МК. При помощи power.setSystemPrescaler(presc) можно поставить его обратно какой нужен при старте программы. Для чего это нужно? Это позволяет автоматически запуститься на низкой частоте, и, после проверки напряжения питания и других факторов, "разогнаться" до нужной частоты, что может повысить надёжность старта автономного устройства.

В библиотеке есть ещё один инструмент: калибровка внутреннего тактового генератора:

adjustInternalClock(int8_t adj) - корректирует внутренний 8 МГц генератор на величину adj - от -127 до 128.

Заводская калибровка внутреннего генератора 8 МГц производится при температуре 20 градусов и напряжении питания 3V. Так как на RC генератор напрямую воздействует эти два параметра, можно немного подкорректировать частоту. В целом пользоваться функцией стоит с осторожностью и подстраивать генератор незначительно. Да, это позволяет значительно замедлить или разогнать генератор, но отклонение от частоты 8 МГц на 10% в обе стороны может привести к потере стабильности, ошибкам при записи EEPROM и FLASH из программы.

Управление периферией


В библиотеке есть возможность включать и выключать выбранную периферию (таймеры, интерфейсы и т.д.), см. пример hardwareControl.

  • power.hardwareEnable(параметры) - включить указанную периферию
  • power.hardwareDisable(параметры) - отключить указанную периферию

Полный список доступной периферии (для разных МК доступны не все, см. свой конкретный МК):

  • PWR_ALL - всё железо
  • PWR_ADC - АЦП и компаратор
  • PWR_TIMER1 - Таймер 0
  • PWR_TIMER0 - Таймер 1
  • PWR_TIMER2 - Таймер 2
  • PWR_TIMER3 - Таймер 3
  • PWR_TIMER4 - Таймер 4
  • PWR_TIMER5 - Таймер 5
  • PWR_UART0 - Serial 0
  • PWR_UART1 - Serial 1
  • PWR_UART2 - Serial 2
  • PWR_UART3 - Serial 3
  • PWR_I2C - Wire
  • PWR_SPI - SPI
  • PWR_USB - USB
  • PWR_USI - Wire + Spi (ATtinyXX)
  • PWR_LIN - USART LIN (ATtinyXX)

Для управления несколькими блоками сразу разделяем их вертикальной линией. Пример:

// отключили АЦП, таймеры 1 и 0
power.hardwareDisable(PWR_ADC | PWR_TIMER1 | PWR_TIMER0);

Примечание: в зависимости от режима сна некоторая периферия автоматически отключается при уходе в сон и автоматически включается при пробуждении (см. описание режимов сна выше).

Примечание: отключенная вручную периферия отключается до ручного включения, т.е. сон на её состояние не влияет.

void hardwareEnable(uint16_t data);				// включение указанной периферии (см. ниже "Константы периферии")
void hardwareDisable(uint16_t data);			// выключение указанной периферии (см. ниже "Константы периферии")
void setSystemPrescaler(prescalers_t prescaler);// установка делителя системной частоты (см. ниже "Константы делителя")
void bodInSleep(bool en);						// Brown-out detector в режиме сна (true вкл - false выкл) по умолч. отключен!
void setSleepMode(sleepmodes_t mode);			// установка текущего режима сна (см. ниже "Режимы сна")
void autoCalibrate(void);						// автоматическая калибровка таймера сна, выполняется 2 секунды
uint16_t getMaxTimeout(void);					// возвращает реальный период "8 секунд", выполняется ~8 секунд
void calibrate(uint16_t ms);					// ручная калибровка тайм-аутов watchdog для sleepDelay (ввести макс период из getMaxTimeout)
void sleep(sleepprds_t period);					// сон на фиксированный период (см. ниже "Периоды сна")
uint8_t sleepDelay(uint32_t ms);				// сон на произвольный период в миллисекундах (до 52 суток), возвращает остаток времени для коррекции таймеров
void correctMillis(bool state);					// корректировать миллис на время сна sleepDelay() (по умолчанию включено)
void wakeUp(void);								// помогает выйти из sleepDelay прерыванием (вызывать в будящем прерывании)	
void adjustInternalClock(int8_t adj);     		// подстройка частоты внутреннего генератора (число -120...+120)
/* 
РЕЖИМЫ СНА для setSleepMode()
  
IDLE_SLEEP			- Легкий сон, отключается только клок CPU и Flash, просыпается от любых прерываний
ADC_SLEEP			- Легкий сон, отключается CPU и system clock, АЦП начинает преобразование при уходе в сон (см. пример ADCinSleep)	
POWERDOWN_SLEEP		- Наиболее глубокий сон, отключается всё кроме WDT и внешних прерываний, просыпается от аппаратных (обычных + PCINT) или WDT за 1000 тактов (62 мкс)
STANDBY_SLEEP		- Глубокий сон, идентичен POWERDOWN_SLEEP + system clock активен, пробуждение за 6 тактов (0.4 мкс)
POWERSAVE_SLEEP		- Глубокий сон, идентичен POWERDOWN_SLEEP + timer 2 активен (+ можно проснуться от его прерываний), можно использовать для счета времени (см. пример powersaveMillis)
EXTSTANDBY_SLEEP	- Глубокий сон, идентичен POWERSAVE_SLEEP + system clock активен, пробуждение за 6 тактов (0.4 мкс)
*/

/* 
ПЕРИОДЫ СНА для sleep()
  
SLEEP_16MS
SLEEP_32MS
SLEEP_64MS
SLEEP_128MS
SLEEP_256MS
SLEEP_512MS
SLEEP_1024MS
SLEEP_2048MS
SLEEP_4096MS
SLEEP_8192MS
SLEEP_FOREVER	- вечный сон без таймера
*/

/* 
КОНСТАНТЫ ДЕЛИТЕЛЯ для setSystemPrescaler()
  
PRESCALER_1
PRESCALER_2
PRESCALER_4
PRESCALER_8
PRESCALER_16
PRESCALER_32
PRESCALER_64
PRESCALER_128
PRESCALER_256
*/

/* 
КОНСТАНТЫ ПЕРИФЕРИИ для hardwareDisable() и hardwareEnable()
  
PWR_ALL		- всё железо
PWR_ADC		- АЦП и компаратор
PWR_TIMER1	- Таймер 0
PWR_TIMER0	- Таймер 1
PWR_TIMER2	- Таймер 2
PWR_TIMER3	- Таймер 3
PWR_TIMER4	- Таймер 4
PWR_TIMER5	- Таймер 5	
PWR_UART0	- Serial 0
PWR_UART1	- Serial 1
PWR_UART2	- Serial 2
PWR_UART3	- Serial 3
PWR_I2C		- Wire
PWR_SPI		- SPI
PWR_USB		- USB	
PWR_USI		- Wire + Spi (ATtinyXX)
PWR_LIN		- USART LIN (ATtinyXX)
*/

ПРИМЕРЫ


Остальные примеры смотри в папке examples библиотеки, также примеры можно открыть из Arduino IDE/Файл/Примеры

// демо возможностей библиотеки

#include 

void setup() {
  pinMode(13, OUTPUT); // настраиваем вывод со светодиодом на выход
  Serial.begin(9600);

  // калибровка таймаутов для максимальной точности sleepDelay (подробнее в примере WDT_calibration)
  //Serial.println(getMaxTimeout());  // вывести реальный макс. период
  //calibrate(8935); 			            // ввести реальный макс. период
  //calibrate(getMaxTimeout());       // автоматически посчитать и откалибровать
  
  power.autoCalibrate(); // автоматическая калибровка ~ 2 секунды , средняя но достаточная точность

  // отключение ненужной периферии
  power.hardwareDisable(PWR_ADC | PWR_TIMER1); // см раздел константы в GyverPower.h, разделяющий знак " | "

  // управление системной частотой
  power.setSystemPrescaler(PRESCALER_2); // см константы в GyverPower.h
  
  // настройка параметров сна
  power.setSleepMode(STANDBY_SLEEP); // если нужен другой режим сна, см константы в GyverPower.h (по умолчанию POWERDOWN_SLEEP)
  power.bodInSleep(false); // рекомендуется выключить bod во сне для сохранения энергии (по умолчанию false - выключен!!)

  // пример однократного ухода в сон
  Serial.println("go to sleep");
  delay(100); // даем время на отправку
  
  power.sleep(SLEEP_2048MS); // спим ~ 2 секунды
  
  Serial.println("wake up!");
  delay(100); // даем время на отправку
}

void loop() {
  // пример циклического сна
  power.sleepDelay(1500); // спим 1.5 секунды
  
  digitalWrite(13, !digitalRead(13)); // инвертируем состояние на пине
}
// просыпаемся по аппаратному прерыванию из sleepDelay

#include 

// sleepDelay состоит из нескольких sleep, и команда wakeUp()
// позволяет окончательно проснуться по прерыванию.
// Без неё мы будем "досыпать" некоторое время

void setup() {
  Serial.begin(9600);

  // кнопка подключена к GND и D3
  pinMode(3, INPUT_PULLUP);

  // подключаем прерывание на пин D3 (Arduino NANO)
  attachInterrupt(1, isr, FALLING);

  // глубокий сон
  power.setSleepMode(POWERDOWN_SLEEP);
}

// обработчик аппаратного прерывания
void isr() {
  // дёргаем за функцию "проснуться"
  // без неё проснёмся чуть позже (через 0-8 секунд)
  power.wakeUp();  
}

void loop() {
  Serial.println("go sleep");
  delay(300);
  
  // правильно будет вот тут включать прерывание
  // attachInterrupt(1, isr, FALLING);

  // спим 12 секунд, но можем проснуться по кнопке
  power.sleepDelay(12000);
  // тут проснулись по кнопке или через указанный период
  
  // а вот тут сразу отключать
  // detachInterrupt(1);

  Serial.println("wake up!");
  delay(300);
}

ВИДЕО


ПОДДЕРЖАТЬ


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

5/5 - (4 голоса)
Подписаться
Уведомить о
guest

40 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии