3. RTC闹钟唤醒与日历更新
任务定义
本案例面向STM32L0系列MCU(以STM32L071为例),实现以下闭环:
- 配置RTC在闹钟A中断中周期性唤醒MCU(周期10秒);
- 每次唤醒后读取RTC日历时间,通过UART打印当前日期时间;
- 进入低功耗停机模式(STOP Mode),用实际电流测量验证唤醒前后功耗差异。
硬件连接与测量点
代码实现
使用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) | 低功耗模式下仍保持计时 |
| 异步预分频 | 124 | 37k/(124+1)=296Hz |
| 同步预分频 | 295 | 296/(295+1)=1Hz 日历秒脉冲 |
| 闹钟掩码 | 匹配秒==10 | 每10秒触发一次闹钟A |
| 低功耗模式 | STOP (LP regulator) | 唤醒后需重新配置系统时钟 |
编译与烧录
- 使用STM32CubeIDE创建工程,选择STM32L071RBTx;
- 将上述代码替换
main.c中对应部分,并确保huart2已初始化(PA2-TX, PA3-RX); - 编译生成
.elf,通过ST-Link烧录到开发板; - 连接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秒后)...