【STM32 + CubeMX 教程】RTC 实时时钟 之 闹钟 -- F407篇

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 */ 中调用:
cpp 复制代码
RTC_SetAlarmA(14, 43, 30);   // 每天14:43:30触发

添加完成后,位置参考下图:

提示:

  1. RTC_SetAlarmA() 函数的参数是:时、分、秒。
  2. 调试时,把参数时间设置比当前时间晚 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 */ 中调用:
cpp 复制代码
RTC_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 中未开启闹钟中断
  • 回调函数名称写错
相关推荐
weiyvyy2 小时前
接口开发的完整流程:从需求到验证
驱动开发·嵌入式硬件·硬件架构·硬件工程
MC_J2 小时前
STM32+FMC驱动W9825G6 SDRAM程序以及遇到的问题讲解
stm32·单片机
少年潜行2 小时前
【开源】STM32驱动BH1750(附开源代码)
单片机
0南城逆流02 小时前
【STM32】知识点介绍八:UART/USART串口功能
stm32·单片机·嵌入式硬件
国家一级保护废物...2 小时前
51单片机day1
单片机·嵌入式硬件·51单片机
小白学电子_2 小时前
STM32常用HAL常见库函数快速运用和讲解
stm32·单片机·嵌入式硬件
woshihonghonga2 小时前
解决Eclipse的Copilot终端依赖问题
stm32·mcu·eclipse·copilot·ai编程
busideyang2 小时前
STM32中__weak(弱定义)函数核心总结
c语言·stm32·单片机·嵌入式硬件·嵌入式
可乐鸡翅好好吃2 小时前
RTC时钟源及其低功耗应用
单片机·嵌入式硬件·实时音视频