普冉PY32系列(十五) PY32F0系列的低功耗模式
目录
- 普冉PY32系列(一) PY32F0系列32位Cortex M0+ MCU简介
- 普冉PY32系列(二) Ubuntu GCC Toolchain和VSCode开发环境
- 普冉PY32系列(三) PY32F002A资源实测 - 这个型号不简单
- 普冉PY32系列(四) PY32F002A/003/030的时钟设置
- 普冉PY32系列(五) 使用JLink RTT代替串口输出日志
- 普冉PY32系列(六) 通过I2C接口驱动PCF8574扩展的1602LCD
- 普冉PY32系列(七) SOP8,SOP10,SOP16封装的PY32F002A/PY32F003管脚复用
- 普冉PY32系列(八) GPIO模拟和硬件SPI方式驱动无线收发芯片XN297LBW
- 普冉PY32系列(九) GPIO模拟和硬件SPI方式驱动无线收发芯片XL2400
- 普冉PY32系列(十) 基于PY32F002A的6+1通道遥控小车I - 综述篇
- 普冉PY32系列(十一) 基于PY32F002A的6+1通道遥控小车II - 控制篇
- 普冉PY32系列(十二) 基于PY32F002A的6+1通道遥控小车III - 驱动篇
- 普冉PY32系列(十三) SPI驱动WS2812全彩LED
- 普冉PY32系列(十四) 从XL2400迁移到XL2400P
- 普冉PY32系列(十五) PY32F0系列的低功耗模式
声明
任何在厂家数据手册之外的资源都是无保证的, 本文内容仅对当前测试中使用的样品有效, 请勿以此作为选型参考, 一切以厂家手册为准. 因为使用本文数据产生的任何问题本人概不负责.
PY32F0系列的低功耗
Cortex M0/M0+相对于Cortex M3/M4性能稍弱, 但是优势在于低价格和低功耗, 这使得M0特别适合性能要求不高且电池供电的便携类应用, 比如遥控器, 墨水屏, 电子宠物, 电子烟等. 根据 PY32F0 各个型号的数据手册, 对比其最低功耗状态(STOP模式)下的电流, 全系列可以大致分为三档
- PY32F04x PY32F07x: 最低 10.5 uA
- PY32F030 PY32F003 PY32F002A: 最低 4.5 uA
- PY32F002B: 最低 1.5 uA
可以看出待机功耗和片上外设的丰富程度基本上是成正比的.
- PY32F04X外设丰富功耗也大, 面向的是替代M3的场景, 低功耗可能不是最重要的特性
- PY32F030 系列, PY32F002A是一个特例, 具体原因大家也都知道的
- PY32F002B 资源最少, 但是功耗非常低, 待机电流1.5uA. 实际测试电流能做到1uA以下(看本文末尾的说明)
电池供电的便携设备, 待机功耗基本上要控制在十个uA以内. 例如一个用主板电池CR2032供电的设备要求一年的电池使用寿命. CR2032电量为200mAH, 假定工作电流20mA, 待机5uA, 工作时间占比0.1%(比如每隔十秒采集上报一次数据, 上报耗时10毫秒), 电池寿命就差不多是一年. 对于这种场景使用 PY32F030 系列比较勉强, 而使用 PY32F002B 则功耗还有富余.
这里具体说明 PY32F030(适用于PY32F003和PY32F002A) 和 PY32F002B 这两类型号的低功耗设置.
测试方法
要测量的目标为 10uA 以下的电流, 可以用万用表的微安档, 但是MCU启动状态和正常工作状态电流差异巨大, 从十几个mA到几个uA, 为方便测量, 可以在万用表的正负极并联一个开关, 启动时开关闭合, 电流走开关, 当工作稳定后开关打开, 由微安表读出电流.
因为测量微小电流很容易受电路其它元件干扰, 为避免因为各种电流泄漏造成的测试结果不准确:
- 不要用普通的开发板(除非是专门设计用于测试低功耗场景的), 用简单的分线板最可靠
- 不要用低管脚数的封装, 因为存在管脚复用的情况, 当复用的管脚没有正确配置时, 在内部管脚之间也会产生电流泄漏
PY32F030 系列(PY32F002A, PY32F003, PY32F030)
这个系列属于 PY32F0 中的通用型号, 片上资源可以满足大部分场景的需求. 待机电流虽然没那么低(4.5uA), 但是面对普通电池应用也是绰绰有余, 一节五号电池可以轻松工作半年以上. PY32F030 系列低功耗状态支持两种模式 SLEEP 和 DEEP SLEEP(STOP).
- 正常运行模式下, 使用LSI可以显著降低功耗, 启用Flash睡眠后功耗电流可以控制到 100uA 以内
- SLEEP 模式下只是关闭了CPU时钟, 外设还能工作, 时钟频率高的时候切换到SLEEP后节能效果明显, 时钟频率越低则越无区别, 根据时钟源为HSI还是LSI, 电流大小区间为 0.1 mA 到 2.x mA
- STOP 模式大部分外设停止, 时钟 HSI, HSE 和 PLL 停止. LPTIM 基于 LSI 工作, 当切换到低压调节器后, 电流大小在 4.5 uA 到 6 uA 区间
工作于内置低速时钟LSI时的功耗控制
下面的代码用于演示如何从内部高速时钟切换到内部低速时钟, 并一步步降低功耗
/** * 启用LSI并将其设为系统时钟 */ static void APP_RCC_LSI_Config(void) { LL_RCC_LSI_Enable(); while(LL_RCC_LSI_IsReady() != 1); LL_RCC_SetAHBPrescaler(LL_RCC_SYSCLK_DIV_1); /* 设置 LSI 为系统时钟源 */ LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_LSI); while(LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_LSI); LL_FLASH_SetLatency(LL_FLASH_LATENCY_0); LL_RCC_SetAPB1Prescaler(LL_RCC_APB1_DIV_1); LL_SetSystemCoreClock(LSI_VALUE); /* 重设 SysTick 时钟计数周期, 如果没有这步, LL_mDelay()延迟就会不正常 */ LL_Init1msTick(32768); } int main(void) { // 设置 HSI 24MHz 作为系统时钟 BSP_RCC_HSI_24MConfig(); // 系统运行于 HSI, 测得电流约 1.3 mA LL_mDelay(3000); // 系统时钟切换到内部低速时钟 LSI APP_RCC_LSI_Config(); // 系统运行于 LSI, 但是 HSI 未关闭, 电流约 360 uA LL_mDelay(3000); // 关闭 HSI LL_RCC_HSI_Disable(); // 电流降至约 180 uA LL_mDelay(3000); // 开启 flash sleep SET_BIT(FLASH->STCR, FLASH_STCR_SLEEP_EN); // 电流降至约 100 uA while (1); }
测量的时候, 可以观察到上电后, 每隔三秒电流会降一档, 切换时钟源到 LSI 后, 从 1.3mA 降到 360uA, 关闭 HSI 后, 降到 180uA, 启用 Flash Sleep 后, 降到 100 uA 以内.
进入 SLEEP 模式
进入SLEEP模式的代码很简单, 启用PWR时钟并调用LL_LPM_EnableSleep
就启用了SLEEP, 然后等待事件或中断唤醒
// 使能低功耗控制模块(PWR)时钟 LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_PWR); // 设置低功耗状态为 Sleep, 清除SLEEPDEEP状态位, CLEAR_BIT(SCB->SCR, ((uint32_t)SCB_SCR_SLEEPDEEP_Msk)) LL_LPM_EnableSleep(); /* * 等待事件唤醒 * 如果是等待中断, 将下面的代码换成 __WFI(); */ __SEV(); __WFE(); __WFE();
进入 STOP 模式
启用低功耗STOP模式, 并等待事件唤醒.
注意这里面的LL_PWR_SetRegulVoltageScaling
方法, 如果 STOP 模式下测得的电流一直在 6 uA 以上, 很可能是电压没有调整为 1.0V
// 使能低功耗控制模块(PWR)时钟 LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_PWR); /* * 设置低功耗STOP电压为1.0V, 默认电压为1.2V, 会增大电流, * 对于 PY32F030 系列, 1.0V和1.2V对应的电流为 4.5uA~4.8uA 和 6uA ~ 7uA */ LL_PWR_SetRegulVoltageScaling(LL_PWR_REGU_VOLTAGE_SCALE2); /* * 设置电压调节器从工作状态转换为低功耗状态, SET_BIT(PWR->CR1, PWR_CR1_LPR) * 在开启 STOP 模式前, 必须调用这个方法 */ LL_PWR_EnableLowPowerRunMode(); /* * 设置低功耗状态的模式为Deep sleep, 即STOP模式, * 对应寄存器命令为 SET_BIT(SCB->SCR, ((uint32_t)SCB_SCR_SLEEPDEEP_Msk)); */ LL_LPM_EnableDeepSleep(); /* * 等待事件唤醒 * 如果是等待中断, 将下面的代码换成 __WFI(); */ __SEV(); __WFE(); __WFE(); /* * 退出 STOP 模式时, 设置低功耗状态为 Sleep, 清除SLEEPDEEP状态位, * 对应寄存器命令为 CLEAR_BIT(SCB->SCR, ((uint32_t)SCB_SCR_SLEEPDEEP_Msk)) */ LL_LPM_EnableSleep();
事件唤醒和中断唤醒 - 按键唤醒
下面配置的外部中断, 用于事件唤醒或中断唤醒 SLEEP/STOP 模式
static void APP_EXTIConfig(void) { LL_GPIO_InitTypeDef GPIO_InitStruct; LL_EXTI_InitTypeDef EXTI_InitStruct; // GPIOA时钟使能 LL_IOP_GRP1_EnableClock (LL_IOP_GRP1_PERIPH_GPIOA); // 选择PA06引脚 GPIO_InitStruct.Pin = LL_GPIO_PIN_6; // 选择输入模式 GPIO_InitStruct.Mode = LL_GPIO_MODE_INPUT; // 选择上拉 GPIO_InitStruct.Pull = LL_GPIO_PULL_UP; // GPIOA初始化 LL_GPIO_Init(GPIOA, &GPIO_InitStruct); // 选择EXTI6做外部中断输入 LL_EXTI_SetEXTISource(LL_EXTI_CONFIG_PORTA,LL_EXTI_CONFIG_LINE6); // 选择EXTI6 EXTI_InitStruct.Line = LL_EXTI_LINE_6; // 使能 EXTI_InitStruct.LineCommand = ENABLE; /* * 选择中断模式 * 事件唤醒使用 EXTI_InitStruct.Mode = LL_EXTI_MODE_EVENT; * 中断唤醒使用 EXTI_InitStruct.Mode = LL_EXTI_MODE_IT; */ EXTI_InitStruct.Mode = LL_EXTI_MODE_EVENT; // 选择下降沿触发 EXTI_InitStruct.Trigger = LL_EXTI_TRIGGER_FALLING; // 外部中断初始化 LL_EXTI_Init(&EXTI_InitStruct); // 设置中断优先级 NVIC_SetPriority(EXTI4_15_IRQn,1); // 使能中断 NVIC_EnableIRQ(EXTI4_15_IRQn); }
如果配置为中断唤醒, 那么还需要加上下面的中断回调函数清理中断位
void EXTI4_15_IRQHandler(void) { if(LL_EXTI_ReadFlag(LL_EXTI_LINE_6) == LL_EXTI_LINE_6) { LL_EXTI_ClearFlag(LL_EXTI_LINE_6); } }
LPTIM 唤醒 - 自动间隔唤醒
PY32F030 系列的 LPTIM 只有LL_LPTIM_OPERATING_MODE_ONESHOT
这一种模式, 不能连续加载. 如果需要保持定期唤醒, 需要在主循环中, 再次开始 LPTIM 计数 LL_LPTIM_StartCounter
.
配置 LPTIM 的代码
// 开启 LPTIM1时钟 LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_LPTIM1); // 开启内部低速时钟 LSI LL_RCC_LSI_Enable(); while(LL_RCC_LSI_IsReady() == 0); // 配置LSI为LPTIM时钟源 Freq = 32.768 kHz LL_RCC_SetLPTIMClockSource(LL_RCC_LPTIM1_CLKSOURCE_LSI); // LPTIM预分频器128分频 LL_LPTIM_SetPrescaler(LPTIM1,LL_LPTIM_PRESCALER_DIV128); // LPTIM计数周期结束更新ARR LL_LPTIM_SetUpdateMode(LPTIM1,LL_LPTIM_UPDATE_MODE_ENDOFPERIOD); // 使能NVIC请求 NVIC_SetPriority(LPTIM1_IRQn,0); NVIC_EnableIRQ(LPTIM1_IRQn); // 使能ARR中断 LL_LPTIM_EnableIT_ARRM(LPTIM1); // 使能LPTIM LL_LPTIM_Enable(LPTIM1); // 配置重装载值 51 LL_LPTIM_SetAutoReload(LPTIM1,51);
配合中断回调函数
void LPTIM1_IRQHandler(void) { if(LL_LPTIM_IsActiveFlag_ARRM(LPTIM) == 1) { // 清理中断标志位 LL_LPTIM_ClearFLAG_ARRM(LPTIM); // 自定义的中断处理方法 APP_LPTIMCallback(); } }
在使用时, 在每一个循环中先进入低功耗状态, 开启LPTIM, 然后进入STOP 等待中断, MCU会阻塞在__WFI()
方法. 当 LPTIM 计数结束后会唤醒 MCU 继续往下执行.
// 使能低功耗状态 LL_PWR_EnableLowPowerRunMode(); // 重启 LPTIM LL_LPTIM_Disable(LPTIM1); LL_LPTIM_Enable(LPTIM1); // 等待 APP_uDelay(65); // 开启LPTIM单次模式 LL_LPTIM_StartCounter(LPTIM1,LL_LPTIM_OPERATING_MODE_ONESHOT); // 使能STOP模式并等待中断唤醒 LL_LPM_EnableDeepSleep(); __WFI();
PY32F002B
PY32F002B 片上资源相比 PY32F030 系列缩水了不少, 存储只有 24K flash / 3K RAM, 只有两个定时器, 还只有一个定时器带4个IO输出, 但是胜在低功耗, STOP 模式电流只有 1.5 uA, 可以胜任很多低功耗的需求.
进入 SLEEP 模式
- PY32F002B 的 SLEEP 模式电流在 1 mA 以下, 整体比正常运行模式低 20% 左右
- PY32F002B 的 STOP 模式, 当切换到低压调节器后, 电流大小陡然降到 1.5 uA - 1.7 uA 区间
启用 SLEEP 模式的代码和 PY32F030 完全一样
// Enable PWR clock LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_PWR); // Enter Sleep mode LL_LPM_EnableSleep(); /* * 等待事件唤醒 * 如果是等待中断, 将下面的代码换成 __WFI(); */ __SEV(); __WFE(); __WFE();
进入 STOP 模式
PY32F002B 开启 STOP 模式的过程和 PY32F030 系列有区别
- 下面的
LL_PWR_SetLprMode
等价于F030中的LL_PWR_EnableLowPowerRunMode
方法, 都是切换到低电压调节器 - 使用
LL_PWR_SetStopModeSramVoltCtrl
设置SRAM保持电压 - 启用 Deep Sleep (STOP) 模式
- 等待事件或中断唤醒
// 使能低功耗控制模块(PWR)时钟 LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_PWR); // STOP 模式启用低电压调节器 LL_PWR_SetLprMode(LL_PWR_LPR_MODE_LPR); // SRAM保持电压与数字LDO输出对齐 LL_PWR_SetStopModeSramVoltCtrl(LL_PWR_SRAM_RETENTION_VOLT_CTRL_LDO); // Enter DeepSleep mode LL_LPM_EnableDeepSleep(); /* * 等待事件唤醒 * 如果是等待中断, 将下面的代码换成 __WFI(); */ __SEV(); __WFE(); __WFE(); LL_LPM_EnableSleep();
事件唤醒和中断唤醒 - 按键唤醒
PY32F002B 的按键唤醒和 PY32F030 系列是一样的, 略.
LPTIM 唤醒 - 定时器自动唤醒
配置 LPTIM, PY32F002B 的 LPTIM 和 PY32F030 系列相比, 增加了一个连续模式 LL_LPTIM_OPERATING_MODE_CONTINUOUS
.
- 如果是单次模式
LL_LPTIM_OPERATING_MODE_ONESHOT
, 下次进入STOP模式后, 要重启启动 LPTIM 才能唤醒 - 如果是连续模式
LL_LPTIM_OPERATING_MODE_CONTINUOUS
, 下次进入STOP模式后, 不需要再设置 LPTIM, 会自动唤醒
// Enable LPTIM clock LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_LPTIM1); // Enabel LSI LL_RCC_LSI_Enable(); while(LL_RCC_LSI_IsReady() == 0); // Select LSI as LTPIM clock source LL_RCC_SetLPTIMClockSource(LL_RCC_LPTIM1_CLKSOURCE_LSI); // prescaler: 128 LPTIM_InitStruct.Prescaler = LL_LPTIM_PRESCALER_DIV128; // Registers are updated after each APB bus write access LPTIM_InitStruct.UpdateMode = LL_LPTIM_UPDATE_MODE_IMMEDIATE; // Init LPTIM if (LL_LPTIM_Init(LPTIM, &LPTIM_InitStruct) != SUCCESS) { APP_ErrorHandler(); } // Enable LPTIM1 interrupt NVIC_SetPriority(LPTIM1_IRQn, 0); NVIC_EnableIRQ(LPTIM1_IRQn); // Enable LPTIM autoreload match interrupt LL_LPTIM_EnableIT_ARRM(LPTIM); // Enable LPTIM LL_LPTIM_Enable(LPTIM); // Set autoreload value LL_LPTIM_SetAutoReload(LPTIM, 51); /* * LPTIM starts in single mode * 如果是连续模式, 则用 LL_LPTIM_StartCounter(LPTIM, LL_LPTIM_OPERATING_MODE_CONTINUOUS); */ LL_LPTIM_StartCounter(LPTIM, LL_LPTIM_OPERATING_MODE_ONESHOT);
回调
void LPTIM1_IRQHandler(void) { APP_LptimIRQCallback(); } void APP_LptimIRQCallback(void) { if((LL_LPTIM_IsActiveFlag_ARRM(LPTIM) == 1) && (LL_LPTIM_IsEnabledIT_ARRM(LPTIM) == 1)) { /* Clear autoreload match flag */ LL_LPTIM_ClearFLAG_ARRM(LPTIM); } }
文末的彩蛋: PY32F002B 的隐藏资源
1. 开启 48MHz 运行时钟
PY32F002B 手册上的最高运行时钟是 24MHz, 但是在 py32f002bx5.h 中增加一行 #define RCC_HSI48M_SUPPORT
, 就能开启 PY32F002B 的 48MHz 时钟支持. 对手里的几片 PY32F002B 测试, 以及对一些渠道厂商的合封芯片的测试, 开启 48MHz 没有问题, 按 48MHz 的时钟基准设置定时器和PWM, 反过来也能验证是真实的 48MHz 频率.
2. 开启 DEEP STOP 模式
PY32F002B 手册上没有列出 DEEP STOP 模式, 但是在 py32f002bx5.h 中增加如下几行
#define PWR_DEEPSTOP_SUPPORT /*!< PWR feature available only on specific devices: Deep stop feature */ #define PWR_CR1_SRAM_RETV_DLP_Pos (18U) #define PWR_CR1_SRAM_RETV_DLP_Msk (0x1UL << PWR_CR1_SRAM_RETV_DLP_Pos) /*!< 0x00040000 */ #define PWR_CR1_SRAM_RETV_DLP PWR_CR1_SRAM_RETV_DLP_Msk /*!< SRAM retention voltage control in DeepStop mode */
使用以下的代码就能使 MCU 进入 DEEP STOP 模式
static void APP_EnterDeepStop(void) { /* Enable PWR clock */ LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_PWR); /* STOP mode with deep low power regulator ON */ LL_PWR_SetLprMode(LL_PWR_LPR_MODE_DLPR); /* SRAM retention voltage aligned with digital LDO output */ LL_PWR_SetStopModeSramVoltCtrl(LL_PWR_SRAM_RETENTION_VOLT_CTRL_LDO); /* Enter DeepSleep mode */ LL_LPM_EnableDeepSleep(); /* Request Wait For Event */ __SEV(); __WFE(); __WFE(); LL_LPM_EnableSleep(); }
PY32F002B 在 DEEP STOP 模式下, 待机电流能降到约 0.6uA.
3. DEEP STOP 模式通过 LPTIM 唤醒
经过实际测试, PY32F002B 的 DEEP STOP 模式可以通过 LPTIM 唤醒
- 当 LPTIM 时钟源使用 LSE 时, 定时准确, 精度取决于32K晶振的精度, 唤醒的间隔时间最长可以设置到256秒. 但是耗电稍高, 因为开启 LSE 需要额外的 0.6uA 左右的电流, 待机电流 1.2uA 左右
- 当 LPTIM 时钟源使用 LSI 时, 耗电最省, 待机只需要 0.6uA, 但是定时不准确. 当设置为4秒时, 实际间隔稍大于4秒, 当设置为5秒时, 实际间隔为7秒左右, 设置为6秒时, 实际间隔为26秒, 在7秒以上则很可能无法唤醒. 猜测在 DEEP STOP 模式下内部低速时钟是依靠内部存留(电容?)电量运行, 会随着内部电量消耗电压降低而逐渐变慢.
相关代码
以上的相关例程和代码可以在下面的链接中找到
- https://github.com/IOsetting/py32f0-template/tree/main/Examples/PY32F002B/LL/PWR
PY32F002B 的 STOP 和 DEEP STOP 模式 - https://github.com/IOsetting/py32f0-template/tree/main/Examples/PY32F0xx/LL/RCC/LSI_CurrentTest
PY32F002A/PY32F003/PY32F030 的 LSI 电流测试 - https://github.com/IOsetting/py32f0-template/tree/main/Examples/PY32F0xx/LL/LPTIM/LPTIM1_Wakeup
PY32F002A/PY32F003/PY32F030 的 STOP 模式和 LPTIM 唤醒
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· 【全网最全教程】使用最强DeepSeekR1+联网的火山引擎,没有生成长度限制,DeepSeek本体
2023-05-01 沁恒 CH32V208(一): CH32V208WBU6 评估板上手报告和Win10环境配置
2022-05-01 斯里兰卡正在发生的经济危机