【原创】STM32低功耗模式及中断唤醒(基于BMI160及RTC)的研究

预研目标

       六轴静止时,终端进入低功耗模式;六轴震动时,终端正常工做模式,从而极大减小非工做时的电流消耗。git

解决方案

       机器静止时,依据六轴算法,CPU进入休眠(中止)模式;机器工做时,触发六轴中断唤醒CPU,再配合系统空闲时进入CPU睡眠模式,从而极大下降机器非工做时的电流消耗和下降工做时底电流消耗。github

关键技术

STM32功耗模式

按功耗由高到低排列,STM32具备运行、睡眠、中止和待机四种工做模式。上电复位后STM32处于运行状态时,当内核不须要继续运行,就能够选择进入后面的三种低功耗模式下降功耗,这三种模式中,电源消耗不一样、唤醒时间不一样、唤醒源不一样,用户须要根据应用需求,选择最佳的低功耗模式。三种低功耗的模式说明见表1。   算法

表 1  STM32的低功耗模式说明ide

模式测试

说明ui

进入方式spa

唤醒方式调试

对1.2V区域时钟的影响code

对VDD区域时钟的影响orm

调压器

睡眠

内核中止,全部外设包括M4核心的外设,如NVIC、系统时钟(SysTick)等仍在运行

调用WFI命令

任一中断

内核时钟关,对其余时钟和ADC时钟无影响

调用WFE命令

唤醒事件

中止

全部的时钟都已中止

配置PWR_CR寄存器的PDDS +LPDS 位+SLEEPDEEP位

+WFI或WFE命令

任一外部中断( 在外部中断寄存器中设置)

关闭全部1.2V区域的时钟

HSI和HSE的振荡器关闭

开启或处于低功耗模式( 依据电源控制寄存器的设定)

待机

1.2V 电源关闭

配置PWR_CR寄存器的PDDS +SLEEPDEEP位

+WFI或WFE命令

WKUP 引脚的上升沿、RTC闹钟事件、NRST 引脚上的外部复位、IWDG 复位

从表中能够看到,这三种低功耗模式层层递进,运行的时钟或芯片功能愈来愈少,于是功耗愈来愈低。

睡眠模式

在睡眠模式中,仅关闭了内核时钟,内核中止运行,但其片上外设,CM4核心的外设全都还照常运行。有两种方式进入睡眠模式,它的进入方式决定了从睡眠唤醒的方式,分别是WFI(wait for interrupt)和WFE(wait for event),即由等待"中断"唤醒和由"事件"唤醒。睡眠模式的各类特性见表 2。

表 2  睡眠模式的各类特性

特性

说明

当即睡眠

在执行WFI 或WFE 指令时当即进入睡眠模式。

退出时睡眠

在退出优先级最低的中断服务程序后才进入睡眠模式。

进入方式

内核寄存器的SLEEPDEEP = 0 ,而后调用WFI或WFE指令便可进入睡眠模式;

另外若内核寄存器的SLEEPONEXIT=0时,进入"当即睡眠"模式,SLEEPONEXIT=1时,进入"退出时睡眠"模式。

唤醒方式

若是是使用WFI指令睡眠的,则可以使用任意中断唤醒;

若是是使用WFE指令睡眠的,则由事件唤醒。

睡眠时

关闭内核时钟,内核中止,而外设正常运行,在软件上表现为再也不执行新的代码。这个状态会保留睡眠前的内核寄存器、内存的数据。

唤醒延迟

无延迟。

唤醒后

若由中断唤醒,先进入中断,退出中断服务程序后,接着执行WFI指令后的程序;若由事件唤醒,直接接着执行WFE后的程序。

中止模式

在中止模式中,进一步关闭了其它全部的时钟,因而全部的外设都中止了工做,但因为其1.2V区域的部分电源没有关闭,还保留了内核的寄存器、内存的信息,因此从中止模式唤醒,并从新开启时钟后,还能够从上次中止处继续执行代码。中止模式能够由任意一个外部中断(EXTI)唤醒。在中止模式中能够选择电压调节器为开模式或低功耗模式,可选择内部FLASH工做在正常模式或掉电模式。中止模式的各类特性见表3。

表 3 中止模式的各类特性

特性

说明

调压器低功耗模式

在中止模式下调压器可工做在正常模式或低功耗模式,可进一步下降功耗

FLASH掉电模式

在中止模式下FLASH可工做在正常模式或掉电模式,可进一步下降功耗

进入方式

内核寄存器的SLEEPDEEP =1,PWR_CR寄存器中的PDDS=0,而后调用WFI或WFE指令便可进入中止模式;

PWR_CR 寄存器的LPDS=0时,调压器工做在正常模式,LPDS=1时工做在低功耗模式;

PWR_CR 寄存器的FPDS=0时,FLASH工做在正常模式,FPDS=1时进入掉电模式。

唤醒方式

若是是使用WFI指令睡眠的,可以使用任意EXTI线的中断唤醒;

若是是使用WFE指令睡眠的,可以使用任意配置为事件模式的EXTI线事件唤醒。

中止时

内核中止,片上外设也中止。这个状态会保留中止前的内核寄存器、内存的数据。

唤醒延迟

基础延迟为HSI振荡器的启动时间,若调压器工做在低功耗模式,还须要加上调压器从低功耗切换至正常模式下的时间,若FLASH工做在掉电模式,还须要加上FLASH从掉电模式唤醒的时间。

唤醒后

若由中断唤醒,先进入中断,退出中断服务程序后,接着执行WFI指令后的程序;若由事件唤醒,直接接着执行WFE后的程序。唤醒后,STM32会使用HIS做为系统时钟。

待机模式

待机模式,它除了关闭全部的时钟,还把1.2V区域的电源也彻底关闭了,也就是说,从待机模式唤醒后,因为没有以前代码的运行记录,只能对芯片复位,从新检测boot条件,从头开始执行程序。它有四种唤醒方式,分别是WKUP(PA0)引脚的上升沿,RTC闹钟事件,NRST引脚的复位和IWDG(独立看门狗)复位。

表 4 待机模式的各类特性

特性

说明

进入方式

内核寄存器的SLEEPDEEP =1,PWR_CR寄存器中的PDDS=1,PWR_CR寄存器中的唤醒状态位WUF=0,而后调用WFI或WFE指令便可进入待机模式;

唤醒方式

经过WKUP引脚的上升沿,RTC闹钟、唤醒、入侵、时间戳事件或NRST引脚外部复位及IWDG复位唤醒。

待机时

内核中止,片上外设也中止;内核寄存器、内存的数据会丢失;除复位引脚、RTC_AF1引脚及WKUP引脚,其它I/O口均工做在高阻态。

唤醒延迟

芯片复位的时间

唤醒后

至关于芯片复位,在程序表现为从头开始执行代码。

在以上讲解的睡眠模式、中止模式及待机模式中,若备份域电源正常供电,备份域内的RTC均可以正常运行、备份域内的寄存器及备份域内的SRAM数据会被保存,不受功耗模式影响。

 

功耗模式选择及配置

功耗模式选择

睡眠模式:RTOS空闲任务进入休眠,sysTick中断唤醒;

中止模式:机器不工做(六轴静止)时,进入中止模式;六轴动时中断/RTC定时中断触发唤醒。

 

功耗模式配置

注:STOP模式下,唤醒后系统使用HSI做为系统时钟,用户可能须要未进入STOP模式前的系统时钟配置。

 

RTC定时唤醒喂狗

       系统进入STOP模式后,须要RTC中判定时唤醒喂狗防止系统复位。

RTC定时唤醒中断使能:

HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, 0xA017, RTC_WAKEUPCLOCK_RTCCLK_DIV16);

RTC定时唤醒中断失能:

HAL_RTCEx_DeactivateWakeUpTimer(&hrtc);

RTC定时唤醒喂狗:

void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc)

{

    feed_dog();

}

 

六轴BMI160

电源模式

在咱们的产品场景中,Gyroscope(陀螺仪)未使用,默认配置为Suspend mode,Accelermoter(加速度)使用Normal mode(退出CPU休眠后)或Low power mode(进入休眠前配置);

 

中断模式

 

模式选择及配置

这里推荐使用BMI160官方驱动,源码获取参见参考资料第3项,关于驱动具体使用参照源码README.md文件,下面

struct bmi160_dev sensor;

BMI160初始化

struct bmi160_dev sensor; /* BMI160初始化 */ void AccelInit(void) { /* You may assign a chip select identifier to be handled later */ sensor.id = 0; sensor.interface = BMI160_SPI_INTF; sensor.read = user_spi_read; sensor.write = user_spi_write; sensor.delay_ms = user_delay_ms; int8_t rslt = BMI160_OK; rslt = bmi160_init(&sensor); printf("bmi160 chip id: %02x, accel power:%d, gyro power:%d\r\n", sensor.chip_id, sensor.accel_cfg.power, sensor.gyro_cfg.power); /* After the above function call, accel_cfg and gyro_cfg parameters in the device structure are set with default values, found in the datasheet of the sensor */ } /* BMI160电源模式配置 */ void AccelConfig(void) { int8_t rslt = BMI160_OK; struct bmi160_pmu_status pmuStatus; /* Select the Output data rate, range of accelerometer sensor */ sensor.accel_cfg.odr = BMI160_ACCEL_ODR_400HZ; sensor.accel_cfg.range = BMI160_ACCEL_RANGE_4G; sensor.accel_cfg.bw = BMI160_ACCEL_BW_NORMAL_AVG4; /* Select the power mode of accelerometer sensor */ sensor.accel_cfg.power = BMI160_ACCEL_NORMAL_MODE; /* Set the sensor configuration */ rslt = bmi160_set_sens_conf(&sensor); bmi160_get_power_mode( &pmuStatus, &sensor); //printf("accel power:%d, gyro power:%d\r\n", pmuStatus.accel_pmu_status, pmuStatus.gyro_pmu_status); } /* BMI160 Any-motion中断配置 */ void AnyMotionIntCfg(void) { struct bmi160_int_settg int_config; /* Select the Interrupt channel/pin */ int_config.int_channel = BMI160_INT_CHANNEL_BOTH;// Interrupt channel/pin 1

    /* Select the Interrupt type */ int_config.int_type = BMI160_ACC_ANY_MOTION_INT;// Choosing Any motion interrupt

    /* Select the interrupt channel/pin settings */ int_config.int_pin_settg.output_en = BMI160_ENABLE;// Enabling interrupt pins to act as output pin int_config.int_pin_settg.output_mode = BMI160_DISABLE;// Choosing push-pull mode for interrupt pin int_config.int_pin_settg.output_type = BMI160_DISABLE;// Choosing active low output int_config.int_pin_settg.edge_ctrl = BMI160_ENABLE;// Choosing edge triggered output int_config.int_pin_settg.input_en = BMI160_DISABLE;// Disabling interrupt pin to act as input int_config.int_pin_settg.latch_dur = BMI160_LATCH_DUR_NONE;// non-latched output

    /* Select the Any-motion interrupt parameters */ int_config.int_type_cfg.acc_any_motion_int.anymotion_en = BMI160_ENABLE;// 1- Enable the any-motion, 0- disable any-motion int_config.int_type_cfg.acc_any_motion_int.anymotion_x = BMI160_ENABLE;// Enabling x-axis for any motion interrupt int_config.int_type_cfg.acc_any_motion_int.anymotion_y = BMI160_ENABLE;// Enabling y-axis for any motion interrupt int_config.int_type_cfg.acc_any_motion_int.anymotion_z = BMI160_ENABLE;// Enabling z-axis for any motion interrupt int_config.int_type_cfg.acc_any_motion_int.anymotion_dur = 0;// any-motion duration int_config.int_type_cfg.acc_any_motion_int.anymotion_thr = 20;// (2-g range) -> (slope_thr) * 3.91 mg, (4-g range) -> (slope_thr) * 7.81 mg, (8-g range) ->(slope_thr) * 15.63 mg, (16-g range) -> (slope_thr) * 31.25 mg

    /* Set the Any-motion interrupt */ bmi160_set_int_config(&int_config, &sensor); /* sensor is an instance of the structure bmi160_dev */ }

 

 

方案验证

测试用例

typedef enum
{
  STOP_MODE_WAKE_FROM_NULL,
  STOP_MODE_WAKE_FROM_RTC,
  STOP_MODE_WAKE_FROM_ACCEL,
}StopModeWakeEnum;
 StopModeWakeEnum wakeReason = STOP_MODE_WAKE_FROM_NULL; void StopModeTest(void *pdata)
{ RCC_ClkInitTypeDef clkCfgPre; uint32_t flatencyPre
= 0; uint32_t freqPre = 0; GPIO_InitTypeDef GPIO_InitStruct; AccelInit(); AccelConfig(); AnyMotionIntCfg(); for(;;) { printf("\r\nSTM32 runing, system led off\r\n"); PrintSysClkInfo(); SysLedOff(); printf("\r\nSTM32 enter stop mode\r\n"); /*## Configure the Wake up timer ###########################################*/ /* RTC Wake-up Interrupt Generation: Wake-up Time Base = (RTC_WAKEUPCLOCK_RTCCLK_DIV /(LSI)) Wake-up Time = Wake-up Time Base * WakeUpCounter = (RTC_WAKEUPCLOCK_RTCCLK_DIV /(LSI)) * WakeUpCounter ==> WakeUpCounter = Wake-up Time / Wake-up Time Base To configure the wake up timer to 20s the WakeUpCounter is set to 0xA017: RTC_WAKEUPCLOCK_RTCCLK_DIV = RTCCLK_Div16 = 16 Wake-up Time Base = 16 /(~32.768KHz) = ~0,488 ms Wake-up Time = ~20s = 0,488ms * WakeUpCounter ==> WakeUpCounter = ~20s/0,488ms = 40983 = 0xA017 */ HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, 0xA017, RTC_WAKEUPCLOCK_RTCCLK_DIV16); /* FLASH Deep Power Down Mode enabled */ HAL_PWREx_EnableFlashPowerDown(); /* Enter Stop Mode */ HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI); HAL_PWREx_DisableFlashPowerDown(); HAL_RCC_GetClockConfig( &clkCfgPre, &flatencyPre); freqPre = HAL_RCC_GetSysClockFreq (); SYSCLKConfig_STOP(); /* Disable Wake-up timer */ if(HAL_RTCEx_DeactivateWakeUpTimer(&hrtc) != HAL_OK) { /* Initialization Error */ Error_Handler(); } switch(wakeReason) { case STOP_MODE_WAKE_FROM_RTC: printf("\r\nSTM32 wake from stop mode from RTC, system led on\r\n"); break; case STOP_MODE_WAKE_FROM_ACCEL: printf("\r\nSTM32 wake from stop mode from ACCEL, system led on\r\n"); break; default: printf("\r\nSTM32 wake from stop mode from %d\r\n", wakeReason); break; } printf("SysFreq:%u, ClkSrc:%d(0:HSI, 1:HSE, 2:PLL, 3:PLLR)\r\n", freqPre, clkCfgPre.SYSCLKSource); printf("\r\nSTM32 recover system clk config\r\n"); PrintSysClkInfo(); OSTimeDlyHMSM( 0, 0, 5, 0); } } void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc) { SysLedOn(); feed_dog(); wakeReason = STOP_MODE_WAKE_FROM_RTC; } void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
  switch(GPIO_Pin) {     case WAKED_FROM_BT_Pin: if(GPIO_PIN_SET == HAL_GPIO_ReadPin(WAKED_FROM_BT_GPIO_Port, WAKED_FROM_BT_Pin)) {/*wake start*/ BleSpiRxRestart(); }break; case INT_ALARM_Pin: break; case GYRO_INT1_Pin: if(GPIO_PIN_SET == HAL_GPIO_ReadPin(GYRO_INT1_GPIO_Port, GYRO_INT1_Pin)) { } break; case GYRO_INT2_Pin: if(GPIO_PIN_SET == HAL_GPIO_ReadPin(GYRO_INT2_GPIO_Port, GYRO_INT2_Pin)) { SysLedOn(); wakeReason = STOP_MODE_WAKE_FROM_ACCEL; } break;
default:        break;
 }
}

 

实验步骤

  1. 测试用例烧录终端,链接打印串口、打开串口调试助手;
  2. 18:15:49前保持终端静止、待进入CPU休眠后再次震动终端1次,重复操做2次;

测试log输出:

实验结论

CPU进入中止模式后,RTC定时中断/六轴中断可唤醒CPU,唤醒后CPU使用HSI做为系统时钟源,CPU进入中止模式后功耗极大下降,可实现机器非工做时电流消耗的极大下降。

参考资料

1. 《STM32F401xB/C and STM32F401xD/E advanced Arm®-based 32-bit MCUs》

2.《Description of STM32F4 HAL and LL drivers》

3.https://ae-bst.resource.bosch.com/media/_tech/media/datasheets/BST-BMI160-DS000.pdf

4. https://github.com/BoschSensortec/BMI160_driver

 

做者:大毛孩 出处: https://www.cnblogs.com/damaohai/
本文版权归做者和博客园共有,欢迎转载,但未经做者赞成必须保留此段声明,且在文章页面明显位置给出原文链接,不然保留追究法律责任的权利。若是以为还有帮助的话,能够点一下右下角的【推荐】。