cortex_m3_stm32嵌入式学习笔记(十四):RTC实时时钟(秒中断)

ARM 364浏览

STM32 的实时时钟( RTC)是一个独立的定时器。 STM32 的 RTC 模块拥有一组连续计数的计数器,在相应软件配置下,可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。

由于时钟只需要配置一次,下次开机不需要重新配置(开发板有电池的情况下),所以需要用到备份区域(BKP)来标记是否配置过时钟

简单介绍BKP:备份寄存器是 42 个 16 位的寄存器( Mini 开发板就是大容量的),可用来存储 84 个字节的用户应用程序数据。他们处在备份域里, 当 VDD 电源被切断,他们仍然由 VBAT 维持供电。即使系统在待机模式下被唤醒,或系统复位或电源复位时,他们也不会被复位此外,
BKP 控制寄存器用来管理侵入检测和 RTC 校准功能。

简单说一下我对时钟工作原理的理解:一个32位的计数器,从0向上计数的话,假设每加一就是1秒,那么一个32位的计数器跑到溢出需要100多年。。已经很长了,这里时钟自带一个秒中断,当每加一的时候就会触发一次秒中断,我们通过往秒中断里写更新时间的函数来达到时间同步的效果

由于rtc.c里函数很多。。我就贴一下说几个比较重要的吧。。


#include "rtc.h"
_calendar_obj calendar;//时钟结构体
const u8 mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31};
const u8  table_week[12]={0,3,3,6,1,4,6,2,5,0,3,5}; //月修正数据表
u8 Is_LeapYear(u16 year)
{
	return (year%4==0&&year%100!=0)||year%400==0;
}

//获得现在是星期几
//功能描述:输入公历日期得到星期(只允许1901-2099年)
//输入参数:公历年月日 
//返回值:星期号
u8 RTC_Get_Week(u16 year,u8 month,u8 day)
{
	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);
}
static void RTC_NVIC_Config(void)
{	
  NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;		//RTC全局中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;	//先占优先级1位,从优先级3位
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;	//先占优先级0位,从优先级4位
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;		//使能该通道中断
	NVIC_Init(&NVIC_InitStructure);		//根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
}
//得到当前的时间,结果保存在calendar结构体里面
//返回值:0,成功;其他:错误代码.
u8 RTC_Get(void)
{
	static u16 daycnt=0;
	u32 timecount=RTC_GetCounter();
	u32 daynum=timecount/86400;
	u16 tem=0;
	if(daycnt!=daynum)//大于1天
	{
			daycnt=daynum;
			tem=1970;
			while(daynum>=365)
			{
				if(Is_LeapYear(tem))
				{
					if(daynum>=366)daynum-=366;
					else break;
				}
				else daynum-=365;
				tem++;
			 }
			calendar.w_year=tem;//年
			tem=0;
			while(daynum>=28)
			{
				if(Is_LeapYear(calendar.w_year)&&tem==1)
				{
					if(daynum>=29)daynum-=29;
					else break;
				}
				else
				{
					if(daynum>=mon_table[tem])daynum-=mon_table[tem];
					else break;
				}
				tem++;
			}
			calendar.w_month=tem+1;//月
			calendar.w_date=daynum+1;//日
	}
	  daynum=timecount%86400;
		calendar.hour=daynum/3600;//时
		calendar.min=(daynum%3600)/60;//分
		calendar.sec=(daynum%3600)%60;//秒
		calendar.week=RTC_Get_Week(calendar.w_year,calendar.w_month,calendar.w_date);
    return 0;
}


//设置时钟
//把输入的时钟转换为秒钟
//以 1970 年 1 月 1 日为基准
//1970~2099 年为合法年份
//返回值:0,成功;其他:错误代码.
//平年的月份日期表
//year,mon,day,hour,min,sec:年月日时分秒
//返回值:设置结果。 0,成功; 1,失败。
u8 RTC_Set(u16 year,u8 mon,u8 day,u8 hour,u8 min,u8 sec)
{
	u16 i;u32 seccnt=0;
	if(year<1970||year>2099)return 1;
	for(i=1970;i<year;i++)
	{
		if(Is_LeapYear(i))seccnt+=31622400;
		else seccnt+=31536000;
	}
	mon-=1;
	for(i=0;i<mon;i++)
	{
		seccnt+=(u32)mon_table[i]*86400;
		if(Is_LeapYear(year)&&i==1)
			seccnt+=86400;
	}
	seccnt+=(u32)(day-1)*86400;
	seccnt+=(u32)hour*3600;
	seccnt+=(u32)min*60;
	seccnt+=(u32)sec;
	//使能 PWR 和 BKP 外设时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR|RCC_APB1Periph_BKP, ENABLE);  
  PWR_BackupAccessCmd(ENABLE); //使能 RTC 和后备寄存器访问
  RTC_SetCounter(seccnt);  //设置 RTC 计数器的值
  RTC_WaitForLastTask();  //等待最近一次对 RTC 寄存器的写操作完成
  return 0;
}
//初始化RTC
u8 RTC_Init(void)
{
	u8 tem=0;
	if(BKP_ReadBackupRegister(BKP_DR1)!=0x5050)//如果没配置过
	{
		//使能 PWR 和 BKP外设时钟 
		RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR|RCC_APB1Periph_BKP, ENABLE);  
		PWR_BackupAccessCmd(ENABLE);//使能后备寄存器访问
		BKP_DeInit();//③复位备份区域
		RCC_LSEConfig(RCC_LSE_ON);//设置外部低速晶振(LSE)
		//检查指定的RCC标志位设置与否,等待低速晶振就绪
		while(RCC_GetFlagStatus(RCC_FLAG_LSERDY)==RESET)
		{
			tem++;
			delay_ms(10);
		}
		if(tem>=250)return 1;//初始化时钟失败,晶振有问题
		RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);//设置RTC时钟
		RCC_RTCCLKCmd(ENABLE);//使能 RTC 时钟
		RTC_WaitForLastTask();//等待最近一次对 RTC 寄存器的写操作完成
		RTC_WaitForSynchro();//等待 RTC 寄存器同步
		RTC_ITConfig(RTC_IT_SEC,ENABLE);//使能 RTC 秒中断
		RTC_WaitForLastTask(); //等待最近一次对 RTC 寄存器的写操作完成
    RTC_SetPrescaler(32767);  //设置 RTC 预分频的值
    RTC_WaitForLastTask(); //等待最近一次对 RTC 寄存器的写操作完成
    RTC_Set(2009,12,2,10,0,55); //设置时间
    RTC_ExitConfigMode();  //退出配置模式
		//向指定的后备寄存器中写入用户程序数据 0x5050
    BKP_WriteBackupRegister(BKP_DR1,0X5050);
	}
	else
	{
		RTC_WaitForSynchro(); //等待最近一次对 RTC 寄存器的写操作完成
    RTC_ITConfig(RTC_IT_SEC,ENABLE); //使能 RTC 秒中断
    RTC_WaitForLastTask(); //等待最近一次对 RTC 寄存器的写操作完成
	}
	RTC_NVIC_Config(); //RCT 中断分组设置  
  RTC_Get(); //更新时间
  return 0; //ok
}
void RTC_IRQHandler(void)
{
	if(RTC_GetITStatus(RTC_IT_SEC)!=RESET)//秒中断
	{
		RTC_Get();//更新时间
	}
	if(RTC_GetITStatus(RTC_IT_ALR)!=RESET)//闹钟中断
	{
		RTC_ClearITPendingBit(RTC_IT_ALR);//清闹钟中断
	}
	RTC_ClearITPendingBit(RTC_IT_SEC|RTC_IT_OW);//清中断
	RTC_WaitForLastTask();
}

我们是以1970年为基准,往后计时的,即当1970年一月一日00点00分00秒时,计数器从0开始计时。。

说一个比较粗心的地方(导致调试了一晚上都没调好),在写RTC_Get()函数(更新时间)的时候不小心将一个16位的变量当成32的用了。。结果是一晚上都忙的热火朝天还没找到是哪错了,早晨来到一直到中午才弄好。。sad

rtc.h

#ifndef _RTC_H
#define _RTC_H
#include "sys.h"
#include "delay.h"
#include "usart.h"
typedef struct
{
	vu8 hour;
  vu8 min;
  vu8 sec;
  //公历日月年周
  vu16 w_year;
  vu8 w_month;
	vu8 w_date;
	vu8 week;
}_calendar_obj;
extern _calendar_obj calendar;//日历结构体
//void Disp_Time(u8 x,u8 y,u8 size); //在制定位置开始显示时间
//void Disp_Week(u8 x,u8 y,u8 size,u8 lang);//在指定位置显示星期
u8 RTC_Init(void); //初始化 RTC,返回 0,失败;1,成功;
u8 Is_LeapYear(u16 year); //平年,闰年判断
u8 RTC_Get(void); //更新时间
u8 RTC_Get_Week(u16 year,u8 month,u8 day);
u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec);//设置时间
#endif

主函数

#include "led.h"
#include "delay.h"
#include "sys.h"
#include "usart.h"
#include "lcd.h"
#include "usmart.h"
#include "rtc.h"

void init(void)
{
	NVIC_Configuration();
	delay_init();
	uart_init(9600);
	LED_Init();
	LCD_Init();
	usmart_dev.init(72);//初始化SMART组件
}
int main(void)
{
	u8 t;
	init();
  POINT_COLOR=RED;
	while(RTC_Init())		//RTC初始化	,一定要初始化成功
	{ 
		LCD_ShowString(60,130,200,16,16,"RTC ERROR!   ");	
		delay_ms(800);
		LCD_ShowString(60,130,200,16,16,"RTC Trying...");	
	}
	//显示时间
	POINT_COLOR=BLUE;//设置字体为蓝色					 
	LCD_ShowString(60,130,200,16,16,"    -  -     ");	   
	LCD_ShowString(60,162,200,16,16,"  :  :  ");	 		    
	while(1)
	{								    
		if(t!=calendar.sec)
		{
			t=calendar.sec;
			LCD_ShowNum(60,130,calendar.w_year,4,16);									  
			LCD_ShowNum(100,130,calendar.w_month,1,16);									  
			LCD_ShowNum(124,130,calendar.w_date,2,16);	 
			switch(calendar.week)
			{
				case 0:
					LCD_ShowString(60,148,200,16,16,"Sunday   ");
					break;
				case 1:
					LCD_ShowString(60,148,200,16,16,"Monday   ");
					break;
				case 2:
					LCD_ShowString(60,148,200,16,16,"Tuesday  ");
					break;
				case 3:
					LCD_ShowString(60,148,200,16,16,"Wednesday");
					break;
				case 4:
					LCD_ShowString(60,148,200,16,16,"Thursday ");
					break;
				case 5:
					LCD_ShowString(60,148,200,16,16,"Friday   ");
					break;
				case 6:
					LCD_ShowString(60,148,200,16,16,"Saturday ");
					break;  
			}
			LCD_ShowNum(60,162,calendar.hour,2,16);									  
			LCD_ShowNum(84,162,calendar.min,2,16);									  
			LCD_ShowNum(108,162,calendar.sec,2,16);
			LED0=!LED0;
		}	
		delay_ms(10);								  
	}  											
}

通过USMART就可以设置任意时间啦。。