STM32-RTC原理和驱动程序


杨桃32学习笔记,本文图片文字皆为转述

RTC电路原理图

在这里插入图片描述
在这里插入图片描述

RTC实现原理

在这里插入图片描述

STM32的RTC只用一个32位计数器来计时,而不是用年月日时分秒的分组寄存器。
通过设置可以让这个计数器1秒加1,从0-0XFFFFFFFF大约可计时136年。
时间起点一般设置为1970-01-01 00:00:00 (因现有函数如此定义)
如果要读当前的年月日时分秒,先读出32位RTC计数器值,
然后以1970-01-01 00:00:00为起点,加上计数器中的秒数,
再换算成年月日时分秒,即可得出当前时间。

烧录示例文件的hex查看程序效果

实现效果:led1按秒数的奇数偶数亮灭,奇数亮偶数灭,led2按分钟的奇数偶数亮灭。
时间通过RTC时钟产生,然后通过led状态表示出来。

添加相应的文件并添加进工程文件

在这里插入图片描述
在这里插入图片描述

由于我们添加了RTC的官方固件库,所以要在lib文件夹下rtc的相关文件。

在这里插入图片描述

rtc.h/main.c解读

在这里插入图片描述

#ifndef __RTC_H
#define __RTC_H	 
#include "sys.h" 
//全局变量的声明,在rtc.c文件中定义
//以下2条是使用extern语句声明全局变量
//注意:这里不能给变量赋值
extern u16 ryear;
extern u8 rmon,rday,rhour,rmin,rsec,rweek;
u8 RTC_Get(void);//读出当前时间值	
void RTC_First_Config(void);//首次启用RTC的设置
void RTC_Config(void);//实时时钟初始化
u8 Is_Leap_Year(u16 year);//判断是否是闰年函数                    
u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec);//写入当前时间
u8 RTC_Get_Week(u16 year,u8 month,u8 day);//按年月日计算星期
#endif

在这里插入图片描述

#include "stm32f10x.h" //STM32头文件
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "key.h"
#include "buzzer.h"
#include "usart.h"
#include "rtc.h"
int main (void){//主程序
	RCC_Configuration(); //系统时钟初始化
	RTC_Config(); //实时时钟初始化
	LED_Init();//LED初始化
	KEY_Init();//按键初始化
	BUZZER_Init();//蜂鸣器初始化
	USART1_Init(115200); //串口初始化,参数中写波特率
	USART1_RX_STA=0xC000; //初始值设为有回车的状态,即显示一次欢迎词
	while(1){
		if(RTC_Get()==0){ //读出时间值,同时判断返回值是不是0,非0时读取的值是错误的。	
			GPIO_WriteBit(LEDPORT,LED1,(BitAction)(rsec%2)); //LED1接口
			GPIO_WriteBit(LEDPORT,LED2,(BitAction)(rmin%2)); //LED2接口
		}
	}
}

rtc.c解读

在这里插入图片描述

rtc.c文件中涉及到RTC固件库相关函数

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

#include "sys.h"
#include "rtc.h"


//以下2条全局变量--用于RTC时间的读取
u16 ryear; //4位年
u8 rmon,rday,rhour,rmin,rsec,rweek;//2位月日时分秒周



void RTC_First_Config(void){ //首次启用RTC的设置
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);//启用PWR电源管理部分和BKP备用寄存器的时钟(from APB1)
    PWR_BackupAccessCmd(ENABLE);//后备域解锁
    BKP_DeInit();//备份寄存器模块复位
    RCC_LSEConfig(RCC_LSE_ON);//外部32.768KHZ晶振开启   
    while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET);//等待稳定    
    RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);//RTC时钟源配置成LSE(外部低速晶振32.768KHZ)    
    RCC_RTCCLKCmd(ENABLE);//RTC开启    
    RTC_WaitForSynchro();//开启后需要等待APB1时钟与RTC时钟同步,才能读写寄存器    
    RTC_WaitForLastTask();//读写寄存器前,要确定上一个操作已经结束
    RTC_SetPrescaler(32767);//设置RTC分频器,使RTC时钟为1Hz,RTC period = RTCCLK/RTC_PR = (32.768 KHz)/(32767+1)   
    RTC_WaitForLastTask();//等待寄存器写入完成	
    //当不使用RTC秒中断,可以屏蔽下面2条
//    RTC_ITConfig(RTC_IT_SEC, ENABLE);//使能秒中断   
//    RTC_WaitForLastTask();//等待写入完成
}

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时钟同步

        //当不使用RTC秒中断,可以屏蔽下面2条
//        RTC_ITConfig(RTC_IT_SEC, ENABLE);//使能秒中断        
//        RTC_WaitForLastTask();//等待操作完成
    }
	#ifdef RTCClockOutput_Enable   
	    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
	    PWR_BackupAccessCmd(ENABLE);   
	    BKP_TamperPinCmd(DISABLE);   
	    BKP_RTCOutputConfig(BKP_RTCOutputSource_CalibClock);
	#endif
}

void RTC_IRQHandler(void){ //RTC时钟1秒触发中断函数(名称固定不可修改)
	if (RTC_GetITStatus(RTC_IT_SEC) != RESET){

	}
	RTC_ClearITPendingBit(RTC_IT_SEC); 
	RTC_WaitForLastTask();
}

void RTCAlarm_IRQHandler(void){	//闹钟中断处理(启用时必须调高其优先级)
	if(RTC_GetITStatus(RTC_IT_ALR) != RESET){
	
	}
	RTC_ClearITPendingBit(RTC_IT_ALR);
	RTC_WaitForLastTask();
}

//判断是否是闰年函数
//月份   1  2  3  4  5  6  7  8  9  10 11 12
//闰年   31 29 31 30 31 30 31 31 30 31 30 31
//非闰年 31 28 31 30 31 30 31 31 30 31 30 31
//输入:年份
//输出:该年份是不是闰年.1,是.0,不是
u8 Is_Leap_Year(u16 year){                    
	if(year%4==0){ //必须能被4整除
		if(year%100==0){		
			if(year%400==0)return 1;//如果以00结尾,还要能被400整除          
			else return 0;  
		}else return 1;  
	}else return 0;
}                           
//设置时钟
//把输入的时钟转换为秒钟
//以1970年1月1日为基准
//1970~2099年为合法年份

//月份数据表                                                                       
u8 const table_week[12]={0,3,3,6,1,4,6,2,5,0,3,5}; //月修正数据表  
const u8 mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31};//平年的月份日期表

//写入时间
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,成功;其他:错误代码.    
}

//读出时间
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;
}    

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

在这里插入图片描述

  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值