40. 电源管理——低功耗模式

本章参考资料:《RA6M5 Group User’s Manual:Hardware》第10章 “Low Power Modes”。

40.1. RA芯片电源管理简介

电源对电子设备的重要性不言而喻,它是保证系统稳定运行的基础,而保证系统能稳定运行后,又有低功耗的要求。 在很多应用场合中都对电子设备的功耗要求非常苛刻,如某些传感器信息采集设备,仅靠小型的电池提供电源,要求工作长达数年之久, 且期间不需要任何维护;由于智慧穿戴设备的小型化要求,电池体积不能太大导致容量也比较小,所以也很有必要从控制功耗入手, 提高设备的续行时间。 因此,RA芯片有专门管理设备的运行模式,确保系统正常运行,并尽量降低器件的功耗。

40.1.1. 降低功耗的方式

RA6M5 具有多种降低功耗的功能,如设置时钟分频器、EBCLK(External Bus Clock)输出控制、 停止模块、在正常模式下选择电源控制模式以及转换到低功耗模式等, 下表是对于各种低功耗功能的描述。

低功耗功能描述

低功耗功能

描述

通过切换时钟信号降低功耗

系统时钟(ICLK),外设时钟(PCLK),外部总线时钟(EBCLK),

Flash接口时钟(FCLK)的分频比可独立选择

EBCLK输出控制

可以选择时钟输出或高电平输出

停止模块

各外设模块可以独立停止

低功耗模式

根据工作需求选择4种低功耗模式:

睡眠模式(Sleep Mode)

软件待机模式(Software Standby Mode)

贪睡模式(Snooze Mode)

深度软件待机模式(Deep Software Standby Mode)

功率控制功能

根据工作频率选择合适的工作功率控制模式,可以在正常和睡眠模式下降低功耗

三种可选的功率控制模式:

高速模式(High-speed Mode)

低速模式(Low-speed Mode)

子时钟速度模式(Subosc-speed Mode)

40.1.2. 通过切换时钟信号降低功耗

可以通过修改SCKDIVCR(System Clock Division Control Register)寄存器修改时钟频率。寄存器如下图所示, 根据需求,在对应的位段设置分频值即可。设分频前时钟频率为CLKIN,分频后时钟频率为CLKOUT, 分频值为div。则CLKOUT=CLKIN/2div。例如,将SCKDIVCR寄存器的ICK修改为1, 则ICLK = PLL/21= 100MHz(RA6M5中ICLK默认时钟源是PLL,PLL = 200MHz)。

../../_images/SCKDIVCR.png

图片来源:《RA6M5 Group User’s Manual:Hardware》第10章 “Low Power Modes”,第167页。

40.1.3. 停止模块功能

停止模块功能可以停止各外设的时钟供应,通过设置MSTPCRn(Module Stop Control Register)(n=A~E)寄存器, 可以单独停止外设的时钟供应,此时CPU会继续运行,在寄存器对应位置1则停止对应的外设的时钟供应, 置0则恢复时钟供应,具体对应关系可以在《RA6M5 Gruop User’s Manual》的第10章的寄存器描述查看。 例如,要停止SCI4,则在MSTPCRB的第27位置1。当外设对应的位为1时,禁止访问该外设。

选择PLL作为时钟源时,每次只能修改一个位,修改位之后,需要等待至少250ns才能进行后续处理。

40.1.4. 功率控制模式

根据工作频率选择合适的功率控制模式,确保频率范围等工作条件在正常范围内。 下图为不同功率模式下可用的时钟源。

../../_images/power_control_mode.png

图片来源:《RA6M5 Group User’s Manual:Hardware》第10章 “Low Power Modes”,第257页。

40.2. 低功耗模式

40.2.1. 模式切换框图

图38-1

图片来源:《RA6M5 Group User’s Manual:Hardware》第10章 “Low Power Modes”,第224页。

40.2.2. 睡眠模式(Sleep Mode)

上电时,默认的低功耗模式即睡眠模式。睡眠模式是最方便的低功耗模式,它不需要任何额外的配置,只需要配置好用于唤醒的中断源。 在睡眠模式下,SRAM、处理寄存器和外设状态都会被保留,片上外设可以继续工作,进入睡眠模式以及从睡眠模式唤醒所消耗的时间都是极少的。 任何中断或者复位都会将MCU从睡眠模式下唤醒,并开始处理中断,这也包括Systick系统计时器,因此读者如果用到了RTOS, 进入睡眠模式前需要暂停Systick。

40.2.3. 软件待机模式(Software Standby Mode)

在软件待机模式下,CPU以及大部分片上外设功能和所有内部晶振都停止工作。但是会保留CPU内部寄存器和SRAM数据的内容, 片上外设以及IO口的状态。软件待机模式可以显著降低功耗,因为大多数振荡器在这种模式下停止。 与睡眠模式一样,待机模式需要配置一个中断,并使用它来唤醒MCU。退出软件待机模式时,所有内部晶振都会被启动, 待所有晶振稳定后,MCU返回正常模式。

40.2.4. 贪睡模式(Snooze Mode)

贪睡模式与软件待机模式相似,但是在贪睡模式下,可以运行很多核心外设和所有时钟,可以执行一些比较简单的任务,与软件待机模式相比, 贪睡模式可以实现更加灵活的低功耗配置。

40.2.5. 深度软件待机模式(Deep Software Standby Mode)

深度软件待机模式类似于设备关机,CPU状态、SRAM存储的数据以及大部分外设寄存器的数据都会丢失,只有少量外设, 如RTC和AGTn(n=0~3)可以运行。因此MCU无法保存上下文,只能以重置的状态唤醒。

下图是用于唤醒软件待机模式,贪睡模式,深度软件待机模式的的各中断源是否支持的对比。

../../_images/standbymode_wake_source.png

图片来源:《RA6M5 Group User’s Manual:Hardware》第10章 “Low Power Modes”,第223页。

40.3. 低功耗模式需要使用的函数及指令

40.3.1. WFI和WFE指令

进入低功耗模式都需要调用WFI(Wait for Interrupt)和WFE(Wait for Event)指令,实际上就是一行汇编指令, 在cmsis_gcc.h中被封装为函数。只使用WFI指令可以直接进入睡眠模式。

WFI和WFE指令
/**
  *@brief   Wait For Interrupt
  *@details Wait For Interrupt is a hint instruction that suspends execution until one of a number of events occurs.
  */
#define __WFI()               __ASM volatile ("wfi":::"memory")


/**
  *@brief   Wait For Event
  *@details Wait For Event is a hint instruction that permits the processor to enter
  *        a low-power state until one of a number of events occurs.
  */
#define __WFE()               __ASM volatile ("wfe":::"memory")

40.3.2. FSP库函数

低功耗模式的库函数很少,功能也很简单,在下表中列举。

低功耗模式库函数

函数

描述

R_LPM_Open

执行必要的初始化

R_LPM_LowPowerModeEnter

进入低功耗模式,并在唤醒后恢复MCU功能

R_LPM_Close

关闭LPM实例

R_LPM_LowPowerReconfigure

对低功耗模式进行配置

R_LPM_IoKeepClear

从深度软件待机模式唤醒后清除IOkeep位

40.4. 实验1:睡眠模式实验

在本次实验中,开发板将会执行一个流水灯任务,执行完毕后控制RA6M5进入睡眠状态,随后用任意的中断将其唤醒,再执行一次流水灯任务, 再次控制其进入睡眠状态,如此往复。

40.4.1. 硬件设计

将按键Key1以及串口配置为中断源。

40.4.2. 软件设计

40.4.2.1. 正常模式与睡眠模式的切换框图

../../_images/sleep_to_normal.png

在SBYCR(Standby Control Register)寄存器的SSBY(Software Standby Mode Select)位等于0的时候, 只需要一个WFI指令就可以进入睡眠模式,任何中断都能将MCU从睡眠模式切换到正常模式。

40.4.2.2. 新建工程

对于 e2 studio 开发环境:

拷贝一份我们之前的 e2s 工程模板 “05_Template”, 然后将工程文件夹重命名为 “38_LPM_Sleep_Mode”, 最后再将它导入到我们的 e2 studio 工作空间中。

对于 Keil 开发环境:

拷贝一份我们之前的 Keil 工程模板 “06_Template”, 然后将工程文件夹重命名为 “38_LPM_Sleep_Mode”, 并进入该文件夹里面双击 Keil 工程文件,打开该工程。

工程新建好之后,我们需要添加LED、按键与串口这三个模块就可以进行我们的实验, LED,按键与串口的配置读者可以翻看本教程第10章,16章和第19章,这里不再赘述, 添加好的工程文件结构如下。

文件结构
38_LPM_Sleep_Mode
├─ ......
└─ src
   ├─ led
   │  ├─ bsp_led.c
   │  └─ bsp_led.h
   ├─ debug_uart
   │  ├─ bsp_debug_uart.c
   │  └─ bsp_debug_uart.h
   ├─ key
   │  ├─ key.c
   │  └─ key.h
   └─ hal_entry.c

40.4.2.3. FSP配置

在Stack中如下图加入LPM模块。

../../_images/lpm_config_1.png

在刚刚加入的LPM模块中如下图进行配置,配置极其简单,需要修改的只有Name和Low Power Mode。

../../_images/lpm_config_2.png
表38-1:LPM属性描述

属性

描述

Standby Limit

待机限制,如果配置为Enabled,只能使用

R_LPM_LowPowerModeEnter进入待机模式。

Name

模块名字,根据需求取名即可。

Low Power Mode

低功耗模式选择,可选睡眠模式(Sleep mode),

软件待机模式(Software standby mode),

贪睡模式(Snooze mode),

深度软件待机模式(Software standby mode)

Output port state in standby and deep standby

选择待机期间输出引脚的状态。适用于地址输出、

数据输出等总线控制输出引脚。

选择No change则不改变输出引脚状态,

选择High impedance state则输出引脚为高阻态

40.4.2.4. LED流水灯任务

代码清单38-1:LED流水灯任务
void LED_Task(void)
{
   /*流水灯任务*/
   LED1_ON;
   R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS);
   LED2_ON;
   R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS);
   LED3_ON;
   R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS);

   LED1_OFF;
   R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS);
   LED2_OFF;
   R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS);
   LED3_OFF;

   /*睡眠前打印*/
   printf("MCU进入睡眠模式\r\n");

   /*执行完流水灯任务,进入睡眠模式*/
   R_LPM_LowPowerModeEnter(sleep.p_ctrl);

   /*被唤醒后打印*/
   printf("MCU已被唤醒\r\n");
}

这里LED灯依次亮起,随后依次熄灭,全部熄灭后进入睡眠模式,进入睡眠模式前用串口打印信息。

40.4.2.5. 按键中断服务函数

代码清单38-2:按键中断服务函数
/*按键1中断回调函数,用于唤醒待机模式*/
void key1_irq_callback(external_irq_callback_args_t *p_args)
{
   /*防止编译器产生没有使用形参的警告*/
  FSP_PARAMETER_NOT_USED(p_args);
}

在本实验中,按键仅仅用于唤醒MCU,因此不需要在回调函数中放任何代码,只需添加一行“(void)p_args”使用一次回调函数的参数, 避免编译器警告即可。

40.4.2.6. hal_entry函数

hal_entry函数非常简单,先初始化外设并打开LPM,随后在while(1)中执行LED_Task函数。

代码清单38-3:hal_entry函数
void hal_entry(void)
{
   /* TODO: add your own code here */
   Debug_UART4_Init(); //初始化debug串口
   IRQ_Init(); //按键中断初始化
   R_BSP_PinAccessEnable(); //启用对PFS寄存器的访问,因为后面写IO口都用BSP内联函数
   R_LPM_Open(sleep.p_ctrl, sleep.p_cfg); //打开LPM

   while(1)
   {
       LED_Task(); //流水灯任务
   }
 #if BSP_TZ_SECURE_BUILD
   /* Enter non-secure code */
   R_BSP_NonSecureEnter();
 #endif
}

40.4.3. 下载验证

下载程序后,用Type-C线连接开发板“USB TO UART”接口跟电脑。本次实验配置了开发板Key1按键的外部中断和串口UART中断, 流水灯熄灭即表示MCU已进入睡眠模式。在睡眠模式下,MCU可以被任何中断唤醒,此时按下按键1或者向串口发送任何信息, 即可产生中断并唤醒MCU。

../../_images/verification_11.png

40.5. 实验2:软件待机模式

软件待机模式与睡眠模式相似,但是此时大部分外设是停止工作的,需要指定用于唤醒MCU的中断源,而且只有少量中断源可以用于唤醒。 本次实验与实验1基本相同,开发板将会执行一个流水灯任务,执行完毕后进入软件待机模式,随后用我们指定的中断将其唤醒, 再执行一次流水灯任务,进入软件待机模式,如此往复。

40.5.1. 硬件设计

40.5.1.1. 正常模式与软件待机模式的切换框图

../../_images/SWSB_to_normal.png

SBYCR寄存器的SSBY位为1的时候,使用WFI指令即可进入软件待机模式,并通过指定的中断源进行唤醒。

40.5.2. 软件设计

40.5.2.1. 新建工程

由于我们实验2与实验1基本一致,所以我们可以直接利用刚刚写好的工程进行修改。

对于 e2 studio 开发环境:

拷贝一份我们刚刚的 e2s 工程 “38_LPM_Sleep_Mode”, 然后将工程文件夹重命名为 “38_LPM_Software_Standby_Mode”, 最后再将它导入到我们的 e2 studio 工作空间中。

对于 Keil 开发环境:

拷贝一份我们刚刚的 Keil 工程 “38_LPM_Sleep_Mode”, 然后将工程文件夹重命名为 “38_LPM_Software_Standby_Mode”, 并进入该文件夹里面双击 Keil 工程文件,打开该工程。

40.5.2.2. FSP配置

如下图所示。主要差别在LPM的属性窗口的配置,只需要配置低功耗的模式为 “Software Standby mode”, 为了体现软件待机模式与睡眠模式的区别,这里唤醒源只选择使用IRQ9,即Key1的外部中断,与上一节实验所使用中断相同即可,省去了重新配置的麻烦。

../../_images/lpm_config_2_1.png

下面是对于Standby Options的属性的具体讲解。

Standby Options 属性描述

属性

描述

Wake Sources

用于设置唤醒源,与睡眠模式不同,软件待机模式下只能使用少量中断唤醒

Snooze End Sources

贪睡结束源,选择用于将贪睡模式切换至软件待机模式的事件

Snooze Request Source

贪睡请求源,选择用于将软件待机模式切换至贪睡模式的事件

DTC state in Snooze Mode

是否使用DTC将MCU从贪睡模式切换至软件待机模式

Snooze Cancel Source

选择用于将MCU从贪睡模式下唤醒的事件

40.5.3. 代码

本实验代码与实验一不同的地方只有“printf”打印信息与模块的名称需要修改,其他部分完全相同即可。

40.5.4. 下载验证

下载程序后,用Type-C线连接开发板“USB TO UART”接口跟电脑。流水灯熄灭即表示MCU已进入软件待机模式。 MCU进入软件待机模式前和唤醒后都会通过串口向调试助手发送对应信息,在本次实验中,我们配置的唤醒源为IRQ9,因此只有按下用Key1可以唤醒MCU, 我们可以尝试在待机模式下使用其它非指定中断,可以看到MCU是没有任何反应的。

../../_images/verification_2.png

注解

在待机模式下,我们是无法向开发板烧写程序的, 需要按RES复位或者通过设定的唤醒源将MCU唤醒才能继续烧写程序。注意不要上电复位或者唤醒后瞬间进入软件待机模式, 因为这时候只能使用串口烧录了。

40.6. 实验3:贪睡模式

在软件待机模式下,大部分时钟是关闭的,只有RTC,AGT,IWGT这些低功耗时钟可以运行。 但贪睡模式下,所有时钟都能运行,这也包括了GPT,因此本次实验为了体现贪睡模式的特点, 并使用GPT产生的PWM来控制开发板的LED亮度。

40.6.1. 硬件设计

40.6.1.1. 软件待机模式与贪睡模式的转变框图

../../_images/SW_to_Snooze.png

进入贪睡模式前需要进入软件待机模式,在SNZCR(Snooze Control Register)寄存器的SNZE(Snooze Mode Enable)位为1的时候, 再通过贪睡请求(Snooze requests)进入贪睡模式。同时也能通过贪睡结束(Snooze end)下再次进入软件待机模式, 这两个模式共用唤醒源(Wake Source)进行唤醒。

40.6.2. 软件设计

40.6.2.1. 新建工程

对于 e2 studio 开发环境:

拷贝一份我们刚刚的 e2s 工程 “38_LPM_Sleep_Mode”, 然后将工程文件夹重命名为 “38_LPM_Snooze_Mode”, 最后再将它导入到我们的 e2 studio 工作空间中。

对于 Keil 开发环境:

拷贝一份我们刚刚的 Keil 工程 “38_LPM_Sleep_Mode”, 然后将工程文件夹重命名为 “38_LPM_Snooze_Mode”, 并进入该文件夹里面双击 Keil 工程文件,打开该工程。

工程新建好之后,我们还需要添加GPT用来进行实验,关于GPT的配置读者可以翻看本书第27章查阅, 在例程中为了方便我们不再额外添加GPT模块函数,而是直接放到LED中使用, 只需要让PWM占空比大一些,能通过视觉看出差异即可,这里不进行赘述。

40.6.2.2. FSP配置

如图,将Low Power Mode修改为 Snooze mode,名字按自己需求取即可。Wake Sources与实验2相同,选择IRQ9。 Snooze Request Source选择IRQ10,其他按照默认设置即可。

../../_images/lpm_config_3_1.png

40.6.2.3. LED_Task流水灯任务

代码清单38-4:LED_Task流水灯任务
void LED_Task()
{
    /*流水灯任务*/
    LED2_ON;
    R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS);
    LED3_ON;
    R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS);

    LED2_OFF;
    R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS);
    LED3_OFF;

    /*贪睡前打印*/
    printf("MCU进入贪睡模式状态\r\n");

    /*执行完流水灯任务,进入贪睡模式*/
    R_LPM_LowPowerModeEnter(snooze.p_ctrl);

    /*被唤醒后打印*/
    printf("MCU被唤醒成功\r\n");
}

与实验1和2不同,LED_R,即红色LED需要用于表现PWM输出,因此这里的流水灯只有蓝色LED和绿色LED。 注意,在任务流水灯结束后进入的是软件待机模式,需要通过贪睡请求(Snooze Request)才能进入贪睡模式, 也就是我们刚刚设置的的Key2按键。

40.6.2.4. hal_entry函数

代码清单38-5:hal_entry函数
void hal_entry(void)
{
    /* TODO: add your own code here */
    Debug_UART4_Init(); //初始化debug串口
    Key_Init(); //初始化按键
    R_BSP_PinAccessEnable(); //启用对PFS寄存器的访问,因为后面写IO口都用BSP内联函数
    R_LPM_Open(snooze.p_ctrl, snooze.p_cfg); //打开LPM
    R_GPT_Open(gpt_pwm.p_ctrl, gpt_pwm.p_cfg);  //打开GPT计时器
    R_GPT_Start(gpt_pwm.p_ctrl); //启动GPT计时器
    while(1)
    {
        GPT_LED1_PWM_ON_Snooze_Mode(); //LED1呼吸灯任务
        LED_Task(); //led任务
    }
#if BSP_TZ_SECURE_BUILD
    /* Enter non-secure code */
    R_BSP_NonSecureEnter();
#endif
}

hal_entry函数中初始化外设并打开LPM后,在while(1)中执行LED任务。

40.6.3. 下载验证

下载程序后,需要断电再供电重启,程序即可正常运行。正确连接硬件并供电后,可以看见,流水灯任务执行完毕后, 用于表现PWM输出的红色LED熄灭或者和开发板的红色电源LED灯一样亮(这取决与进入低功耗模式时红色LED的亮灭状态), 这是因为此时处于软件待机模式,GPT时钟是停止工作的,自然也不会产生PWM。 按下按键2后,MCU收到贪睡请求,进入贪睡模式,此时GPT恢复工作,因此红色LED也恢复运行状态时的原本亮度。

../../_images/verification_3.png

注解

贪睡模式下也无法对MCU进行烧写,如果发现无法烧写程序,可以试着按一下复位键或者将MCU唤醒后再进行烧写。

40.7. 实验4:深度软件待机模式

最后我们来学习深度软件待机模式,这个模式的耗电是四种低功耗模式中最低的,能工作的外设和时钟也是最少的。

40.7.1. 硬件设计

所需硬件较少,两个按键,一个LED和一个串口即可。

40.7.2. 软件设计

40.7.2.1. 普通模式与深度软件待机模式的转换框图

../../_images/normal_to_DSWSB.png

如上图,进入深度软件待机模式前会先进入软件待机模式, 若DPSBYCR(Deep Standby Control Register)寄存器的DPSBY(Deep Software Standby)位为1, 则进入深度软件待机模式。在深度软件待机模式下,CPU,不在备份域的寄存器和SRAM的数据都会丢失, 因此无法在进入该模式的地方唤醒,只能通过内部复位让程序从头开始运行。

40.7.2.2. 新建工程

对于 e2 studio 开发环境:

拷贝一份我们刚刚的 e2s 工程 “38_LPM_Sleep_Mode”, 然后将工程文件夹重命名为 “38_LPM_Deep_SW_Standby_Mode”, 最后再将它导入到我们的 e2 studio 工作空间中。

对于 Keil 开发环境:

拷贝一份我们刚刚的 Keil 工程 “38_LPM_Sleep_Mode”, 然后将工程文件夹重命名为 “38_LPM_Deep_SW_Standby_Mode”, 并进入该文件夹里面双击 Keil 工程文件,打开该工程。

40.7.2.3. FSP配置

将Low Power Mode配置为 Deep Software Standby mode,在Deep Standby Options中配置唤醒源, 因为我们开发板的按钮是按下后接地,默认唤醒边沿就是下降沿,故不需要配置唤醒边沿,其他配置按默认即可。

../../_images/lpm_config_4_1.png

下表为Deep Standby Options的属性描述

Deep Standby Options 属性描述

属性

描述

Cancel Sources

选择用于将MCU从深度软件待机模式唤醒的唤醒源

Cancel Edges

用于选择唤醒源的边沿,默认为下降沿唤醒,这里可以将唤醒源的边沿修改为上升沿

I/O Port Retention

选择是否在进入深度软件待机模式时,保留IO口状态

Power-Supply-Control

选择在深度软件待机模式下,内部的供电状态

40.7.2.4. hal_entry函数

代码清单38-6:hal_entry函数
void hal_entry(void)
{
    /* TODO: add your own code here */
    Debug_UART4_Init (); //初始化debug串口
    IRQ_Init (); //按键中断初始化
    R_BSP_PinAccessEnable (); //启用对PFS寄存器的访问,因为后面写IO口都用BSP内联函数

    /*判断是否在深度软件待机模式下被唤醒*/
    if (1U == R_SYSTEM->RSTSR0_b.DPSRSTF)
    {
        /*从深度软件待机模式唤醒后清除IOkeep位,以允许使用IO端口*/
        R_LPM_IoKeepClear (NULL);

        /*清除唤醒标志位*/
        R_SYSTEM->RSTSR0_b.DPSRSTF = 0;

        /*判断唤醒源是IRQ-9还是IRQ-10,并将判断结果打印出来*/
        if (R_SYSTEM->DPSIFR1 >> 1 & 0x01U)
        {
            printf("MCU唤醒源为IRQ-9\r\n");
        }
        else if (R_SYSTEM->DPSIFR1 >> 2 & 0x01U)
        {
            printf("MCU唤醒源为IRQ-10\r\n");
        }
    }

    LED_Flicker (10); //LED闪烁10次

    while (1)
    {
        if (key_flag == true)
        {
            /*标志位置0,防止程序不断进入if的代码块里面*/
            key_flag = false;

            /*LED闪烁2次*/
            LED_Flicker (2);

            /*进入低功耗模式前打印*/
            printf("MCU进入深度软件待机状态\r\n");

            /*打开LPM,进入深度软件待机状态*/
            R_LPM_Open (deep_sw_standby.p_ctrl, deep_sw_standby.p_cfg);
            R_LPM_LowPowerModeEnter (deep_sw_standby.p_ctrl);

            /*唤醒后打印,但是在进入深度软件待机模式后不能保存上下文,
            故这里正常来说无法被执行,如果执行了则说明进入的是睡眠模式,
            也就是程序出现了问题,一般刚烧写程序会导致这个问题,重新上电即可恢复正常*/
            printf("MCU被唤醒(出现本消息说明程序出错)\r\n");
        }
    }
#if BSP_TZ_SECURE_BUILD
    /* Enter non-secure code */
    R_BSP_NonSecureEnter();
#endif
}

hal_entry函数首先初始化外设, 并检测RSTSR0(Reset Status Register)寄存器的DPSRSTF(Deep Software Standby Reset Flag)位, 判断MCU是否从深度软件待机模式中唤醒,若该位为1,则清除IOkeep位和DPSRSTF位,允许使用IO口,并通过串口打印唤醒源。 在进入while(1)循环前再次让LED闪烁。注意,LED闪烁和打印前必须清除IOkeep位,否则IO口无法工作。

在while(1)中,通过key_flag标志位判断按钮是否按下,按下后,LED闪烁两次,通过串口打印信息并进入深度软件待机模式。

40.7.2.5. 按键外部中断回调函数

代码清单38-7:按键外部中断回调函数
volatile _Bool key_flag = false; //设置标志位

/*按键1中断回调,用于进入深度睡眠或唤醒*/
void key1_irq_callback(external_irq_callback_args_t *p_args)
{
    /*防止编译器产生没有使用形参的警告*/
    FSP_PARAMETER_NOT_USED(p_args);
    key_flag = true;
}

/*按键2中断回调,用于唤醒*/
void key2_irq_callback(external_irq_callback_args_t *p_args)
{
    /*防止编译器产生没有使用形参的警告*/
    FSP_PARAMETER_NOT_USED(p_args);
}

key_flag是一个volatile修饰的全局变量,用于hal_entry函数判断按键是否被按下。

40.7.2.6. LED闪烁函数

代码清单38-8:LED闪烁函数
void LED_Flicker(int num)
{
    /*LED1闪烁‘num’次*/
    for (int i = 0; i < num*2; i++)
    {
        LED1_TOGGLE;
        R_BSP_SoftwareDelay (50, BSP_DELAY_UNITS_MILLISECONDS);
    }
}

40.7.3. 下载验证

用Type-C线连接开发板“USB TO UART”接口跟电脑,烧写代码后,给开发板重新供电,并打开串口调试助手。 红色LED连续闪烁10次后,按下按键1,LED闪烁两次,并进入深度软件待机模式。然后再按下按钮2或1, 红色LED再次连续10次闪烁,并在串口调试助手中看见唤醒信息,说明唤醒成功。

../../_images/verification_4.png

注解

深度软件待机模式下也是无法烧写代码的,可以参考上述方法烧写。