STM32 - энергосбережение и WatchDog
![](http://istarik.ru/uploads/images/00/00/01/2019/02/28/f20b8b.png)
Здравствуйте.
В статье описано использование различных режимов энергосбережения и сторожевых таймеров в микроконтроллере stm32 с библиотекой HAL.
Действия будут производится на плате Discovery F3, на ней есть разъём для подключения амперметра…
![](http://istarik.ru/uploads/images/00/00/01/2019/03/01/5d7fba.jpg)
Схема питания микроконтроллера stm32F3 выглядит следующим образом…
![](http://istarik.ru/uploads/images/00/00/01/2019/02/28/f42f82.jpg)
обозначения ножек питания
VDDA domain
HSI, LSI, PLL, АЦП, ЦАП, температурный датчик, OPAMP, COMP и Reset block. Питается от входов
На некоторых МК есть вход
VDD domain
Напряжение подаваемое на входы
RTC domain
RTC, внешний кварц для часов (32.768кГц), LSE и Backup-регистры.
В обычном режиме
Если батарейка не подключена, то рекомендуется соединять
На некоторых stm32 есть специальный вход —
У stm32 есть
POR (Power On Reset – система сброса при подаче питания) — микроконтроллер будет находиться в режиме сброса пока напряжение питания на VDD не достигнет 2v. POR контролирует только напряжение питания VDD. Во время запуска микроконтроллера VDDA должен запитываться первым и быть больше или равным VDD.
PDR (Power Down Reset – система сброса при снижении питания) — при снижении напряжения питания ниже порога, микроконтроллер будет переведён в состояние сброса. PDR контролирует напряжение питания VDD и VDDA. Контроль за напряжением VDDA можно отключать через Option bytes. Так же PDR отвечает за переключение на Vbat.
Ещё есть PVD (Programmable Voltage Detector) — см. ниже.
Режимы пониженного энергопотребления
Есть три режима:
•
•
•
Sleep mode — в этом режиме тактирование ядра остановлено, все цепи ввода-вывода микроконтроллера сохраняют своё состояние.
Вход в режим осуществляется командой…
HAL_PWR_EnterSLEEPMode(PWR_LOWPOWERREGULATOR_ON, PWR_SLEEPENTRY_WFI);
Первый аргумент —
Для второго аргумента есть два варианта:
PWR_SLEEPENTRY_
PWR_SLEEPENTRY_
Режим
Для примера я сделал программу, которая в бесконечном цикле постоянно усыпляет МК, а таймер раз в пять секунд пробуждает его прерыванием и переключает светодиод в обработчике.
Настраиваем таймер №1 на переполнение каждые 5 сек…
![](http://istarik.ru/uploads/images/00/00/01/2019/03/01/c1f43b.jpg)
И включаем прерывание…
![](http://istarik.ru/uploads/images/00/00/01/2019/03/01/691b57.jpg)
Прописываем колбек:
/* USER CODE BEGIN 0 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM1) //check if the interrupt comes from TIM1
{
HAL_ResumeTick();
HAL_GPIO_TogglePin(LD10_GPIO_Port, LD10_Pin);
}
}
Про HAL_ResumeTick() см. ниже.
Запускаем таймер:
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim1);
В бесконечном цикле усыпляем:
/* USER CODE BEGIN WHILE */
while (1)
{
HAL_SuspendTick();
HAL_PWR_EnterSLEEPMode(PWR_LOWPOWERREGULATOR_ON, PWR_SLEEPENTRY_WFI);
...
Перед тем как войти в режим Sleep нужно вызвать функцию HAL_SuspendTick(), которая отключит прерывания от SysTick. Это таймер, отвечающий за функции задержки, типа HAL_Delay(). При выходе из режима включаем эти прерывания функцией HAL_ResumeTick() (см. выше).
Держите в голове, что программа будет выходить из режима Sleep при возникновении любого прерывания, следовательно функция HAL_ResumeTick() должна быть во всех обработчиках прерываний, которые у вас задействованы.
Ещё есть дополнительное условие входа в спящий режим: если установить бит SLEEPONEXIT, то программа будет входить в этот режим только после того, как закончится обработка последнего прерывания. Если очистить бит, то вход произойдёт сразу же после вызова HAL_PWR_EnterSLEEPMode().
HAL_PWR_EnableSleepOnExit(); // установить
HAL_PWR_DisableSleepOnExit(); // очистить
После выхода из режима
Stop mode — режим глубокой спячки ядра. Останавливаются все генераторы тактовой частоты в
![](http://istarik.ru/uploads/images/00/00/01/2019/03/01/6b8369.jpg)
Вход в это режим осуществляется функцией…
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI); // или PWR_STOPENTRY_WFE
У первого аргумента есть два варианта:
У второго аргумента тот же функционал, что и у спящего режима.
Выход из режима остановки возможен только с помощью прерывания или события на любой линии EXTI
При выходе из режима остановки используется HSI RC генератор, поэтому нужно заново сконфигурировать тактирование с помощью функции
Настройте WakeUp…
![](http://istarik.ru/uploads/images/00/00/01/2019/02/23/7a41e9.jpg)
Программа будет оживать каждые пять секунд.
Активируйте прерывание…
![](http://istarik.ru/uploads/images/00/00/01/2019/02/23/7ddbf0.jpg)
В бесконечном цикле мигнём светиком, выведем значение счётчика и остановим программу:
/* USER CODE BEGIN WHILE */
while (1)
{
HAL_GPIO_WritePin(LD10_GPIO_Port, LD10_Pin, GPIO_PIN_SET);
HAL_Delay(100);
HAL_GPIO_WritePin(LD10_GPIO_Port, LD10_Pin, GPIO_PIN_RESET);
HAL_Delay(100);
uint32_t wu = HAL_RTCEx_GetWakeUpTimer(&hrtc); // посмотреть значение счётчика WakeUp
snprintf(trans_str, 63, "WakeUp %lu\n", wu);
HAL_UART_Transmit(&huart1, (uint8_t*)trans_str, strlen(trans_str), 1000);
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI); // PWR_MAINREGULATOR_ON
// после wakeup'а программа стартует отсюда
SystemClock_Config(); // рестартуем системный клок
...
У F103 нету WakeUp'а, поэтому можно воспользоваться будильником.
Когда МК находится в режиме остановки, то прошиваться он не будет. Нужно зажать кнопочку Reset и отпустить её за мгновение до появления надписи
![](http://istarik.ru/uploads/images/00/00/01/2019/03/01/babcd6.jpg)
Atolic TrueStudio
Standby mode
Это самый экономичный режим. Отключается почти что всё, кроме RTC и IWDG
Вход в режим осуществляется функцией…
HAL_PWR_EnterSTANDBYMode();
Выходить из режима можно тремя способами: WakeUp, будильник и IWDG. Сам по себе выход похож на то, как если бы был нажат Reset, то есть МК начинает работу с самого начала.
Для проверки можно использовать код от предыдущего режима изменив функцию входа…
/* USER CODE BEGIN WHILE */
while (1)
{
HAL_GPIO_WritePin(LD10_GPIO_Port, LD10_Pin, GPIO_PIN_SET);
HAL_Delay(100);
HAL_GPIO_WritePin(LD10_GPIO_Port, LD10_Pin, GPIO_PIN_RESET);
HAL_Delay(100);
uint32_t wu = HAL_RTCEx_GetWakeUpTimer(&hrtc); // посмотреть значение счётчика WakeUp
snprintf(trans_str, 63, "WakeUp %lu\n", wu);
HAL_UART_Transmit(&huart1, (uint8_t*)trans_str, strlen(trans_str), 1000);
HAL_PWR_EnterSTANDBYMode();
...
Здесь есть один нюанс. Дело в том, что после срабатывания WakeUp'а микроконтроллер сразу же ресетится, а флаг WakeUp'а остаётся в регистрах взведённым. Соответственно после инициализации и очередного входа в режим, МК видит этот флаг и тут же опять ресетится, и так по кругу.
Побороть это очень просто, надо сбрасывать флаг до бесконечного цикла:
/* USER CODE BEGIN 2 */
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
/* USER CODE END 2 */
При использовании будильника нужно так же сбрасывать этот флаг.
Важно! Когда будете экспериментировать со способами выхода из режима, обесточивайте плату чтоб все регистры очистились и не возникало непоняток.
IWDG выводит МК из Standby просто обресетивая его. Описание ниже.
Помимо режимов энергосбережения можно использовать другие методы снижения энергопотребления: уменьшить тактовую частоту, использовать внутренний генератор (HSI RC), понижать частоту перед переходом в спящий режим, отключать различные шины (см. файл stm32f3xx_hal_rcc.h) и т.д.
PVD (Programmable Voltage Detector) — мониторинг напряжения питания (VDD) микроконтроллера.
В программе задаётся определённый уровень напряжения…
… и если напряжение поднимется выше или опустится ниже этого уровня, то сработает прерывание.
Далее поясню всё на примере.
Активируйте прерывание…
![](http://istarik.ru/uploads/images/00/00/01/2019/03/03/b08e85.jpg)
В соседней вкладке создаём инициализацию прерывания…
![](http://istarik.ru/uploads/images/00/00/01/2019/03/03/f2c315.jpg)
В коде появится функция...
static void MX_NVIC_Init(void)
{
/* PVD_IRQn interrupt configuration */
HAL_NVIC_SetPriority(PVD_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(PVD_IRQn);
}
В программе объявляем глобальный флажок:
/* USER CODE BEGIN PV */
char trans_str[64] = {0,};
volatile uint8_t flag = 0; // флажок
/* USER CODE END PV */
Создаём две функции — инициализация PVD и колбек:
/* USER CODE BEGIN 0 */
static void PVD_Config(void)
{
PWR_PVDTypeDef sConfigPVD = {0,};
sConfigPVD.PVDLevel = PWR_PVDLEVEL_6; // 2.8V
sConfigPVD.Mode = PWR_PVD_MODE_IT_RISING_FALLING; // прерывание при падении и при превышении
HAL_PWR_ConfigPVD(&sConfigPVD); // конфигурируем
HAL_PWR_EnablePVD(); // активируем PVD
}
void HAL_PWR_PVDCallback(void)
{
flag = 1;
}
Чтоб сконфигурировать PVD нужно передать структуру (sConfigPVD) с двумя элементами: первый — пороговое значение, а второй определяет признак, по которому будет вызываться прерывание или событие. В данном случае прерывание будет вызываться и при падении напряжения ниже порога и при превышении. Можно сделать чтоб только при понижении или наоборот.
вариантыPWR_PVD_MODE_NORMAL — Basic mode is used
PWR_PVD_MODE_IT_RISING — External Interrupt Mode with Rising edge trigger detection
PWR_PVD_MODE_IT_FALLING — External Interrupt Mode with Falling edge trigger detection
PWR_PVD_MODE_IT_RISING_FALLING — External Interrupt Mode with Rising/Falling edge trigger detection
PWR_PVD_MODE_EVENT_RISING — Event Mode with Rising edge trigger detection
PWR_PVD_MODE_EVENT_FALLING — Event Mode with Falling edge trigger detection
PWR_PVD_MODE_EVENT_RISING_FALLING — Event Mode with Rising/Falling edge trigger detection
Инициализируем PVD, а в бесконечном цикле проверяем флажок и мигаем светиком:
/* USER CODE BEGIN 2 */
PVD_Config();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
if(flag == 1)
{
HAL_GPIO_WritePin(LD10_GPIO_Port, LD10_Pin, GPIO_PIN_SET);
HAL_Delay(100);
HAL_GPIO_WritePin(LD10_GPIO_Port, LD10_Pin, GPIO_PIN_RESET);
HAL_Delay(100);
flag = 0;
snprintf(trans_str, 63, "FLAG PVDO %d\n", __HAL_PWR_GET_FLAG(PWR_FLAG_PVDO));
HAL_UART_Transmit(&huart1, (uint8_t*)trans_str, strlen(trans_str), 1000);
}
Помимо мигания будем проверять флаг
Если нет необходимости оперативно реагировать на изменение напряжения, то можно просто (без прерываний) проверять этот флаг где-то в программе и делать какие-то действия. При инициализации укажите режим —
Теперь прошейте плату и уменьшайте напряжение (нужно раздобыть какой-нибудь регулятор напряжения), как только напряжение станет ниже 2.8v, то сработает прерывание и флаг
Прерывание сработает только в момент
Во избежание ложных срабатываний (если напряжение будет «дрожать» около порогового значения), у PVD предусмотрен гистерезис ~100mV.
картинка![](//istarik.ru/uploads/images/00/00/01/2019/03/02/05f2d4.jpg)
![](http://istarik.ru/uploads/images/00/00/01/2019/03/02/05f2d4.jpg)
PVD может пробуждать МК из режима Sleep, а в режиме Standby он не работает.
WatchDog
WatchDog — сторожевой таймер — предназначен для предотвращения зависания микроконтроллера. По сути это просто счётчик, который запускается во время старта, а в бесконечном цикле постоянно сбрасывается. Если по каким-то причинам бесконечный цикл зависнет и счётчик не будет сброшен, то он досчитав до конца сделает микроконтроллеру Reset.
У stm32 есть два WatchDog'а, один независимый — IWDG, тактируется от HSI RC и работает в любых режимах, а второй WWDG, тактируется той же частотой что и шина APB1. Соответственно в режимах глубокой спячки не функционирует.
Счётчики обоих WatchDog'ов считают «вниз» от установленного значения.
Настройте IWDG следующим образом…
![](http://istarik.ru/uploads/images/00/00/01/2019/03/03/da5f43.jpg)
Предделитель частоты устанавливаем — 64, а счётчик будет считать от 3125.
IWDG тактируется от LSI RC (40кГц), соответственно он досчитает до нуля через пять секунд. 40кГц / 64 = 625 тиков в секунду, 3125 / 625 = 5 сек. Если мы не сбросим счётчик в течении пяти секунд, то он обресетит МК.
Для проверки соорудим вот такой код:
/* USER CODE BEGIN 2 */
snprintf(trans_str, 63, "START\n");
HAL_UART_Transmit(&huart1, (uint8_t*)trans_str, strlen(trans_str), 1000);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
static uint16_t sec = 1;
HAL_GPIO_WritePin(LD10_GPIO_Port, LD10_Pin, GPIO_PIN_SET);
HAL_Delay(500);
HAL_GPIO_WritePin(LD10_GPIO_Port, LD10_Pin, GPIO_PIN_RESET);
HAL_Delay(500);
snprintf(trans_str, 63, "Sec %d\n", sec++);
HAL_UART_Transmit(&huart1, (uint8_t*)trans_str, strlen(trans_str), 1000);
//HAL_IWDG_Refresh(&hiwdg); // сброс WatchDog'а
...
Поскольку сброс WatchDog'а закомментирован, то МК ресетится каждые 5 сек.
![](http://istarik.ru/uploads/images/00/00/01/2019/03/03/c13574.png)
Теперь раскомментируйте
Пять секунд конечно много, это значение должно быть чуть больше времени работы цикла + прерывания, если они есть. Однако можно устанавливать большую задержку (до 26 сек) и вгонять МК в режим
Например, если указать 2000 (окно от 3125 до 2000)…
![](http://istarik.ru/uploads/images/00/00/01/2019/03/03/c1e31a.jpg)
… то WatchDog будет постоянно ресетить МК.
![](http://istarik.ru/uploads/images/00/00/01/2019/03/03/d307d5.png)
Это происходит из-за того, что WatchDog сбрасывается раньше чем успевает досчитать от 3125 до 2000. Если указать 2600, то всё будет окей.
Таким образом можно контролировать не только зависание программы, но и слишком быстрое выполнение её.
зачем это надо
Допустим мы добавили в наш пример какую-то функцию, которая должна выполнятся не менее трёх секунд, тогда создаём окно от 3125 до 1300, а сброс WatchDog'а прописываем после выполнения этой функции. Теперь если что-то пойдёт не так и программа не войдёт в эту функцию, то WatchDog сделает Reset.
Чтоб «окно» не участвовало в работе, оставляйте его значение равным или больше счётчика.
На F103 «окно» есть только у WWDG. Он кстати поэтому так и называется — WWDG (Window Watchdog).
WWDG не такой крутой как IWDG, он не умеет выводить МК из режима
Тактируется на частоте PCLK1 (APB1) делённой на постоянный делитель 4096. Настройки предделителя, «окна» и счётчика такие же как и у IWDG. Счётчик считает в диапазоне от 127 до 64. Да, вот так вот.
Для наглядности эксперимента пришлось понизить частоту шины до 162кГц...
![](http://istarik.ru/uploads/images/00/00/01/2019/03/03/59b064.jpg)
… а WWDG настроил так...
![](http://istarik.ru/uploads/images/00/00/01/2019/03/03/36bda3.jpg)
162кГц / 4096 / 4 = 9.89 тиков в секунду, 127 — 64 = 63, 63 / 9.89 = 6.37 сек.
Если активировать
Колбек выглядит так:
void HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef* hwwdg)
{
// последнее желание
}
Счётчик сбрасывается командой…
HAL_WWDG_Refresh(&hwwdg);
На этом пожалуй всё.
Всем спасибо
![](https://istarik.ru/uploads/images/00/00/01/2019/10/17/1db9cc.jpg)
Чтиво
Телеграм-чат istarik
Телеграм-чат STM32
![](http://istarik.ru/uploads/images/thnd.png)
- 0
- stD
47787
Поддержать автора
Комментарии (0)