RISC-V MCU应用教程之低功耗待机模式
以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); } } </details>
参考代码 - 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拉低,然后重新上电即可重新下载程序。
:::
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?