2. RTC闹钟中断唤醒

任务定义

本案例使用STM32L0系列MCU(以STM32L071为例),完成以下闭环:配置RTC闹钟事件,使MCU进入STOP低功耗模式,闹钟触发后通过EXTI线17唤醒MCU,在RTC闹钟中断服务函数中翻转LED并记录唤醒次数。核心目标是验证RTC闹钟中断能够将MCU从STOP模式唤醒,并执行指定任务。

硬件连接

STM32L071 STOP模式 PB0 PB1 NRST LED1 闹钟中断翻转 LED2 主循环心跳 RTC 闹钟A 匹配秒数 → 触发中断 EXTI线17 → 唤醒STOP 闹钟秒 = 当前秒 + 5 循环触发 RTC唤醒中断 VDD = 3.3V

代码实现


#include "stm32l0xx_hal.h"

/* 闹钟中断唤醒次数 */
volatile uint32_t alarm_wakeup_count = 0;

/* RTC句柄 */
RTC_HandleTypeDef hrtc;

/* LED引脚定义 */
#define LED1_PIN       GPIO_PIN_0
#define LED1_PORT      GPIOB
#define LED2_PIN       GPIO_PIN_1
#define LED2_PORT      GPIOB

/* 闹钟中断服务函数:翻转LED1并计数 */
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
{
    HAL_GPIO_TogglePin(LED1_PORT, LED1_PIN);
    alarm_wakeup_count++;
}

/* RTC初始化:LSE 32.768kHz,闹钟A使能 */
static void MX_RTC_Init(void)
{
    __HAL_RCC_RTC_ENABLE();

    hrtc.Instance = RTC;
    hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
    hrtc.Init.AsynchPrediv = 127;   /* 异步分频 */
    hrtc.Init.SynchPrediv  = 255;   /* 同步分频,得到1Hz */
    hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
    hrtc.Init.OutPutRemap = RTC_OUTPUT_REMAP_NONE;
    hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
    hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;
    HAL_RTC_Init(&hrtc);

    /* 使能闹钟A中断 */
    HAL_RTC_AlarmA_EnableIt(&hrtc);
}

/* 设置闹钟:在当前时间基础上加5秒 */
static void Set_Alarm_After_5s(void)
{
    RTC_AlarmTypeDef sAlarm = {0};
    RTC_TimeTypeDef sTime = {0};
    RTC_DateTypeDef sDate = {0};

    /* 读取当前时间 */
    HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
    HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);

    /* 计算目标秒数 */
    uint32_t target_sec = sTime.Seconds + 5;
    if (target_sec >= 60) {
        target_sec -= 60;
    }

    /* 配置闹钟A:每秒匹配 */
    sAlarm.AlarmTime.Hours = 0;
    sAlarm.AlarmTime.Minutes = 0;
    sAlarm.AlarmTime.Seconds = target_sec;
    sAlarm.AlarmTime.SubSeconds = 0;
    sAlarm.AlarmTime.TimeFormat = RTC_HOURFORMAT_24;
    sAlarm.AlarmMask = RTC_ALARMMASK_DATE_WEEKDAY |   /* 不关心日期 */
                       RTC_ALARMMASK_HOURS |           /* 不关心小时 */
                       RTC_ALARMMASK_MINUTES;          /* 不关心分钟 */
    /* 只匹配秒字段 */
    sAlarm.Alarm = RTC_ALARM_A;
    sAlarm.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_ALL;
    HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN);
}

/* GPIO初始化 */
static void MX_GPIO_Init(void)
{
    __HAL_RCC_GPIOB_CLK_ENABLE();

    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin = LED1_PIN | LED2_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}

/* 进入STOP模式 */
static void Enter_Stop_Mode(void)
{
    HAL_SuspendTick();
    HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
    /* 唤醒后重新配置系统时钟 */
    SystemClock_Config();
    HAL_ResumeTick();
}

/* 系统时钟配置:MSI 2.097MHz */
void SystemClock_Config(void)
{
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSE | RCC_OSCILLATORTYPE_MSI;
    RCC_OscInitStruct.LSEState = RCC_LSE_ON;
    RCC_OscInitStruct.MSIState = RCC_MSI_ON;
    RCC_OscInitStruct.MSIClockRange = RCC_MSIRANGE_5;  /* 2.097MHz */
    RCC_OscInitStruct.MSICalibrationValue = 0;
    HAL_RCC_OscConfig(&RCC_OscInitStruct);

    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK |
                                  RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_MSI;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
    HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0);
}

/* 主函数 */
int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_RTC_Init();

    /* 初始状态:LED1灭,LED2亮表示运行 */
    HAL_GPIO_WritePin(LED1_PORT, LED1_PIN, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(LED2_PORT, LED2_PIN, GPIO_PIN_SET);

    while (1)
    {
        /* 设置5秒后的闹钟 */
        Set_Alarm_After_5s();

        /* 进入STOP模式,等待闹钟唤醒 */
        Enter_Stop_Mode();

        /* 唤醒后翻转LED2,表示主循环恢复 */
        HAL_GPIO_TogglePin(LED2_PORT, LED2_PIN);

        /* 延时1秒,避免频繁循环 */
        HAL_Delay(1000);
    }
}

关键配置说明

RTC使用LSE外部32.768kHz晶振,异步分频127、同步分频255,得到1Hz的秒时钟。闹钟A设置为仅匹配秒字段,因此每分钟内每个目标秒数都会触发一次。代码中每次唤醒后重新读取当前时间并设置“当前秒+5”的闹钟,实现周期性唤醒。EXTI线17由HAL库在HAL_RTC_AlarmA_EnableIt内部自动映射,无需手动配置NVIC以外的EXTI设置。

编译与烧录


# 使用STM32CubeIDE或Makefile工程编译
make clean && make

# 烧录(以ST-Link为例)
st-flash write build/rtc_alarm_wakeup.bin 0x08000000

验证方法与判定标准

将程序烧录至STM32L071开发板,连接3.3V电源和ST-Link调试器。观察以下现象:

  1. 上电后LED2常亮,LED1熄灭,表示程序进入主循环;
  2. 约5秒后LED1翻转一次(点亮或熄灭),同时LED2翻转一次,表示MCU被RTC闹钟从STOP模式唤醒并执行了中断回调;
  3. 之后每隔约5秒,LED1和LED2各翻转一次,形成连续周期;
  4. 使用调试器查看变量alarm_wakeup_count,其值每唤醒一次递增1,与LED1翻转次数一致。

判定通过标准:LED1和LED2以约5秒周期同步翻转,且alarm_wakeup_count持续递增,证明RTC闹钟中断成功将MCU从STOP模式唤醒并执行了指定任务。