尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

STM32F103 RTC实战:从备份域到OLED与DS18B20的完整实现

STM32F103 RTC实战:从备份域到OLED与DS18B20的完整实现 简介这是一份基于STM32F103的RTC实时时钟综合应用工程面向嵌入式初学者与物联网开发者演示如何利用内置RTC模块保持精准时间并通过I2C总线驱动OLED屏显示日期、时间与温度配合按键完成调时等人机交互。工程采用Keil MDK开发共93个文件、压缩后仅475KB其中包含44个.h头文件与42个.c源文件覆盖RTC、OLED、按键、DS18B20温度采集、IIC通信、延时、串口等核心模块另有启动文件与应用配置文件目录结构清晰适合对照学习底层驱动与中断处理思路。已有377人关注学习。整个项目包含完整的初始化、时间读取与设置流程以及OLED显示、按键扫描与温度采集示例可直接烧录运行并二次开发是理解STM32片上外设组合应用的实用参考资料。1. 一块电池撑起的时间从 RTC 掉电保持说起做嵌入式设备的人多半遇到过这种尴尬设备断电重启后时间回到 2000 年 1 月 1 日。如果项目只是做个时钟显示倒无所谓但一旦涉及数据记录、定时上报、日志时间戳时间错乱直接导致业务数据作废。STM32F103 内置的 RTCReal-Time Clock模块就是为这个场景设计的——它挂在备份域供电回路上主电源断开后由 VBAT 引脚上的纽扣电池继续供电秒计数器照常走时。这份工程是基于标准外设库StdPeriph_Lib V3.5实现的完整 RTC 应用包含 RTC 初始化与时间设置、I2C 接口 OLED 显示、独立按键调时、DS18B20 温度采集四部分。适合两类读者一类是把 RTC 当“黑盒”调用、想搞明白备份域寄存器和 BKP 寄存器关系的开发者另一类是想在一个工程里同时理顺 OLED 驱动、按键消抖和单总线传感器读取的入门者。下文所有代码基于 STM32F103C8T6主频 72MHz标准外设库 V3.5Keil MDK5 工程可直接编译下载。2. 从寄存器到代码RTC 模块的配置链路与 BKP 域操作2.1 为什么 RTC 寄存器要挂在备份域上STM32F103 的 RTC 由两部分组成一个 32 位可编程预分频器和一个 32 位向上计数寄存器RTC_CNT。计数器的时钟源来自 RTC 时钟域这个时钟域由 LSE32.768kHz 外部低速晶振、LSI40kHz 内部低速 RC 振荡器或 HSE 分频后的时钟驱动。关键在于RTC 的供电域是 VBAT 和 VDD 的“或”关系——主电源掉电时VBAT 引脚上的电池自动接管。因此 RTC_CNT、RTC_PRL、RTC_CNT 等寄存器都位于备份域内访问前必须先使能 PWR 和 BKP 外设时钟并操作 PWR_CR 寄存器的 DBP 位打开备份域写保护。这个设计带来的实际影响有两个。第一RTC 的初始化代码必须放在系统上电后最先执行的位置因为只要 VBAT 有电RTC 就在走时你读到的 RTC_CNT 是持续累加的秒数。第二RTC 一旦配置过再次上电时不能重复执行初始化序列否则会触发 RTC 配置标志位RCC_CSR 的 RTCEN 位和 RTC_CRL 的 RTCEN 位的保护机制导致写操作被忽略。工程里通过检查 BKP_ReadBackupRegister 的备份寄存器值来决定是否跳过初始化这是官方推荐做法也是判断 RTC 是否“跑过”的依据。2.2 时钟选型LSE 还是 LSI代码中 RTC 时钟源选择 LSE 外部低速晶振。选 LSE 而不是 LSI原因在于精度LSI 的频率在 30kHz 到 50kHz 之间漂移具体值因芯片个体和温度而异用来做日历时钟一天能差出几十秒LSE 晶振的精度通常在 ±20ppm 以内换算下来一天误差不到 2 秒。代价是 LSE 起振慢上电后需要等待 LSE_RDY 标志置位极端情况下要等 1 到 2 秒。如果你的板子上没焊 32.768kHz 晶振代码会卡死在等待循环里这时可以改用 LSI但要在校准上做补偿——这属于后文排错章节的讨论范围。RTC 预分频器的配置是一个数学问题。RTC 时钟源频率为 32.768kHz要得到 1Hz 的秒脉冲需要设置 RTC_PRL 32767即 32768 分频。如果使用 LSI约 40kHz分频值应设为 39999并在校准寄存器中做微调。代码中的 RTC_SetPrescaler(32767) 就是写预分频寄存器这个值决定了秒计数器 RTC_CNT 的递增频率不要随意改动否则时间跑快或跑慢。2.3 初始化与时间设置的可复现代码下面这段是工程中 RTC 初始化的核心函数已按标准外设库风格整理。注意这里用的是库函数封装如果直接操作寄存器本质是相同的——先打开备份域写保护再操作 RTC 寄存器。void RTC_Init(void) { // 使能 PWR 和 BKP 外设时钟访问备份域寄存器的前提 RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); // 打开备份域写保护PWR_CR 的 DBP 位置 1 PWR_BackupAccessCmd(ENABLE); // 检查备份寄存器标志判断是否首次配置 if (BKP_ReadBackupRegister(BKP_DR1) ! 0xA5A5) { // 使能 LSE 外部低速晶振等待其稳定 RCC_LSEConfig(RCC_LSE_ON); while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) RESET); // 选择 LSE 作为 RTC 时钟源使能 RTC 时钟 RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); RCC_RTCCLKCmd(ENABLE); // 等待 RTC 寄存器同步和写入完成 RTC_WaitForSynchro(); RTC_WaitForLastTask(); // 使能 RTC 秒中断用于主循环轮询或唤醒 RTC_ITConfig(RTC_IT_SEC, ENABLE); RTC_WaitForLastTask(); // 配置预分频32768 / 32767 - 1Hz 秒计数 RTC_SetPrescaler(32767); RTC_WaitForLastTask(); // 设置初始时间2024 年 1 月 1 日 00:00:00 // 换算为 UNIX 时间戳UTC8 时区需自行调整 RTC_SetCounter(1704067200); RTC_WaitForLastTask(); // 写入备份寄存器标志下次上电跳过初始化 BKP_WriteBackupRegister(BKP_DR1, 0xA5A5); } else { // 非首次上电等待 RTC 与 APB1 总线同步后直接使用 RTC_WaitForSynchro(); } }逻辑说明这段代码的关键在 if/else 分支。首次上电时写入备份寄存器标志此后每次复位或重新上电RTC 配置被跳过只等待同步——因为 RTC 时钟域和 APB1 总线时钟域不同步直接读 RTC_CNT 可能拿到中间值RTC_WaitForSynchro 就是等 RTC 寄存器内容映射到 APB1 接口。值得注意的参数是 RTC_SetCounter 的入参它是一个 UNIX 时间戳自 1970 年 1 月 1 日以来的秒数。工程中通常用时间戳而不是年月日时分秒的数组因为时间戳可以直接参与加减运算省去日期转换的麻烦。2.4 读时间与 UTC 转换RTC 读时间同样需要注意同步问题。直接读 RTC_CNT 寄存器时由于 RTC 时钟与 APB1 时钟异步读到的值可能比实际值大或小(在边界翻转时)。标准做法是先等同步标志再读取。uint32_t RTC_GetTimeStamp(void) { // 等待 RTC 寄存器同步完成防止读到跳变中的值 RTC_WaitForSynchro(); // 读取当前秒计数值 return RTC_GetCounter(); } void RTC_TimeToDateTime(uint32_t timestamp, RTC_DateTime_t *dt) { // 基于格里高利历的日期换算算法 // 使用时间戳换算标准公式注意 1900 年起始的偏移校准 // 此处省略具体换算实现可参考 C 标准库 gmtime 逻辑 }一个实用建议UTC 与本地时间的偏移不要在 RTC 层处理。RTC 始终存储 UTC 时间戳显示层做时区偏移。否则夏令时切换或跨时区调试时你会被时间错乱搞到头大。日期换算的开销极低一个除法取模的事情现代 MCU 上微秒级完成不需要用查表法优化。3. OLED 显示链路I2C 时序与 0.96 寸屏的驱动细节3.1 I2C 地址与起始时序工程中的 OLED 模块型号为 0.96 寸 128x64SSD1306 控制器通过 I2C 接口连接。它的 I2C 地址由 SA0 引脚的电平决定SA0 接地时地址为 0x787 位地址 0x3C接 VCC 时为 0x7A7 位地址 0x3D。代码里使用软件模拟 I2C——用两个 GPIO 引脚模拟 SCL 和 SDA 时序。选软件模拟而不是硬件 I2C 的理由很现实STM32F103 的硬件 I2C 在总线异常后容易锁死需要繁琐的恢复时序而软件模拟只需要把引脚配成开漏输出加上拉电阻就能很好避免这类问题。软件模拟 I2C 的起始条件是SCL 高电平时SDA 产生一个高到低的跳变。停止条件相反SCL 高电平时SDA 从低到高。数据位在 SCL 高电平期间必须保持稳定在 SCL 低电平期间变化。这个时序用 GPIO 的 set/reset 操作直接实现延时通常设为 5us 左右对应 100kHz~400kHz 的 I2C 速率。void OLED_WriteByte(uint8_t dat, uint8_t cmd) { uint8_t i; // I2C 起始信号SCL1 时 SDA 拉低 I2C_Start(); // 发送设备地址0x78SA00 写方向 I2C_SendByte(OLED_ADDR_WRITE); I2C_WaitAck(); // 控制字节0x00 表示后跟命令0x40 表示后跟数据 if (cmd OLED_CMD) I2C_SendByte(0x00); else I2C_SendByte(0x40); I2C_WaitAck(); // 写入数据或命令字节 I2C_SendByte(dat); I2C_WaitAck(); // 停止信号 I2C_Stop(); }代码说明控制字节是 SSD1306 在 I2C 模式下的特有机制0x00 表示后续字节是命令0x40 表示后续字节是显示数据GRAM 内容。每次 I2C 事务可以连续发送多个命令字节先发 0x00 控制字节再跟一串命令但工程中为了代码简洁每次只发一个字节。逻辑说明OLED 的 GRAM 有 128x64 位SSD1306 将显示内存分 8 页Page0~Page7每页 128 字节对应屏幕上的 8 行像素块。写显示数据时需要先设置页地址和列地址再连续写入数据字节。3.2 显存刷新策略与汉字字模存储OLED 驱动方式有两种直接写和显存缓冲。工程里采用的是直接写即每次更新内容时直接操作 SSD1306 的 GRAM。这种方式省 RAM但有个问题频繁局部刷新时屏幕会出现闪烁。 我一般建议在资源允许时 加一个显存缓冲区先修改缓冲区再整体写入。128x64 的显存只需 1KB RAM对 F103 的内存压力不大。字模的存储方式直接决定了编码的复杂度和字模的读取方式。工程中使用的是标准的16x16汉字字模——每个汉字占32字节按行排列。字模数据的排列顺序与SSD1306的页扫描方式每页8像素逐列扫描是对应的。取模方式不同字模的数据顺序就不同直接用错的字模数据汉字会出现上下或左右颠倒。// OLED 显示字符串支持可见 ASCII 和 16x16 汉字混排 void OLED_ShowString(uint8_t x, uint8_t y, const char *str, uint8_t size) { uint8_t ch; // 遍历字符串 while (*str ! \0) { ch *str; // ASCII 可视字符范围 0x20~0x7E if (ch 0x20 ch 0x7E) { OLED_ShowChar(x, y, ch, size); x size / 2; // ASCII 字符宽度为汉字的一半 if (x 128 - size / 2) // 到达右边界换行 { x 0; y size; } } else { // 中文字符UTF-8 编码下占 3 字节需合并后查表 // 取前两字节 第三字节组合成 GB2312 区位码 // 实际工程中通过字模取模软件生成数组按内码索引查表 OLED_ShowChinese(x, y, ch, size); x size; if (x 128 - size) { x 0; y size; } } } }注意一个工程细节标准外设库版本的 OLED 驱动中汉字编码通常采用 GB2312 或 GBK而编辑器的源文件编码如果是 UTF-8中文字符串的存储方式与显示函数的解析方式不一致导致显示乱码。 常见做法是显示函数根据编码方式将 UTF-8 三字节合并为 GBK 内码去查表。 或者使用专门的取模软件把需要的汉字预先取出定义成数组变量直接通过下标访问避开编码转换问题。 我的建议是在屏幕上要显示的内容如果固定就预置字模数组如果是动态内容就用显示函数。这样取模工具生成一个 .h 文件包含所有需要的汉字字模数组代码做好索引即可。3.3 OLED 初始化序列SSD1306 上电后默认进入睡眠模式需要发送初始化命令才能点亮。初始化序列是固定的参照数据手册即可。下面给出与工程一致的序列void OLED_Init(void) { // 延时等待 SSD1306 内部复位完成 delay_ms(100); OLED_WriteCmd(0xAE); // 关闭显示 OLED_WriteCmd(0x20); // 设置内存寻址模式 OLED_WriteCmd(0x02); // 页寻址模式便于按页写入 OLED_WriteCmd(0xB0); // 设置页起始地址 Page0 OLED_WriteCmd(0xC8); // 扫描方向从上到下COM0~COM63 OLED_WriteCmd(0x00); // 设置低列起始地址 OLED_WriteCmd(0x10); // 设置高列起始地址 OLED_WriteCmd(0x40); // 设置显示起始行0x40 为行 0 OLED_WriteCmd(0x81); // 设置对比度 OLED_WriteCmd(0x7F); // 对比度值1~256 可调越大越亮 OLED_WriteCmd(0xA1); // 段重映射Segin0-SEG0镜像设置 OLED_WriteCmd(0xA6); // 正常显示非反色 OLED_WriteCmd(0xA8); // 设置复用比 OLED_WriteCmd(0x3F); // 1/64 duty128x64 屏 OLED_WriteCmd(0xA4); // 恢复 RAM 内容显示 OLED_WriteCmd(0xD3); // 设置显示偏移 OLED_WriteCmd(0x00); // 无偏移 OLED_WriteCmd(0xD5); // 设置时钟分频因子 OLED_WriteCmd(0x80); // 分频因子 F1振荡器频率 8:1 OLED_WriteCmd(0xD9); // 设置预充电周期 OLED_WriteCmd(0x22); // 相位 12相位 22 OLED_WriteCmd(0xDA); // 设置引脚硬件配置 OLED_WriteCmd(0x12); // 使用顺序 COM 配置无左右偏移 OLED_WriteCmd(0xDB); // 设置 VCOMH 电平 OLED_WriteCmd(0x20); // 0.77xVCC OLED_WriteCmd(0x8D); // 设置电荷泵 OLED_WriteCmd(0x14); // 使能电荷泵关键不使能则屏幕不亮 OLED_WriteCmd(0xAF); // 开启显示 OLED_Clear(); // 清屏 }重要参数说明0x8D 和 0x14 这一对命令经常被遗漏如果没有使能电荷泵OLED 上电后会保持黑屏但 I2C 通信正常——很多初学者卡在这一步以为屏幕坏了或驱动有 bug。对比度寄存器 0x81 的值决定屏幕亮度工程中设为 0x7F若要降低功耗可以减到 0x3F。4. 按键交互中断读取与消抖处理的工程取舍4.1 GPIO 外部中断配置工程中有按键用于调整时间和切换显示。按键读取方式有原生查询和外部中断两种工程采用 EXTI 外部中断。选用中断而不是查询的原因RTC 每秒产生一次秒中断加上 OLED 刷新、DS18B20 读取主循环可能被占满轮询按键会漏掉短按或长按。EXTI 方式只在按键动作发生时唤醒 CPU处理上下文不占用主循环时间。void EXTI_Key_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; EXTI_InitTypeDef EXTI_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; // 使能 GPIOA 和 AFIO 时钟外部中断需要复用功能时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); // 配置 PA0 为下拉输入模式按键另一端接 VCC 或 GND GPIO_InitStructure.GPIO_Pin GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; // 上拉输入 GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); // 将 PA0 映射到 EXTI0 线 GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0); // 配置 EXTI0下降沿触发按键按下时 PA0 由高变低 EXTI_InitStructure.EXTI_Line EXTI_Line0; EXTI_InitStructure.EXTI_Mode EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger EXTI_Trigger_Falling; EXTI_InitStructure.EXTI_LineCmd ENABLE; EXTI_Init(EXTI_InitStructure); // 配置 NVICEXTI0 中断优先级设为 2数值越小优先级越高 NVIC_InitStructure.NVIC_IRQChannel EXTI0_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 2; NVIC_InitStructure.NVIC_IRQChannelSubPriority 2; NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStructure); }关键参数EXTI_Trigger_Falling 对应按键按下瞬间的电平跳变方向。如果按键接在 VCC 与 GPIO 之间按下时 GPIO 被拉低就配下降沿按键接在 GND 与 GPIO 之间按下时 GPIO 被拉高就配上升沿。工程中采用上拉输入加接地按键所以用下降沿。NVIC 优先级分组需要在 main 函数开头调用 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2) 配置否则中断优先级按默认分组工作可能与别的中断冲突。4.2 消抖与长短按识别按键按下时机械触点会产生抖动持续时间约 5~20ms。在中断服务函数里直接做状态判断抖动可能造成一次按下触发多次中断。常见的处理办法有硬件上并联 100nF 电容、软件上的延时消抖或计数消抖。这里的环境资源少使用延时的消抖方式简单有效但会阻塞 CPU。 一个改进方法是利用 RTC 秒中断作为时间基准在秒中断里递减消抖计数。volatile uint8_t key_debounce_cnt 0; volatile uint8_t key_press_event 0; volatile uint16_t key_hold_time 0; void EXTI0_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line0) ! RESET) { // 消抖若计数达到阈值则确认为有效按下 if (key_debounce_cnt 2) { key_press_event 1; // 标记有效按键事件 key_hold_time 0; // 清零长按计时 } else { key_debounce_cnt; } EXTI_ClearITPendingBit(EXTI_Line0); } } // 在 1ms 定时器中断或 RTC 秒中断中调用 void Key_Scan(void) { if (key_press_event) { // 处理短按事件切换 OLED 显示页面 / 进入调时模式 key_press_event 0; } // 长按检测每 1ms 计一次超过 2000ms 判定为长按 if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) RESET) { key_hold_time; if (key_hold_time 2000) { // 长按进入快速调时模式 key_hold_time 0; } } }逻辑说明消抖计数法的思路是——按键抖动产生的边缘无法稳定地跨越计数阈值只有真正的按下能保持电平稳定连续性触发中断。 如果按键中断一拍完就松开计数器被清掉不会误报。这里的定时器中断和按键中断配合避免了在中断服务函数中做长时间等待属于常用做法。4.3 调时状态机调时逻辑处理时用状态机管理当前的时间修改单元秒、分、时、日、月、年。短按切换状态长按退出调时。以下列出状态定义typedef enum { TIME_SET_NONE 0, // 正常显示模式 TIME_SET_HOUR, // 调小时 TIME_SET_MIN, // 调分钟 TIME_SET_SEC, // 调秒归零用 TIME_SET_YEAR, // 调年份 TIME_SET_MONTH, // 调月份 TIME_SET_DAY // 调日期 } TimeSetState_t; TimeSetState_t time_set_state TIME_SET_NONE;注意边界判断月的变化会改变当月最大天数年份的闰年也要处理。 这部分逻辑能直接复用日期换算算法的逆过程。 常见做法是读取当前显示的时间戳将其转成分量的日期结构在结构体上做字段增减再调 RTC_SetCounter 写回。 数据结构层面你只需要维护一个 uint32_t 时间戳一切日期计算都在显示层处理否则会出现改万年历还要维护一个 y/m/d 数组的混乱局面。5. 单总线 DS18B20温度采集与 RTC 数据同屏展示5.1 单总线时序与 ROM 校验DS18B20 的温度数据用单总线协议(1-Wire)传输一根数据线完成读写双向通信。 时序分为初始化、写 0/1、读 0/1 四种。 与 I2C 不同单总线的时序时间要求是微秒级的对延时函数的精度非常敏感。 工程中使用的 GPIO 模拟时序核心延时由空循环实现。关键概念DS18B20 的 64 位 ROM 码在出厂时激光写入包含 8 位家族码、48 位序列号和 8 位 CRC 校验。 总线上只要接了一个传感器可以跳过 ROM 匹配0xCC 命令直接转换温度多个传感器时需要用 0x55 命令匹配对应的 ROM 码。 工程里直接采用跳过 ROM 的方式适合单点测温场景。 实际部署时若总线上挂了多片 DS18B20每条总线上不同地址的器件需要扫描并保存其 ROM 码代码复杂度会高一个量级选型时可以改用一个带唯一 ID 的 TMP117 等 I2C 温度传感器。5.2 温度转换与读取姿势float DS18B20_GetTemperate(void) { uint8_t low, high; int16_t temp_raw; float temp; // 复位单总线检测设备是否存在 if (DS18B20_Reset() 0) return -999.0f; // 设备不存在 // 跳过 ROM 匹配直接向所有设备发送温度转换命令 DS18B20_WriteByte(0xCC); DS18B20_WriteByte(0x44); // 等待转换完成12 位精度下最长 750ms delay_ms(750); // 再次复位发送读暂存器命令 if (DS18B20_Reset() 0) return -999.0f; DS18B20_WriteByte(0xCC); DS18B20_WriteByte(0xBE); // 读取暂存器中 9 字节数据 low DS18B20_ReadByte(); // 温度低字节 high DS18B20_ReadByte(); // 温度高字节 // 组合成 16 位有符号原始值右移 4 位去掉小数位 temp_raw (high 8) | low; temp temp_raw * 0.0625f; // 12 位分辨率下每位代表 0.0625°C return temp; }延时 750ms 是 DS18B20 在 12 位分辨率下的最大转换时间——必须等待转换完成否则读回的数据是上次转换的旧值。应用层需要权衡如果系统要求每秒刷新一次温度750ms 的等待会占用大量时间。工程优化方案是——主循环先发起温度转换命令然后去做 OLED 刷新和按键扫描最后回来读温度整个过程被拆成异步流程。若工程中直接使用阻塞延时读出的数据不正确时先检查延时是否足够再检查时序是否被中断打断。一个体感知识DS18B20 的精度是 ±0.5°C换算出的温度值不是准确的物理温度要校准的话在冰水混合物里测量一次校正偏移量参数即可。这与 RTC 的校准字节类似都是对传感器本身偏差的补偿。6. 掉电与校准RTC 在真实产品中的三个坑这一章把实践中常遇到的三个问题展开。都是不看原理很难定位的坑说结论、给解法。坑一RTC 时间掉了但 VBAT 电压正常这个问题排查时要区分“初始化代码有 bug”还是“备份域掉电写失败”。 RTC 初始化代码里 if 判断读备份寄存器如果 BKP_DR1 读回的值一直是 0说明备份域已经掉电——可能原因不是 VBAT 没接好而是复位时 RTC 写操作还没完成 就把备份域写保护关掉了。 解决办法是加一个掉电检测电容VBAT 引脚并联 1uF 到 GND保持几毫秒确保 RTC 写操作完成后再切备用电源。坑二LSE 晶振不起振LSE 晶振对负载电容要求是 6~12.5pF 之间很多低成本 PCB 上直接省掉了匹配电容导致起振困难。 定位方式是执行初始化代码后隔一会再读 RCC_FLAG_LSERDY如果一直是 RESET排查晶振焊盘和电容布局。 调试时也可临时切换到 LSI但时间会跑得快不能用产品发布。坑三RTC 秒中断与外部中断的优先级冲突RTC 秒中断优先级如果高于 EXTI 按键中断按键响应会出现不确定的延迟如果反过来按键处理会打断 RTC 的寄存器同步读取出现随机跳变。 统一的做法是预取 NVIC_PriorityGroup_2 分组RTC 中断设 PreemptionPriority1按键 EXTI 设 PreemptionPriority2这样 RTC 的时间基准不会被 UI 操作打断。校准方法利用 RTC 的写保护寄存器不能直接做时间校准但可以在示波器上测量 RTC 秒中断输出引脚TAMPER 引脚复用的频率对比标准 1Hz 偏差。RTC 校准在 F103 中需要通过 BKP 寄存器配合特定命令序列才能写校准值这是小众机制处于产品需求推荐的做法是每分钟用外部参考时间源如 GNSS 模块对时直接 RTC_SetCounter 校准比在寄存器层面做校准要简单稳定。最后给一个调试建议RTC 秒中断里可以使用示波器直接测电平反转将 PB5 配置为推挽输出在秒中断服务函数中翻转 PB5。 这样可以用示波器看到 1Hz 方波判断走时是否精确。 这一个方法能帮你过滤掉一半的 RTC 问题值的尝试。本文还有配套的精品资源点击获取
返回列表