STM32F407 的 RTC,有两路可编程闹钟。
本篇图解教程,示范实现 RTC 的闹钟功能。
**示范 1:**闹钟 A,指定具体时间,如 6:34:00。适合场景:定时唤醒、每日定时任务。
**示范 2:**闹钟 B,相对时间闹钟,即当前时间 + N 秒后触发。适合场景:间隔喂鱼、浇灌、周期性任务。
目录
[二、CubeMX 配置 RTC 闹钟](#二、CubeMX 配置 RTC 闹钟)
[三、实现 闹钟A (固定时间・每日触发)](#三、实现 闹钟A (固定时间・每日触发))
[四、实现 闹钟 B(相对时间・延时触发)](#四、实现 闹钟 B(相对时间・延时触发))
[五、RTC 闹钟 常见问题](#五、RTC 闹钟 常见问题)
一、工程准备
上一篇,我们已实现了 RTC 的日历功能:RTC 初始化、修改日历数据。
本篇作为续章,需要在上篇 RTC 日历功能的基础上,添加闹钟功能。
因此,请先实现上篇:【STM32 + CubeMX 教程】RTC 实时时钟 之 日历 -- F407篇
二、CubeMX 配置 RTC 闹钟
步骤 1-1:使能 闹钟与中断
具体操作:
- Alarm A(闹钟A):选择 Internal Alarm 。
- Alarm B(闹钟B):选择 Internal Alarm 。
- 打勾使能 RTC 闹钟 A/B 的中断(重点!不开启则无法进入中断回调)
注意:
很多教程会在此页面直接设置闹钟参数,这种方式不够灵活、可控。
我们不采用这种方式,而是通过代码灵活配置。
步骤 1-2:生成初始化代码
具体操作:
- 点击右上角 Generate Code 按钮,CubeMX 自动将 RTC 闹钟功能添加到工程中
至此,CubeMX 对 RTC 闹钟的配置已完成,接下来进入应用层代码编写。
三、实现 闹钟A (固定时间・每日触发)
**闹钟 A 示范:**指定 时、分、秒 每天触发,如 6:30:00。
适用场景:起床闹铃、定时喂鱼、定时浇灌等。
步骤 3-1:编写 闹钟 A 配置函数 RTC_SetAlarmA ()
具体操作:
打开 main.c 文件,
在 /* USER CODE BEGIN 0 */ 和 /* USER CODE END 0 */ 之间添加:
cpp
/******************************************************************************
* 函 数: RTC_SetAlarmA
* 功 能: 配置 RTC 闹钟A
* 指定具体时间触发 (如 6:34:00); 适合场景:定时唤醒等。
* 参 数: uint8_t hours 闹钟小时 0~23
* uint8_t mins 闹钟分钟 0~59
* uint8_t secs 闹钟秒数 0~59
* 返回值: HAL_StatusTypeDef HAL状态
* 备 注: 最后修改_2026年03月17日
******************************************************************************/
HAL_StatusTypeDef RTC_SetAlarmA(uint8_t hours, uint8_t mins, uint8_t secs)
{
/* 声明结构体 */
RTC_AlarmTypeDef sAlarm = {0}; // 闹钟配置结构体
/* 关闭闹钟A */
HAL_RTC_DeactivateAlarm(&hrtc, RTC_ALARM_A); // 关闭闹钟; 必须先关闭再配置
/* 配置闹钟A参数 */
sAlarm.AlarmTime.Hours = hours; // 闹钟 小时
sAlarm.AlarmTime.Minutes = mins; // 闹钟 分钟
sAlarm.AlarmTime.Seconds = secs; // 闹钟 秒数
sAlarm.AlarmDateWeekDay = 1; // (被屏蔽了, 此值无效)sAlarm.AlarmDateWeekDay 是一个二义性字段,它的含义完全由 sAlarm.AlarmDateWeekDaySel 决定; 指每周/每月的是第几天
sAlarm.AlarmTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE; // 无夏令时(中国不实行夏令时)
sAlarm.AlarmTime.StoreOperation = RTC_STOREOPERATION_RESET; // 复位操作(固定此值即可)
sAlarm.AlarmMask = RTC_ALARMMASK_DATEWEEKDAY ; // 闹钟屏蔽位: 屏蔽日期/星期,只匹配时分秒,即每天触发
sAlarm.AlarmDateWeekDaySel = RTC_ALARMDATEWEEKDAYSEL_DATE; // 日期/星期模式切换。DATE-日期、WEEK-星期
sAlarm.Alarm = RTC_ALARM_A; // 配置的是: 闹钟A
/* 设置闹钟、开启中断 */
if (HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN) != HAL_OK) // 设置闹钟A并开启中断,失败则执行错误处理
{
printf("闹钟A 设置失败! \r\n"); // 打印 错误提示
return HAL_ERROR; // 返回 错误标志
}
else
{
printf("闹钟A 设置成功! 触发时间:%02d:%02d:%02d \n", hours, mins, secs); // 打印 闹钟提示
return HAL_OK; // 返回 正常标志
}
}
闹钟初始化结构体中,关键参数解释:
| 成员、参数 | 作用 |
|---|---|
| Hours | 时 |
| Minutes | 分 |
| Seconds | 秒 |
| DayLightSaving | 夏令时,中国不实行。固定设置为 NONE |
| StoreOperation | 闹钟触发后,硬件清零亚秒计数器,固定写 RESET。 |
| AlarmDateWeekDaySel | 重点。选择 日期 / 星期。DATE-月,WEEKDAY-星期。此参数配合AlarmDateWeekDay一起作用。 |
| AlarmDateWeekDay | 重点。第几天。此参数配合 AlarmDateWeekDaySel 一起使用。 |
| AlarmMask | 重重重点。闹钟屏蔽位 。决定哪些时间字段不参与对比。这是 RTC 闹钟最重要、最容易搞错、必须理解透 的一个参数。如,上面代码中,赋值 RTC_ALARMMASK_DATEWEEKDAY 后,闹钟将忽视 "日/周" 的值,匹配 "时、分、秒" 就能触发中断。当需要控制多个字段时:RTC_ALARMMASK_DATEWEEKDAY | RTC_ALARMMASK_HOURS | RTC_ALARMMASK_MINUTES,如上,闹钟将忽视 "日/周、时、分", 只匹配秒数即可触发闹钟。 |
步骤 3-2: 编写 闹钟 A 中断回调函数
当闹钟A触发后(中断),程序会自动调用对应的中断回调函数。
我们在代码中编写闹钟A的中断回调函数即可。
具体操作:
- 在刚才RTC_SetAlarmA()函数的下方。
- 添加 闹钟A的中断回调函数: HAL_RTC_AlarmAEventCallback(),具体如下:
cpp/****************************************************************************** * 函 数: HAL_RTC_AlarmAEventCallback * 功 能: RTC 闹钟 A 中断回调函数 * 说 明: 1. 此函数在闹钟A触发时,由NVIC处理调用,无需程序或手动调用 * 2. 如需循环触发,可在此重新设置闹钟 * 参 数: hrtc:RTC句柄 * 返回值: 无 * 备 注: 最后修改_2026年03月17日 ******************************************************************************/ void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc) { /* 执行闹钟任务操作 */ printf("闹钟A 触发!! \r\n"); /* 如需每天相同时间执行,无需重新设置,因为AlarmA的配置是每天都能触发 */ // RTC_SetAlarmA(6,30,00); }
**注意 1:**每一个中断回调函数,都有固定的名称,不可自定义修改,否则中断时程序会找不到!!
**注意 2:**闹钟A与闹钟B的中断回调函数,名称不一样,注意细看!
- 闹钟A 中断回调函数:HAL_RTC_AlarmAEventCallback (RTC_HandleTypeDef *hrtc); // 无Ex
- 闹钟B 中断回调函数:HAL_RTCEx_AlarmBEventCallback (RTC_HandleTypeDef *hrtc); // 有Ex
步骤 3-3:main 函数调用
具体操作:
- 在 main() 函数的 /* USER CODE BEGIN 2 */ 中调用:
cppRTC_SetAlarmA(14, 43, 30); // 每天14:43:30触发
添加完成后,位置参考下图:

提示:
- RTC_SetAlarmA() 函数的参数是:时、分、秒。
- 调试时,把参数时间设置比当前时间晚 1 分钟左右。这样编译、烧录、打开串口助手后,稍等 ,即可看到触发效果。
步骤 3-4:烧录 & 验证
烧录后,通过串口助手的调试信息,可以看到 闹钟A 按指定的"时分秒"触发。

四、实现 闹钟 B(相对时间・延时触发)
闹钟 B 示范: 当前时间 + N 秒后触发一次,可循环延时。
适用场景:间隔喂鱼、浇灌、周期性任务。
闹钟B 与 闹钟A 的实现方法,区别在于:
- 闹钟初始化结构体,名称上有 A 与 B 的区别
- 闹钟A 中断回调函数:HAL_RTC_AlarmAEventCallback (RTC_HandleTypeDef *hrtc); // 无Ex
- 闹钟B 中断回调函数:HAL_RTCEx_AlarmBEventCallback (RTC_HandleTypeDef *hrtc); // 有Ex
步骤 4-1:编写 闹钟B 配置函数 RTC_SetAlarmB ( )
具体操作:
- 打开 main.c 文件,
- 在 RTC_SetAlarmA () 函数下方, 添加闹钟B 配置函数 RTC_SetAlarmB() , 具体如下:
cpp/****************************************************************************** * 函 数: RTC_SetAlarmB * 功 能: 配置 RTC 闹钟B * 相对时间闹钟(当前时间N秒后触发) * 参 数: uint32_t secs: 相对当前时间的秒数 0~2592000(最大30天) * 返回值: HAL_StatusTypeDef HAL状态 * 备 注: 最后修改_2026年03月17日 ******************************************************************************/ HAL_StatusTypeDef RTC_SetAlarmB(uint32_t secs) { /* 声明结构体 */ RTC_TimeTypeDef sTime = {0}; // 时间结构体(时/分/秒) RTC_DateTypeDef sDate = {0}; // 日期结构体(年/月/日/周) RTC_AlarmTypeDef sAlarm = {0}; // 闹钟配置结构体 /* 关闭闹钟B */ HAL_RTC_DeactivateAlarm(&hrtc, RTC_ALARM_B); // 关闭闹钟; 必须先关闭再配置 /* 获取当前RTC时间和日期 */ HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN); // 读取当前RTC的时间; 必须读日期,否则时间锁存不更新 HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN); // 读取当前RTC的日期; 必须读日期,否则时间锁存不更新 /* 计算目标闹钟时间(时间戳转换,处理时分秒进位)*/ uint32_t nowSecs = sTime.Hours*3600 + sTime.Minutes*60 + sTime.Seconds; uint32_t alarm_secs = nowSecs + secs; uint8_t hh = (alarm_secs / 3600) % 24; uint8_t mm = (alarm_secs % 3600) / 60; uint8_t ss = alarm_secs % 60; /* 配置闹钟B参数 */ sAlarm.AlarmTime.Hours = hh; // 闹钟 小时 sAlarm.AlarmTime.Minutes = mm; // 闹钟 分钟 sAlarm.AlarmTime.Seconds = ss; // 闹钟 秒数 sAlarm.AlarmTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE; // 无夏令时(中国不实行夏令时) sAlarm.AlarmTime.StoreOperation = RTC_STOREOPERATION_RESET; // 复位操作(固定此值即可) sAlarm.AlarmDateWeekDaySel = RTC_ALARMDATEWEEKDAYSEL_DATE; // 选择日期/星期模式;新手最易混淆点。DATE=每月中的第几天、WEEKDAY=每星期中的第几天。 sAlarm.AlarmDateWeekDay = sDate.Date; // (被屏蔽了)sAlarm.AlarmDateWeekDay 是一个二义性字段,它的含义完全由 sAlarm.AlarmDateWeekDaySel 决定; 指每周/每月的是第几天 sAlarm.AlarmMask = RTC_ALARMMASK_DATEWEEKDAY ; // 闹钟屏蔽位; 屏蔽日期/星期,只匹配时分秒,即每天触发 sAlarm.Alarm = RTC_ALARM_B; // 配置的是: 闹钟B /* 设置闹钟并开启中断 */ if (HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN) != HAL_OK) // 设置闹钟B并开启中断,失败则执行错误处理 { printf("闹钟B 设置失败! \r\n"); // 打印 错误提示 return HAL_ERROR; // 返回 错误标志 } else { printf("闹钟B 设置成功! 触发时间:%02d:%02d:%02d \n", hh, mm, ss); // 打印 下次触发时间 return HAL_OK; // 返回 正常标志 } }
步骤 4-2: 编写 闹钟B 中断回调函数
当 闹钟B 触发后(中断),程序会自动调用其中断回调函数。
我们在代码中编写 闹钟B 的中断回调函数即可。
具体操作:
- 在 闹钟A 中断回调函数的下方
- 编写如下闹钟B的 中断回调函数 (注意函数名称):
cpp/****************************************************************************** * 函 数: HAL_RTCEx_AlarmBEventCallback * 功 能: RTC 闹钟B 中断回调函数 * 说 明: 1. 此函数在闹钟B触发时,由NVIC处理调用,无需程序或手动调用 * 2. 如需循环触发,必须在此重新调用 RTC_SetAlarmB() * 参 数: hrtc:RTC句柄 * 返回值: 无 * 备 注: 最后修改_2026年03月17日 ******************************************************************************/ void HAL_RTCEx_AlarmBEventCallback(RTC_HandleTypeDef *hrtc) { /* 执行闹钟任务操作 */ printf("闹钟B 触发!! \r\n"); /* 闹钟B的配置,只能按时分秒执行一次,如需循环执行,需要再次调用 */ RTC_SetAlarmB(5); // 设置下一个5秒再次触发 }
**注意 1:**闹钟的触发,是按设定条件触发的。刚才闹钟A是设定为每天按指定时间触发,因而不需要在回调函数里再次调用设置。而闹钟B,我们设计期望是每隔固定时间就触发一次,因此,需要在回调函数里执行 RTC_SetAlarmB(5),即把闹钟B设置为5秒后触发,这样即可循环触发 闹钟B。
**注意 2:**再次提醒,闹钟A与闹钟B的中断回调函数,名称不一样,写错了将无法触发。
- 闹钟A 中断回调函数:HAL_RTC_AlarmAEventCallback (RTC_HandleTypeDef *hrtc); // 无Ex
- 闹钟B 中断回调函数:HAL_RTCEx_AlarmBEventCallback (RTC_HandleTypeDef *hrtc); // 有Ex
步骤 4-3:main 函数调用
具体操作:
- 在 main() 函数的 /* USER CODE BEGIN 2 */ 中调用:
cppRTC_SetAlarmB(5); // 5秒后触发
添加完成后,位置参考下图:

注意 1:
- RTC_SetAlarmB() 函数,参数是 秒数。
- 分钟 的操作:秒数*60,如,要求15分钟后触发,参数就写 (15*60)。
- 小时 的操作:秒数*60*60。如,要求5小时后触发,参数写 (5*3600)。
步骤4-4:烧录 & 验证
烧录后,通过串口助手的调试信息,可以看到 闹钟A 按指定的"时分秒"触发。

五、RTC 闹钟 常见问题
1、调试完闹钟A、B后,后面修改了程序,没有再设置闹钟A/B,但为什么它俩还能触发?
如果在调试时,配置过闹钟A或B后, 闹钟配置会保存在后备域,由纽扣电池供电保持。
即使修改程序后不再配置闹钟,但如果闹钟的中断函数、回调函数还在,不断纽扣电池,那么,闹钟配置不会消失,将继续按最后设定的条件进行触发。
如果想彻底消除 闹钟,两种方法:
- 在新的程序中,调用一次 HAL_RTC_DeactivateAlarm(&hrtc, RTC_ALARM_A) ,可以关掉闹钟A, 闹钟B亦如是。
- 板子在断电后扣掉纽扣电池,这样闹钟配置就消失了。
2、闹钟不触发常见原因
- CubeMX 中未开启闹钟中断
- 回调函数名称写错

