Введите текст заголовка

Страница на этапе разработки
В данном уроке мы рассмотрим режимы сна для микроконтроллеров фирмы STM32.

Микроконтроллеры STM32 имеют несколько режимов сна:  Run, Sleep, Stop, Standby и Shutdown, которые позволяют значительно снизить энергопотребление, останавливая работу определенных частей микроконтроллера, таких как процессорное ядро или тактовые генераторы. Каждый режим имеет свои особенности, что позволяет выбрать оптимальное решение для конкретного приложения.
Основные режимы сна:
  • Run (Работа): В этом режиме процессор и все периферийные устройства активны, и потребление энергии зависит от частоты работы тактового генератора. 
  • Sleep (Сон): Процессорное ядро останавливается, но все периферийные устройства и тактовые генераторы остаются активными. Это самый быстрый режим выхода из сна.
  • Stop (Остановка): Все внутренние тактовые генераторы останавливаются, что приводит к значительному снижению энергопотребления. Содержимое оперативной памяти и регистров сохраняется. 
  • Standby (Ожидание): В этом режиме останавливаются как процессор, так и тактовые генераторы. 
  • Shutdown (Выключение): Наиболее глубокий режим сна, при котором отключаются все источники питания и периферийные устройства. Только питание резервной батареи (VBAT) может сохранять содержимое регистров в памяти. 

Факторы, влияющие на режимы сна:

  • Периферийные устройства: При переходе в более глубокие режимы сна отключаются периферийные устройства, что снижает потребление тока. 
  • Тактовые генераторы: Остановка внутренних и внешних тактовых генераторов (HSI, HSE) также значительно снижает энергопотребление. 
  • Источники питания: Сохранение данных и работа определенных функций (например, схемы сброса) может потребовать питания от источника VBAT. 
Применение:
 
      • Режимы сна подходят для приложений с батарейным питанием, где требуется минимизировать потребление энергии. 
      • Режим Sleep идеален, когда скорость пробуждения важнее, чем максимальная экономия энергии. 
      • Более глубокие режимы, такие как Standby и Shutdown, используются, когда нужно достичь максимального срока службы батареи. 
         
         

Рассмотрим один из примеров применения режима сна в микроконтроллере STM32G031F4P6. В данном микроконтроллере есть несколько режимов сна (не все режимы сна можно реализовать на том или ином микроконтроллере), из доступных нас будет интересовать режим STOP1 (режим STOP2 для более глубокова сна в данном микроконтроллере не доступен).

В данном микроконтроллере есть LPTIM таймер который имеет очень низкое потребление и может выводить из сна наш микроконтроллер.  Для работы с ним мы активируем его:

и пока оставим все остальные настройки без изменения.
Для проверки его работы мы будем мигать светодиодом, который подключен к ножке PA4. Рабочую частоту микроконтроллера выберем 8МГц (тактирование от внутреннего источника), а тактирование LPTIM1 таймера от LSI:

Теперь можно сгенерировать проект.
Напишем две программы: первая и вторая будет выполнять один и тот же код, но в первой случаи мы просто будем использовать задержку и замеряем ток потребления, во второй на время задержки мы будем ложить микроконтроллер в не глубокий сон (Stop1) и так же замеряем ток потребления.
  HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_4);
  HAL_Delay(1000);

Программный код довольно простой, но он нам полностью подойдет для понимания экономии в потреблении. Ток потребления у Вас будет зависит от резистора который идет на светодиод и от самого светодиода. У меня ток составил около 4 мА.
Теперь рассмотрим второй программный код:

/* USER CODE BEGIN 0 */
void Sleep_ms(uint32_t ms){
      uint32_t start = HAL_GetTick();

      while ((HAL_GetTick() — start) < ms) {
      // Входим в режим сна CPU (Sleep)

          HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
        }
       HAL_ResumeTick();// восстанавливаем HAL tick
}
/* USER CODE END 0 */

/* USER CODE BEGIN 2 */
HAL_Delay(2000);
/* USER CODE END 2 */

//main
  HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_4);
  Sleep_ms(1000);

Прежде чем запустить программный код, проверьте, что у Вас в коде стоит вначале небольшая задержка, это нужно для того, что бы можно было подключаться к микроконтроллеру и записывать новый код. Иначе микроконтроллер заснет, и вы не можете к нему подключиться и Вам придется ловить момент когда он не спит, что бы перезалить программный код.
Сам программный код довольно прост, мы указываем на какое время нам нужно лечь спать,  отключаем все что можно безопасно отключить, спим, по истечению времени наш таймер будит микроконтроллер и программа продолжается дальше с того места где остановилась.
Единственно, что остается под вопросом это делитель:
Мы его должны выставить так, что бы не зависимости от выбранного времени у нас счетчик не переполнялся и при этом точно считал.
Так же нужно помнить про источник тактирования:
LSI (~32.768 kHz RC) → точность ±1–2%.
LSE (32768 Hz кварц) → высокая точность (рекомендуется для RTC/таймера).

Что бы нам не просчитывать самим необходимый делитель, мы расширим свою программу: 

void Sleep_ms(uint32_t ms){
    uint32_t start = HAL_GetTick();
    // LSI (~32 kHz)
    __HAL_RCC_LSI_ENABLE();
    while (__HAL_RCC_GET_FLAG(RCC_FLAG_LSIRDY) == RESET);
 
    uint32_t lsi_freq = 32000; // LSI
    uint32_t prescaler = 1;
    uint32_t arr = (ms * lsi_freq) / 1000;
 
    //prescaler, arr <= 0xFFFF
    if (arr > 0xFFFF) { prescaler = 2; arr /= 2; }
    if (arr > 0xFFFF) { prescaler = 4; arr /= 2; }
    if (arr > 0xFFFF) { prescaler = 8; arr /= 2; }
    if (arr > 0xFFFF) { prescaler = 16; arr /= 2; }
    if (arr > 0xFFFF) { prescaler = 32; arr /= 2; }
    if (arr > 0xFFFF) { prescaler = 64; arr /= 2; }
    if (arr > 0xFFFF) { prescaler = 128; arr /= 2; }
    if (arr > 0xFFFF) arr = 0xFFFF; //
 
    // LPTIM prescaler
    switch (prescaler)
    {
        case 1:   hlptim1.Init.Clock.Prescaler = LPTIM_PRESCALER_DIV1; break;
        case 2:   hlptim1.Init.Clock.Prescaler = LPTIM_PRESCALER_DIV2; break;
        case 4:   hlptim1.Init.Clock.Prescaler = LPTIM_PRESCALER_DIV4; break;
        case 8:   hlptim1.Init.Clock.Prescaler = LPTIM_PRESCALER_DIV8; break;
        case 16:  hlptim1.Init.Clock.Prescaler = LPTIM_PRESCALER_DIV16; break;
        case 32:  hlptim1.Init.Clock.Prescaler = LPTIM_PRESCALER_DIV32; break;
        case 64:  hlptim1.Init.Clock.Prescaler = LPTIM_PRESCALER_DIV64; break;
        case 128: hlptim1.Init.Clock.Prescaler = LPTIM_PRESCALER_DIV128; break;
    }
HAL_LPTIM_Init(&hlptim1); 
while ((HAL_GetTick() — start) < ms)    {
       HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
    }
   HAL_ResumeTick();

И теперь текже замеряем ток потребления, он у меня составил немного меньше 3мА. Как мы видим потребление снизилось на 1мА в среднем.

Для использования полноценного STOP1, необходимо немного доработать рабочие файлы.  
Для начало надо активировать прерывание на таймере LPTIM1 и скомпилировать файл.
Дале найти файл stm32g0xx_it.c и в конце добавить функцию:

/* USER CODE BEGIN 1 */
void HAL_LPTIM_AutoReloadMatchCallback(LPTIM_HandleTypeDef *hlptim){
   if (hlptim->Instance == LPTIM1){
   HAL_LPTIM_Counter_Stop_IT(hlptim);
  }
}
/* USER CODE END 1 */
Далее в теле программы добавить подпрограмму:

extern LPTIM_HandleTypeDef hlptim1;

void Sleep_Stop1_ms(uint32_t ms){
    // 1 тик LPTIM ≈ 30.5 мкс (при LSI = 32768 Гц, PSC = 1)
    uint32_t ticks = (ms * 32768UL) / 1000UL;
    if (ticks < 1) ticks = 1; // минимум 1 тик
    if (ticks > 0xFFFF) ticks = 0xFFFF;

   // Сбрасываем счётчик
   __HAL_LPTIM_DISABLE(&hlptim1);
   __HAL_LPTIM_ENABLE(&hlptim1);

   // Загружаем ARR = ticks
   __HAL_LPTIM_AUTORELOAD_SET(&hlptim1, ticks);

   // Разрешаем прерывание по совпадению
   __HAL_LPTIM_CLEAR_FLAG(&hlptim1, LPTIM_FLAG_ARRM);
   __HAL_LPTIM_ENABLE_IT(&hlptim1, LPTIM_IT_ARRM);

  // Запускаем счётчик
  HAL_LPTIM_Counter_Start_IT(&hlptim1, ticks);

  // Уходим в STOP1
  HAL_PWR_EnterSTOPMode(PWR_MAINREGULATOR_ON, PWR_STOPENTRY_WFI);

  // После пробуждения MCU продолжит отсюда
  HAL_LPTIM_Counter_Stop_IT(&hlptim1);
}

Использование функции:
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_6);
Sleep_Stop1_ms(4); // сон ровно 4 мс

Теперь так же замеряем ток потребления, он у меня составил ???

Теперь рассмотрим режимы сна, в микроконтроллере stm32f030, в нем нет LPTIM таймера, но зато есть другие возможности переходить в режим сна.

Доступные режимы сна у STM32F030

  1. Sleep mode
    CPU спит, периферия работает (TIM, UART, I²C).
    Ток: ~1–2 мА.
    Выход: любое прерывание.

  2. Stop mode
    CPU и большинство тактировок выключены.
    RAM сохраняется.
    Ток: ~5–10 µA.
    Выход: по EXTI или RTC (обычные TIM тут не работают).

  3. Standby
    Всё выключено, RAM теряется.
    Ток: ~1 µA.
    Выход: только по Reset/RTC/WKUP.

Реализуем  сон используя таймер. В качестве таймера возьмем TIM2.

Настройка параметров:

В CubeMX включаем TIM2, Internal Clock.
Prescaler = (SystemCoreClock / 1000) — 1 → таймер считает в миллисекундах.
ARR = нужное количество мс (например, 4).
Включить Update Interrupt.

Программный код:

void Sleep_ms(uint32_t ms){

    __HAL_TIM_SET_AUTORELOAD(&htim2, ms);
    __HAL_TIM_SET_COUNTER(&htim2, 0);
    __HAL_TIM_CLEAR_FLAG(&htim2, TIM_FLAG_UPDATE);
    __HAL_TIM_ENABLE_IT(&htim2, TIM_IT_UPDATE);
    HAL_TIM_Base_Start_IT(&htim2);

   // Уходим в Sleep
   HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI); HAL_TIM_Base_Stop_IT(&htim2);
}
В обработчике прерывания пишем:

void TIM2_IRQHandler(void){
    HAL_TIM_IRQHandler(&htim2);
}

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
    if (htim->Instance == TIM2){
    // факт пробуждения
  }
}

микроконтроллер спит 4 мс и пробуждается по таймеру.

Если нам нужно спать секунды, код необходимо немного переделать:
Для длинного сна (секунды, минуты) → включаем RTC и уходим в Stop mode.

 

// Пример: сон на 1 секунду через RTC wake-up
void Sleep_Stop(uint32_t seconds){
   HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, seconds,  RTC_WAKEUPCLOCK_CK_SPRE_16BITS);
   HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_SLEEPENTRY_WFI);
   HAL_RTCEx_DeactivateWakeUpTimer(&hrtc);
}
Как мы видим д
ля STM32F030 короткие задержки (мс → десятки мс) → Sleep + TIM2.
Для длинных (секунды, минуты) → Stop + RTC wake-up.

Подготовка проекта в CubeMX:

  1. Включаем TIM2 → Clock Source = Internal Clock.

  2. Включаем прерывание Update Event (UIF).

  3. NVIC → включаем TIM2_IRQn.

  4. Код генерации → HAL.

Программный код:

extern TIM_HandleTypeDef htim2;

static volatile uint8_t tim2_wakeup = 0;
void Sleep_ms_F0(uint32_t ms){
  tim2_wakeup = 0;

// Настроить автоперезагрузку под миллисекунды
// Предполагаем, что в CubeMX PSC выставлен так, чтобы 1 тик = 1 мс
   __HAL_TIM_SET_AUTORELOAD(&htim2, ms);
   __HAL_TIM_SET_COUNTER(&htim2, 0);
   __HAL_TIM_CLEAR_FLAG(&htim2, TIM_FLAG_UPDATE);

// Разрешаем прерывание
   __HAL_TIM_ENABLE_IT(&htim2, TIM_IT_UPDATE);
   HAL_TIM_Base_Start_IT(&htim2);

// Уходим в Sleep до прерывания
  while (!tim2_wakeup){
     HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
  }
 HAL_TIM_Base_Stop_IT(&htim2);
}
Так же прописываем прерывания:
void TIM2_IRQHandler(void){
   HAL_TIM_IRQHandler(&htim2);
}

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
   if (htim->Instance == TIM2){
      tim2_wakeup = 1; // флаг для выхода из сна
   }
}
Пример использования:
while (1)
{
   HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_6);
   Sleep_ms_F0(4); // сон ровно 4 мс
}
Как это работает:

  • Таймер TIM2 настроен так, что 1 тик = 1 мс
    (Prescaler = (SystemCoreClock/1000)-1, ARR = ms).

  • микроконтроллер  уходит в Sleep (WFI), ток падает, но TIM2 продолжает работать.

  • Через ms мс TIM2 даёт прерывание → MCU просыпается → флаг tim2_wakeup = 1.

Теперь напишем код для STM32F030, где микроконтроллер будет засыпать в STOP mode на секунды/минуты и просыпаться от RTC Wake-Up таймера.

Настройка проекта в CubeMX:
Включаем RTC.
Clock Source → LSI (32 kHz) или LSE (32.768 kHz кварц).

Включаем опцию Activate Clock Source for RTC.
В RTC настрой WakeUp Timer (можно оставить выключенным, мы включим его в коде).
В NVIC включаем RTC_IRQn.

Программный код:

extern RTC_HandleTypeDef hrtc;

void Sleep_Stop_RTC(uint32_t seconds){
  // Отключаем старый wake-up
  HAL_RTCEx_DeactivateWakeUpTimer(&hrtc);

  // Устанавливаем новый wake-up таймер
  // Таймер работает от 1 Hz при LSI/LSE → счётчик = секунды
  HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, seconds,   RTC_WAKEUPCLOCK_CK_SPRE_16BITS);

  // Уходим в STOP mode, ждём прерывания
  HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_SLEEPENTRY_WFI);

  // MCU проснулся → выключаем wake-up таймер
  HAL_RTCEx_DeactivateWakeUpTimer(&hrtc);

  // После STOP режимов надо восстановить тактирование
  SystemClock_Config();
}

Прописываем прерывание:

void RTC_IRQHandler(void){
  HAL_RTCEx_WakeUpTimerIRQHandler(&hrtc);
}
 
void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc){
// Здесь можно вставить код, выполняемый при пробуждении
// Например, включить датчик или помигать светодиодом
}
Пример использования:
 

while (1){
   HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_6); // мигнули светодиодом
   Sleep_Stop_RTC(5); // спим 5 секунд в STOP mode
}

Нюансы использования:
STOP mode = ток ~5–10 µA.
RAM сохраняется, массивы и переменные не теряются.
После выхода из STOP нужно перезапустить тактирование (через SystemClock_Config()).
Максимальное время зависит от RTC (до 18 бит →  65535 секунд ≈ 18 часов).

 

SVG to HTML/CSS