heyanmei 发表于 2025-6-30 23:02

APM32F402实现RTC定时唤醒STOP模式

# APM32F402实现RTC定时唤醒STOP模式

#申请原创# [@21小跑堂](https://bbs.21ic.com/space-uid-760190.html)

## 低功耗模式

按功耗由高到低排列,APM32F402具有运行、睡眠、停止和待机四种工作模式。上电复位后APM32F402处于运行状态时,当内核不需要继续运行,就可以选择进入后面的三种低功耗模式降低功耗,这三种模式中,功耗消耗不同、唤醒时间不同、唤醒源不同,用户需要根据应用需求,选择最佳的低功耗模式;三种低功耗的模式说明见下表。

!(data/attachment/forum/202506/30/222034ums2hxjs7m5ms9xm.png "image.png")

!(data/attachment/forum/202506/30/222102ytc8zb8znbcttiee.png "image.png")

### SLEEP模式

SLEEP模式又叫睡眠模式,在睡眠模式中,仅关闭了内核时钟,内核停止运行,但其片上外设,内核的外设全都还照常运行。睡眠模式有两种方式进入睡眠模式, 它的进入方式也决定了从睡眠模式唤醒的方式,分别是WFI(等待中断)和WFE(等待事件);睡眠模式的各种特性见下表。

!(data/attachment/forum/202506/30/220735t2jlt2cjmxtqx8ll.png "image.png")

在APM32F402中我们可以通过PMU_EnterSleepMode函数来进入睡眠模式,其参数PMU_SLEEPENTRY_WFI和PMU_SLEEPENTRY_WFE决定我们怎么进入和唤醒睡眠模式,分别代表中断和事件。

### STOP模式

STOP模式又叫停止模式,在停止模式中,在睡眠模式的基础上进一步关闭了其它所有的时钟,因此其所有的外设都停止了工作,但由于其1.2V区域的部分电源没有关闭,还保留了内核的寄存器、内存的信息,所以从停止模式唤醒,并重新开启时钟后,还可以从上次停止处继续执行代码。停止模式可以由任意一个外部中断(EINT)唤醒,在停止模式中可以选择电压调节器为正常模式或低功耗模式;停止模式的各种特性见下表。

!(data/attachment/forum/202506/30/221251nf7kxbss36uxusft.png "image.png")

!(data/attachment/forum/202506/30/221312tinnndnt15tnam3i.png "image.png")

在APM32F402中我们可以通过PMU_EnterSTOPMode函数来进入停止模式,其参数1决定调压器处于正常模式(PMU_REGULATOR_ON)还是低功耗模式(PMU_REGULATOR_LOWPOWER),其参数2决定是中断进入停止模式还是事件进入停止模式,分别是PMU_STOP_ENTRY_WFI&PMU_STOP_ENTRY_WFE。

### STANDBY模式

STANDBY模式又叫待机模式,待机模式除了关闭所有的时钟,还把1.2V区域的电源也完全关闭了,也就是说,从待机模式唤醒后,没有之前代码的运行记录,只能对芯片复位,重新检测boot条件,从头开始执行程序。它有四种唤醒方式,分别是WKUP(PA0)引脚的上升沿,RTC闹钟事件,NRST引脚的复位和IWDG(独立看门狗)复位;待机模式的各种特性见下表。

!(data/attachment/forum/202506/30/221810t5w3xvuc3ajx3uic.png "image.png")

## RTC实现定时唤醒停止模式

上面的停止模式中描述有说,停止模式可以通过任意中断唤醒,当然前提是中断进入的停止模式哈,在EINT章节中我们可以看到RTC Alarm 事件映射到EINT 17线,那么我们也可以通过RTC闹钟来实现定时唤醒停止模式。

### RTC闹钟配置

在RTC闹钟配置中,我们使用内部的LSI配置为RTC时钟源,配置5s的闹钟,并且把RTC Alarm和EINT 17想关联起来

```
void RTC_Config_Init(void)
{
        EINT_Config_T EINT_Configure;

        /* 使能BKP和PWR时钟 */
        RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_PMU | RCM_APB1_PERIPH_BAKR);
    /* 允许访问备份域 */
        PMU_EnableBackupAccess();
    /* 备份域复位 */
        BAKPR_Reset();

    /* 使能LSI */
        RCM_EnableLSI();
    /* 等待直到LSI就绪 */
        while(RCM_ReadStatusFlag(RCM_FLAG_LSIRDY) == RESET);
    /* 选择LSI作为RTC时钟源 */
        RCM_ConfigRTCCLK(RCM_RTCCLK_LSI);
    /* 使能RTC时钟 */
        RCM_EnableRTCCLK();

    /* 等待RTC寄存器同步 */
        RTC_WaitForSynchro();
    /* 等待RTC寄存器上的最后一次写操作完成 */
        RTC_WaitForLastTask();
    /* 使能RTC闹钟中断 */
        RTC_EnableInterrupt(RTC_INT_ALR);
    /* 等待RTC寄存器上的最后一次写操作完成 */
        RTC_WaitForLastTask();
    /* 设置RTC分频值: 设置RTC周期为1s */
        RTC_ConfigPrescaler(40000);
    /* 等待RTC寄存器上的最后一次写操作完成 */
        RTC_WaitForLastTask();
        /* 设置RTC计数器值为0 */
        RTC_ConfigCounter(0U);
    /* 等待RTC寄存器上的最后一次写操作完成 */
        RTC_WaitForLastTask();
        /* 设置RTC闹钟值为5s */
        RTC_ConfigAlarm(ALARM_TIME_INTERVAL);
    /* 等待RTC寄存器上的最后一次写操作完成 */
        RTC_WaitForLastTask();

    /* EXTI配置 */
        EINT_Reset();
        EINT_Configure.line = EINT_LINE_17;
        EINT_Configure.lineCmd = ENABLE;
        EINT_Configure.mode = EINT_MODE_INTERRUPT;
        EINT_Configure.trigger = EINT_TRIGGER_RISING;
        EINT_Config(&EINT_Configure);

        /* 标志清除 */
        RTC_ClearStatusFlag(RTC_FLAG_ALR);
        EINT_ClearIntFlag(EINT_LINE_17);

        /* NVIC配置 */
        NVIC_EnableIRQRequest(RTC_Alarm_IRQn, 1, 1);
}
```

### RTC闹钟中断配置

在RTC闹钟中断服务函数,我们检测到RTC闹钟中断,先清除闹钟标志和EINT 17的标志,然后我们在重新设置RTC的计数器的值和闹钟值,实现再配置5sRTC闹钟

```
void RTC_Alarm_IRQHandler(void)
{
    if(RTC_ReadIntFlag(RTC_INT_ALR) == SET)
        {
      /* 清除RTC闹钟和EXTI_Line17中断标志 */
                RTC_ClearIntFlag(RTC_INT_ALR);
                EINT_ClearIntFlag(EINT_LINE_17);
      /* 等待RTC寄存器同步 */
      RTC_WaitForSynchro();
                /* 等待RTC寄存器上的最后一次写操作完成 */
      RTC_WaitForLastTask();
                /* 设置RTC计数器值为0 */
      RTC_ConfigCounter(0U);

                /* 等待RTC寄存器上的最后一次写操作完成 */
      RTC_WaitForLastTask();
                /* 设置RTC闹钟值为5s */
      RTC_ConfigAlarm(ALARM_TIME_INTERVAL);
                /* 等待RTC寄存器上的最后一次写操作完成 */
      RTC_WaitForLastTask();

    }
}
```

### 进入STOP模式前IO的配置

进入停止模式前把所有未使用的IO配置模拟输入,这样子功耗可以到达最低。

```
void GPIO_ALL_Init(void)
{
        GPIO_Config_T GPIO_Configure;

        /* 使能所有GPIO时钟 */
        RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_GPIOA | RCM_APB2_PERIPH_GPIOB | RCM_APB2_PERIPH_GPIOC | RCM_APB2_PERIPH_GPIOD);

        /* IO配置模拟输入,为了达到STOP模式功耗最低,保留PA0中断唤醒 */
        GPIO_Configure.mode = GPIO_MODE_ANALOG;
        GPIO_Configure.pin = GPIO_PIN_ALL&(~GPIO_PIN_0);
        GPIO_Config(GPIOA, &GPIO_Configure);

        GPIO_Configure.pin = GPIO_PIN_ALL;
        GPIO_Config(GPIOB, &GPIO_Configure);
        GPIO_Config(GPIOC, &GPIO_Configure);
        GPIO_Config(GPIOD, &GPIO_Configure);

        /* 失能所有GPIO时钟 */
        RCM_DisableAPB2PeriphClock(RCM_APB2_PERIPH_GPIOA | RCM_APB2_PERIPH_GPIOB | RCM_APB2_PERIPH_GPIOC | RCM_APB2_PERIPH_GPIOD);
}
```

### 进入停止模式

```
void System_Enter_StopMode(void)
{
        /* 使能PWR时钟 */
        RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_PMU);
        /* 清除唤醒标志 */
        PMU_ClearStatusFlag(PMU_FLAG_WUE);
        /* 进入Stop模式 */
        PMU_EnterSTOPMode(PMU_REGULATOR_LOWPOWER, PMU_STOP_ENTRY_WFI);
}
```

## RTC闹钟定时唤醒STOP模式验证

在主函数里面实现延时5s后进入STOP模式,然后RTC闹钟5s之后就会再唤醒MCU,然后在重新配置主频和其他外设,最后进入while循环。

```
//RTC闹钟值的宏
#define ALARM_TIME_INTERVAL(5U)

void System_Enter_StopMode(void);
void GPIO_ALL_Init(void);
void RTC_Config_Init(void);


int main(void)
{
        NVIC_ConfigPriorityGroup(NVIC_PRIORITY_GROUP_2);

        BSP_HSI64_SysclkConfig();
        BSP_Systick_Init(64);
        BSP_LED_Init(GPIOB, GPIO_PIN_8);
        BSP_LED_Init(GPIOB, GPIO_PIN_9);
        BSP_KEY2_Init(BUTTON_MODE_EXTI);
        BSP_USART1_Init(115200);

        printf("SYSCLKFreq = %d\r\n", RCM_ReadSYSCLKFreq());

        //延时5s再进入Standby模式,方便复位的时候擦除和重新下载程序
        delay_ms(1000);
        delay_ms(1000);
        delay_ms(1000);
        delay_ms(1000);
        delay_ms(1000);
        printf("After a 5-second delay, enter Stop mode\r\n");
        GPIO_ALL_Init();
        RTC_Config_Init();
        System_Enter_StopMode();

        //唤醒需要重新配置时钟
        BSP_HSI64_SysclkConfig();
        BSP_Systick_Init(64);
        BSP_LED_Init(GPIOB, GPIO_PIN_8);
        BSP_LED_Init(GPIOB, GPIO_PIN_9);
        BSP_KEY2_Init(BUTTON_MODE_EXTI);
        BSP_USART1_Init(115200);
        printf("Exti Stop mode\r\n");
    while (1)
    {
                printf("RUNNING\r\n");
                delay_ms(200);
    }
}
```

串口工具查看是否实现RTC闹钟5s唤醒STOP模式,通过串口工具查看确实是已经实现RTC闹钟定时唤醒STOP模式。

!(data/attachment/forum/202506/30/225915r66htzusjidh69dh.png "image.png")

夜幕叙事曲 发表于 2025-7-1 10:22

这个RTC的电力消耗算哪里的啊?

heyanmei 发表于 2025-7-1 19:44

本帖最后由 heyanmei 于 2025-7-2 15:45 编辑

夜幕叙事曲 发表于 2025-7-1 10:22
这个RTC的电力消耗算哪里的啊?
RTC算备份域的,如果VDD断电,VBAT供电,备份域就是VBAT供电的,如果VDD没断电,备份域就是VDD供电

jobszheng 发表于 2025-7-3 10:17

这个实验要做一做。低功耗必备的方案与策略。

夜幕叙事曲 发表于 2025-7-3 23:38

heyanmei 发表于 2025-7-1 19:44
RTC算备份域的,如果VDD断电,VBAT供电,备份域就是VBAT供电的,如果VDD没断电,备份域就是VDD供电 ...

也是啊!
即使我vbat没有电了,理论上,我连接着VDD,这块备份区我也能操作。

穷得响叮当侠 发表于 2025-7-4 14:43

这个帖子详细介绍了APM32F402如何通过RTC定时器实现从STOP模式唤醒,非常实用!
页: [1]
查看完整版本: APM32F402实现RTC定时唤醒STOP模式