主题 | 内容 | 教学目的/扩展视频 |
---|---|---|
RTC时钟的使用重点课程 | RTC时钟的原理,电路原理分析,固件库分析,驱动程序分析。在超级终端上显示时钟。 | 做可修改的超级终端显示RTC的项目。 |
师从洋桃电子,杜洋老师
📑文章目录
- 一、系统功能概述
- 二、硬件系统架构
- 2.1 核心硬件组成
- 2.2 经典电路设计
- 三、软件核心逻辑解析
- 3.1 主程序流程图
- 3.2 多任务处理机制
- 四、RTC核心操作详解
- 4.1 时钟初始化流程
- 4.2 时间设置算法
- 五、时间显示格式定制
- 5.1 格式化输出实现
- 5.2 星期显示算法
- 六、关键代码解析
- 6.1 主循环处理逻辑
- 6.2 时间显示优化技巧
- 七、开发经验总结
- 八、相关资源
▲ 回顾上期🔍STM32实时时钟(RTC)代码深度解析 | 零基础入门STM32第三十九步
一、系统功能概述
本系统实现通过串口终端实时监控和修改STM32内置RTC时钟,主要功能包括:
- 实时时钟显示:每秒更新日期、时间及星期
- 串口指令控制:支持"初始化时钟"、"设置时间"等指令
- 多任务处理:在时钟更新同时响应外部指令
- 错误检测机制:数据校验与错误提示
二、硬件系统架构
2.1 核心硬件组成
模块 | 型号/参数 | 功能说明 |
---|---|---|
MCU | STM32F103C8T6 | 主控制器 |
RTC时钟源 | 32.768kHz晶振 | 提供精准时间基准 |
串口转换芯片 | CH340C | USB转TTL通信 |
后备电池 | CR2032纽扣电池 | RTC掉电保护 |
2.2 经典电路设计
// 后备电源切换电路
VBAT引脚 → 100nF电容 → CR2033电池座
LSE晶振 → 6pF负载电容 ×2 → GND
三、软件核心逻辑解析
3.1 主程序流程图
3.2 多任务处理机制
状态机实现原理:
// 串口接收状态寄存器
void USART1_IRQHandler(void){ //串口1中断服务程序(固定的函数名不能修改)
u8 Res;
//以下是字符串接收到USART_RX_BUF[]的程序,(USART_RX_STA&0x3FFF)是数据的长度(不包括回车)
//当(USART_RX_STA&0xC000)为真时表示数据接收完成,即超级终端里按下回车键。
//在主函数里写判断if(USART_RX_STA&0xC000),然后读USART_RX_BUF[]数组,读到0x0d 0x0a即是结束。
//注意在主函数处理完串口数据后,要将USART_RX_STA清0
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET){ //接收中断(接收到的数据必须是0x0d 0x0a结尾)
Res =USART_ReceiveData(USART1);//(USART1->DR); //读取接收到的数据
printf("%c",Res); //把收到的数据以 a符号变量 发送回电脑
if((USART1_RX_STA&0x8000)==0){//接收未完成
if(USART1_RX_STA&0x4000){//接收到了0x0d
if(Res!=0x0a)USART1_RX_STA=0;//接收错误,重新开始
else USART1_RX_STA|=0x8000; //接收完成了
}else{ //还没收到0X0D
if(Res==0x0d)USART1_RX_STA|=0x4000;
else{
USART1_RX_BUF[USART1_RX_STA&0X3FFF]=Res ; //将收到的数据放入数组
USART1_RX_STA++; //数据长度计数加1
if(USART1_RX_STA>(USART1_REC_LEN-1))USART1_RX_STA=0;//接收数据错误,重新开始接收
}
}
}
}
}
四、RTC核心操作详解
4.1 时钟初始化流程
void RTC_Config(void){ //实时时钟初始化
//在BKP的后备寄存器1中,存了一个特殊字符0xA5A5
//第一次上电或后备电源掉电后,该寄存器数据丢失,表明RTC数据丢失,需要重新配置
if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5){//判断寄存数据是否丢失
RTC_First_Config();//重新配置RTC
BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);//配置完成后,向后备寄存器中写特殊字符0xA5A5
}else{
//若后备寄存器没有掉电,则无需重新配置RTC
//这里我们可以利用RCC_GetFlagStatus()函数查看本次复位类型
if (RCC_GetFlagStatus(RCC_FLAG_PORRST) != RESET){
//这是上电复位
}
else if (RCC_GetFlagStatus(RCC_FLAG_PINRST) != RESET){
//这是外部RST管脚复位
}
RCC_ClearFlag();//清除RCC中复位标志
//虽然RTC模块不需要重新配置,且掉电后依靠后备电池依然运行
//但是每次上电后,还是要使能RTCCLK
RCC_RTCCLKCmd(ENABLE);//使能RTCCLK
RTC_WaitForSynchro();//等待RTC时钟与APB1时钟同步
}
4.2 时间设置算法
ASCII转数值算法:
//将超级终端发过来的数据换算并写入RTC
ryear = (USART1_RX_BUF[0]-0x30)*1000+(USART1_RX_BUF[1]-0x30)*100+(USART1_RX_BUF[2]-0x30)*10+USART1_RX_BUF[3]-0x30;
rmon = (USART1_RX_BUF[4]-0x30)*10+USART1_RX_BUF[5]-0x30;//串口发来的是字符,减0x30后才能得到十进制0~9的数据
rday = (USART1_RX_BUF[6]-0x30)*10+USART1_RX_BUF[7]-0x30;
rhour = (USART1_RX_BUF[8]-0x30)*10+USART1_RX_BUF[9]-0x30;
rmin = (USART1_RX_BUF[10]-0x30)*10+USART1_RX_BUF[11]-0x30;
rsec = (USART1_RX_BUF[12]-0x30)*10+USART1_RX_BUF[13]-0x30;
bya=RTC_Set(ryear,rmon,rday,rhour,rmin,rsec); //将数据写入RTC计算器的程序
写入流程:
- 校验输入有效性(范围检查)
- 转换为Unix时间戳
- 写入RTC计数器
五、时间显示格式定制
5.1 格式化输出实现
printf("%d-%02d-%02d %02d:%02d:%02d",
ryear, rmon, rday, rhour, rmin, rsec);
格式说明符:
%02d
:两位数字显示,不足补零\r\n
:回车换行符
5.2 星期显示算法
// 基姆拉尔森计算公式优化版
u8 Get_Week(u16 year, u8 month, u8 day) {
if(month < 3) { month += 12; year--; }
return (day + 2*month + 3*(month+1)/5 + year + year/4
- year/100 + year/400) % 7;
}
六、关键代码解析
6.1 主循环处理逻辑
int main (void){//主程序
u8 bya;
RCC_Configuration(); //系统时钟初始化
RTC_Config(); //实时时钟初始化
LED_Init();//LED初始化
KEY_Init();//按键初始化
BUZZER_Init();//蜂鸣器初始化
USART1_Init(115200); //串口初始化,参数中写波特率
USART1_RX_STA=0xC000; //初始值设为有回车的状态,即显示一次欢迎词
while(1){
if(USART1_RX_STA&0xC000){ //如果标志位是0xC000表示收到数据串完成,可以处理。
if((USART1_RX_STA&0x3FFF)==0){ //单独的回车键再显示一次欢迎词
if(RTC_Get()==0){ //读出时间值,同时判断返回值是不是0,非0时读取的值是错误的。
printf(" 洋桃开发板STM32实时时钟测试程序 \r\n");
printf(" 现在实时时间:%d-%d-%d %d:%d%d:%d%d ",ryear,rmon,rday,rhour,rmin/10,rmin%10,rsec/10,rsec%10);//显示日期时间
if(rweek==0)printf("星期日 \r\n");//rweek值为0时表示星期日
if(rweek==1)printf("星期一 \r\n");
if(rweek==2)printf("星期二 \r\n");
if(rweek==3)printf("星期三 \r\n");
if(rweek==4)printf("星期四 \r\n");
if(rweek==5)printf("星期五 \r\n");
if(rweek==6)printf("星期六 \r\n");
printf(" 单按回车键更新时间。输入字母C初始化时钟 \r\n");
printf(" 请输入设置时间,格式20170806120000,按回车键确定! \r\n");
}else{
printf("读取失败!\r\n");
}
}else if((USART1_RX_STA&0x3FFF)==1){ //判断数据是不是2个
if(USART1_RX_BUF[0]=='c' || USART1_RX_BUF[0]=='C'){
RTC_First_Config(); //键盘输入c或C,初始化时钟
BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);//配置完成后,向后备寄存器中写特殊字符0xA5A5
printf("初始化成功! \r\n");//显示初始化成功
}else{
printf("指令错误! \r\n"); //显示指令错误!
}
}else if((USART1_RX_STA&0x3FFF)==14){ //判断数据是不是14个
//将超级终端发过来的数据换算并写入RTC
ryear = (USART1_RX_BUF[0]-0x30)*1000+(USART1_RX_BUF[1]-0x30)*100+(USART1_RX_BUF[2]-0x30)*10+USART1_RX_BUF[3]-0x30;
rmon = (USART1_RX_BUF[4]-0x30)*10+USART1_RX_BUF[5]-0x30;//串口发来的是字符,减0x30后才能得到十进制0~9的数据
rday = (USART1_RX_BUF[6]-0x30)*10+USART1_RX_BUF[7]-0x30;
rhour = (USART1_RX_BUF[8]-0x30)*10+USART1_RX_BUF[9]-0x30;
rmin = (USART1_RX_BUF[10]-0x30)*10+USART1_RX_BUF[11]-0x30;
rsec = (USART1_RX_BUF[12]-0x30)*10+USART1_RX_BUF[13]-0x30;
bya=RTC_Set(ryear,rmon,rday,rhour,rmin,rsec); //将数据写入RTC计算器的程序
if(bya==0)printf("写入成功! \r\n");//显示写入成功
else printf("写入失败! \r\n"); //显示写入失败
}else{ //如果以上都不是,即是错误的指令。
printf("指令错误! \r\n"); //如果不是以上正确的操作,显示指令错误!
}
USART1_RX_STA=0; //将串口数据标志位清0
}
}
}
6.2 时间显示优化技巧
// 分解数字位显示
printf("%d:%d%d", rhour, rmin/10, rmin%10);
// 输出示例:12:05 → 1 2 : 0 5
七、开发经验总结
- 抗干扰设计:在VBAT引脚并联100nF电容,提高RTC稳定性
- 输入验证:建议增加月份(1-12)、小时(0-23)等范围检查
- 低功耗优化:空闲时进入STOP模式,电流可降至2μA以下
- 扩展功能:可添加温度补偿算法提高计时精度
实测数据:使用外部晶振时,系统年误差小于±30秒。通过软件校准可进一步将误差控制在±5秒/年以内。
八、相关资源
[1] 洋桃电子B站课程-STM32入门100步
[2] STM32F103xx官方数据手册
[3] STM32F103X8-B数据手册(中文)
[4] STM32F103固件函数库用户手册(中文)
[5] 超级终端显示日历程序
💬 技术讨论(请在评论区留言~)
📌 下期预告:下一期将探讨RTC设置程序分析,欢迎持续关注!
点击查阅🔍往期【STM32专栏】文章
版权声明:本文采用[CC BY-NC-SA 4.0]协议,转载请注明来源
实测开发版:洋桃1号开发版(基于STM32F103C8T6)
更新日志:
- v1.0 初始版本(2025-02-28)