Embedded HandbookПриложения
Все разделы › II. Ядро и системные механизмы
STM32F103, STM32F407 · уровень средний · 20 мин чтения
Открыть в интерактивном справочнике

10. Питание и энергосбережение

Устройство от розетки может не спать никогда. Устройство от батарейки обязано спать почти всегда: разница между работающим и спящим камнем — четыре порядка, и именно она решает, проживёт изделие неделю или три года.


10.1 Три режима сна

Как это называется в документации

Оригинал Перевод
low-power modes режимы пониженного потребления
Sleep / Stop / Standby сон / останов / глубокий сон
wakeup pin вывод пробуждения
backup domain резервный домен
voltage regulator стабилизатор напряжения
low-power regulator экономичный режим стабилизатора
programmable voltage detector (PVD) программируемый детектор напряжения
power reset / brown-out reset сброс по питанию / по просадке
to retain the content сохранять содержимое
wakeup latency время выхода из режима
send event on pending (SEVONPEND) событие по ожидающему прерыванию
burden voltage падение напряжения на амперметре
average current средний ток
debug in low-power modes отладка в режимах сна
Sleep Stop Standby
ядро остановлено остановлено выключено
периферия работает остановлена выключена
содержимое ОЗУ сохраняется сохраняется теряется
регистры периферии сохраняются сохраняются теряются
выход любое прерывание EXTI, RTC, IWDG вывод WKUP, RTC, сброс
после выхода продолжение с той же строки продолжение, но такты сброшены на HSI сброс, программа с начала
время выхода такты микросекунды десятки–сотни мкс
потребление (F103, порядок) единицы–десятки мА десятки мкА единицы мкА

Три режима сна ступенями: Sleep, Stop и Standby — ток, время выхода и что теряется

Время выхода считают до первой команды программы: из Standby у F103 это 50 мкс, у F407 — 375 мкс. Но после Standby программа начинает с начала: запуск кварца (около 2 мс), PLL и стартовый код добавляют миллисекунды.

Цифры даны как порядок величин: точные значения зависят от температуры, напряжения и того, что включено. Смотреть их надо в datasheet, раздел Supply current characteristics — там таблицы на каждый режим.

Как выбирать. Если пауза короткая (миллисекунды между опросами датчика) и периферия должна продолжать работать — Sleep. Если пауза длинная, но состояние программы нужно сохранить — Stop. Если устройство просыпается редко и по внешнему событию, а состояние можно восстановить — Standby.


10.2 Sleep: останавливается только ядро

Самый простой режим. Ядро останавливается, всё остальное продолжает работать: таймеры считают, UART принимает, DMA передаёт.

void enter_sleep(void)
{
    SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk;   /* обычный сон, не глубокий */
    __WFI();                              /* Wait For Interrupt */
    /* Проснулись ровно здесь: контекст цел. */
}

Если в программе идёт SysTick (в HAL он есть всегда), он будит ядро каждую миллисекунду. На время сна его останавливают — HAL_SuspendTick() или SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk — и после пробуждения включают обратно.

__WFI() — инструкция ядра «ждать прерывания». Есть и __WFE() — «ждать события»; она реагирует и на события без вызова обработчика (те самые режимы GPIO_MODE_EVT_* из раздела 8).

Полезный приём — бит SLEEPONEXIT: с ним ядро автоматически засыпает при выходе из каждого обработчика. Программа превращается в набор реакций на прерывания, а между ними камень спит:

SCB->SCR |= SCB_SCR_SLEEPONEXIT_Msk;

Ещё один бит этого регистра, SEVONPEND, делает событием для __WFE() любое прерывание, ставшее ожидающим, — даже запрещённое в NVIC. Камень просыпается, а обработчик не вызывается: так ждут флаг периферии без обработчика прерывания.


10.3 Stop: остановлены такты, память цела

Останавливаются все генераторы, но содержимое ОЗУ и регистров сохраняется. Просыпается камень по внешнему прерыванию, будильнику RTC или сторожевому таймеру.

Есть и условие входа: все ожидающие флаги EXTI, флаги прерываний периферии и флаг будильника RTC должны быть сброшены. Если висит хоть один, Stop не наступит — программа просто пойдёт дальше после WFI. Флаги EXTI сбрасывают все сразу строкой EXTI->PR = EXTI->PR;: флаг сбрасывается записью единицы, и так уходит всё, что висит.

void enter_stop(void)
{
    RCC->APB1ENR |= RCC_APB1ENR_PWREN;    /* доступ к регистрам питания */

    PWR->CR &= ~PWR_CR_PDDS;              /* 0 -> Stop, а не Standby */
    PWR->CR |= PWR_CR_LPDS;               /* регулятор в экономичный режим */
    SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;    /* глубокий сон */

    __WFI();

    /* ← сюда возвращаемся после пробуждения */
    SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk;
    SystemClock_72MHz();                  /* обязательно! см. ниже */
}

⚠️ Главная грабля всего раздела. После выхода из Stop система тактируется от HSI, а не от вашего PLL. Кварц выключен, множитель сброшен. Программа продолжается с той же строки и выглядит рабочей, но идёт в девять раз медленнее: задержки растягиваются, UART сыплет мусором, ШИМ меняет частоту. Настройку тактирования из раздела 6 нужно применить заново — первым же делом после пробуждения.

Обратите внимание, что Stop и Standby различаются одним битом PDDS. Перепутать их легко, а последствия разные: во втором случае программа не продолжится, а начнётся заново.


10.4 Standby: выход через сброс

Выключается почти всё, включая основной стабилизатор. Живым остаётся только резервный домен. Потребление — единицы микроампер, то есть камень может ждать годами.

void enter_standby(void)
{
    RCC->APB1ENR |= RCC_APB1ENR_PWREN;

    PWR->CSR |= PWR_CSR_EWUP;             /* разрешить вывод WKUP (PA0) */
    PWR->CR |= PWR_CR_PDDS;               /* 1 -> Standby */
    PWR->CR |= PWR_CR_CWUF;               /* сбросить флаг — после EWUP */
    SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;

    __WFI();
    /* Сюда исполнение уже не вернётся. */
}

Пробуждение из Standby — это сброс: программа начинается с Reset_Handler, ОЗУ потеряно (там случайные значения, нули в .bss заново пишет стартовый код), периферия в исходном состоянии. Отличить такой старт от обычного включения можно по флагу:

uint8_t woke_from_standby(void)
{
    RCC->APB1ENR |= RCC_APB1ENR_PWREN;
    if (PWR->CSR & PWR_CSR_SBF) {
        PWR->CR |= PWR_CR_CSBF;           /* сбросить признак */
        return 1u;
    }
    return 0u;
}

⚠️ Сбрасывайте CWUF перед входом, последним. Если флаг пробуждения остался взведённым, камень выйдет из Standby мгновенно — и вы увидите бесконечную перезагрузку вместо сна. У F1 включение EWUP при высоком уровне на PA0 само взводит флаг, поэтому CWUF ставят после EWUP, прямо перед __WFI.


10.5 Резервный домен

Отдельная область, питающаяся от вывода VBAT — обычно от литиевой батарейки или ионистора. Она продолжает работать, когда всё остальное обесточено.

Что там живёт:

Запись туда защищена, и защиту надо снимать явно:

void backup_domain_write(uint32_t value)
{
    RCC->APB1ENR |= RCC_APB1ENR_PWREN | RCC_APB1ENR_BKPEN;
    PWR->CR |= PWR_CR_DBP;                /* снять защиту записи */
    BKP->DR1 = (uint16_t)value;
    PWR->CR &= ~PWR_CR_DBP;               /* вернуть защиту */
}

Забытый DBP — типичная причина «пишу в резервный регистр, а он не меняется». Ошибки при этом нет, запись просто игнорируется.

Резервные регистры удобны как раз для Standby: сохранить перед сном то, что нельзя потерять, и прочитать после пробуждения.


10.6 Чем можно разбудить

Источник Из Sleep Из Stop Из Standby
любое прерывание периферии да нет нет
внешнее прерывание EXTI да да нет
будильник RTC да да да
сторожевой таймер IWDG через сброс через сброс через сброс
вывод WKUP (PA0) да да да
сигнал NRST да да да

Ключевое: из Standby будят только WKUP, RTC и сброс. Обычные выводы там не работают — контроллер EXTI обесточен. Если устройство должно просыпаться по нажатию кнопки, кнопку разводят на PA0.

Для периодического пробуждения используют RTC: он тактируется от LSE или LSI, которые в резервном домене продолжают работать.

Запущенный сторож во сне не останавливается. У F1 и F4 IWDG нельзя выключить ни программой, ни сном: он считает и в Stop, и в Standby. Если сон длиннее его периода (у F1 не больше 4096 × 256 / 40 кГц ≈ 26 с), камень перезагрузится. Тогда RTC будит камень раньше срока, программа сбрасывает сторож и засыпает снова. У L4, G0 и G4 сторож в Stop и Standby замораживают битами опций IWDG_STOP и IWDG_STDBY.


10.7 Детектор напряжения (PVD)

Сравнивает питание с порогом и сообщает, когда оно просело. Это не сброс, а предупреждение: у программы остаётся время сохранить данные во Flash или резервные регистры, пока конденсаторы ещё держат заряд.

PWR->CR = (PWR->CR & ~PWR_CR_PLS) | PWR_CR_PLS_2V7;   /* порог 2,7 В */
PWR->CR |= PWR_CR_PVDE;         /* включить детектор */
EXTI->RTSR |= EXTI_RTSR_TR16;   /* нарастающий фронт PVDO: питание упало ниже порога */
EXTI->IMR  |= EXTI_IMR_MR16;
NVIC_EnableIRQ(PVD_IRQn);

Полезно в изделиях, где отключение питания может произойти в любой момент: счётчики, регистраторы, всё, где обрыв посреди записи портит данные.


10.8 Как измерить ток сна

Цифры таблицы 10.1 — порядок, а не обещание. Своё устройство надо измерить: забытая подтяжка или отладчик легко добавляют больше, чем тратит сам спящий камень. А измерить труднее, чем кажется: за миллисекунды ток меняется в сотни раз.

Пример на весь подраздел — датчик от двух пальчиковых батареек (3,0 В, около 2500 мА·ч). Раз в секунду он просыпается на 5 мс и берёт в это время 10 мА, остальное время спит в Stop при 20 мкА.

Куда включать прибор. Амперметр ставят в разрыв питания самого камня, а не всей платы: стабилизатор и светодиод питания платы тоже берут ток, часто больше спящего камня. На платах Nucleo для этого есть перемычка IDD: если её снять, разрывается питание только микроконтроллера.

Прибор сам мешает. Амперметр — это шунт, и на нём падает напряжение. У простого 3½-разрядного мультиметра шунт каждого предела подобран так, чтобы полная шкала давала на нём 200 мВ:

Предел Шунт Сон, 20 мкА Пробуждение, 10 мА
200 мкА 1 кОм 20 мВ, шаг 0,1 мкА нужно 10 В — невозможно
2 мА 100 Ом 2 мВ, шаг 1 мкА 1 В: остаётся 2,0 В
20 мА 10 Ом 0,2 мВ, шаг 10 мкА 0,1 В

На пределе 200 мкА через прибор пройдёт не больше (3,0 − 2,0) В / 1 кОм = 1,0 мА. При попытке проснуться питание проседает ниже 2,0 В — нижней границы для F103. Какое-то время камень работает на заряде конденсаторов (10 мкФ при токе 10 мА разряжаются на вольт за миллисекунду), а потом уходит в сброс. Короткое пробуждение иногда проходит, длинное — никогда, и со стороны это похоже на загадочный сбой. На пределе 2 мА камень просыпается ровно на границе допустимого, а на пределе 20 мА ток сна уже не разглядеть.

Отсюда приём: перемычка параллельно прибору. Запуск и пробуждения идут через замкнутую перемычку, а когда камень заснул, её размыкают и читают показания.

Мультиметр усредняет не то. Показания обновляются два-три раза в секунду, а усредняются по окну порядка 100 мс. Если всплеск попал в окно, прибор покажет (10 мА · 5 мс + 20 мкА · 95 мс) / 100 мс = 519 мкА, если не попал — 20 мкА. Показания прыгают между этими числами, а настоящее среднее, около 70 мкА, не показывает ни одно из них.

Ток датчика на измерителе и на осциллографе: всплески 10 мА по 5 мс, сон 20 мкА, среднее 69,9 мкА

Три замера вместо одного.

  1. Ток сна. Пробуждения выключены (или будильник поставлен на минуту), предел 200 мкА, перемычка.
  2. Всплеск. Шунт 10 Ом в питании и осциллограф: 10 мА дают на нём 100 мВ, высоту и ширину импульса видно. Ток сна на том же шунте даёт 0,2 мВ и тонет в шумах, поэтому его и мерят отдельно.
  3. Средний ток. Его считают:
I_ср = I_акт · t_акт / T + I_сна · (1 − t_акт / T)
     = 10 мА · 0,005 + 20 мкА · 0,995 = 50 + 19,9 = 69,9 мкА

Приборы, которые меряют всё сразу, существуют — это измерители тока с автоматическим переключением пределов. Задача для них непростая: ток меняется в 500 раз, а чтобы видеть ток сна с точностью 1 %, нужно 10 мА / 0,2 мкА = 50 000 отсчётов. Это в 25 раз больше, чем у 3½-разрядного мультиметра.

Что даёт расчёт. Пять миллисекунд из тысячи — полпроцента времени — дают 50 из 69,9 мкА, то есть 72 % расхода. Батарея 2500 мА·ч проживёт 2500 / 0,0699 ≈ 35 800 ч, это 4,1 года. Что будет, если менять по одной части:

Что изменили Средний ток Срок батареи
ничего 69,9 мкА 4,1 года
всплеск вдвое короче, 2,5 мс 45 мкА 6,3 года
ток сна вдвое меньше, 10 мкА 60 мкА 4,8 года
забыта подтяжка на выводе, прижатом к земле 145 мкА 2,0 года

Вдвое более короткий всплеск прибавил больше двух лет, а вдвое меньший ток сна — восемь месяцев. Охотиться за микроамперами сна стоит, когда активная часть уже ужата. Встроенная подтяжка — около 40 кОм. Если вывод снаружи прижат к земле нажатой кнопкой или выходом соседней микросхемы, через неё течёт 3,0 В / 40 кОм = 75 мкА — больше, чем весь средний ток примера. Одна такая подтяжка сокращает срок батареи вдвое.

Подготовить камень к замеру. Неиспользуемые выводы переводят в аналоговый режим: у него выключен входной триггер, и висящий вход ток не берёт. У F1 это четыре нулевых бита в CRL/CRH, у F4 — две единицы в MODER. После сброса выводы и F1, и F4 — входы (кроме выводов отладки), так что переводить приходится самому; у G4 и L4 выводы после сброса уже аналоговые.

/* keep — выводы порта, которые трогать нельзя (1 — оставить как есть) */
void port_to_analog(GPIO_TypeDef *port, uint16_t keep)
{
#if defined(STM32F10X_MD)
    uint32_t lo = 0, hi = 0;                /* по четыре бита на вывод */
    for (int i = 0; i < 8; i++) {
        if (keep & (1u << i))       lo |= 0xFu << (4 * i);
        if (keep & (1u << (i + 8))) hi |= 0xFu << (4 * i);
    }
    port->CRL &= lo;                        /* аналоговый вход у F1 — 0000 */
    port->CRH &= hi;
#elif defined(STM32F407xx)
    uint32_t m = 0;                         /* по два бита на вывод */
    for (int i = 0; i < 16; i++)
        if (keep & (1u << i))
            m |= 3u << (2 * i);
    port->MODER |= ~m;                      /* аналоговый режим у F4 — 11 */
    port->PUPDR &= m;                       /* и подтяжки снять */
#endif
}

void sleep_measure_prepare(void)
{
#if defined(STM32F10X_MD)                   /* без такта запись в порт не проходит */
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_IOPCEN;
#elif defined(STM32F407xx)
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN | RCC_AHB1ENR_GPIOBEN | RCC_AHB1ENR_GPIOCEN;
#endif
    port_to_analog(GPIOA, (1u << 13) | (1u << 14));   /* SWDIO и SWCLK */
    port_to_analog(GPIOB, 0);
    port_to_analog(GPIOC, (1u << 14) | (1u << 15));   /* кварц часов LSE */
    debug_off_in_sleep();
}

Выводы отладки PA13 и PA14 оставлены как есть: в аналоговом режиме программатор может потерять связь с камнем, и прошивать придётся с подключением под сбросом. Выводы кварца часов PC14 и PC15 оставлены на случай, если RTC будит камень от LSE. Остальные порты обрабатываются так же.

Отладчик. Чтобы не терять камень во сне, среда отладки ставит в DBGMCU->CR биты DBG_SLEEP, DBG_STOP и DBG_STANDBY — и в Stop продолжают идти такты от HSI: вместо десятков микроампер получается ток порядка Sleep. Кнопка сброса эти биты не снимает, их сбрасывает только выключение питания. Поэтому после отладки питание передёргивают или снимают биты сами (у F1 надёжен только первый способ: без отладчика DBGMCU_CR программе недоступен, раздел 2а.4):

void debug_off_in_sleep(void)
{
    DBGMCU->CR &= ~(DBGMCU_CR_DBG_SLEEP | DBGMCU_CR_DBG_STOP | DBGMCU_CR_DBG_STANDBY);
}

10.9 F0 и G0: у F0 всё как у F1, у G0 — поле режима

F0 повторяет F1 до бита: два регистра PWR->CR и PWR->CSR, Stop и Standby различаются PDDS, регулятор — LPDS, флаги сбрасывают CWUF/CSBF, будящий вывод разрешает EWUP1. Разница одна: после Stop система стартует от HSI 8 МГц, и 48 МГц из 6.7 нужно включить заново.

    PWR->CR &= ~PWR_CR_PDDS;                    /* 0 -> Stop, а не Standby */
    PWR->CR |= PWR_CR_LPDS;                     /* регулятор в экономичный режим */
    SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
    __WFI();

G0 устроен иначе, и это устройство общее с L4, G4, U5: режим сна — не набор битов, а трёхбитное поле LPMS в CR1. Значения — из заголовка LL stm32g0xx_ll_pwr.h:

LPMS Режим Что остаётся
000 Stop 0 ОЗУ, регуляторы в обычном режиме — быстрый выход
001 Stop 1 ОЗУ, регулятор в экономичном режиме
011 Standby только резервный домен и, по выбору, часть ОЗУ
100 Shutdown ничего, кроме резервного домена: без ОЗУ и без датчика напряжения

Флаги пробуждения читают в SR1, а сбрасывают записью единиц в отдельный регистр SCR; будящие выводы — EWUP1 в CR3. Код F0/F1 на G0 не собирается: no member named 'CR' in 'PWR_TypeDef', PWR_CR_PDDS не объявлен.

    PWR->CR1 = (PWR->CR1 & ~PWR_CR1_LPMS) | LL_PWR_MODE_STOP1;   /* Stop 1: регулятор в экономичном режиме */
    SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
    __WFI();
    PWR->SCR = PWR_SCR_CWUF1 | PWR_SCR_CSBF;    /* флаги — записью единиц в SCR, а не в CR */
    PWR->CR3 |= PWR_CR3_EWUP1;                  /* вывод WKUP1 (PA0) */
    PWR->CR1 = (PWR->CR1 & ~PWR_CR1_LPMS) | LL_PWR_MODE_STANDBY;

Два режима, которых у F0 и F1 нет вовсе. Shutdown — глубже Standby: выключен даже датчик напряжения, ОЗУ не сохраняется, выход — сброс. Работа на пониженном напряжении (LPR в CR1): ядро продолжает исполнять код, но регулятор переведён в экономичный режим — годится для медленных задач между снами. После Stop G0 стартует от HSI16 — 64 МГц из 6.7 включают заново, как и у F0.


10.10 Грабли

Забытый такт на PWR. Регистры питания на APB1, и без RCC_APB1ENR_PWREN запись в них не проходит. Молча.

Не восстановлено тактирование после Stop. Разобрано выше — самая частая и самая незаметная ошибка режима.

Stop не наступает, ток как в работе. Висит флаг: ожидающий в EXTI->PR — часто от кнопки, которая будила камень в прошлый раз, — флаг прерывания периферии или будильника RTC. WFI тогда не усыпляет, и программа идёт дальше. Сбросить флаги перед входом (10.3).

Не сброшен CWUF или сброшен до EWUP. Мгновенный выход из Standby, похожий на перезагрузку по сбою.

Отладчик мешает измерять. Пока подключён ST‑Link, часть блоков остаётся запитанной: биты DBG_* в DBGMCU->CR оставляют такты в Stop, и потребление выглядит гораздо больше настоящего. Кнопка сброса эти биты не снимает — после отладки надо передёрнуть питание или снять их программно (10.8).

Амперметр роняет питание. На пределе 200 мкА у мультиметра шунт 1 кОм, и проснуться камень не может: питание проседает, пробуждение превращается в сброс. Спасает перемычка параллельно прибору (10.8).

Выводы в неопределённом состоянии. Ток утекает не только через ядро: вход, оставленный «плавающим», может потреблять больше, чем спящий камень. Перед сном имеет смысл перевести неиспользуемые выводы в аналоговый режим — у него потребление нулевое (раздел 8, готовая функция — в 10.8).

Периферия продолжает есть ток. В Sleep всё включённое работает: таймеры, АЦП, USB. Если задача — экономить, лишнее надо выключить в RCC до засыпания.

Слишком частое пробуждение. Выход из Stop занимает микросекунды, а из Standby вместе с перезапуском программы — миллисекунды, и каждый раз это всплеск тока. Просыпаться сто раз в секунду ради Standby бессмысленно — выигрыш съедается переходами.


10.11 Итоги

Полные исходники

pwr_f0.c — режимы сна STM32F072: регистры PWR как у F1.

/* Раздел 10.9: режимы сна STM32F072 — регистры PWR как у F1: CR и CSR. */
#include <stddef.h>
#include "stm32f0xx.h"

_Static_assert(offsetof(PWR_TypeDef, CSR) == 0x04u, "у F0 два регистра PWR — CR и CSR, как у F1");

void enter_stop_f0(void)
{
    RCC->APB1ENR |= RCC_APB1ENR_PWREN;
    PWR->CR &= ~PWR_CR_PDDS;                    /* 0 -> Stop, а не Standby */
    PWR->CR |= PWR_CR_LPDS;                     /* регулятор в экономичный режим */
    SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
    __WFI();
    SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk;
    /* после Stop система тактируется от HSI 8 МГц — 48 МГц из 6.7 включить заново */
}

void enter_standby_f0(void)
{
    RCC->APB1ENR |= RCC_APB1ENR_PWREN;
    PWR->CR |= PWR_CR_CWUF;                     /* сбросить флаг пробуждения */
    PWR->CR |= PWR_CR_PDDS;                     /* 1 -> Standby */
    PWR->CSR |= PWR_CSR_EWUP1;                  /* вывод WKUP1 (PA0) */
    SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
    __WFI();
}

pwr_g0.c — режимы сна STM32G071: поле LPMS, сброс флагов через SCR, будящие выводы в CR3.

/* Раздел 10.9: режимы сна STM32G071 — режим выбирает поле LPMS в CR1,
 * флаги сбрасываются записью в SCR, будящие выводы разрешают в CR3. */
#include <stddef.h>
#include "stm32g0xx.h"
#include "stm32g0xx_ll_pwr.h"

_Static_assert(offsetof(PWR_TypeDef, SCR) == 0x18u && offsetof(PWR_TypeDef, SR1) == 0x10u, "SR1 и SCR");
_Static_assert(LL_PWR_MODE_STOP0 == 0u && LL_PWR_MODE_STOP1 == PWR_CR1_LPMS_0
               && LL_PWR_MODE_STANDBY == (PWR_CR1_LPMS_1 | PWR_CR1_LPMS_0) && LL_PWR_MODE_SHUTDOWN == PWR_CR1_LPMS_2,
               "LPMS: 000 Stop 0, 001 Stop 1, 011 Standby, 100 Shutdown");

void enter_stop1_g0(void)
{
    RCC->APBENR1 |= RCC_APBENR1_PWREN;
    PWR->CR1 = (PWR->CR1 & ~PWR_CR1_LPMS) | LL_PWR_MODE_STOP1;   /* Stop 1: регулятор в экономичном режиме */
    SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
    __WFI();
    SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk;
    /* после Stop система тактируется от HSI16 — 64 МГц из 6.7 включить заново */
}

void enter_standby_g0(void)
{
    RCC->APBENR1 |= RCC_APBENR1_PWREN;
    PWR->SCR = PWR_SCR_CWUF1 | PWR_SCR_CSBF;    /* флаги — записью единиц в SCR, а не в CR */
    PWR->CR3 |= PWR_CR3_EWUP1;                  /* вывод WKUP1 (PA0) */
    PWR->CR1 = (PWR->CR1 & ~PWR_CR1_LPMS) | LL_PWR_MODE_STANDBY;
    SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
    __WFI();
}

/* Чего нет у F0 и F1: работа на пониженном напряжении регулятора (LPR)
 * и Shutdown — глубже Standby, без ОЗУ и без часов сна. */
void enter_shutdown_g0(void)
{
    PWR->CR1 = (PWR->CR1 & ~PWR_CR1_LPMS) | LL_PWR_MODE_SHUTDOWN;
    SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
    __WFI();
}

Дальше: раздел 10а «Малое потребление на L4 и U5» — Stop 2, Stop 3, Shutdown и почему батарейка от них живёт лишь на четверть дольше.

Открыть в интерактивном справочнике