сайт для палких паяльників

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

Схема питания. Домены питания

Микроконтроллер STM32 имеет несколько блоков (доменов), на которые питание может подаваться отдельно. Главное питание подается на Vss, Vdd и должно быть от 2.0 до 3.6В. Питание АЦП обычно подается отдельно. Это делается для повышения точности работы АЦП. Также микроконтроллер имеет Backup Domain, который содержит Backup registers (BKP) и Часы реального времени (RTC). Питание для Backup Domain подается отдельно. Как правило это батарейка 3В. Конечно, если мы не собираемся использовать какие-то блоки, мы просто не подаем на них питание. Но обычно нас интересует снижение потребления основного питания.

stm32_pwr1

Влияние частоты тактирования на энергопотребление

Рабочая частота микроконтроллера, и частота тактирования периферии существенно влияет на общее потребление. Чем больше частота, тем выше мощность, которую потребляет микроконтроллер.

STM32_Clock_02

STM32_Clock_03

Поэтому, когда речь идет об энергосбережении, тактовую частоту контроллера надо выбирать минимальную, которая удовлетворяет потребностям. То же касается периферии. Если текущие задачи позволяют на некоторое время отключать тактирование периферии, это тоже может стать действенным приемом. Например, Вам нужно периодически измерять уровень заряда батареи. Достаточно включить АЦП, выполнить измерение и выключить тактирование АЦП до следующего раза. Такой подход также может экономить потребление.

Режимы пониженного энергопотребления Sleep, Stop, Standby

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

  • спящий режим Sleep
  • режим остановки Stop
  • режим ожидания Standby

Режим Sleep

В спящем режиме тактирование ядра остановлено, периферия, в том числе и ядро, работают, выходы микроконтроллера сохраняют свое состояние. Перевести микроконтроллер в режим Sleep можно с помощью одной из двух команд:


__WFI();

или


__WFE();

WFI (Wait for Interrupt) – как видно из названия, пробуждение состоится когда возникает прерывание. Выход из режима Sleep по прерыванию, может быть спровоцирован любым прерыванием в NVIC.

WFE (Wait for Event) – пробуждения состоится когда возникает событие.

После выхода из режима Sleep выполнение программы продолжается с того места, где она была остановлена.

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

Установить бит SLEEPONEXIT:


NVIC_SystemLPConfig(NVIC_LP_SLEEPONEXIT, ENABLE);

Сбросить бит SLEEPONEXIT:


NVIC_SystemLPConfig(NVIC_LP_SLEEPONEXIT, DISABLE);

Это самый простой и “мягкий” режим. После выхода из спячки, контроллер продолжает выполнять программу с того места, где остановился. Этот режим часто используют при реализации программного обеспечения, которое базируется на прерываниях. То есть, тогда, когда контроллеру ничего делать, пока нет прерываний.

Пример:

#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_tim.h"
#include "misc.h"

void SetSysClockToHSE(void)
{
	ErrorStatus HSEStartUpStatus;
	/* SYSCLK, HCLK, PCLK2 and PCLK1 configuration -----------------------------*/
    /* RCC system reset(for debug purpose) */
    RCC_DeInit();

    /* Enable HSE */
    RCC_HSEConfig( RCC_HSE_ON);

    /* Wait till HSE is ready */
    HSEStartUpStatus = RCC_WaitForHSEStartUp();

    if (HSEStartUpStatus == SUCCESS)
    {
        /* HCLK = SYSCLK */
        RCC_HCLKConfig( RCC_SYSCLK_Div1);

        /* PCLK2 = HCLK */
        RCC_PCLK2Config( RCC_HCLK_Div1);

        /* PCLK1 = HCLK */
        RCC_PCLK1Config(RCC_HCLK_Div1);

        /* Select HSE as system clock source */
        RCC_SYSCLKConfig( RCC_SYSCLKSource_HSE);

        /* Wait till PLL is used as system clock source */
        while (RCC_GetSYSCLKSource() != 0x04)
        {
        }
    }
    else
    { /* If HSE fails to start-up, the application will have wrong clock configuration.
     User can add here some code to deal with this error */

        /* Go to infinite loop */
        while (1)
        {
        }
    }
}

void TIM4_IRQHandler(void)
{
        if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET)
        {
        	TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
			GPIOC->ODR ^= GPIO_Pin_13;
        }
}

int main(void)
{
	SetSysClockToHSE();

	/* Initialize LED which connected to PC13 */
	GPIO_InitTypeDef  GPIO_InitStructure;
	// Enable PORTC Clock
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
	/* Configure the GPIO_LED pin */
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOC, &GPIO_InitStructure);

	GPIO_ResetBits(GPIOC, GPIO_Pin_13); // Set C13 to Low level ("0")

    // TIMER4
    TIM_TimeBaseInitTypeDef TIMER_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

  	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);

  	TIM_TimeBaseStructInit(&TIMER_InitStructure);
    TIMER_InitStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIMER_InitStructure.TIM_Prescaler = 8000;
    TIMER_InitStructure.TIM_Period = 500;
    TIM_TimeBaseInit(TIM4, &TIMER_InitStructure);
    TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE);
    TIM_Cmd(TIM4, ENABLE);

    /* NVIC Configuration */
    /* Enable the TIM4_IRQn Interrupt */
    NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    NVIC_SystemLPConfig(NVIC_LP_SLEEPONEXIT, ENABLE);
    while(1)
    {
    	/* Sleep */
    	__WFI();
    }
}

В этом примере микроконтроллер впадает в спячку и выходит из нее только для обработки прерываний.

Режим Stop

Режим остановки – это режим глубокой спячки ядра. В этом режиме останавливаются все генераторы тактовой частоты, а также отключаются PLL, HSI и HSE RC. В режиме остановки все выводы микроконтроллера сохраняют свое состояние.

Для того, чтобы ввести контроллер в режим остановки Stop, надо выполнить функцию PWR_EnterSTOPMode.

Пример:


PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI); // WFI

или

PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFE); // WFE

Эта функция описана в файле stm32f10x_pwr.h. Она выставляет бит SLEEPDEEP, сбрасывает бит PDDS в регистре PWR_CR, и вызывает __WFI () или __WFE () в зависимости от того что указать во втором параметре PWR_STOPEntry_WFI или PWR_STOPEntry_WFE.

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

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

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

Пример:

#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_tim.h"
#include "stm32f10x_pwr.h"
#include "stm32f10x_exti.h"
#include "misc.h"

void SetSysClockToHSE(void)
{
	ErrorStatus HSEStartUpStatus;
	/* SYSCLK, HCLK, PCLK2 and PCLK1 configuration -----------------------------*/
    /* RCC system reset(for debug purpose) */
    RCC_DeInit();

    /* Enable HSE */
    RCC_HSEConfig( RCC_HSE_ON);

    /* Wait till HSE is ready */
    HSEStartUpStatus = RCC_WaitForHSEStartUp();

    if (HSEStartUpStatus == SUCCESS)
    {
        /* HCLK = SYSCLK */
        RCC_HCLKConfig( RCC_SYSCLK_Div1);

        /* PCLK2 = HCLK */
        RCC_PCLK2Config( RCC_HCLK_Div1);

        /* PCLK1 = HCLK */
        RCC_PCLK1Config(RCC_HCLK_Div1);

        /* Select HSE as system clock source */
        RCC_SYSCLKConfig( RCC_SYSCLKSource_HSE);

        /* Wait till PLL is used as system clock source */
        while (RCC_GetSYSCLKSource() != 0x04)
        {
        }
    }
    else
    { /* If HSE fails to start-up, the application will have wrong clock configuration.
     User can add here some code to deal with this error */

        /* Go to infinite loop */
        while (1)
        {
        }
    }
}

int main(void)
{
	int i;
	SetSysClockToHSE();

	/* Initialize LED which connected to PC13 */
	GPIO_InitTypeDef  GPIO_InitStructure;
	/* Enable PORTC Clock */
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
	/* Configure the GPIO_LED pin */
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOC, &GPIO_InitStructure);

	GPIO_ResetBits(GPIOC, GPIO_Pin_13); // Set C13 to Low level ("0")

	/* Enable clock for AFIO */
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);

	/* Set pin as input */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_Init(GPIOB, &GPIO_InitStruct);

    /* Tell system that you will use PB0 for EXTI_Line0 */
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);

    EXTI_InitTypeDef EXTI_InitStruct;
    /* PD0 is connected to EXTI_Line0 */
    EXTI_InitStruct.EXTI_Line = EXTI_Line0;
    /* Enable interrupt */
    EXTI_InitStruct.EXTI_LineCmd = ENABLE;
    /* Interrupt mode */
    EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Event;
    /* Triggers on falling edge */
    EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling;
    /* Add to EXTI */
    EXTI_Init(&EXTI_InitStruct);

    while(1)
    {
    	/* Stop */
    	PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFE);

    	/* Toggle LED */
    	GPIOC->ODR ^= GPIO_Pin_13;

        /* Delay */
        for(i=0;i<0x100000;i++);

    	/* Configures system clock after wake-up */
    	SetSysClockToHSE();
    }
}

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

Режим Standby

Режим Standby самый экономичный. Микроконтроллер в этом состоянии выключает почти все – PLL, HSI, HSE, регулятор питания. Вся информация в памяти и регистрах теряется. Выводы переводятся в высокоимпедансное состояние, то есть отключаются. Состояние контроллера почти такое, как и без питания. Почти, потому что перед переходом в режим Standby можно настроить некоторые вещи, которые будут работать и могут вывести его из режима Standby. А именно:

  • IWDT – независимый сторожевой таймер
  • RTC – часы реального времени

Вход в режим Standby выполняется командой:


PWR_EnterSTANDBYMode();

Вывести микроконтроллер из режима Standby может:

  • нарастающий фронт на входе WKUP
  • нарастающий фронт сигнала RTC
  • внешний сигнал NRST (т.е. RESET)
  • перезагрузка от IWGT.

При выходе из режима Standby микроконтроллер ведет себя так же, как и при нажатии на кнопку RESET. Работу IWGT мы рассматривали ранее, поэтому приводить пример не буду. Пример для RTC будет немного ниже.

Пример использования входа WKUP:

#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_pwr.h"

int main(void)
{
  int i, j;
  /* Initialize Leds mounted on STM32 board */
  GPIO_InitTypeDef  GPIO_InitStructure;
  /* Initialize LED which connected to PC13, Enable the Clock*/
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
  /* Configure the GPIO_LED pin */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOC, &GPIO_InitStructure);

  // ENABLE Wake Up Pin
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);
  PWR_WakeUpPinCmd(ENABLE);

  while (1)
  {
	  /* LED blink */
	  for(j=0; j<6; j++) { /* Toggle LED which connected to PC13*/ GPIOC->ODR ^= GPIO_Pin_13;
		  /* delay */
		  for(i=0; i<0x100000; i++);
	  }

	  /* Disable LED */
	  GPIO_SetBits(GPIOC, GPIO_Pin_13);
	  /* Enters STANDBY mode */
	  PWR_EnterSTANDBYMode();
  }
}

В этом примере для пробуждения используется вход WKUP. Для STM32F103 это вход PA0.

Выход из режима пониженного энергопотребления с помощью RTC

Для пробуждения микроконтроллера из режимов с низким потреблением можно использовать часы реального времени (RTC), которые позволяет программировать интервал времени для периодического пробуждения из режима Stop или Standby.

Чтобы вывести микроконтроллер из эконом-режима с помощью будильника RTC необходимо выполнить следующие операции:

  • настроить линию 17 EXTI на растущий фронт
  • настроить RTC на генерацию сигнала тревоги (будильника)

Для пробуждения микроконтроллера из режима Standby конфигурировать линию 17 EXTI не требуется.

Пример выхода из режима Standby по сигналу от RTC:

#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_pwr.h"
#include "stm32f10x_rtc.h"

unsigned char RTC_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
	PWR_BackupAccessCmd(ENABLE);
	if ((RCC->BDCR & RCC_BDCR_RTCEN) != RCC_BDCR_RTCEN)
	{
		RCC_BackupResetCmd(ENABLE);
		RCC_BackupResetCmd(DISABLE);

		RCC_LSEConfig(RCC_LSE_ON);
		while ((RCC->BDCR & RCC_BDCR_LSERDY) != RCC_BDCR_LSERDY) {}
		RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);

		/* Set prescaler */
		RTC_SetPrescaler(0x7FFF);

		/* Enable RTC */
		RCC_RTCCLKCmd(ENABLE);

		RTC_WaitForSynchro();

		return 1;
	}
	return 0;
}


int main(void)
{
  int i, j;
  /* Initialize Leds mounted on STM32 board */
  GPIO_InitTypeDef  GPIO_InitStructure;
  /* Initialize LED which connected to PC13, Enable the Clock*/
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
  /* Configure the GPIO_LED pin */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOC, &GPIO_InitStructure);

  RTC_Init();
  /* Enable the RTC Alarm interrupt */
  RTC_ITConfig(RTC_IT_ALR, ENABLE);
  /* Wait until last write operation on RTC registers has finished */
  RTC_WaitForLastTask();

  while (1)
  {
	  /* LED blink */
	  for(j=0; j<6; j++) { /* Toggle LED which connected to PC13*/ GPIOC->ODR ^= GPIO_Pin_13;
		  /* delay */
		  for(i=0; i<0x100000; i++);
	  }

	  /* Alarm in 5 second */
	  RTC_SetAlarm(RTC_GetCounter()+ 5);
	  /* Wait until last write operation on RTC registers has finished */
	  RTC_WaitForLastTask();

	  /* Disable LED */
	  GPIO_SetBits(GPIOC, GPIO_Pin_13);

	  /* Enters STANDBY mode */
	  PWR_EnterSTANDBYMode();
  }
}

В этом примере программа мигает светодиодом, указывает часам RTC когда “разбудить” микроконтроллер, в нашем случае через 5 секунд, и переводит контроллер в режим Standby. Пробуждение происходит по сигналу от RTC каждые 5 секунд.

Уровень энергопотребления в различных режимах

Чем глубже сон, тем меньше контроллер потребляет энергии. В документации приведены следующие данные:

stm32_pwr3

stm32_pwr4

Время выхода из режима пониженного энергопотребления

Для выхода из режима пониженного энергопотребления микроконтроллеру требуется определенное время. Чем “глубже” сон, тем больше времени требуется для возвращения к работе. В документации приведены следующие данные:

stm32_pwr5

PVD (Programmable Voltage Detector)

Programmable Voltage Detector используют когда есть необходимость мониторить напряжение питания чтобы выполнить какие-то действия когда напряжение питания упадет ниже указанного уровня. Можно установить уровень от 2.2В до 2.9В с шагом 0.1В:

  • PWR_PVDLevel_2V2
  • PWR_PVDLevel_2V3
  • PWR_PVDLevel_2V4
  • PWR_PVDLevel_2V5
  • PWR_PVDLevel_2V6
  • PWR_PVDLevel_2V7
  • PWR_PVDLevel_2V8
  • PWR_PVDLevel_2V9

Пример:

#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_pwr.h"
#include "stm32f10x_exti.h"
#include "misc.h"

void PVD_IRQHandler(void)
{
  if(EXTI_GetITStatus(EXTI_Line16) != RESET)
  {
	  /* Toggle LED which connected to PC13*/
	  if (PWR_GetFlagStatus(PWR_FLAG_PVDO) == RESET) {
		  GPIO_SetBits(GPIOC, GPIO_Pin_13);
	  }
	  else {
		  GPIO_ResetBits(GPIOC, GPIO_Pin_13);
	  }

	  /* Clear the Key Button EXTI line pending bit */
	  EXTI_ClearITPendingBit(EXTI_Line16);
  }
}

int main(void)
{
  /* Initialize Leds mounted on STM32 board */
  GPIO_InitTypeDef  GPIO_InitStructure;
  /* Initialize LED which connected to PC13, Enable the Clock*/
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
  /* Configure the GPIO_LED pin */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOC, &GPIO_InitStructure);

  GPIO_SetBits(GPIOC, GPIO_Pin_13);

  /* Enable PWR and BKP clock */
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);

  /* Configure the PVD Level to 2.9V */
  PWR_PVDLevelConfig(PWR_PVDLevel_2V9);

  /* Enable the PVD Output */
  PWR_PVDCmd(ENABLE);

  /* Configure EXTI Line16(PVD Output) to generate an interrupt on rising and falling edges */
  EXTI_InitTypeDef EXTI_InitStructure;
  EXTI_ClearITPendingBit(EXTI_Line16);
  EXTI_InitStructure.EXTI_Line = EXTI_Line16;
  EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
  EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;
  EXTI_InitStructure.EXTI_LineCmd = ENABLE;
  EXTI_Init(&EXTI_InitStructure);

  /* Enable the PVD Interrupt */
  NVIC_InitTypeDef NVIC_InitStructure;
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
  NVIC_InitStructure.NVIC_IRQChannel = PVD_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);

  while (1)
  {

  }
}

В этом примере, когда напряжение питания становится ниже 2.9В, загорается светодиод на плате.

Все примеры можно скачать здесь

Подробнее о low-power modes для STM32F101xx, STM32F102xx, STM32F103xx написано в
AN2629 Application note

Желаю успехов!

Смотри также:

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *

 
Translate
Архіви

© 2011-2017 Андрій Корягін, Кременчук - Київ, Україна