3. RTC闹钟唤醒与日历更新

RTC闹钟唤醒与日历更新 学习卡片

任务定义

本案例面向STM32L0系列MCU(以STM32L071为例),实现以下闭环:

  • 配置RTC在闹钟A中断中周期性唤醒MCU(周期10秒);
  • 每次唤醒后读取RTC日历时间,通过UART打印当前日期时间;
  • 进入低功耗停机模式(STOP Mode),用实际电流测量验证唤醒前后功耗差异。

硬件连接与测量点

STM32L071 Cortex-M0+ RTC 闹钟A UART PWR 32.768kHz 晶振 USB-TTL (CH340) TX (PA2) 万用表 (电流档) VDD (3.3V) — 串联电流表 GND 测量点:将万用表串联在 VDD 供电回路中 唤醒时电流 ~mA 级,停机时 ~µA 级

代码实现

使用STM32CubeIDE + HAL库,完整工程包含以下关键文件。主逻辑在main.c中实现,RTC配置与唤醒回调分离清晰。

// main.c 核心代码(省略芯片初始化部分)
#include "stm32l0xx_hal.h"

/* RTC句柄与时间结构体 */
RTC_HandleTypeDef hrtc;
RTC_AlarmTypeDef sAlarm;
RTC_DateTypeDef sDate;
RTC_TimeTypeDef sTime;

/* 唤醒计数器 */
volatile uint8_t wake_count = 0;

/* 系统时钟配置为MSI 2.097MHz(低功耗模式) */
void SystemClock_Config(void) {
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

    __HAL_RCC_PWR_CLK_ENABLE();
    __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSI;
    RCC_OscInitStruct.LSIState = RCC_LSI_ON;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
        Error_Handler();
    }

    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);
}

/* RTC初始化:使用LSI(37kHz)经分频得到1Hz日历 */
void MX_RTC_Init(void) {
    hrtc.Instance = RTC;
    hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
    hrtc.Init.AsynchPrediv = 124;   // 37kHz / (124+1) = 296Hz
    hrtc.Init.SynchPrediv = 295;    // 296 / (295+1) = 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;
    if (HAL_RTC_Init(&hrtc) != HAL_OK) {
        Error_Handler();
    }
}

/* 闹钟A回调:每次唤醒后执行 */
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc) {
    wake_count++;
}

/* 设置RTC初始时间:2025-01-15 09:30:00 星期三 */
void Set_Initial_Time(void) {
    sDate.Year = 25;    // 2025年
    sDate.Month = 1;
    sDate.Date = 15;
    sDate.WeekDay = RTC_WEEKDAY_WEDNESDAY;
    HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN);

    sTime.Hours = 9;
    sTime.Minutes = 30;
    sTime.Seconds = 0;
    sTime.TimeFormat = RTC_HOURFORMAT24;
    sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
    sTime.StoreOperation = RTC_STOREOPERATION_RESET;
    HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
}

/* 配置闹钟A:每10秒触发一次 */
void Configure_AlarmA(void) {
    sAlarm.AlarmTime.Hours = 0;
    sAlarm.AlarmTime.Minutes = 0;
    sAlarm.AlarmTime.Seconds = 10;
    sAlarm.AlarmTime.TimeFormat = RTC_HOURFORMAT24;
    sAlarm.AlarmMask = RTC_ALARMMASK_DATE_WEEKDAY |  // 不关心日期/星期
                       RTC_ALARMMASK_HOURS |          // 不关心小时
                       RTC_ALARMMASK_MINUTES;         // 不关心分钟
    // 只匹配秒==10,因此每10秒触发一次
    sAlarm.Alarm = RTC_ALARM_A;
    sAlarm.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_ALL;
    sAlarm.AlarmDateWeekDaySel = RTC_ALARMDATEWEEKDAYSEL_DATE;
    sAlarm.AlarmDateWeekDay = 1;
    HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN);
}

/* 打印当前时间到UART */
void Print_Current_Time(void) {
    char buf[64];
    HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
    HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
    snprintf(buf, sizeof(buf), "[%02d-%02d-%02d %02d:%02d:%02d] wake#%d\r\n",
             2000 + sDate.Year, sDate.Month, sDate.Date,
             sTime.Hours, sTime.Minutes, sTime.Seconds, wake_count);
    HAL_UART_Transmit(&huart2, (uint8_t*)buf, strlen(buf), 100);
}

int main(void) {
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_USART2_UART_Init();
    MX_RTC_Init();

    Set_Initial_Time();
    Configure_AlarmA();

    /* 使能RTC闹钟A中断 */
    HAL_NVIC_SetPriority(RTC_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(RTC_IRQn);

    while (1) {
        /* 进入停机模式前打印当前时间 */
        Print_Current_Time();

        /* 进入STOP模式,等待闹钟唤醒 */
        HAL_SuspendTick();
        HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
        HAL_ResumeTick();

        /* 唤醒后系统时钟需重新配置 */
        SystemClock_Config();
    }
}

/* RTC中断服务函数 */
void RTC_IRQHandler(void) {
    HAL_RTC_AlarmIRQHandler(&hrtc);
}

关键配置说明

参数值作用
RTC时钟源LSI (37kHz)低功耗模式下仍保持计时
异步预分频12437k/(124+1)=296Hz
同步预分频295296/(295+1)=1Hz 日历秒脉冲
闹钟掩码匹配秒==10每10秒触发一次闹钟A
低功耗模式STOP (LP regulator)唤醒后需重新配置系统时钟

编译与烧录

  1. 使用STM32CubeIDE创建工程,选择STM32L071RBTx;
  2. 将上述代码替换main.c中对应部分,并确保huart2已初始化(PA2-TX, PA3-RX);
  3. 编译生成.elf,通过ST-Link烧录到开发板;
  4. 连接USB-TTL模块到PA2(TX),打开串口助手,波特率115200。

验证方法与判定标准

功能验证:

  • 串口每10秒输出一行时间戳,格式如[2025-01-15 09:30:10] wake#1;
  • 时间每秒递增,日期在跨天时正确更新(可修改初始时间为23:59:55验证);
  • wake计数每次唤醒后加1,证明闹钟中断确实触发。

低功耗验证:

  • 将万用表调至µA档,串联在VDD供电回路中;
  • 观察电流变化:唤醒打印瞬间电流升至数mA,随后降至µA级(典型值<5µA);
  • 用示波器测量VDD电流波形,可见周期性脉冲(每10秒一次),脉冲宽度约几十ms。

判定标准:

  • 串口输出连续10次以上无丢失,时间正确递增;
  • 停机电流低于10µA(LSI+ RTC备份寄存器供电);
  • 唤醒后系统功能正常,无死机或复位。
预期输出示例(与代码逻辑严格一致,非实测数据):
[2025-01-15 09:30:00] wake#0(首次打印)
[2025-01-15 09:30:10] wake#1(10秒后闹钟唤醒)
[2025-01-15 09:30:20] wake#2(20秒后)
...