• 《嵌入式 – GD32开发实战指南》第15章 低功耗(电源管理)


    开发环境:
    MDK:Keil 5.30
    开发板:GD32F207I-EVAL
    MCU:GD32F207IK

    15.1 GD32电源管理

    GD32的工作电压(VDD)为2.0~3.6V。通过内置的电压调节器提供所需的1.8V电源。当主电源VDD掉电后,通过VBAT脚为实时时钟(RTC)和备份寄存器提供电源。

    在这里插入图片描述

    使用电池或其他电源连接到VBAT脚上,当VDD断电时,可以保存备份寄存器的内容和维持RTC的功能。

    VBAT脚为RTC、 LSE振荡器和PC13至PC15端口供电,可以保证当主电源被切断时RTC能继续工作。切换到VBAT供电的开关,由复位模块中的掉电复位功能控制。

    当备份域由VDD供电(VBAK连接至VDD)时,以下功能可用:
    ● PC13可以作为通用I/O口或RTC功能引脚;
    ● PC14和PC15可以作为通用I/O口或LXTAL晶振引脚。
    ● PI8可以作为通用I/O口或RTC功能引脚

    当备份域由VBAT电源供电时(VBAK连接至VBAT),以下功能可用:
    ● PC13仅可以作为RTC功能引脚;
    ● PC14和PC15仅可作为LXTAL晶振引脚。
    ● PI8仅可以作为RTC功能引脚

    注意:由于PC13至PC15引脚是通过电源切换器供电的,电源切换器仅可通过小电流,因此当PC13至PC15的GPIO口在输出模式时,其工作的速度不能超过2MHz(最大负载为30pF)。

    15.2 GD32低功耗模式

    在系统或电源复位以后,微控制器处于运行状态。当CPU不需继续运行时,可以利用多种低功耗模式来节省功耗,例如等待某个外部事件时。用户需要根据最低电源消耗、最快速启动时间和可用的唤醒源等条件,选定一个最佳的低功耗模式。

    GD32F207有三种低功耗模式:
    ● 睡眠模式(Cortex™-M3内核停止,所有外设包括Cortex-M3核心的外设,如NVIC、系统时钟(SysTick)等仍在运行)
    ● 深度睡眠模式(所有的时钟都已停止)
    ● 待机模式(1.8V电源关闭)

    此外,在运行模式下,可以通过以下方式中的一种降低功耗:
    ● 降低系统时钟
    ● 关闭APB和AHB总线上未被使用的外设时钟。

    在这里插入图片描述

    从表中可以看到,这三种低功耗模式层层递进,运行的时钟或芯片功能越来越少,因而功耗越来越低。
    在睡眠模式中,仅关闭了 CPU 时钟,CPU 停止运行,但其片上外设,CM3 核心外设全都还照常运行。有两种方式进入睡眠模式,它的进入方式决定了从睡眠唤醒的方式,分别是 WFI(wait for interrupt)和 WFE(wait for event),即由等待“中断”唤醒和由“事件”唤醒。

    在深度睡眠模式中,进一步关闭了其它所有的时钟,于是所有的外设都停止了工作,但由于其 1.8V 区域的电源没有关闭,还保留了 CPU 的寄存器、内存的信息,所以深度睡眠模式唤醒,并重新开启时钟后,还可以从上次深度睡眠处继续执行代码。深度睡眠模式可以由任意一个外部中断(EXTI)唤醒。在深度睡眠模式中可以选择电压调节器为开模式或低功耗模式,若选择低功耗模式,在唤醒时会加上电压调节器的唤醒延迟。

    待机模式,这与我们平时印象中的手机关机模式相似,它除了关闭所有的时钟,还把1.8V 区域的电源也关闭了,也就是说,从待机模式唤醒后,由于没有之前代码的运行记录,只能对芯片复位,重新检测 boot 条件,从头开始执行程序。它有四种唤醒方式,分别是 WKUP(PA0)引脚的上升沿,RTC 闹钟事件,NRST 引脚的复位和FWDGT复位。

    在运行模式下,任何时候都可以通过停止为外设和内存提供时钟来减少功耗。为了在睡眠模式下更多地减少功耗,可在执行WFI或WFE指令前关闭所有外设的时钟。

    通过设置AHB外设时钟使能寄存器、APB2外设时钟使能寄存器和APB1外设时钟使能寄存器来开关各个外设模块的时钟。

    15.2.1睡眠模式

     进入睡眠模式

    通过执行WFI或WFE指令进入睡眠状态。根据Cortex™-M3中SCR(系统控制寄存器)的SLEEPONEXIT位,有两种睡眠进入机制选项:

    ● Sleep-now:如果SLEEPONEXIT位被清零,一旦执行WFI或WFE指令, MCU立即进入睡眠模式;
    ● Sleep-on-exit:如果SLEEPONEXIT位被置位,当系统从最低优先级的中断处理程序离开后, MCU立即进入睡眠模式。

    在睡眠模式下,所有的I/O引脚都保持它们在运行模式时的状态。

     退出睡眠模式
    如果执行WFI指令进入睡眠模式,任意一个被嵌套向量中断控制器响应的外设中断都能将系统从睡眠模式唤醒。

    如果执行WFE指令进入睡眠模式,则一旦发生唤醒事件时,微处理器都将从睡眠模式退出。唤醒事件可以通过下述方式产生:
    ● 在外设控制寄存器中使能一个中断,而不是在NVIC(嵌套向量中断控制器)中使能,并且在Cortex-M3系统控制寄存器中使能SEVONPEND位。当MCU从WFE中唤醒后,外设的中断挂起位和外设的NVIC中断通道挂起位(在NVIC中断清除挂起寄存器中)必须被清除。
    ● 配置一个外部或内部的EXIT线为事件模式。当MCU从WFE中唤醒后,因为与事件线对应的挂起位未被设置,不必清除外设的中断挂起位或外设的NVIC中断通道挂起位。

    该模式唤醒所需的时间最短,因为没有时间损失在中断的进入或退出上。

    SLEEP-NOW模式说明
    进入在以下条件下执行WFI(等待中断)或WFE(等待事件)指令:
    – SLEEPDEEP = 0 和– SLEEPONEXIT = 0参考Cortex-M3系统控制寄存器。
    退出如果执行WFI进入睡眠模式:中断。
    如果执行WFE进入睡眠模式:唤醒事件。
    唤醒延时
    SLEEP-ON_EXIT模式说明
    进入在以下条件下执行WFI指令:
    –SLEEPDEEP = 0和–SLEEPONEXIT = 1参考Cortex™-M3系统控制寄存器
    退出中断:参考中断向量表
    唤醒延时

    15.2.2深度睡眠模式

    深度睡眠模式是在Cortex™-M3的深睡眠模式基础上结合了外设的时钟控制机制,深度睡眠模式与 Cortex™-M3 的 SLEEPDEEP 模式相对应。在深度睡眠模式下,1.2V 域中的所有时钟全部关闭,IRC8M、HXTAL及PLLs 也全部被禁用。SRAM和寄存器中的内容被保留。

    在停止模式下,所有的I/O引脚都保持它们在运行模式时的状态。

     进入深度睡眠模式
    在深度睡眠模式下,通过设置电源控制寄存器PMU_CTL的LDOLP位使内部调节器进入低功耗模式,能够降低更多的功耗。

    如果正在进行闪存编程,直到对内存访问完成,系统才进入深度睡眠模式。
    如果正在进行对APB的访问,直到对APB访问完成,系统才进入深度睡眠模式。可以通过对独立的控制位进行编程。

    在深度睡眠模式下,如果在进入该模式前ADC和DAC没有被关闭,那么这些外设仍然消耗电流。

     退出深度睡眠模式
    当一个中断或唤醒事件导致退出深度睡眠模式时,IRC8M、IRC40K振荡器被选为系统时钟。

    当电压调节器处于低功耗模式下,当系统从深度睡眠模式退出时,将会有一段额外的启动延时。如果在深度睡眠模式期间保持内部调节器开启,则退出启动时间会缩短,但相应的功耗会增加。

    深度睡眠模式说明
    进入在以下条件下执行WFI(等待中断)或WFE(等待事件)指令:
    – 设置Cortex-M3系统控制寄存器中的SLEEPDEEP位
    – 清除电源控制寄存器(PMU_CTL)中的STBMOD位
    – 通过设置PMU_CTL中LDOLP位选择电压调节器的模式
    注:为了进入停止模式,所有的外部中断的请求位(挂起寄存器)和RTC的闹钟标志都必须被清除,否则停止模式的进入流程将会被跳过,程序继续运行。
    退出如果执行WFI进入停止模式:
    设置任一外部中断线为中断模式(在NVIC中必须使能相应的外部中断向量)。参见中断向量。
    如果执行WFE进入停止模式:
    设置任一外部中断线为事件模式。参见唤醒事件管理。
    唤醒延时IRC8M、IRC40K唤醒时间 + 电压调节器从低功耗唤醒的时间。

    15.2.3待机模式

    待机模式可实现系统的最低功耗。该模式是在Cortex-M3深睡眠模式时关闭电压调节器。整个1.8V供电区域被断电。IRC8M、HXTAL 和 PLL振荡器也被断电。SRAM和寄存器内容丢失。只有备份的寄存器和待机电路维持供电。

     进入待机模式
    进入待机模式前,先将Cortex™-M3 系统控制寄存器的 SLEEPDEEP 位置 1,再将 PMU_CTL 寄存器的 STBMOD 位置 1,再清除 PMU_CS 寄存器的 WUF 位,然后执行 WFI 或 WFE 指令,系统进入待机模式,PMU_CS 寄存器的 STBF 位状态表示 MCU 是否已进入待机模式。

     退出待机模式
    当一个外部复位(NRST引脚)、 FWDGT复位、 WKUP引脚上的上升沿或RTC闹钟事件的上升沿发生时,微控制器从待机模式退出。从待机唤醒后,除了电源控制/状态寄存器(PWR_CS),所有寄存器被复位。

    从待机模式唤醒后的代码执行等同于复位后的执行(采样启动模式引脚、读取复位向量等)。 电源控制/状态寄存器(PWR_CS)将会指示内核由待机状态退出。

    待机模式可实现系统的最低功耗。该模式是在Cortex-M3深睡眠模式时关闭电压调节器。整个1.8V供电区域被断电。IRC8M、HXTAL 和 PLL振荡器也被断电。SRAM和寄存器内容丢失。只有备份的寄存器和待机电路维持供电。

    待机模式说明
    进入在以下条件下执行WFI(等待中断)或WFE(等待事件)指令:
    – 设置Cortex™-M3系统控制寄存器中的SLEEPDEEP位
    – 设置电源控制寄存器(PWR_CTL)中的STBMOD位
    – 清除电源控制/状态寄存器(PWR_CS)中的WUF位
    退出WKUP引脚的上升沿、RTC闹钟事件的上升沿、NRST引脚上外部复位、FWDGT复位。
    唤醒延时复位阶段时电压调节器的启动

    15.3低功耗的寄存器描述

    电源控制寄存器(PWR_CTL),该寄存器的各位描述如下图所示:

    在这里插入图片描述

    我们通过设置 PWR_CTL的 STBMOD位,使 CPU 进入深度睡眠时进入待机模式。

    电源控制/状态寄存器( PWR_CS)的各位描述如图所示。

    在这里插入图片描述

    通过设置 PWR_CS的 WUPEN 位,来使能 WKUP 引脚用于待机模式唤醒。我们还可以从 WUF 来检查是否发生了唤醒事件。

    15.4低功耗具体代码实现

    通过以上介绍,我们了解了进入低功耗模式的三种方法,在者三种模式中待机模式功耗最低。笔者这里使用的是按键唤醒,其电路如下:

    在这里插入图片描述

    15.4.1睡眠模式

    睡眠模式很简单,就是通过以下指令进入睡眠:

    __WFI();	
    __WFE(); 
    
    • 1
    • 2

    那么以上两条指令又是啥意思呢?WFI(Wait for interrupt)和WFE(Wait for event)是两个让ARM核进入low-power standby模式的指令,由ARM architecture定义,由ARM core实现。我们可以在core_cmx3.h(笔者使用的是GD32F207,对应的就是core_cmx3.h,其他内核类似),中找到以上指令的定义。

    static __INLINE void __WFI()                      { __ASM volatile ("wfi"); }
    static __INLINE void __WFE()                      { __ASM volatile ("wfe"); }
    
    • 1
    • 2

    以上就是把汇编指令都封装成了诸如__Commnad()的函数形式,并且预编译为二进制包。那么以上指令都能让ARM进入睡眠模式,又有啥区别呢?
    对WFI来说,执行WFI指令后,ARM core会立即进入low-power standby state,直到有WFI Wakeup events发生。
    而WFE则稍微不同,执行WFE指令后,根据Event Register(一个单bit的寄存器,每个PE一个)的状态,有两种情况:如果Event Register为1,该指令会把它清零,然后执行完成(不会standby);如果Event Register为0,和WFI类似,进入low-power standby state,直到有WFE Wakeup events发生。

    总结一下,这两条指令的作用都是令MCU进入休眠/待机状态以便降低功耗,但是略有区别:

    WFI: wait for Interrupt 等待中断,即下一次中断发生前都在此hold住不干活

    WFE: wait for Events 等待事件,即下一次事件发生前都在此hold住不干活

    因此我们要项唤醒MCU,最简单的就是通过中断唤醒。

    睡眠模式时通过按键中断唤醒,代码如下:

    /*
        brief      main function
        param[in]  none
        param[out] none
        retval     none
    */
    int main(void)
    {
        //usart init 115200 8-N-1
        com_init(COM1);
    
        /* configure LED1 GPIO port */
        led_init(LED1);
    
        /* configure LED2 GPIO port */
        led_init(LED2);
    
        /* configure LED3 GPIO port */
        led_init(LED3);
    
        /* configure LED4 GPIO port */
        led_init(LED4);
    	
        //key init
        key_init(KEY_WAKEUP, KEY_MODE_EXTI);
    
        printf("\r\n Sleep Test \r\n");
    
        while(1)
        {      
            led_toggle(LED1);
            Delay(0xFFFFF);
            led_toggle(LED2);
            Delay(0xFFFFF);
            led_toggle(LED3);
            Delay(0xFFFFF);
            led_toggle(LED4);
            //__WFI();					//进入睡眠模式,等待中断唤醒  方式一
            __WFE(); //方式二
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    进入睡眠模式也可调用库函数。

    void pmu_to_sleepmode(uint8_t sleepmodecmd)
    
    • 1

    值得注意的是,这里不能用滴答定时器来延时,因为这里使用的是中断方式实现的。

    WFI和WFE两条指令让MCU进入睡眠模式,均可通过按键中断唤醒,但是他们的唤醒本质是有区别的。

    在这里插入图片描述

    如上图所示,图中的蓝色虚线箭头标出了中断信号的传输路径,而红色箭头标出了事件的传输路径。虽然中断和事件的产生源都是一样的,都是通过按键产生,但是路径却有不同,中断是需要CPU参与的,需要软件的中断服务函数才能完成中断后产生的结果;事件是靠脉冲发生器产生一个脉冲,进而由硬件自动完成这个事件产生的结果,当然相应的联动部件需要先设置好,比如引起DMA操作,AD转换等。

    15.4.2停止模式

    进入停止模式之后,任何外部中断都可以唤醒低功耗,但是需要重新配置时钟,不然系统将以默认时钟(没有经过倍频)运行。笔者这里还是使用外部中断唤醒。我们先看看主函数。

    /*
        brief      main function
        param[in]  none
        param[out] none
        retval     none
    */
    int main(void)
    {
        //usart init 115200 8-N-1
        com_init(COM1);
    
        /* configure LED1 GPIO port */
        led_init(LED1);
    
        /* configure LED2 GPIO port */
        led_init(LED2);
    
        /* configure LED3 GPIO port */
        led_init(LED3);
    
        /* configure LED4 GPIO port */
        led_init(LED4);
    	
        //key init
        key_init(KEY_WAKEUP, KEY_MODE_EXTI);
    
        /* 使能电源管理单元的时钟 */
    rcu_periph_clock_enable(RCU_PMU);
    
        printf("\r\n Enter stop mode \r\n");
    
        /* 进入深度睡眠模式,设置电压调节器为低功耗模式,等待中断唤醒*/
        pmu_to_deepsleepmode(PMU_LDO_LOWPOWER,WFI_CMD);	
    	
        while(1)
        {      
            led_toggle(LED1);
            Delay(0xFFFFF);
            led_toggle(LED2);
            Delay(0xFFFFF);
            led_toggle(LED3);
            Delay(0xFFFFF);
            led_toggle(LED4);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    以上最重要的就一句:

    pmu_to_deepsleepmode(PMU_LDO_LOWPOWER,WFI_CMD);	
    
    • 1

    这是MCU进入低功耗模式的库函数,在gd32f20x_pmu.c中实现,原型如下:

    /*!
        \brief      PMU work in deepsleep mode
        \param[in]  ldo:
                    only one parameter can be selected which is shown as below:
          \arg        PMU_LDO_NORMAL: LDO work at normal power mode when pmu enter deepsleep mode
          \arg        PMU_LDO_LOWPOWER: LDO work at low power mode when pmu enter deepsleep mode
        \param[in]  deepsleepmodecmd:
                    only one parameter can be selected which is shown as below:
          \arg        WFI_CMD: use WFI command
          \arg        WFE_CMD: use WFE command
        \param[out] none
        \retval     none
    */
    void pmu_to_deepsleepmode(uint32_t ldo, uint8_t deepsleepmodecmd)
    {
        static uint32_t reg_snap[4];
        /* clear stbmod and ldolp bits */
        PMU_CTL &= ~((uint32_t)(PMU_CTL_STBMOD | PMU_CTL_LDOLP));
    
        /* set ldolp bit according to pmu_ldo */
        PMU_CTL |= ldo;
    
        /* set sleepdeep bit of Cortex-M3 system control register */
        SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
    
        reg_snap[0] = REG32(0xE000E010U);
        reg_snap[1] = REG32(0xE000E100U);
        reg_snap[2] = REG32(0xE000E104U);
        reg_snap[3] = REG32(0xE000E108U);
    
        REG32(0xE000E010U) &= 0x00010004U;
        REG32(0xE000E180U)  = 0XFF7FF83DU;
        REG32(0xE000E184U)  = 0XBFFFF8FFU;
        REG32(0xE000E188U)  = 0xFFFFFFFFU;
    
        /* select WFI or WFE command to enter deepsleep mode */
        if(WFI_CMD == deepsleepmodecmd) {
            __WFI();
        } else {
            __SEV();
            __WFE();
            __WFE();
        }
    
        REG32(0xE000E010U) = reg_snap[0] ;
        REG32(0xE000E100U) = reg_snap[1] ;
        REG32(0xE000E104U) = reg_snap[2] ;
        REG32(0xE000E108U) = reg_snap[3] ;
    
        /* reset sleepdeep bit of Cortex-M3 system control register */
        SCB->SCR &= ~((uint32_t)SCB_SCR_SLEEPDEEP_Msk);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    上述函数主要节做了四件事:

    1.设置Cortex-M3系统控制寄存器中的SLEEPDEEP位(SCB_SCR参考Cortex-M3权威指南182页)。
    2.清除电源控制寄存器(PWR_CTL)中的STBMOD位。
    3.通过设置PWR_CR中LDOLP位选择电压调节器的模式。
    4.执行WFI或者WFE汇编指令。

    我们可以选择事件和中断唤醒两种方式,选择哪种方式是根据库函数的第二个参数决定的,笔者这里使用的中断唤醒。

    在这里插入图片描述

    深度睡眠模式下MCU唤醒之后,时钟和频率是没有经过倍频的,在GD32F207上,低功耗唤醒之后,是8M频率运行,而正常运行是120M。所以,在唤醒停机模式之后,需要重新配置时钟。最简单就是直接调用库函数:

    SystemInit();
    
    • 1

    当然也可以自己重写时钟初始化函数。

    好了,我们最后再看看中断唤醒的函数:

    /*!
        \brief      this function handles external lines 0 interrupt request
        \param[in]  none
        \param[out] none
        \retval     none
    */
    void EXTI0_IRQHandler(void)
    {
        if(RESET != exti_interrupt_flag_get(EXTI_0))
        {
           printf("\r\n Enter interrupt \r\n");	
    
           SystemInit();
    
           printf("\r\n Quit interrupt \r\n");
           exti_interrupt_flag_clear(EXTI_0);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    进入中断后主要是重启时钟,然后就会接着运行程序。

    15.4.3待机模式

    待机模式的功耗最低。待机模式的具体步骤如下:

    1)使能电源时钟。

    因为要配置电源控制寄存器,所以必须先使能电源时钟。在库函数中,使能电源时钟的方法是:

    rcu_periph_clock_enable(RCU_PMU); //使能 PWR 外设时钟
    
    • 1

    2)设置 WK_UP 引脚作为唤醒源。

    使能时钟之后再设置 PWR_CS 的WUPEN位,使能WUPEN用于将 CPU 从待机模式唤醒。在库函数中,设置使能WUPEN用于唤醒 CPU 待机模式的函数是:

    pmu_wakeup_pin_enable (); //使能唤醒管脚功能
    
    • 1

    3)设置 SLEEPDEEP 位,设置 STBMOD位,执行 WFI 指令,进入待机模式。

    进入待机模式, 首先要设置 SLEEPDEEP 位( 该位在系统控制寄存器( SCB_SCR)的第二位,详见《 CM3 权威指南》), 接着我们通过 PWR_CTL设置 STBMOD位,使得 CPU 进入深度睡眠时进入待机模式,最后执行 WFI 指令开始进入待机模式,并等待 WK_UP中断的到来。在库函数中,进行上面三个功能进入待机模式是在函数pmu_to_standbymode中实现的:

    void pmu_to_standbymode(uint8_t standbymodecmd)
    • 1

    pmu_to_standbymode ()函数原型如下所示:

    /*!
        \brief      pmu work in standby mode
        \param[in]  standbymodecmd:
                    only one parameter can be selected which is shown as below:
          \arg        WFI_CMD: use WFI command
          \arg        WFE_CMD: use WFE command
        \param[out] none
        \retval     none
    */
    void pmu_to_standbymode(uint8_t standbymodecmd)
    {
        /* set sleepdeep bit of Cortex-M3 system control register */
        SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
    
        /* set stbmod bit */
        PMU_CTL |= PMU_CTL_STBMOD;
    
        /* reset wakeup flag */
        PMU_CTL |= PMU_CTL_WURST;
    
        /* select WFI or WFE command to enter standby mode */
        if(WFI_CMD == standbymodecmd) {
            __WFI();
        } else {
            __WFE();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    该函数中先配置了STBMOD寄存器位及 SLEEPDEEP 寄存器位,接着调用__force_stores函数确保存储操作完毕后再调用 WFI 指令,从而进入待机模式。这里值得注意的是,待机模式也可以使用 WFE 指令进入的。

    在进入待机模式后,除了被使能了的用于唤醒的 I/O,其余 I/O 都进入高阻态,而待机模式唤醒后,相当于复位 GD32 芯片,程序重新从头开始执行。

    4)最后编写 WK_UP 中断/事件函数。

    因为我们通过 WK_UP 中断/事件( PA0 中断/事件)来唤醒MCU,所以我们有必要设置一下该中断函数,同时我们也通过该函数里面进入待机模式。

    /**
      * @brief  用于检测按键是否被长时间按下
      * @param  无
      * @retval 1 :按键被长时间按下  0 :按键没有被长时间按下
      */
    uint8_t pwr_check_standby(void)
    {			
    	uint8_t downCnt =0;																				//记录按下的次数
    	uint8_t upCnt =0;																					//记录松开的次数			
    
    	while(1)																										//死循环,由return结束
    	{	
    		if(RESET == gpio_input_bit_get(GPIOA, GPIO_PIN_0))//检测到按下按键
    		{
    			led_on(LED1);led_on(LED2);led_on(LED3);led_on(LED4);					   	//点亮所有LED灯
    
    			downCnt++;																					//记录按下次数
    			upCnt=0;																						//清除按键释放记录
    			Delay(0xFFFF);
    			if(downCnt>=100)																		//按下时间足够
    			{
    				led_off(LED1);led_off(LED2);led_off(LED3);led_off(LED4); 	 
    				return 1; 																				//检测到按键被时间长按下
    			}
    		}
    		else 
    		{
    			upCnt++; 																						//记录释放次数
    			if(upCnt>5)																					//连续检测到释放超过5次
    			{
    				led_off(LED1);led_off(LED2);led_off(LED3);led_off(LED4);	  		//关闭所有LED灯	
    				return 0;																				  //按下时间太短,不是按键长按操作
    			}
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    通过以上几个步骤的设置,我们就可以使用 GD32 的待机模式了,并且可以通过 WK_UP来唤醒 MCU。

    主函数如下:

    /*
        brief      main function
        param[in]  none
        param[out] none
        retval     none
    */
    int main(void)
    {
        //usart init 115200 8-N-1
        com_init(COM1);
    
        /* configure LED1 GPIO port */
        led_init(LED1);
    
        /* configure LED2 GPIO port */
        led_init(LED2);
    
        /* configure LED3 GPIO port */
        led_init(LED3);
    
        /* configure LED4 GPIO port */
        led_init(LED4);
    	
        //key init
        key_init(KEY_WAKEUP, KEY_MODE_GPIO);
    
        /* 使能电源管理单元的时钟 */
        rcu_periph_clock_enable(RCU_PMU);
    
        if(pmu_flag_get(PMU_FLAG_WAKEUP) == SET)
        {
            printf("\r\n Standby wake-up reset \r\n");
    
        }
        else
        {
            printf("\r\n Power-on reset\r\n");
        }
        while(1)
        {      
            led_toggle(LED1);
            Delay(0xFFFFF);
            led_toggle(LED2);
            Delay(0xFFFFF);
            led_toggle(LED3);
            Delay(0xFFFFF);
            led_toggle(LED4);
            if(pwr_check_standby())
            {
                printf("\r\n Enter standby mode\r\n");
                /*清除 WU 状态位*/
                pmu_flag_clear (PMU_FLAG_RESET_WAKEUP);
                /* 使能WKUP引脚的唤醒功能 */
                pmu_wakeup_pin_enable ();
                /* 进入待机模式,等待唤醒*/
                pmu_to_standbymode(WFI_CMD);	
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    在循环体外使用库函数 pmu_flag_get检测 PMU_FLAG_WAKEUP标志位,当这个标志位为SET 状态的时候,表示本次系统是从待机模式唤醒的复位,否则可能是上电复位。其中PMU_FLAG_WAKEUP的标志位在gd32f20x_pmu.h中定义。

    pwr_check_standby()函数用于检测渐渐是否长按,当长按后就进入待机模式。

    15.5低功耗实验现象

    15.5.1睡眠模式

    将程序编译好后下载到板子上,可以看到4个LED依次闪烁,然后熄灭。按下KEY1按键,3个LED再次依次闪烁。

    在这里插入图片描述

    15.5.2深度睡眠模式
    将程序编译好后下载到板子上。按下KEY1按键,4个LED依次闪烁。串口依次打印信息如下:

    在这里插入图片描述

    当程序运行后会首先深度睡眠模式,当按键按下后,退出深度睡眠模式,则程序又会继续运行。

    15.5.3待机模式
    将程序编译好后下载到板子上。按下按键,4个LED依次闪烁。长按KEY1按键,MCU进入待机模式,LED熄灭,再次按下KEY1按键,LED会同时点亮,直到同时熄灭,松开KEY1按键,KEY1按下会使 PA0 引脚产生一个上升沿,从而唤醒系统。

    系统唤醒后会进行复位,从头开始执行上述过程,与第一次上电时不同的是,这样的复位会使 PWR_FLAG_WU 标志位改为 SET 状态,LED再次依次闪烁。串口打印信息如下。

    在这里插入图片描述



    欢迎访问我的网站

    BruceOu的哔哩哔哩
    BruceOu的主页
    BruceOu的博客
    BruceOu的CSDN博客
    BruceOu的简书
    BruceOu的知乎


    资源获取方式

    1.关注公众号[嵌入式实验楼]
    2.在公众号回复关键词[GD32开发实战指南]获取资料提取码

  • 相关阅读:
    使用query请求数据出现500的报错
    C++——string类用法指南
    【Spring】面向切面编程详解(AOP)
    Python 潮流周刊#39:Rust 开发的性能超快的打包工具
    Day7—zookeeper基本操作
    HTTPS的加密流程
    Linux-正则三剑客
    Mac远程连接Windows 11
    C#应用处理传入参数 - 开源研究系列文章
    nodejs 时钟案例(fs模块),重复使用fs.writeFile方法,旧内容会被覆盖
  • 原文地址:https://blog.csdn.net/u013162035/article/details/126170037