STM32 - энергосбережение и WatchDog
Здравствуйте.
В статье описано использование различных режимов энергосбережения и сторожевых таймеров в микроконтроллере stm32 с библиотекой HAL.
Действия будут производится на плате Discovery F3, на ней есть разъём для подключения амперметра…
Схема питания микроконтроллера stm32F3 выглядит следующим образом…
обозначения ножек питания
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 сек…
И включаем прерывание…
Прописываем колбек:
/* 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 — режим глубокой спячки ядра. Останавливаются все генераторы тактовой частоты в
Вход в это режим осуществляется функцией…
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI); // или PWR_STOPENTRY_WFE
У первого аргумента есть два варианта:
У второго аргумента тот же функционал, что и у спящего режима.
Выход из режима остановки возможен только с помощью прерывания или события на любой линии EXTI
При выходе из режима остановки используется HSI RC генератор, поэтому нужно заново сконфигурировать тактирование с помощью функции
Настройте WakeUp…
Программа будет оживать каждые пять секунд.
Активируйте прерывание…
В бесконечном цикле мигнём светиком, выведем значение счётчика и остановим программу:
/* 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 и отпустить её за мгновение до появления надписи
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) микроконтроллера.
В программе задаётся определённый уровень напряжения…
… и если напряжение поднимется выше или опустится ниже этого уровня, то сработает прерывание.
Далее поясню всё на примере.
Активируйте прерывание…
В соседней вкладке создаём инициализацию прерывания…
В коде появится функция...
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.
картинка
PVD может пробуждать МК из режима Sleep, а в режиме Standby он не работает.
WatchDog
WatchDog — сторожевой таймер — предназначен для предотвращения зависания микроконтроллера. По сути это просто счётчик, который запускается во время старта, а в бесконечном цикле постоянно сбрасывается. Если по каким-то причинам бесконечный цикл зависнет и счётчик не будет сброшен, то он досчитав до конца сделает микроконтроллеру Reset.
У stm32 есть два WatchDog'а, один независимый — IWDG, тактируется от HSI RC и работает в любых режимах, а второй WWDG, тактируется той же частотой что и шина APB1. Соответственно в режимах глубокой спячки не функционирует.
Счётчики обоих WatchDog'ов считают «вниз» от установленного значения.
Настройте IWDG следующим образом…
Предделитель частоты устанавливаем — 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 сек.
Теперь раскомментируйте
Пять секунд конечно много, это значение должно быть чуть больше времени работы цикла + прерывания, если они есть. Однако можно устанавливать большую задержку (до 26 сек) и вгонять МК в режим
Например, если указать 2000 (окно от 3125 до 2000)…
… то WatchDog будет постоянно ресетить МК.
Это происходит из-за того, что WatchDog сбрасывается раньше чем успевает досчитать от 3125 до 2000. Если указать 2600, то всё будет окей.
Таким образом можно контролировать не только зависание программы, но и слишком быстрое выполнение её.
зачем это надо
Допустим мы добавили в наш пример какую-то функцию, которая должна выполнятся не менее трёх секунд, тогда создаём окно от 3125 до 1300, а сброс WatchDog'а прописываем после выполнения этой функции. Теперь если что-то пойдёт не так и программа не войдёт в эту функцию, то WatchDog сделает Reset.
Чтоб «окно» не участвовало в работе, оставляйте его значение равным или больше счётчика.
На F103 «окно» есть только у WWDG. Он кстати поэтому так и называется — WWDG (Window Watchdog).
WWDG не такой крутой как IWDG, он не умеет выводить МК из режима
Тактируется на частоте PCLK1 (APB1) делённой на постоянный делитель 4096. Настройки предделителя, «окна» и счётчика такие же как и у IWDG. Счётчик считает в диапазоне от 127 до 64. Да, вот так вот.
Для наглядности эксперимента пришлось понизить частоту шины до 162кГц...
… а WWDG настроил так...
162кГц / 4096 / 4 = 9.89 тиков в секунду, 127 — 64 = 63, 63 / 9.89 = 6.37 сек.
Если активировать
Колбек выглядит так:
void HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef* hwwdg)
{
// последнее желание
}
Счётчик сбрасывается командой…
HAL_WWDG_Refresh(&hwwdg);
На этом пожалуй всё.
Всем спасибо
Чтиво
Телеграм-чат istarik
Телеграм-чат STM32
- 0
- stD
54860
Поддержать автора
Комментарии (0)