一、综述
随着物联网终端向电池供电和长续航方向发展,MCU 的待机功耗已成为系统设计的关键指标。本文基于 Renesas RA4L1,对 Sleep、Snooze、Software Standby 三种低功耗模式进行了实验验证,并制作了一个低功耗运行的简易秒表。
下面是三种低功耗模式的简介,参考了FSP用户手册:
1.Sleep Mode|睡眠模式
上电后,默认的低功耗模式即为 Sleep。
该模式使用最方便,不需要额外配置,只需设置可用于唤醒 MCU 的中断或事件,即可在唤醒后回到正常程序执行。在 Sleep 模式中,SRAM、处理器寄存器以及硬件外设的状态都会被保留,进入和退出所需时间极短。任何中断都会使 MCU 从 Sleep 模式唤醒,包括 RTOS 调度器使用的 SysTick 中断。
2.Software Standby Mode|软件待机模式
在 Software Standby 模式下,CPU、片上大多数外设功能以及所有内部振荡器都会停止,但 CPU 寄存器内容、SRAM 数据、外设状态以及 I/O 端口配置都保持不变。由于大多数振荡器停止,该模式可显著降低功耗。与 Sleep 类似,Standby 也必须配置并启用中断或事件才能唤醒。
3.Snooze Mode|贪睡模式
Snooze 模式允许部分 MCU 外设在 MCU 保持低功耗的同时执行基本任务。许多核心外设以及全部时钟都可以选择在 Snooze 中运行,使其相比 Software Standby 拥有更灵活的低功耗配置能力。要启用 Snooze,需要在“Low Power Mode”选项中选择“启用 Snooze 的 Software Standby 模式”。Snooze 的进入/退出条件可在“Standby Options”中配置。
三种模式的工作条件及支持的中断源
查硬件手册表10.2得下表,展示了三种不同低功耗模式下可以使用的外设,已经翻译成中文:
表10.2 每种低功耗模式的工作条件
| 项目 | 睡眠模式(Sleep mode) | 软件待机模式(Software Standby mode) | 贪睡模式(Snooze mode) |
| 进入条件 | SBYCR.SSBY = 0 时执行 WFI 指令 | SBYCR.SSBY = 1 时执行 WFI 指令 | 在 Software Standby 模式下由 Snooze 请求触发,SNZCR.SNZE = 1 |
| 取消方式(退出方式) | 所有中断;该模式下可用的任何复位 | 表 10.3 所示中断;该模式下可用的任何复位 | 表 10.3 所示中断;该模式下可用的任何复位 |
| 中断取消后的状态 | 程序执行状态(中断处理) | 程序执行状态(中断处理) | 程序执行状态(中断处理) |
| 复位取消后的状态 | 复位状态 | 复位状态 | 复位状态 |
| 主时钟振荡器(Main clock) | 可选 | 停止 | 可选 |
| 子时钟振荡器(Sub-clock) | 可选 | 可选 | 可选 |
| 高速片上振荡器(HOCO) | 可选 | 停止 | 可选 |
| 中速片上振荡器(MOCO) | 可选 | 停止 | 可选 |
| 低速片上振荡器(LOCO) | 可选 | 可选 | 可选 |
| IWDT 专用振荡器 | 可选 | 可选 | 可选 |
| PLL | 可选 | 停止 | 可选 |
| 振荡停止检测功能 | 可选 | 禁止操作 | 禁止操作 |
| 子振荡停止检测功能 | 可选 | 可选 | 可选 |
| 时钟/蜂鸣器输出功能 | 可选 | 停止 | 可选 |
| CPU | 停止(保持寄存器) | 停止(保持寄存器) | 停止(保持寄存器) |
| SRAM | 可选 | 停止(保持) | 可选 |
| Flash 存储器 | 运行 | 停止(保持) | 停止(保持) |
| DMA 控制器(DMAC) | 可选 | 停止(保持) | 禁止操作 |
| DTC 数据传输控制器 | 可选 | 停止(保持) | 可选 |
| USB FS | 可选 | 停止(保持),支持 USB 恢复检测 | 禁止操作(但可检测 USB 恢复) |
| WDT | 可选 | 停止(保持) | 停止(保持) |
| IWDT | 可选 | 可选 | 可选 |
| RTC 实时时钟 | 可选 | 可选 | 可选 |
| AGT(低功耗定时器) | 可选 | 可选 | 可选 |
| 12-bit ADC(ADC12) | 可选 | 停止(保持) | 可选 (需阈值比较) |
| DAC | 可选 | 停止(保持) | 可选 |
| CTSU 电容触摸 | 可选 | 可选 | 可选 |
| DOC(数据操作电路) | 可选 | 停止(保持) | 可选 |
| SCI0(串口) | 可选 | 停止(保持) | 可选(仅异步模式,RXD0 下降沿可进入 Snooze) |
| SCI(1,3,5,9 等) | 可选 | 停止(保持) | 禁止操作 |
| UARTA(UARTA0, UARTA1) | 可选 | 可选 | 可选 |
| I2C(IICO) | 可选 | 可选 | 可选(仅支持唤醒中断) |
| I3C 总线 | 可选 | 可选 | 可选 |
| ELC(事件链接控制器) | 可选 | 停止(保持) | 可选 |
| 模拟比较器(ACMPLP) | 可选 | 可选 | 可选 |
| SLCDC 段式 LCD 驱动 | 可选 | 停止(保持) | 可选 |
| 外部中断 IRQ | 可选 | 可选 | 可选 |
| NMI | 可选 | 可选 | 可选 |
| LVD(低电压检测) | 可选 | 可选 | 可选 |
| 上电复位电路 POR | 运行 | 运行 | 运行 |
| 其他外设模块 | 可选 | 停止(保持) | 禁止操作 |
| I/O 端口 | 运行 | 保持 | 运行 |
表 10.3 退出 Snooze / Software Standby 模式的中断源
中断源名称 |
Software Standby 模式 |
Snooze 模式 |
|---|---|---|
NMI |
是 |
是 |
端口中断 PORT_IRQn(n = 0~15) |
是 |
是 |
LVD(低电压检测) |
||
LVD_LVD1 |
是 |
是 |
LVD_LVD2 |
是 |
是 |
IWDT |
||
IWDT_NMIUNDF(下溢 NMI) |
是 |
是 |
USBFS |
||
USBFS0_USBR |
是 |
是 |
RTC 实时时钟 |
||
RTC_ALM(闹钟中断) |
是 |
是 |
RTC_PRD(周期中断) |
是 |
是 |
UARTA |
||
UARTA0_INTUR |
是 |
是 |
UARTA0_INTURE |
是 |
是 |
UARTA1_INTUR |
是 |
是 |
UARTA1_INTURE |
是 |
是 |
SOSC(子时钟) |
||
SOSC_STOP(停止中断) |
是 |
是 |
I3C |
||
I3C_WU(唤醒中断) |
是 |
是 |
AGT1 |
||
AGT1_AGTI |
是 |
是 |
AGT1_AGTCMAI |
是 |
是 |
AGT1_AGTCMBI |
是 |
是 |
ACMPLP(低功耗模拟比较器) |
||
ACMP_LP0 |
是 |
是 |
IIC0 |
||
IIC0_WUI |
是 |
是 |
ADC12n(n=0) |
||
ADC12n_WCMPM(窗口比较中断:进入窗口) |
否 |
是(需 SELSR0 ) |
ADC12n_WCMPUM(窗口比较中断:超出窗口) |
否 |
是(需 SELSR0 ) |
SCI0(串口) |
||
SCI0_AM(异步模式唤醒) |
否 |
是(需 SELSR0 ) |
SCI0_RXI_OR_ERI |
否 |
是(需 SELSR0 ) |
DTC |
||
DTC_COMPLETE |
否 |
是(需 SELSR0 ) |
DOC(数据操作电路) |
||
DOC_DOPCI |
否 |
是(需 SELSR0 ) |
CTSU 电容触摸 |
||
CTSU_CTSUFN |
否 |
是(需 SELSR0 ) |
DMAC / DTC |
||
DMA_TRANSERR |
否 |
是 |
ICU(中断控制单元) |
||
ICU_SNZCANCEL |
否 |
是 |
总而言之,三者的区别简单概括如下:
| 模式 | 功耗 | 唤醒速度 | 外设运行 | 典型用途 |
|---|---|---|---|---|
| Sleep | 低功耗模式中最高 | 最快 | 大部分外设继续运行 | CPU 空闲时节能 |
| Snooze | 中等 | 适中 | 特定外设允许运行,如 ADC/UART | 自动检测事件并唤醒 |
| Software Standby | 最低功耗 | 最慢 | 几乎全部关闭 | 长时间待机 |
下面是手册中给出的低功耗系统框图:

二、低功耗测试
1.电流测试
按照图示新建低功耗模组:

点击低功耗模组,在Properties里面修改低功耗模式,分别有Sleep Mode,Snooze Mode 和 Software Standby Mode。

fsp_err_t err = R_LPM_Open(&g_lpm0_ctrl, &g_lpm0_cfg);
/* Handle any errors. */
assert(FSP_SUCCESS == err);
R_BSP_SoftwareDelay(1000,BSP_DELAY_UNITS_MILLISECONDS);
err = R_LPM_LowPowerModeEnter(&g_lpm0_ctrl);
assert(FSP_SUCCESS == err);
下面是没有任何外设下测试的结果,测试的是3V3到GND间的电流,由于板上3V3网络连接的串口芯片、电源芯片等的影响,测试效果可能不够准确:
非低功耗模式下的电流:21.7mA

Sleep模式下的电流:17.0mA

snooze模式下的电流:17.8mA

Software Standby模式下的电流:13.0mA
根据官方介绍,RA4L1 mcu采用专有的低功耗技术,在保留所有SRAM的情况下,可提供168 μ A/MHz的有源模式@ 80 MHz,待机电流仅为1.70 μ A。测试结果差别较大,因为不完全是单纯对芯片的测试,在板上直接测量无法排除其他硬件的干扰。即便如此,测试得到的功耗最低也只有40mW,可见该芯片的功耗之低。
三、做一个具有低功耗特性的小秒表
1.时钟:
内部时钟HOCO和LOCO的精度都不高,如果是对始终精度高、并且不太要求低功耗的应用场合,建议使用外部时钟;在这里,为了满足低功耗的需求,我们直接使用HOCO和LOCO作为时钟源;


- 禁用外部晶振和锁相环,使用HOCO作为高频时钟;

2.按键外部中断:
开启定时和停止定时的按键,板上按键为P000和P001;

添加外部中断引脚,P000对应通道6,P001对应通道7,二选一即可;

添加中断模组,设置通道和回调函数名称等属性;
- IRQ6(P000)的配置:
- IRQ7(P001)的配置:
3.SLCDC液晶显示屏的配置
具体可以参考我之前写的文章
- SLCDC模组的配置:
SLCDC管脚的配置:
4.AGT低功耗定时器的配置:
注意到Software Standby模式只响应AGT1通道的中断信号,所以我们必须把时钟通道配置为1;


5.低功耗模式配置
配置为Software Standby模式;

配置低功耗模式下的中断源,若不配置会导致无法进入中断;

6.代码实现
LCD的驱动使用我前面实现的查表函数文件,具体查看文章的代码示例4:
这边可以直接复制到文件目录下,新建一个文件夹BSP,同时在Keil中加入此文件和目录:



文件前面包含该头文件:
#include "segment_LCD_4c_disp.h"
变量声明
定义要用到的LCD位数,因为只实现分钟和秒钟,所以只用到前4位;同时声明一个uint8_t类型的数组,用于临时存放要显示的数字:
#define TRANSFER_LENGTH 4
uint8_t num_array[4]={0};
外部中断响应状态标志位,此处按第一下开始计时,状态量值为true,再按一下取反,状态量值为false:
bool g_external_irq_complete = false;
用于计算进入中断次数的变量:
uint16_t Time_Count = 0;
AGT低功耗定时器中断状态标志位:
volatile bool agt_flag = 0;//AGT延时1s标志位
循环用的变量i:
uint8_t i = 0;
定义一个结构体,存放分钟和秒钟:
typedef struct
{
uint8_t minute;
uint8_t second;
} time_ms_t;
函数定义
用于将定时器计时值转化为分、秒的函数:
time_ms_t convert_time(uint16_t total_seconds)
{
time_ms_t t;
t.minute = total_seconds / 60; // 分钟
t.second = total_seconds % 60; // 秒
return t;
}
用于将定时器计时值转化为显示缓冲区每一位的值的函数,封装了convert_time():
void update_num_array(uint16_t total_seconds)
{
time_ms_t t = convert_time(total_seconds);
uint8_t min_tens = t.minute / 10;
uint8_t min_ones = t.minute % 10;
uint8_t sec_tens = t.second / 10;
uint8_t sec_ones = t.second % 10;
num_array[0] = min_tens;
num_array[1] = min_ones;
num_array[2] = sec_tens;
num_array[3] = sec_ones;
}
外部中断回调函数,这边根据各自配置的IRQ通道和回调函数编写,IRQ6和IRQ7只需要选一种:
//外部中断回调函数,标志位逻辑不是置1/清零,而是取反:用于主函数中判断,最开始是0,第一次按下的时候是1,当按下第二次的时候取反为0,循环往复,起到一个第一次按下开始计时,第二次按下停止计时、显示数据的效果
void external_irq6_callback (external_irq_callback_args_t * p_args)
{
(void) p_args;
g_external_irq_complete = !g_external_irq_complete;
}
void external_irq7_callback (external_irq_callback_args_t * p_args)
{
(void) p_args;
g_external_irq_complete = !g_external_irq_complete;
}
AGT回调函数,事件标志是TIMER_EVENT_CYCLE_END,当然此处可不判断,当有多种AGT中断事件时可以加入;
//AGT回调函数
void agt_callback(timer_callback_args_t *p_args)
{
//AGT定时中断的标志是TIMER_EVENT_CYCLE_END
if(p_args->event == TIMER_EVENT_CYCLE_END)
agt_flag=1;
}
hal_entry()主函数
主循环前的初始化:
fsp_err_t err;
//打开外部中断
err = R_ICU_ExternalIrqOpen(&g_external_irq6_ctrl, &g_external_irq6_cfg);
assert(FSP_SUCCESS == err);
err = R_ICU_ExternalIrqOpen(&g_external_irq7_ctrl, &g_external_irq7_cfg);
assert(FSP_SUCCESS == err);
err = R_ICU_ExternalIrqEnable(&g_external_irq6_ctrl);
assert(FSP_SUCCESS == err);
err = R_ICU_ExternalIrqEnable(&g_external_irq7_ctrl);
assert(FSP_SUCCESS == err);
//打开AGT
err = R_AGT_Open(&g_timer1_ctrl, &g_timer1_cfg);
assert(FSP_SUCCESS == err);
err = R_AGT_Enable(&g_timer1_ctrl);
assert(FSP_SUCCESS == err);
//打开SLCDC
err = R_SLCDC_Open(&g_slcdc0_ctrl, &g_slcdc0_cfg);
assert(FSP_SUCCESS == err);
err = R_SLCDC_Start(&g_slcdc0_ctrl);
assert(FSP_SUCCESS == err);
//显示全0
for(i=1;i<=TRANSFER_LENGTH;i++){
R_SLCDC_Modify(&g_slcdc0_ctrl,get_seg_pos(i)[0],get_seg_num(num_array[i-1])[0],0xFF);
R_SLCDC_Modify(&g_slcdc0_ctrl,get_seg_pos(i)[1],get_seg_num(num_array[i-1])[1],0xFF);
}
//打开低功耗模式
err = R_LPM_Open(&g_lpm0_ctrl, &g_lpm0_cfg);
assert(FSP_SUCCESS == err);
//err = R_LPM_LowPowerRecondiv(&g_lpm0_ctrl, &g_lpm0_cfg);
// assert(FSP_SUCCESS == err);
err = R_LPM_LowPowerModeEnter(&g_lpm0_ctrl);
assert(FSP_SUCCESS == err);
主循环:
while(1){
if(g_external_irq_complete == true)//判断是否是第一次/奇数次按下,标志位不清零,当按下第二次的时候清零
{
if (Time_Count == 0)//判断是否是刚开始按下,还未计时
{
err = R_AGT_Start(&g_timer1_ctrl);//打开定时器
assert(FSP_SUCCESS == err);
}
if(agt_flag == 1)//判断是定时器中断
{
agt_flag = 0;
//转换Time_Count 并显示
update_num_array(Time_Count);
for(i=1;i<=TRANSFER_LENGTH;i++)
{
R_SLCDC_Modify(&g_slcdc0_ctrl,
get_seg_pos(i)[0],
get_seg_num(num_array[i-1])[0],
0xFF);
R_SLCDC_Modify(&g_slcdc0_ctrl,
get_seg_pos(i)[1],
get_seg_num(num_array[i-1])[1],
0xFF);
}
//显示点并闪烁,一秒钟切换一次
//也可以把定时值设置为500ms,一秒闪烁一次,其他地方按照相应的逻辑改动
switch (Time_Count%2){
case 0:
R_SLCDC_Modify(&g_slcdc0_ctrl,16,0x00,0x08);
break;
case 1:
R_SLCDC_Modify(&g_slcdc0_ctrl,16,0x08,0x08);
break;
}
Time_Count ++; //增加计数值
}
}
else{ //第二次/双数次按下,表示停止
err = R_AGT_Stop(&g_timer1_ctrl); //停止定时器
assert(FSP_SUCCESS == err);
R_SLCDC_Modify(&g_slcdc0_ctrl,16,0x08,0x08);
//显示点
Time_Count = 0;
}
err = R_LPM_LowPowerModeEnter(&g_lpm0_ctrl);
assert(FSP_SUCCESS == err);
}
实验结果:
可以看到板上电流在13.1mA左右,如果不是低功耗模式,电流应该在20mA以上。
⚠问题附录:
Software Standby低功耗模式下的程序下载之后,想要再次下载新的程序,J-Link会报错?
因为Software Standby模式会禁用所有GPIO的功能,所以要再次下载程序,需要把BOOT跳线帽从原本连接3V3改到连接GND,进入BootLoader模式,按下RESET按键重置一下,并使用串口下载。

串口下载需要使用Renesas Flash Programmer :
点击后进入发布页面,下载适合自己系统的压缩包;

安装后打开软件,新建下载工程,工具配置为COM口:

切换到Operation Settings,Command设置为Erase就好,我暂时只用串口方式来擦除闪存:

将串口连接到电脑,BOOT连接到GND,按下RESET;
电脑上Renesas Flash Programmer切换回Operation页面,点击Start,等待擦除成功,后续即可重新用J-Link下载程序。




全部评论