以RISC-V MCU CH32V307为例。
1. 待机模式简介
待机模式(Standby),芯片的运行功耗最低。
该模式下,内核处于深睡眠模式(Sleep Deep), 电压调节器进入停机模式。
待机模式唤醒后,MCU将执行电源复位。
该模式下的唤醒时间约 8.9ms (LDO稳定时间 + HSI RC时钟唤醒 + 代码加载时间)。
此模式下的睡眠电流约为1~2uA。
可以通过配置PWR_CTLR寄存器控制该模式下部分SRAM不掉电,使用变量数据保持,睡眠电流增加约 1.5~2uA 配置方式如下:
| 位 | 控制位名称 | 说明 | 复位值 |
|---|---|---|---|
| 19 | R30K_VBATEN | VBAT供电时,Standby模式下前 30K RAM是否带电: 1:带电 0:不带电 | 0 |
| 18 | R2K_VBATE | VBAT供电时,Standby模式下前 30K RAM是否带电: 1:带电 0:不带电 | 0 |
| 17 | R30K_STYEN | Standby模式下前 30K RAM是否带电: 1:带电 0:不带电 | 0 |
| 16 | R2K_STYEN | Standby模式下前 2K RAM是否带电: 1:带电 0:不带电 | 0 |
2.进入待机模式
进入待机模式步骤如下:
配置 PFIC_SCTLR 系统控制寄存器控制位
SLEEPDEEP = 1配置 PWR_CTLR 电源控制寄存器控制位
PDDS = 1清除电源控制/状态寄存器
PWR_SCR中的WUF位,PWR_CTLR的CWUF位写1清零执行
WFI或WFE指令
3. 退出待机模式
退出待机模式步骤如下:
WKUP引脚上升沿
RTC闹钟事件的上升沿
NRST引脚上的外部复位
IWDG复位
唤醒事件,配置一个外部EXTI线为事件模式,当CPU从WFE唤醒后,因为对应事件线的挂起位没有被置位,不必清除相应外设的中断挂起位或PFIC中断通道挂起位。
:::tip
WKUP引脚上升沿唤醒和RTC闹钟唤醒事件会置电源控制/状态寄存器
PWR_SCR中的WUF位,此寄存器从待机模式唤醒后保持不变,通过PWR_CTLR的CWUF位写1 清除WUF标志。
:::
4. 参考代码
参考代码 - 外部事件唤醒和WKUP引脚唤醒,SRAM不保持
#include "debug.h"
void wakeup_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure = {0};
EXTI_InitTypeDef EXTI_InitStructure = {0};
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA |RCC_APB2Periph_AFIO , ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* GPIOA.3 ----> EXTI_Line3 */
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource3);
EXTI_InitStructure.EXTI_Line = EXTI_Line3;
/* WFI - EXTI_Mode_Interrupt, WFE - EXTI_Mode_Event*/
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Event;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
}
int main(void)
{
/* Configure unused GPIO as IPD to reduce power consumption */
GPIO_InitTypeDef GPIO_InitStructure = {0};
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|
RCC_APB2Periph_GPIOC|RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOE, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_All;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_Init(GPIOE, &GPIO_InitStructure);
/***************************************/
Delay_Init();
USART_Printf_Init(256000);
printf("ch32v307 standby test\r\n");
Delay_Ms(2000);
/* wakeup event, PA3 EXTI trigger falling */
wakeup_init();
/* set PWR register, need enable rcc of pwr */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
/* WKUP or RTC wakeup event flag, clear by set PWR_CTLR CWUF */
printf("PWR_CSR WUF = %ld \r\n",PWR->CSR & 0x1);
if(PWR->CSR & 0x1)
{
PWR->CTLR |= (1<<2); /* clear wakeup flag */
}
/* enable WKUP, PA0 */
PWR_WakeUpPinCmd(ENABLE);
/* standby mode, mcu will reset after wakeup event */
PWR_EnterSTANDBYMode();
printf("wakeup\r\n");
while(1)
{
printf("run in main loop\r\n");
Delay_Ms(1000);
}
}参考代码 - SRAM保持,WKUP 上升沿唤醒
#include "debug.h"
void ram_write_test(void)
{
uint32_t myAddr=0x20000000+10*1024;
uint32_t myAddr2=0x20000000+1*1024+512;
uint32_t i=0;
for(i=0;i<10;i++)//Write 30K RAM
{
*(uint32_t volatile *)(myAddr+(i<<2)) = 0x12345678;
}
for(i=0;i<10;i++)//Check 30K RAM
{
if((*(uint32_t volatile *)(myAddr+(i<<2))) != 0x12345678)
{
printf("Write 30K RAM Error!\r\n");
break;
}
}
printf("Write 30K RAM Successfully!\r\n");
for(i=0;i<10;i++)//Write 2K RAM
{
*(uint32_t volatile *)(myAddr2+(i<<2)) = 0x12345678;
}
for(i=0;i<10;i++)//Check 2K RAM
{
if((*(uint32_t volatile *)(myAddr2+(i<<2))) != 0x12345678)
{
printf("Write 2K RAM Error!\r\n");
break;
}
}
printf("Write 2K RAM Successfully!\r\n");
}
void ram_read_test(void)
{
uint32_t myAddr=0x20000000+10*1024;
uint32_t myAddr2=0x20000000+1*1024+512;
uint32_t i=0;
printf("2K RAM:\r\n");
for(i=0;i<10;i++)
{
printf("0x%08lx=0x%08lx\r\n",myAddr2+(i<<2),*(uint32_t volatile *)(myAddr2+(i<<2)));
}
printf("32K RAM:\r\n");
for(i=0;i<10;i++)
{
printf("0x%08lx=0x%08lx\r\n",myAddr+(i<<2),*(uint32_t volatile *)(myAddr+(i<<2)));
}
}
void wakeup_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure = {0};
EXTI_InitTypeDef EXTI_InitStructure = {0};
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA |RCC_APB2Periph_AFIO , ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* GPIOA.3 ----> EXTI_Line3 */
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource3);
EXTI_InitStructure.EXTI_Line = EXTI_Line3;
/* WFI - EXTI_Mode_Interrupt, WFE - EXTI_Mode_Event*/
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Event;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
}
int main(void)
{
/* Configure unused GPIO as IPD to reduce power consumption */
GPIO_InitTypeDef GPIO_InitStructure = {0};
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|
RCC_APB2Periph_GPIOC|RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOE, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_All;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_Init(GPIOE, &GPIO_InitStructure);
/***************************************/
Delay_Init();
USART_Printf_Init(256000);
printf("ch32v307 standby test\r\n");
Delay_Ms(2000);
/* wakeup event, PA3 EXTI trigger falling */
wakeup_init();
ram_read_test();
ram_write_test();
/* set PWR register, need enable rcc of pwr */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
/* WKUP or RTC wakeup event flag, clear by set PWR_CTLR CWUF */
printf("PWR_CSR WUF = %ld \r\n",PWR->CSR & PWR_FLAG_WU);
if(PWR->CSR & PWR_FLAG_WU)
{
PWR->CTLR |= (1<<2); /* clear wakeup flag */
}
/* enable WKUP, PA0 */
PWR_WakeUpPinCmd(ENABLE);
/* standby mode, mcu will reset after wakeup event */
PWR_EnterSTANDBYMode_RAM_LV();
// PWR_EnterSTANDBYMode_RAM();
printf("wakeup\r\n");
while(1)
{
printf("run in main loop\r\n");
Delay_Ms(1000);
}
}:::tip 当MCU处于停止模式或待机模式时,两线调试仿真接口是处于关闭状态,此时是无法用 WCH-Link 进行程序的下载。若唤醒配置不正确,MCU无法唤醒,此时就会和MCU失联。解决方式如下:
在主函数启动时加一个较长的延时函数,按住板子的复位键,使系统处于复位状态,然后点击 IDE 下载按钮,这时再释放复位键,这样MCU上电后执行延时函数期间对低功耗程序进行擦除。
选择启动方式,通过将BOOT0拉高,重新上电使MCU从SRAM启动,重新选择一个非睡眠程序下载,下载完成后将BOOT0拉低,然后重新上电即可重新下载程序。
:::