ЭНЕРГОПОТРЕБЛЕНИЕ МИКРОКОНТРОЛЛЕРА

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

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

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

ВНИМАНИЕ! Библиотека версии 1.1 несовместима с 1.0: добавлен класс power. , имя нужно добавить перед всеми методами! См. свежие примеры из папки examples.

БИБЛИОТЕКА GYVERPOWER

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. )
  • Убраны дефайны (для безопасности)
  • Добавлена коррекция миллис на время сна
  • Добавлены примеры и расширенное описание
  • Улучшена стабильность калибровки (не зависает)

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


Сон


Режимы сна

Режим сна устанавливается при помощи 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);

Калибровка 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)
*/

ПРИМЕРЫ


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

#include "GyverPower.h"

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
  
  // настройка параметров сна
  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)); // инвертируем состояние на пине
}
// самый простой пример, для слабонервных =)
#include "GyverPower.h"

void setup() {
  //Serial.begin(9600);
  // сериал для демонстрации!
  // по умолчанию стоит самый экономный режим сна POWER DOWN
}

void loop() {
  // опрашиваем датчики, мигаем светодиодами, etc
  // ...
  // ...
  
  //Serial.println("go sleep");
  //delay(300);

  // спим 5 секунд (5000 мс)  
  power.sleepDelay(5000);
  // тут проснулись и код продолжает выполняться
  
  //Serial.println("wake up!");
  //delay(300);
}
#include "GyverPower.h"

void setup() {
  Serial.begin(9600);
  pinMode(13, OUTPUT);    // шоб мигать
  
  // WDT будит МК ото сна, его таймер не очень точный (с завода) и нуждается в калибровке.
  // Калибровка таймаутов для максимальной точности sleepDelay

  // ====== ВАРИАНТ 1 (ручная калибровка 8 секунд) ======
  Serial.println(power.getMaxTimeout()); // вывести реальный макс. период (выполняется ~8 секунд)
  delay(500); // задержка для вывода текста

  // допустим вывело 8321
  power.calibrate(8321); 			// калибруем по реальному периоду
  // можно жёстко задать результат getMaxTimeout() в скетче (как выше) или пихнуть в EEPROM


  // ====== ВАРИАНТ 2 (автокалибровка 8 секунд) ======
  // автоматически посчитать и откалибровать (выполняется ~8 секунд)
  // каждый раз при запуске скетча
  power.calibrate(power.getMaxTimeout());


  // ====== ВАРИАНТ 3 (быстрая автокалибровка) ======
  power.autoCalibrate(); // автоматическая калибровка ~2 секунды
  // калибрует менее точно, чем вариант с 8-ю секундами. Можно оставить на каждый запуск программы
}

void loop() {
  // пример циклического сна
  power.sleepDelay(1500); // спим ровно 1.5 секунды (мы откалиброваны!)
  digitalWrite(13, !digitalRead(13)); // инвертируем состояние на пине
}
// помогаем миллису сохранить свой счёт на время сна sleepDelay
#include "GyverPower.h"

// На время сна в большинстве режимов millis() останавливается,
// но сон при помощи функции sleepDelay автоматически корректирует millis()! 

void setup() {
  Serial.begin(9600);
  Serial.print("calibraion... ");
  
  power.autoCalibrate(); // автоматическая калибровка таймера сна (чтобы спал точное время)
  // выполняется ~2 секунды
  // см. пример WDT_calibration

  Serial.println("done");
  delay(50);

  // по умолчанию стоит самый экономный режим сна POWER DOWN
  
  // power.correctMillis(false); // можно отключить коррекцию миллис (по умолч. включена)
}

void loop() {
  // опрашиваем датчики, мигаем светодиодами, etc
  // таймер на миллис на 3 секунды для теста
  static uint32_t tmr;
  if (millis() - tmr >= 3000) {
    tmr += 3000;
    Serial.println("kek");
    delay(50);
  }

  // спим 1.5 секунды (1500 мс)
  power.sleepDelay(1500);
  // проснулись
  // миллис корректируется автоматически!
}
// помогаем миллису сохранить свой счёт на время сна sleep
#include "GyverPower.h"

// На время сна в большинстве режимов millis() останавливается, скорректировать можно вручную.
// Функция sleep спит не ровно указанное время, так как WDT таймер имеет заводскую неточность.
// Данный пример показан чисто для примера, не рекомендуется его использовать.
// Для точных периодов сна используйте sleedDelay и калибровку WDT
// см. пример sleepDelayMillis

// "вытягиваем" переменную, отвечающую за счётчик миллис
extern volatile unsigned long timer0_millis;

void setup() {
  Serial.begin(9600);
  // по умолчанию стоит самый экономный режим сна POWER DOWN
}

void loop() {
  // опрашиваем датчики, мигаем светодиодами, etc
  // таймер на миллис на 3 секунды для теста
  static uint32_t tmr;
  if (millis() - tmr >= 3000) {
    tmr += 3000;
    Serial.println("kek");
    delay(50);
  }

  // спим ~512 мс
  power.sleep(SLEEP_512MS);

  // скорректировали миллис: прибавили время сна к счётчику миллис
  timer0_millis += 512;
}
// просыпаемся по аппаратному прерыванию из sleep
#include "GyverPower.h"

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

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

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

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

// обработчик аппаратного прерывания
void isr() {
  // в отличие от sleepDelay, ничего вызывать не нужно!
}

void loop() {
  Serial.println("go sleep");
  delay(300);

  // спим ~8 секунд, но можем проснуться по кнопке
  power.sleep(SLEEP_8192MS);
  // тут проснулись, по кнопке или через указанный период

  Serial.println("wake up!");
  delay(300);
}
// просыпаемся по аппаратному прерыванию из sleepDelay

#include "GyverPower.h"

// 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);

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

  Serial.println("wake up!");
  delay(300);
}
// Делаем свой счётчик времени сна
// допустим таймер 0 у нас используется для других целей, но нам
// нужно хотя бы приблизительно считать время и выполнять по нему действия

#include "GyverPower.h"

uint32_t myMillis = 0;  // наш миллис

void setup() {
  power.autoCalibrate(); // автоматическая калибровка ~2 секунды
}

void loop() {
  static uint32_t tmr1, tmr2;

  // "таймер" на 2 секунды
  if (myMillis - tmr1 > 2000) {
    tmr1 += 2000;
    // действие 1
  }

  // "таймер" на 10 секунд
  if (myMillis - tmr2 > 10000) {
    tmr2 += 10000;
    // действие 2
  }

  // спим 1 секунду (1000 мс)
  // sleepDelay возвращает остаток сна в мс, если он есть
  byte left = power.sleepDelay(1000);

  // прибавляем и корректируем наш миллис
  myMillis += 1000 - left;
}
// управление периферией МК
#include "GyverPower.h"

void setup() {
  Serial.begin(9600);
  // можно выборочно включать-выключать периферийные блоки МК, разделитель - |
  // список констант смотри в GyverPower.h / Константы периферии
  
  // отключили АЦП, таймеры 1 и 0
  power.hardwareDisable(PWR_ADC | PWR_TIMER1 | PWR_TIMER0);
  
  // включили ADC обратно
  power.hardwareEnable(PWR_ADC);
}

void loop() {
  Serial.println(millis());
  // отключили таймер 0 - миллис не тикает...
}
// пример управления системной частотой
// мы можем только уменьшить (разделить) системную частоту (на платах ардуино 16 МГц)
// Пониженная частота позволяет чуть снизить потребление или питать МК от пониженного напряжения!

#include "GyverPower.h"

void setup() {
  power.setSystemPrescaler(PRESCALER_16); 	// замедляем в 16 раз
  
  // с понижением системной частоты "уйдут" все завязанные на частоте блоки периферии!
  // чтобы сериал завёлся (если нужен), умножаем скорость на замедление
  // иначе не заведётся на указанной скорости
  Serial.begin(9600 * 16L);  

  Serial.println("serial test");
}

void loop() {
}
// пример управления системной частотой
// мы можем только уменьшить (разделить) системную частоту (на платах ардуино 16 МГц)
// Пониженная частота позволяет чуть снизить потребление или питать МК от пониженного напряжения!
#include "GyverPower.h"

// с понижением частоты "уйдут" функции времени
// для их коррекции можно сделать так:
#define millis() (millis() << (CLKPR & 0xF))
#define micros() (micros() << (CLKPR & 0xF)) #define delay(x) delay((x) >> (CLKPR & 0xf))
#define delayMicroseconds(x) delayMicroseconds((x) >> (CLKPR & 0xf))

// данные дефайны нужно прописать ПЕРЕД подключением остальных библиотек.
// Таким образом дефайн сможет "проникнуть" в библиотеку и скорректировать 
// работу используемых там функций времени

void setup() {
  power.setSystemPrescaler(PRESCALER_16); 	// замедляем в 16 раз
  pinMode(13, OUTPUT);
}

void loop() {
  digitalWrite(13, !digitalRead(13)); // мигаем
  delay(1000);    // держит 1 секунду, несмотря на пониженный клок!
}
// пример настроек сна
#include "GyverPower.h"

void setup() {
  pinMode(13, OUTPUT);    // шоб мигать
  
  // отключение ненужной периферии
  power.hardwareDisable(PWR_ADC | PWR_TIMER1); // см. константы, разделяющий знак " | "
  // можно отключить всё
  // hardwareDisable(PWR_ALL);

  power.setSleepMode(STANDBY_SLEEP); // режим сна (по умолчанию POWERDOWN_SLEEP)
  //bodInSleep(false); // отключение BOD (по у молчанию и так выключен) - экономит энергию
}

void loop() {
  power.sleep(SLEEP_2048MS); // спим ~ 2 секунды (некалиброванный таймаут. Смотри пример с калибрвокой!)
  digitalWrite(13, !digitalRead(13)); // инвертируем состояние на пине
}
// спим в EXTSTANDBY_SLEEP, считаем время таймером 2
// используется uptime2.h (лежит в папке с примером)
// НЕ РЕКОМЕНДУЕТСЯ К ИСПОЛЬЗОВАНИЮ, смотрите пример sleepDelayMillis

#include "uptime2.h"
#include "GyverPower.h"

void setup() {
  Serial.begin(9600);
  uptime2Init();    // запуск миллиса на 2 таймере
  power.setSleepMode(EXTSTANDBY_SLEEP);

  // ВНИМАНИЕ!
  // миллис2 сделан на прерываниях таймера 2
  // прерывания настроены на частоту ~976.56 Гц
  // с этой частотой МК будет просыпаться вне зависимости от указанного периода сна!!
  // можно даже SLEEP_FOREVER поставить
}

void loop() {
  static uint32_t tmr;
  // наш таймер на миллис2
  if (millis2() - tmr >= 1000) {
    tmr += 1000;
    Serial.print("time: ");
    Serial.println(millis2());
    delay2(50);
  }

  // спим
  power.sleep(SLEEP_FOREVER);
  // тут проснулись в любом случае по прерыванию и код продолжает выполняться
}