22. STM32. Програмування STM32F103. PWR


08.11.2016

Зниження енергоспоживання мікроконтролера частіше за все нас цікавить при розробці приладів, які живляться від акумуляторів чи батарей. STM32 дозволяє керувати власним енергоспоживанням. Перш, ніж ми навчимося застосовувати режими зниженого енергоспоживання, розглянемо, як організоване живлення мікроконтролера та загальні методи зниження енергоспоживання.

Схема живлення. Домени живлення

Мікроконтролер STM32 має декілька блоків (доменів), на які живлення може подаватися окремо. Головне живлення подається на Vss, Vdd і має бути від 2.0 до 3.6В. Живлення АЦП зазвичай подається окремо. Це робиться для підвищення точності роботи АЦП. Також мікроконтролер має Backup Domain, який містить Backup registers (BKP) та Годинник реального часу (RTC). Живлення для Backup Domain подається окремо. Звісно, якщо ми не збираємось використовувати якісь блоки, ми їх просто не живимо. Та зазвичай нас цікавить зниження споживання основного живлення. stm32_pwr1

Вплив частоти тактування на енергоспоживання

Робоча частота мікроконтролера, та частота тактування периферії суттєво впливає на загальне споживання. Чим більша частота, тим вища потужність, яку споживає мікроконтролер. STM32_Clock_02 STM32_Clock_03 Тому, коли мова йде про енергозбереження, тактову частоту контролера треба вибирати мінімальну, яка задовольняє потребам. Те саме стосується периферії. Якщо поточні задачі дозволяють на деякий час вимикати тактування периферії, це теж може стати дієвим прийомом. Наприклад, Вам потрібно періодично вимірювати рівень заряду батареї. Достатньо увімкнути АЦП, виконати перетворення і вимкнути до наступного разу. Такий підхід теж може економити живлення.

Режими зниженого енергоспоживання Sleep, Stop, Standby

Після вмикання мікроконтролер STM32 працює у режимі нормального енергоспоживання. Режими зниженого енергоспоживання призначені для зниження споживання енергії контролером, коли немає потреби в його функціонуванні. Наприклад, при очікуванні зовнішнього події. Мікроконтролер може працювати в наступних режимах зниженого енергоспоживання:
  • сплячий режим Sleep
  • режим зупинки Stop
  • режим очікування Standby

Режим 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 Бажаю успіхів!

Дивись також:

STM32
Коментарі:
Додати коментар
Code
* - обов'язкові поля

Архіви