cortex_m3_stm32嵌入式學習筆記(十四):RTC實時時鐘(秒中斷)

ARM 389瀏覽

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就可以設置任意時間啦。。

七星彩走势图2元网官网