STM32F103C8T6 RTC闹钟实战:用LSI时钟源做个定时提醒器(附完整代码)
STM32F103C8T6 RTC闹钟实战打造高精度定时提醒系统在嵌入式开发中实时时钟(RTC)模块的应用场景非常广泛。对于STM32初学者来说掌握RTC闹钟功能不仅能加深对芯片外设的理解更能为实际项目开发打下坚实基础。本文将带你从零开始基于STM32F103C8T6的LSI时钟源构建一个完整的定时提醒系统。1. 项目规划与硬件设计1.1 需求分析与功能定义我们的目标是创建一个可自定义提醒时间的桌面定时器主要功能包括基于RTC的精确时间记录可编程闹钟触发多种提醒方式LED/蜂鸣器简单的时间设置接口硬件选型清单组件型号/参数数量备注MCUSTM32F103C8T61蓝色pill开发板时钟源LSI(内部40kHz)1无需外部晶振提醒装置LED/有源蜂鸣器各1用户可选输入设备轻触按键3设置/调整用显示设备0.96寸OLED1可选1.2 电路连接要点核心电路连接示意图STM32F103C8T6 ├── PC13 → LED阳极阴极接地 ├── PB8 → 蜂鸣器正极 ├── PA0 → 设置按键 ├── PA1 → 增加按键 ├── PA2 → 减少按键 └── I2C1 → OLED显示屏提示蜂鸣器需串联100Ω限流电阻LED建议使用220Ω限流电阻。2. RTC基础与LSI时钟配置2.1 RTC模块工作原理STM32的RTC模块是一个独立的BCD计时器具有以下关键特性32位可编程计数器自动处理闰年计算闹钟中断功能低功耗模式下仍可运行时钟源对比表特性LSILSEHSE精度±5%±20ppm±50ppm频率40kHz32.768kHz4-16MHz功耗低极低高稳定性一般优秀优秀需要外部元件否是是2.2 LSI时钟配置实战使用LSI作为RTC时钟源的初始化代码void RTC_Init(void) { // 启用PWR和BKP时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); PWR_BackupAccessCmd(ENABLE); // 检查是否是首次配置 if (BKP_ReadBackupRegister(BKP_DR1) ! 0xA5A5) { // 启用LSI时钟 RCC_LSICmd(ENABLE); while(RCC_GetFlagStatus(RCC_FLAG_LSIRDY) RESET); // 配置RTC时钟源 RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI); RCC_RTCCLKCmd(ENABLE); // 等待RTC同步 RTC_WaitForSynchro(); RTC_WaitForLastTask(); // 设置预分频器 RTC_SetPrescaler(40000 - 1); // 40kHz/40000 1Hz RTC_WaitForLastTask(); // 标记已初始化 BKP_WriteBackupRegister(BKP_DR1, 0xA5A5); } }注意LSI时钟精度相对较低适合对时间精度要求不高的应用。如需更高精度建议使用外部LSE晶振。3. 闹钟功能实现3.1 闹钟中断配置完整的闹钟中断配置流程void RTC_Alarm_Config(void) { NVIC_InitTypeDef NVIC_InitStructure; // 配置RTC全局中断 NVIC_InitStructure.NVIC_IRQChannel RTC_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority 1; NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStructure); // 使能闹钟中断 RTC_ITConfig(RTC_IT_ALR, ENABLE); RTC_WaitForLastTask(); } // 中断服务程序 void RTC_IRQHandler(void) { if(RTC_GetITStatus(RTC_IT_ALR) SET) { // 触发提醒动作 GPIO_SetBits(GPIOC, GPIO_Pin_13); // LED亮 // 或 GPIO_SetBits(GPIOB, GPIO_Pin_8); // 蜂鸣器响 RTC_ClearITPendingBit(RTC_IT_ALR); RTC_WaitForLastTask(); } }3.2 动态设置闹钟时间实用的闹钟设置函数支持任意时间点触发void Set_Alarm(uint8_t hours, uint8_t minutes, uint8_t seconds) { time_t current_time, alarm_time; struct tm time_struct; // 获取当前RTC时间 current_time RTC_GetCounter(); time_struct *localtime(current_time); // 设置闹钟时间保留年月日只修改时分秒 time_struct.tm_hour hours; time_struct.tm_min minutes; time_struct.tm_sec seconds; alarm_time mktime(time_struct); RTC_SetAlarm(alarm_time); RTC_WaitForLastTask(); // 如果需要重复闹钟可以在此设置下一次触发时间 }4. 用户交互与功能扩展4.1 时间设置界面实现通过三个按键实现完整的时间设置功能// 按键处理函数示例 void KEY_Handler(void) { static uint8_t selected_field 0; // 0-小时,1-分钟,2-秒 if(KEY_Setting_Pressed()) { selected_field (selected_field 1) % 3; } else if(KEY_Up_Pressed()) { Adjust_Time(selected_field, 1); // 增加 } else if(KEY_Down_Pressed()) { Adjust_Time(selected_field, -1); // 减少 } } // 时间调整函数 void Adjust_Time(uint8_t field, int8_t delta) { time_t current_time; struct tm time_struct; current_time RTC_GetCounter(); time_struct *localtime(current_time); switch(field) { case 0: // 小时 time_struct.tm_hour (time_struct.tm_hour delta 24) % 24; break; case 1: // 分钟 time_struct.tm_min (time_struct.tm_min delta 60) % 60; break; case 2: // 秒 time_struct.tm_sec (time_struct.tm_sec delta 60) % 60; break; } RTC_SetCounter(mktime(time_struct)); RTC_WaitForLastTask(); }4.2 番茄钟工作模式实现扩展功能实现25分钟工作5分钟休息的番茄钟循环void Tomato_Mode_Start(void) { time_t current_time; struct tm time_struct; // 获取当前时间 current_time RTC_GetCounter(); time_struct *localtime(current_time); // 设置25分钟后闹钟工作时间 time_struct.tm_min 25; RTC_SetAlarm(mktime(time_struct)); RTC_WaitForLastTask(); // 在OLED显示状态 OLED_ShowString(1, 1, Work Mode: 25:00); } // 在闹钟中断中处理模式切换 void RTC_IRQHandler(void) { if(RTC_GetITStatus(RTC_IT_ALR) SET) { if(current_mode WORK_MODE) { // 切换到休息模式 current_mode BREAK_MODE; Set_Break_Alarm(5); // 5分钟休息 OLED_ShowString(1, 1, Break Time: 5:00); } else { // 切换回工作模式 current_mode WORK_MODE; Set_Work_Alarm(25); // 25分钟工作 OLED_ShowString(1, 1, Work Mode: 25:00); } RTC_ClearITPendingBit(RTC_IT_ALR); RTC_WaitForLastTask(); } }5. 系统优化与实用技巧5.1 提高LSI时钟精度的方法虽然LSI精度有限但可以通过以下方法改善温度补偿记录不同温度下的时钟偏差软件补偿定期同步通过外部时间源如GPS定期校准平均算法统计长期偏差动态调整预分频值校准代码示例void LSI_Calibration(float ppm) { uint32_t prescaler 40000; // 默认值 // 根据ppm误差调整预分频值 prescaler (uint32_t)(40000 * (1 ppm/1000000.0)); RTC_SetPrescaler(prescaler - 1); RTC_WaitForLastTask(); }5.2 低功耗优化实现电池供电时的省电设计void Enter_LowPower_Mode(void) { // 关闭外设时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_ALL, DISABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_ALL, DISABLE); // 配置唤醒源为RTC闹钟 PWR_WakeUpPinCmd(ENABLE); RTC_ClearFlag(RTC_FLAG_ALRAF); // 进入待机模式 PWR_EnterSTANDBYMode(); }5.3 常见问题排查RTC初始化失败的可能原因未正确启用PWR和BKP时钟忘记调用PWR_BackupAccessCmd(ENABLE)未等待LSI就绪(RCC_FLAG_LSIRDY)未正确处理RTC同步和任务等待闹钟不触发的检查步骤确认RTC_ITConfig(RTC_IT_ALR, ENABLE)已调用检查NVIC中断配置是否正确验证闹钟时间是否设置在未来确保RTC计数器正常运行可通过RTC_GetCounter()读取