普冉PY32系列(十五) PY32F0系列的低功耗模式

目录

声明

任何在厂家数据手册之外的资源都是无保证的, 本文内容仅对当前测试中使用的样品有效, 请勿以此作为选型参考, 一切以厂家手册为准. 因为使用本文数据产生的任何问题本人概不负责.

PY32F0系列的低功耗

Cortex M0/M0+相对于Cortex M3/M4性能差点, 但是优势在于低价格和低功耗, 这使得M0特别适合电池供电的便携类应用, 比如遥控器, 墨水屏, 电子宠物, 电子烟等. 根据 PY32F0 各个型号的数据手册, 对比其最低功耗状态(STOP模式)下的电流, 全系列可以大致分为三档

  1. PY32F04x PY32F07x: 最低 10.5 uA
  2. PY32F030 PY32F003 PY32F002A: 最低 4.5 uA
  3. PY32F002B: 最低 1.5 uA

可以看出待机功耗和片上外设的丰富程度基本上是成正比的.

  • PY32F04X外设丰富功耗也大, 面向的是替代M3的场景, 低功耗可能不是最重要的特性
  • PY32F030 系列, PY32F002A是一个特例, 具体原因大家也都知道的
  • PY32F002B 资源最少, 但是功耗非常低, 待机电流1.5uA. 实际测试电流大小与这个数字基本一致.

电池供电的便携设备, 待机功耗基本上要控制在十个uA以内. 例如一个用主板电池CR2032供电的设备要求一年的电池使用寿命. CR2032电量为200mAH, 假定工作电流20mA, 待机5uA, 工作时间占比0.1%(比如每隔十秒采集上报一次数据, 上报耗时10毫秒), 电池寿命就差不多是一年. 对于这种场景使用 PY32F030 系列比较勉强, 而使用 PY32F002B 则功耗还有富余.

这里具体说明 PY32F030(适用于PY32F003和PY32F002A) 和 PY32F002B 这两类型号的低功耗设置.

测试方法

要测量的目标为 10uA 以下的电流, 可以用万用表的微安档, 但是MCU启动状态和正常工作状态电流差异巨大, 从十几个mA到几个uA, 为方便测量, 可以在万用表的正负极接入一个开关, 启动时开关闭合, 电流走开关, 当工作稳定后开关打开, 由微安表读出电流.

因为测量微小电流很容易受电路其它元件干扰, 为避免因为各种电流泄漏造成的测试结果不准确:

  1. 不要用普通的开发板(除非是专门设计用于测试低功耗场景的), 用简单的分线板最可靠
  2. 不要用低管脚数的封装, 因为存在管脚复用的情况, 当复用的管脚没有正确配置时, 在内部管脚之间也会产生电流泄漏

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时的功耗控制

下面的代码用于演示如何从内部高速时钟切换到内部低速时钟, 并一步步降低功耗

c 复制代码
/**
 * 启用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, 然后等待事件或中断唤醒

c 复制代码
// 使能低功耗控制模块(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

c 复制代码
// 使能低功耗控制模块(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 模式

c 复制代码
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);
}

如果配置为中断唤醒, 那么还需要加上下面的中断回调函数清理中断位

c 复制代码
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 的代码

c 复制代码
// 开启 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);

配合中断回调函数

c 复制代码
void LPTIM1_IRQHandler(void)
{
  if(LL_LPTIM_IsActiveFlag_ARRM(LPTIM) == 1)
  {
    // 清理中断标志位
    LL_LPTIM_ClearFLAG_ARRM(LPTIM);
    // 自定义的中断处理方法
    APP_LPTIMCallback();
  }
}

在使用时, 在每一个循环中先进入低功耗状态, 开启LPTIM, 然后进入STOP 等待中断, MCU会阻塞在__WFI()方法. 当 LPTIM 计数结束后会唤醒 MCU 继续往下执行.

c 复制代码
// 使能低功耗状态
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 完全一样

c 复制代码
// 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 系列有区别

  1. 下面的LL_PWR_SetLprMode等价于F030中的LL_PWR_EnableLowPowerRunMode方法, 都是切换到低电压调节器
  2. 使用LL_PWR_SetStopModeSramVoltCtrl设置SRAM保持电压
  3. 启用 Deep Sleep (STOP) 模式
  4. 等待事件或中断唤醒
c 复制代码
// 使能低功耗控制模块(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, 会自动唤醒
c 复制代码
// 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);

回调

c 复制代码
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 运行时钟

在 py32f002bx5.h 中增加一行 #define RCC_HSI48M_SUPPORT, 就能开启 PY32F002B 的 48MHz 时钟支持. 对手里的几片 PY32F002B 测试, 以及对一些渠道厂商的合封芯片的测试, 开启 48MHz 没有问题, 按 48MHz 的时钟基准设置定时器和PWM, 反过来也能验证是真实的 48MHz 频率.

2. 开启 DEEP STOP 模式

在 py32f002bx5.h 中增加如下几行

c 复制代码
#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 模式

c 复制代码
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 上运行上面的代码, 待机电流能降到约 0.6uA. DEEP STOP 模式可以通过前面的按键唤醒方式唤醒.

相关代码

以上的低功耗相关例程和代码可以在下面的链接中找到

相关推荐
Megahertz6617 天前
记一次失败的 FreeRTOS 移植
mcu·py32·py32f003
Milton3 个月前
不到80元的E88无刷电机无人机拆解
xn297lbw·py32f002b·tp4054·pfs122
Milton2 年前
普冉PY32系列(十三) SPI驱动WS2812全彩LED
py32·ws2812·py32f072
Milton2 年前
普冉PY32系列(九) GPIO模拟和硬件SPI方式驱动无线收发芯片XL2400
py32·py32f002a·xn297lbw·xl2400
Milton2 年前
普冉PY32系列(八) GPIO模拟和硬件SPI方式驱动无线收发芯片XN297LBW
py32·py32f002a·xn297l·xn297lbw