Power Management часть 2 (перевод из книги Mastering STM32)

Продолжаем перевод главы, которая описывает управление питанием в микроконтроллерах STM32.

Power Management часть 1. Управление питанием в микроконтроллерах Cortex-M
Power Management часть 3. Управление питанием в микроконтроллерах STM32L
Power Management часть 4. Использование калькулятора энергопотребления CubeMX

18.3 Управление питанием в микроконтроллерах STM32F

До сих пор мы рассматривали параметры, свойственные всем микроконтроллерам STM32. Тем не менее, семейства микроконтроллеров STM32 делится еще на две больших ветки STM32F и STM32L. Вторая серия микроконтроллеров адресована как раз для энергосберегающих применений и обеспечивает большое количество режимов для минимизации потребления питания.

В этой части главы мы начнем с анализа того, как управлять режимами питания в микроконтроллерах STM32F. Важно подчеркнуть, что как это часто бывает с другими функциями, некоторые семейства STM32 и даже некоторые микроконтроллеры в рамках одного семейства имеют специфические особенности, которые отличаются от того, как управление питанием осуществляется в большинстве микроконтроллеров STM32. По этой причине всегда держите под рукой референс-мануал для конкретного микроконтроллера, который используете.

18.3.1 Источники питания

На рисунке 3 показаны все источники питания, которые могут быть у микроконтроллеров STM32 (следует отметить, что схема упрощена и некоторые из микроконтроллеров имеют более сложную схему питания, нежели изображенная). Как уже было сказано ранее, даже если мы используем лишь один источник питания, микроконтроллер имеет внутреннюю структуру распределения, которая определяет несколько областей напряжения, используемых для питания тех периферийных устройств, которые имеют схожие характеристики питания. Для примера, напряжение VDDA питает те аналоговые периферийные устройства, которым требуется отдельный (лучше отфильтрованный) источник питания, через выводы VDDA микроконтроллера.

Источники питания микроконтроллеров серии STM32
Рисунок 3. Источники питания микроконтроллеров STM32F

Напряжения VDD и VDD18 являются наиболее значимыми. VDD подается внешним источником питания, когда как VDD18 формируется внутренним стабилизатором напряжения микроконтроллера. Этот стабилизатор можно настроить для работы в режиме пониженного энергопотребления, как мы увидим далее. Для сохранения содержания резервных регистров и обеспечения функционирования RTC, когда VDD отключен, можно запитать вывод VBAT от дополнительного резервного источника питания или от батареи. Вывод VBAT обеспечивает питание RTC, генератора LSE и одного или двух выводов, используемых для пробуждения микроконтроллера из режимов глубокого сна, обеспечивая работу RTC даже когда нет основного питания VDD. По этой причине источник питания VBAT называют источником питания RTC. Переключение на источник питания VBAT управляется посредством PDR (Power Down Reset), встроенного в блок сброса.

18.3.2 Режимы питания

В первой части этой главы мы увидели, что микроконтроллер на базе Cortex-M ядра обеспечивает три основных режима питания: Run, Sleep и Deep Sleep. И теперь самое время рассмотреть как инженеры ST переработали эти режимы в своих микроконтроллерах STM32F. Таблица 1 суммирует эти режимы и показывает три основные функции, предоставляемые HAL для каждого из режимов питания. Их мы проанализируем немного позже.

Режимы питания STM32F
Таблица 1. Три режима питания, поддерживаемых микроконтроллерами STM32F

18.3.2.1 Режим Run

По умолчанию и после подачи питания или сброса микроконтроллера, STM32F устанавливает режим работы Run или по другому активный режим работы, который потребляет большое количество энергии даже при выполнении несущественных задач. Потребление этого режима и режима Sleep зависит от тактовой частоты микроконтроллера. На рисунке 4 показаны уровни энергопотребления некоторых микроконтроллеров серии STM32F4. В активном режиме основной стабилизатор обеспечивает полную мощность для домена 1.8/1.2V (это ядро, память и цифровая периферия). В этом режиме выходное напряжение стабилизатора 1.8/1.2V в зависимости от конкретного микроконтроллера STM32F, его можно программно масштабировать до различных значений напряжения и подробнее об этом будет сказано чуть позже. Некоторые более новые микроконтроллеры серии STM32F4 предоставляют сразу два режима работы:

  • Нормальный режим: процессор и цифровая логика работают на максимальной частоте при заданном напряжении масштабирования (scale 1, scale 2, scale 3).
  • Режим Over-Drive: этот режим позволяет процессору и основной логике работать на более высоких частотах, чем в обычном режиме для scale 1 и scale 2. Подробнее об этом позже.
Энергопотребление некоторых микроконтроллеров серии STM32F4
Рисунок 4. Энергопотребление некоторых микроконтроллеров серии STM32F4

18.3.2.1.1 Динамическое масштабирование напряжения в микроконтроллерах STM32F4/F7

Потребляемая мощность в цепи постоянного тока определяется потребляемым током и напряжением в цепи. Это означает, что мы можем уменьшить потребляемую мощность, уменьшив напряжение питания. STM32F4/F7 предоставляют технологию интеллектуального контроля питания под названием Dynamic Voltage Scaling (DVS), отличающуюся от той, что есть в серии STM32L. Идея DVS заключается в том, что многим встраиваемым системам не всегда требуются полные возможности обработки системы, поскольку не все подсистемы активны в одно время. В этом случае система может оставаться в активном режиме работы и процессор не будет работать на максимальной тактовой частоте. Напряжение, подаваемое на процессор, может быть уменьшено с уменьшением тактовой частоты. Благодаря такому механизму управления питанием, мы уменьшаем потребляемую мощность, отслеживая входное напряжение процессора в соответствии с требованиями к производительности системы.

Это выражается в масштабировании выходного напряжения регулятора STM32F4, которое падает до 1.2В, когда мы понижаем тактовую частоту. STM32F4/F7 предлагает три коэффициента масштабирования (scale 1, scale 2, scale 3). Максимально возможная тактовая частота ядра для конкретного коэффициента масштабирования зависит от конкретного микроконтроллера STM32. Например, STM32F401 имеет только два коэффициента масштабирования, scale 2 и scale 3, которые позволяют ядру работать на частотах 84МГц и 60МГц соответственно. Для управления масштабированием напряжения CubeHAL предоставляет следующую функцию:

HAL_StatusTypeDef HAL_PWREx_ControlVoltageScaling(uint32_t VoltageScaling);

которая принимает следующие константы:

PWR_REGULATOR_VOLTAGE_SCALE1,
PWR_REGULATOR_VOLTAGE_SCALE2,
PWR_REGULATOR_VOLTAGE_SCALE3

Масштабирование напряжения можно изменить только в том случае, если источником системной тактовой частоты является HSI или HSE. Итак, чтобы увеличить/уменьшить скалирование напряжения вы должны выполнить следующие процедуры:

Во-первых, установите HSI или HSE в качестве источника системной тактовой частоты, используя

HAL_RCC_ClockConfig();

Во-вторых, для настройки PLL вызовите функцию

HAL_RCC_OscConfig();

В-третьих, для настройки скалирования напряжения вызовите

HAL_PWREx_ConfigVoltageScaling();

И в-четвертых, установите новую системную тактовую частоту

HAL_RCC_ClockConfig();

Для получения дополнительной информации по этой теме смотрите апноут AN4365.

18.3.2.1.2 Режим Over/Under-Drive в микроконтроллерах STM32F4/F7

Некоторые микроконтроллеры из серии STM32F4 и все из STM32F7 предоставляют два или даже более вспомогательных режима. Эти режимы называются Over-Drive и Under-Drive. Первый заключается в увеличении тактовой частоты с помощью “разгона”. Входить в режим Over-Drive рекомендуется когда приложение не выполняет критические задачи и источником системной тактовой частоты является HSI или HSE. Эти функции могут быть полезны, когда мы хотим временно увеличить/уменьшить тактовую частоту микроконтроллера без перенастройки тактового дерева, что обычно приводит незначительным издержкам. HAL предоставляет две удобные функции для выполнения этих операций:

HAL_PWREx_EnableOverDrive();
HAL_PWREx_DisableOverDrive();

Режим Under-Drive противоположен Over-Drive и заключается в понижении тактовой частоты и отключении некоторых периферийных устройств. В этом режиме возможно перевести внутренний регулятор напряжения в режим пониженного энергопотребления. В некоторых микроконтроллерах STM32F4/F7 этот режим доступен даже в Stop режиме.

18.3.2.2 Режим Sleep

Войти в спящий режим можно с помощью выполнения инструкций WFI или WFE. В спящем режиме все порты ввода/вывода сохраняют свое состояние. Нет необходимости заботиться о выполнении данных инструкций, т.к. CubeHAL предоставляет для этого специальную функцию:

void HAL_PWR_EnterSLEEPMode(uint32_t Regulator, uint8_t SLEEPEntry);

Первый ее параметр Regulator, не имеет смысла в спящем режиме для всех микроконтроллеров STM32F и оставлен для совместимости с серией STM32L. Второй параметр, SLEEPEntry, может принимать значенияPWR_SLEEPENTRY_WFI или PWR_SLEEPENTRY_WFE: как следует из названий, первый выполняет инструкцию WFI, а второй – WFE.

Если вы посмотрите на функцию HAL_PWR_EnterSLEEPMode(), то вы обнаружите, что, если мы передадим параметр PWR_SLEEPENTRY_WFE, она выполнит две инструкции WFE последовательно. Это приводит к тому, что HAL_PWR_EnterSLEEPMode() входит в спящий режим таким же образом, как если бы она вызывалась с параметром PWR_SLEEPENTRY_WFI (двойной вызов инструкции WFE приводит к тому, что если установлен регистр событий, то он очищается первой инструкцией WFE, а вторая переводит микроконтроллер в спящий режим). Я не знаю почему ST использовали подобный подход. Если вы хотите получить полный контроль над режимами энергопотребления микроконтроллера, вам необходимо изменить содержание этой функции в соответствии с вашей задачей. Ясно, что микроконтроллер выйдет из спящего режима при условии выхода для инструкции WFE.

Если используется инструкция WFI для входа в спящий режим, любое прерывание от периферийного устройств, подтвержденной вложенным вектором в контроллере прерываний (NVIC), может вывести устройство из спящего режима.

Если же для входа в спящий режим используется инструкция WFE, то микроконтроллер выходит из спящего режима как только происходит событие. Событие пробуждения может быть сгенерировано в следующих случаях:

  • включено прерывание регистре управления периферийного устройства, но не в NVIC, установлен бит SEVONPEND в регистре управления System Control Register – когда микроконтроллер возобновляет работу после инструкции WFE, бит ожидания прерывания от периферии и бит IRQ в регистре NVIC должны быть очищены;
  • сконфигурировано внешнее и внутреннее прерывание линии EXTI в режиме события – когда микроконтроллер возобновляет работу после инструкции WFE, нет необходимости очищать бит ожидания прерывания периферийного устройства или NVIC, поскольку бит ожидания, соответствующий данному событию, не устанавливается.

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

18.3.2.3 Режим Stop

Режим Stop или режим остановки основан на режиме глубокого сна Cortex-M в сочетании с выключением тактирования периферийных устройств. В этом режиме все тактовые сигналы в домене 1.8В останавливаются, генераторы PLL, HSI и HSE отключены. SRAM и содержимое регистра сохраняется. Все порты ввода/вывода сохраняют свое состояние. Регулятор напряжения может быть как в нормальном режиме работы, так и в режиме пониженного энергопотребления. Для перевода микроконтроллера в режим остановки HAL предоставляет функцию:

void HAL_PWR_EnterSTOPMode(uint32_t Regulator, uint8_t STOPEntry);

где параметр Regulator принимает значение PWR_MAINREGULATOR_ON, чтобы оставить внутренний регулятор напряжения включенным, или значение PWR_LOWPOWERREGULATOR_ON, чтобы перевести его в режим пониженного энергопотребления. Параметр STOPEntry может принимать значения PWR_STOPENTRY_WFI или PWR_STOPENTRY_WFE.

Чтобы войти в режим остановки, все ожидающие прерывания биты линии EXTI периферийных устройств и RTC Alarm флаг должны быть сброшены. В противном случае процедура входа в режим остановки игнорируется и выполнение программы будет продолжено. Если приложению необходимо отключить внешний тактовый генератор HSE перед входом в режим остановки, источник системной тактовой частоты должен быть сперва переключен на HSI, а затем сброшен бит HSEON. В противном случае, если перед входом в режим остановки бит HSEON не сброшен, необходимо включить функцию систему безопасности CSS, чтобы обнаружить сбой внешнего генератора и избежать неисправности при входе в режим остановки.

Любая линия EXTI, сконфигурированная в режиме прерывания или события, вынуждает процессор выйти из режима остановки, если он был введен в режиме пониженного энергопотребления с использованием инструкций WFI или WFE. Поскольку и HSE, и PLL отключаются перед входом в этот режим, то при выходе из него источником системной тактовой частоты устанавливается HSI. Это означает, что наш код должен переконфигурировать дерево тактирования в соответствии с желаемой SYSCLK.

18.3.2.4 Режим Standby

Режим ожидания позволяет достичь минимального потребления. Он основан на режиме глубокого сна Cortex-M с полностью отключенным внутренним регулятором напряжения. Домен 1.8/1.2V отключен. PLL, генераторы HSI и HSE также отключены. SRAM и содержимое регистров теряется, за исключением регистров в standby режима. Для перевода микроконтроллера в режим ожидания HAL предоставляет функцию:

void HAL_PWR_EnterSTANDBYMode(void);

Микроконтроллер может выйти из режима ожидания, когда происходит внешний сброс (NRST), сброс IWDG, нарастающий фронт на одном из активированных выводов WKUPx или по событию от RTC. Все регистры сбрасываются после выхода из режима, за исключением регистра управления питанием PWR->CSR. После выхода выполнение программы возобновляется так же, как и после сброса. Используя макрос:

__HAL_PWR_GET_FLAG(PWR_FLAG_SB);

мы можем проверить, сбрасывается ли микроконтроллер после выхода из режима ожидания. Поскольку и HSE, и PLL отключаются перед входом в режим ожидания, то при выходе из него источником системной тактовой частоты устанавливается HSI. Это означает, что наш код должен переконфигурировать дерево тактирования в соответствии с желаемой SYSCLK.

Внимание!

Некоторые микроконтроллеры STM32 имеют аппаратную ошибку, которая не позволяет войти или выйти из режима ожидания. Для этого должны быть выполнены особые условия. Более подробную информацию по вашему микроконтроллеру можно получить в Errata.

18.3.2.5 Примеры режимов пониженного энергопотребления

В следующем примере для платы Nucleo-F030R8, показано, как работают режимы пониженного энергопотребления.

Filename: src/main-ex1.c

int main(void) 
{
	char msg[20];

	HAL_Init();
	Nucleo_BSP_Init();

	/* Before we can access to every register of the PWR peripheral we must enable it */
	__HAL_RCC_PWR_CLK_ENABLE();

	while (1) 
	{
		if(__HAL_PWR_GET_FLAG(PWR_FLAG_SB)) 
		{
		/* If standby flag set in PWR->CSR, then the reset is generated from
		 * the exit of the standby mode */
		sprintf(msg, "RESET after STANDBY mode\r\n");
		HAL_UART_Transmit(&huart2, (uint8_t*)msg, strlen(msg), HAL_MAX_DELAY);
		/* We have to explicitly clear the flag */
		__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU|PWR_FLAG_SB);
		}

		sprintf(msg, "MCU in run mode\r\n");
		HAL_UART_Transmit(&huart2, (uint8_t*)msg, strlen(msg), HAL_MAX_DELAY);
		while(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13) == GPIO_PIN_SET) 
		{
		HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
		HAL_Delay(100);
		}

		HAL_Delay(200);

		sprintf(msg, "Entering in SLEEP mode\r\n");
		HAL_UART_Transmit(&huart2, (uint8_t*)msg, strlen(msg), HAL_MAX_DELAY);

		SleepMode();

		sprintf(msg, "Exiting from SLEEP mode\r\n");
		HAL_UART_Transmit(&huart2, (uint8_t*)msg, strlen(msg), HAL_MAX_DELAY);

		while(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13) == GPIO_PIN_SET);
		HAL_Delay(200);

		sprintf(msg, "Entering in STOP mode\r\n");
		HAL_UART_Transmit(&huart2, (uint8_t*)msg, strlen(msg), HAL_MAX_DELAY);

		StopMode();

		sprintf(msg, "Exiting from STOP mode\r\n");
		HAL_UART_Transmit(&huart2, (uint8_t*)msg, strlen(msg), HAL_MAX_DELAY);

		while(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13) == GPIO_PIN_SET);
		HAL_Delay(200);

		sprintf(msg, "Entering in STANDBY mode\r\n");
		HAL_UART_Transmit(&huart2, (uint8_t*)msg, strlen(msg), HAL_MAX_DELAY);

		StandbyMode();

		while(1); //Never arrives here, since MCU is reset when exiting from STANDBY
	}
}

void SleepMode(void)
{
  GPIO_InitTypeDef GPIO_InitStruct;

  /* Disable all GPIOs to reduce power */
  MX_GPIO_Deinit();

  /* Configure User push-button as external interrupt generator */
  __HAL_RCC_GPIOC_CLK_ENABLE();
  GPIO_InitStruct.Pin = B1_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(B1_GPIO_Port, &GPIO_InitStruct);

  HAL_UART_DeInit(&huart2);

  /* Suspend Tick increment to prevent wakeup by Systick interrupt.
     Otherwise the Systick interrupt will wake up the device within 1ms (HAL time base) */
  HAL_SuspendTick();

  __HAL_RCC_PWR_CLK_ENABLE();
  /* Request to enter SLEEP mode */
  HAL_PWR_EnterSLEEPMode(0, PWR_SLEEPENTRY_WFI);

  /* Resume Tick interrupt if disabled prior to sleep mode entry*/
  HAL_ResumeTick();

  /* Reinitialize GPIOs */
  MX_GPIO_Init();

  /* Reinitialize UART2 */
  MX_USART2_UART_Init();
}

void StopMode(void)
{
  GPIO_InitTypeDef GPIO_InitStruct;

  /* Disable all GPIOs to reduce power */
  MX_GPIO_Deinit();

  /* Configure User push-button as external interrupt generator */
  __HAL_RCC_GPIOC_CLK_ENABLE();
  GPIO_InitStruct.Pin = B1_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(B1_GPIO_Port, &GPIO_InitStruct);

  HAL_UART_DeInit(&huart2);

  /* Suspend Tick increment to prevent wakeup by Systick interrupt.
     Otherwise the Systick interrupt will wake up the device within 1ms (HAL time base) */
  HAL_SuspendTick();

  /* We enable again the PWR peripheral */
  __HAL_RCC_PWR_CLK_ENABLE();
  /* Request to enter SLEEP mode */
  HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_SLEEPENTRY_WFI);

  /* Resume Tick interrupt if disabled prior to sleep mode entry*/
  HAL_ResumeTick();

  /* Reinitialize GPIOs */
  MX_GPIO_Init();

  /* Reinitialize UART2 */
  MX_USART2_UART_Init();
}


void StandbyMode(void) 
{
  MX_GPIO_Deinit();

  /* This procedure come from the STM32F030 Errata sheet*/
  __HAL_RCC_PWR_CLK_ENABLE();

  HAL_PWR_DisableWakeUpPin(PWR_WAKEUP_PIN1);

  /* Clear PWR wake up Flag */
  __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);

  /* Enable WKUP pin */
  HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);

  /* Enter STANDBY mode */
  HAL_PWR_EnterSTANDBYMode();
}

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) 
{
  if(GPIO_Pin == B1_Pin) 
  {
    while(HAL_GPIO_ReadPin(B1_GPIO_Port, B1_Pin) == GPIO_PIN_RESET);
  }
}

Макрос __HAL_RCC_PWR_CLK_ENABLE() в строке 9 включает периферийное устройство PWR: прежде чем мы сможем выполнить хоть какую-нибудь операцию, связанную с управлением питанием, нам необходимо включить PWR, даже если мы просто проверяем установлен ли флаг ожидания в регистре PWR->CSR. Это является источником головной боли для многих начинающих разработчиков. 

Строки [13:21] проверяют установку флага ожидания: если установлен, то это означает, что микроконтроллер был сброшен после выхода из режима ожидания Standby. Строки [23:29] представляют режим работы: светодиод LD2 мигает пока не будет нажата кнопка USER на плате Nucleo, подключенная к PC13. Остальные строки в main() просто позволяют переключаться между тремя режимами пониженного энергопотребления при каждом нажатии кнопки USER.

Строки [64:96] определяют функцию SleepMode(), используемую для перевода микроконтроллера в спящий режим. Все GPIO настроены как аналоговые входа, чтобы уменьшить потребление тока на неиспользуемых выводах (особенно те выводы, которые могут быть источником утечек тока). Отключается тактирование периферийных устройств, за исключением порта GPIOC, т.к. PC13 используется для выхода из режимов пониженного энергопотребления. То же самое относится к интерфейсу UART2 и таймеру SysTick, который останавливается для того, чтобы предотвратить выход микроконтроллера из спящего режима через 1мс. Вызов функции HAL_PWR_EnterSLEEPMode() в строке 86 переводит микроконтроллер в спящий режим до тех пор, пока он не пробуждается при нажатии кнопки USER (микроконтроллер пробуждается, потому что мы настроили соответствующее прерывание, которое вызывает инструкцию WFI, которая выводит МК из режима пониженного энергопотребления).

Функция StopMode() практически идентична функции SleepMode(), за исключением того, что она вызывает функцию HAL_PWR_EnterSTOPMode() для перевода микроконтроллера в режим остановки.

И наконец, строки [134:159] определяют функцию StandbyMode(). Здесь мы следуем процедуре, описанной в Errata для микроконтроллера STM32F030, поскольку в этом микроконтроллере есть аппаратная ошибка, которая не позволяет ему войти в режим ожидания: сначала необходимо отключить вывод PWR_WAKEUP_PIN1, а затем очистить флаг пробуждения в PWR->CSR для повторного включения вывода пробуждения, который в микроконтроллере STM32F030 совпадает с выводом PA0.

Микроконтроллеры STM32 обычно имеют два вывода пробуждения, которые называются PWR_WAKEUP_PIN1 и PWR_WAKEUP_PIN2. Для многих микроконтроллеров STM32 в корпусе LQFP64 второй вывод пробуждения совпадает с PC13, который подключен к кнопке USER на всех платах Nucleo (кроме Nucleo-F302, где он подключен к выводу PB13). Тем не менее, мы не можем PWR_WAKEUP_PIN2 в нашем примере, потому что этот вывод подтянут к питанию резистором на печатной плате. Когда мы конфигурируем выводы пробуждения в сочетании с режимом ожидания, мы не используем соответствующую периферию GPIO, которая бы позволила нам настраивать вывод на вход, потому что она отключается перед входом в режим ожидания: выводы пробуждения напрямую обрабатываются периферийным устройством PWR, которое сбрасывает микроконтроллер, если один из двух выводов устанавливается в логическую единицу, т.е. на нем появляется напряжение питания. Итак, в примере мы используем вывод PWR_WAKEUP_PIN1, который соответствует выводу PA0 в микроконтроллере STM32F030.

Рисунок 5. Как измерить потребление тока в платах Nucleo

Платы Nucleo позволяют измерить потребление тока микроконтроллера с помощью штыревого разъема IDD. Перед началом измерений необходимо установить соединения с платой, как показано на рисунке 5, сняв перемычку IDD и подключив щупы амперметра. Убедитесь, что амперметр настроен на шкалу мА. Таким образом, вы можете увидеть энергопотребление для каждого из режимов питания микроконтроллера.

18.3.3 Важное предупреждение для микроконтроллеров STM32F1

Во время разработки примеров для FreeRTOS в соответствующей главе, автор книги столкнулся с неприятным поведением микроконтроллера STM32F103 при входе в режим остановки с помощью функции HAL_PWR_EnterSTOPMode() из CubeF1 HAL. В частности, возникшая проблема связана с выходом из этого режима, когда микроконтроллер входит в него с помощью инструкции WFI. В режим остановки микроконтроллер входит правильно, но во время пробуждения от прерывания, он немедленно генерирует исключение Hard Fault. Изучив данный случай, автор пришел к выводу, что разработчики ST не следуют советам ARM в вопросе перехода в режимы пониженного энергопотребления в микроконтроллерах Cortex-M3, как описано здесь.

Изменение процедуры HAL следующим образом решило данную проблему:

void HAL_PWR_EnterSTOPMode(uint32_t Regulator, uint8_t STOPEntry) 
{
	/* Check the parameters */
	assert_param(IS_PWR_REGULATOR(Regulator));
	assert_param(IS_PWR_STOP_ENTRY(STOPEntry));
	/* Clear PDDS bit in PWR register to specify entering in STOP mode when CPU enter in Dee\
	   psleep */
	CLEAR_BIT(PWR->CR, PWR_CR_PDDS);
	/* Select the voltage regulator mode by setting LPDS bit in PWR register according to Re\
	   gulator parameter value */
	MODIFY_REG(PWR->CR, PWR_CR_LPDS, Regulator);
	/* Set SLEEPDEEP bit of Cortex System Control Register */
	SET_BIT(SCB->SCR, ((uint32_t)SCB_SCR_SLEEPDEEP_Msk));
	/* Select Stop mode entry --------------------------------------------------*/
	if(STOPEntry == PWR_STOPENTRY_WFI)
	{
		/* Request Wait For Interrupt */
		__DSB(); //Added by me
		__WFI();
		__ISB(); //Added by me
	}
	else
	{
		/* Request Wait For Event */
		__SEV();
		PWR_OverloadWfe(); /* WFE redefine locally */
		PWR_OverloadWfe(); /* WFE redefine locally */
	}
	/* Reset SLEEPDEEP bit of Cortex System Control Register */
	CLEAR_BIT(SCB->SCR, ((uint32_t)SCB_SCR_SLEEPDEEP_Msk));
}

Изменение заключается просто в добавлении двух инструкций барьера памяти, одной до и после инструкции WFI.

Автор задал вопрос по этой проблеме на официальном форуме ST, но на момент написания этой главы ответа еще не было.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *