STM32实时时钟(RTC)代码深度解析 | 零基础入门STM32第三十九步
主题 | 内容 | 教学目的/扩展视频 |
---|---|---|
RTC时钟的使用重点课程 |
RTC时钟的原理,电路原理分析,固件库分析,驱动程序分析。在超级终端上显示时钟。 | 做可修改的超级终端显示RTC的项目。 |
师从洋桃电子,杜洋老师
📑文章目录
▲ 回顾上期🔍STM32实时时钟(RTC)开发详解:从晶振原理到LED动态显示 | 零基础入门STM32第三十八步
一、RTC初始化流程分析
1.1 时钟与备份域配置
void RTC_First_Config(void) {
// 1. 开启APB1总线时钟(PWR和BKP模块)
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
// 2. 解除后备寄存器写保护
PWR_BackupAccessCmd(ENABLE);
// 3. 复位备份寄存器
BKP_DeInit();
// 4. 启动外部低速晶振(LSE)
RCC_LSEConfig(RCC_LSE_ON);
while(RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET); // 等待晶振稳定
// 5. 选择LSE作为RTC时钟源
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
// 6. 使能RTC时钟
RCC_RTCCLKCmd(ENABLE);
// 7. 等待时钟同步
RTC_WaitForSynchro();
RTC_WaitForLastTask();
// 8. 设置预分频器(32767+1分频)
RTC_SetPrescaler(32767); // 32.768kHz / 32768 = 1Hz
RTC_WaitForLastTask();
}
关键点说明:
- 必须同时使能PWR和BKP时钟才能操作后备域
BKP_DeInit()
会清除所有备份寄存器内容- 预分频器值计算公式:
分频系数 = 32768 - 1
1.2 初始化检测机制
if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5) {
// 首次初始化流程
RTC_First_Config();//重新配置RTC
BKP_WriteBackupRegister(BKP_DR1, 0xA5A5); // 设置初始化标记
}
工作原理:
二、时间处理核心算法
2.1 闰年判断算法
u8 Is_Leap_Year(u16 year) {
if(year%4==0) { // 能被4整除
if(year%100==0) { // 整百年处理
if(year%400==0)return 1; // 必须能被400整除
else return 0;
}else return 1; // 非整百年直接返回闰年
}else return 0; // 不能被4整除则非闰年
}
算法特点:
2.2 时间戳转换(Unix时间)
u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec){ //写入当前时间(1970~2099年有效),
u16 t;
u32 seccount=0;
if(syear<2000||syear>2099)return 1;//syear范围1970-2099,此处设置范围为2000-2099
for(t=1970;t<syear;t++){ //把所有年份的秒钟相加
if(Is_Leap_Year(t))seccount+=31622400;//闰年的秒钟数
else seccount+=31536000; //平年的秒钟数
}
smon-=1;
for(t=0;t<smon;t++){ //把前面月份的秒钟数相加
seccount+=(u32)mon_table[t]*86400;//月份秒钟数相加
if(Is_Leap_Year(syear)&&t==1)seccount+=86400;//闰年2月份增加一天的秒钟数
}
seccount+=(u32)(sday-1)*86400;//把前面日期的秒钟数相加
seccount+=(u32)hour*3600;//小时秒钟数
seccount+=(u32)min*60; //分钟秒钟数
seccount+=sec;//最后的秒钟加上去
RTC_First_Config(); //重新初始化时钟
BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);//配置完成后,向后备寄存器中写特殊字符0xA5A5
RTC_SetCounter(seccount);//把换算好的计数器值写入
RTC_WaitForLastTask(); //等待写入完成
return 0; //返回值:0,成功;其他:错误代码.
}
计算公式:
总秒数 = 年累计秒数 + 月累计秒数 + 天累计秒数
+ 小时秒数 + 分钟秒数 + 秒数
三、时间读取与转换
3.1 读取计数器值
u8 RTC_Get(void){//读出当前时间值 //返回值:0,成功;其他:错误代码.
static u16 daycnt=0;
u32 timecount=0;
u32 temp=0;
u16 temp1=0;
timecount=RTC_GetCounter();
temp=timecount/86400; //得到天数(秒钟数对应的)
if(daycnt!=temp){//超过一天了
daycnt=temp;
temp1=1970; //从1970年开始
while(temp>=365){
if(Is_Leap_Year(temp1)){//是闰年
if(temp>=366)temp-=366;//闰年的秒钟数
else {temp1++;break;}
}
else temp-=365; //平年
temp1++;
}
ryear=temp1;//得到年份
temp1=0;
while(temp>=28){//超过了一个月
if(Is_Leap_Year(ryear)&&temp1==1){//当年是不是闰年/2月份
if(temp>=29)temp-=29;//闰年的秒钟数
else break;
}else{
if(temp>=mon_table[temp1])temp-=mon_table[temp1];//平年
else break;
}
temp1++;
}
rmon=temp1+1;//得到月份
rday=temp+1; //得到日期
}
temp=timecount%86400; //得到秒钟数
rhour=temp/3600; //小时
rmin=(temp%3600)/60; //分钟
rsec=(temp%3600)%60; //秒钟
rweek=RTC_Get_Week(ryear,rmon,rday);//获取星期
return 0;
}
转换流程:
- 计算总天数 → 年
- 剩余天数 → 月
- 剩余天数 → 日
- 剩余秒数 → 时分秒
3.2 星期计算算法
u8 RTC_Get_Week(u16 year,u8 month,u8 day){ //按年月日计算星期(只允许1901-2099年)//已由RTC_Get调用
u16 temp2;
u8 yearH,yearL;
yearH=year/100;
yearL=year%100;
// 如果为21世纪,年份数加100
if (yearH>19)yearL+=100;
// 所过闰年数只算1900年之后的
temp2=yearL+yearL/4;
temp2=temp2%7;
temp2=temp2+day+table_week[month-1];
if (yearL%4==0&&month<3)temp2--;
return(temp2%7); //返回星期值
}
返回值对应:
四、中断处理机制
4.1 秒中断配置
void RTC_IRQHandler(void){ //RTC时钟1秒触发中断函数(名称固定不可修改)
if (RTC_GetITStatus(RTC_IT_SEC) != RESET){
}
RTC_ClearITPendingBit(RTC_IT_SEC);
RTC_WaitForLastTask();
}
典型应用:
4.2 闹钟中断配置
void RTCAlarm_IRQHandler(void){ //闹钟中断处理(启用时必须调高其优先级)
if(RTC_GetITStatus(RTC_IT_ALR) != RESET){
}
RTC_ClearITPendingBit(RTC_IT_ALR);
RTC_WaitForLastTask();
}
注意事项:
五、LED显示逻辑分析
5.1 显示控制代码
while(1) {
if(RTC_Get()==0){ //读出时间值,同时判断返回值是不是0,非0时读取的值是错误的。
GPIO_WriteBit(LEDPORT,LED1,(BitAction)(rsec%2)); //LED1接口
GPIO_WriteBit(LEDPORT,LED2,(BitAction)(rmin%2)); //LED2接口;
}
}
显示规律:
5.2 硬件连接建议
信号 | GPIO引脚 | LED驱动方式 |
---|---|---|
LED1 LED2 |
PB0 PB1 |
推挽输出+1kΩ限流电阻 |
▲ 完整工程代码示例⏬LED灯显示RTC走时程序
六、常见问题解决方案
6.1 晶振不起振
可能原因:
- 负载电容不匹配(建议6pF)
- PCB布局不良(晶振距离MCU过远)
- 晶振质量差(选择±20ppm精度)
6.2 时间误差过大
校准方法:
// 软件校准(单位:ppm)
RTC_CalibOutputCmd(ENABLE);
RTC_CalibOutputConfig(RTC_CalibOutput_512Hz);
硬件校准:
6.3 后备电池失效
检测方法:
if(RCC_GetFlagStatus(RCC_FLAG_PORRST)) {
// 电源复位,可能丢失时间
LCD_ShowError("RTC数据丢失!");
}
七、相关资源
[1] 洋桃电子B站课程-STM32入门100步
[2] STM32F103xx官方数据手册
[3] STM32F103X8-B数据手册(中文)
[4] STM32F103固件函数库用户手册(中文)
[5] LED灯显示RTC走时程序
💬 技术讨论(请在评论区留言~)
📌 下期预告:下一期将探讨超级终端显示日历程序
重点课程
,欢迎持续关注!点击查阅🔍往期【STM32专栏】文章
版权声明:本文采用[CC BY-NC-SA 4.0]协议,转载请注明来源
实测开发版:洋桃1号开发版(基于STM32F103C8T6)
更新日志:v1.0 初始版本(2025-02-28)
作者:触角01010001