STM32实时时钟(RTC)代码深度解析 | 零基础入门STM32第三十九步

主题 内容 教学目的/扩展视频
RTC时钟的使用重点课程 RTC时钟的原理,电路原理分析,固件库分析,驱动程序分析。在超级终端上显示时钟。 做可修改的超级终端显示RTC的项目。

师从洋桃电子,杜洋老师


📑文章目录

  • 一、RTC初始化流程分析
  • 1.1 时钟与备份域配置
  • 1.2 初始化检测机制
  • 二、时间处理核心算法
  • 2.1 闰年判断算法
  • 2.2 时间戳转换(Unix时间)
  • 三、时间读取与转换
  • 3.1 读取计数器值
  • 3.2 星期计算算法
  • 四、中断处理机制
  • 4.1 秒中断配置
  • 4.2 闹钟中断配置
  • 五、LED显示逻辑分析
  • 5.1 显示控制代码
  • 5.2 硬件连接建议
  • 六、常见问题解决方案
  • 6.1 晶振不起振
  • 6.2 时间误差过大
  • 6.3 后备电池失效
  • 七、相关资源

  • ▲ 回顾上期🔍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();
    }
    

    关键点说明

    1. 必须同时使能PWR和BKP时钟才能操作后备域
    2. BKP_DeInit()会清除所有备份寄存器内容
    3. 预分频器值计算公式:分频系数 = 32768 - 1

    1.2 初始化检测机制

    if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5) {
        // 首次初始化流程
        RTC_First_Config();//重新配置RTC
        BKP_WriteBackupRegister(BKP_DR1, 0xA5A5); // 设置初始化标记
    }
    

    工作原理

  • 利用BKP_DR1作为"指纹寄存器"
  • 断电后VBAT维持备份域供电
  • 检测到非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整除则非闰年
    }
    

    算法特点

  • 符合格里高利历法规则
  • 处理范围:1900-2099年
  • 返回结果:1(闰年)/0(平年)
  • 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;
    }  
    

    转换流程

    1. 计算总天数 → 年
    2. 剩余天数 → 月
    3. 剩余天数 → 日
    4. 剩余秒数 → 时分秒

    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); //返回星期值
    }
    

    返回值对应

  • 0 = 星期日
  • 1 = 星期一
  • 6 = 星期六

  • 四、中断处理机制

    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接口;
        }
    }
    

    显示规律

  • LED1:1Hz闪烁(亮500ms/灭500ms)
  • LED2:1/60Hz变化(每分钟切换状态)
  • 5.2 硬件连接建议

    信号 GPIO引脚 LED驱动方式
    LED1
    LED2
    PB0
    PB1
    推挽输出+1kΩ限流电阻

    ▲ 完整工程代码示例⏬LED灯显示RTC走时程序


    六、常见问题解决方案

    6.1 晶振不起振

    可能原因

    1. 负载电容不匹配(建议6pF)
    2. PCB布局不良(晶振距离MCU过远)
    3. 晶振质量差(选择±20ppm精度)

    6.2 时间误差过大

    校准方法

    // 软件校准(单位:ppm)
    RTC_CalibOutputCmd(ENABLE);
    RTC_CalibOutputConfig(RTC_CalibOutput_512Hz);
    

    硬件校准

  • 使用示波器测量RTCCLK输出
  • 调节TRIMMER电容补偿误差
  • 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

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32实时时钟(RTC)代码深度解析 | 零基础入门STM32第三十九步

    发表回复