前言:stm32系列產品普遍都有實時時鐘RTC模塊,它提供一個掉電保持計時功能,掉電後由後備供電區域供電。除了提供時間和日期之外,還可以設置鬧鐘提醒,且可以在待機模式下設置鬧鐘喚醒系統。在一些小容量、中容量產品中,只有一個32位的計數寄存器,如果該計數寄存器自增1週期設置爲1s,那麼軟件可以根據該計數寄存器的值算出當前的日期和時、分、秒。在一些大容量的產品中,年、月、日、時、分、秒都是獨立的寄存器,可直接讀出需要的值。
1.RTC簡介
實時時鐘是一個獨立的定時器。RTC模塊擁有一組連續計數的計數器,在相應軟件配置下,可提供時鐘日曆的功能。修改計數器的值可以重新設置系統當前的時間和日期。
RTC模塊和時鐘配置系統(RCC_BDCR寄存器)處於後備區域,即在系統復位或從待機模式喚醒後,RTC的設置和時間維持不變。
2.RTC特性
● 可編程的預分頻係數:分頻係數最高爲
● 32位的可編程計數器,可用於較長時間段的測量。(若最小單位爲秒:=4,294,967,295秒=49,710天 大概136年。)
● 2個分離的時鐘:用於APB1接口的PCLK1和RTC時鐘(RTC時鐘的頻率必須小於PCLK1時鐘頻率的四分之一以上)。
● 可以選擇以下三種RTC的時鐘源:
─ HSE時鐘除以128;
─ LSE振盪器時鐘;(常用的是外部低速,穩定精準,重要的是VDD掉電後可有後備供電區域給它供電)
─ LSI振盪器時鐘。
● 2個獨立的復位類型:
─ APB1接口由系統復位;
─ RTC核心(預分頻器、鬧鐘、計數器和分頻器)只能由後備域復位。(可導致後備區域復位:侵入事件、軟件復位、VBAT掉電)
● 3個專門的可屏蔽中斷:
─ 鬧鐘中斷,用來產生一個軟件可編程的鬧鐘中斷。
─ 秒中斷,用來產生一個可編程的週期性中斷信號(最長可達1秒)。
─ 溢出中斷,指示內部可編程計數器溢出並回轉爲0的狀態。
3.RTC功能框圖
從左上角開始到右下角看圖理解,系統通過APB1總線對後備區域的RTC進行通信,那三斜槓的意思是可斷開,因爲在系統掉電的時候RTC是獨立的,只有在系統運行時纔有可能會相通。RTCCLK(RTC時鐘輸入)必須小於PCLK1(低速AHB時鐘)的三分之一以上。
RTC_PRL(預分頻裝載寄存器)的值決定TR_CLK脈衝產生的週期,RTC_DIV(預分頻器餘數寄存器)可讀不可寫,當RTCCLK的一個上升沿到來,RTC_DIV的值減1,減到0後硬件重載爲RTC_PRL的值同時產生一個TR_CLK脈衝,一個TR_CLK脈衝的到來會使RTC_CNT(計數器寄存器)的值加1,同時產生一個RTC_Second中斷(由軟件配置是否使能,“秒中斷”並不一定是一秒觸發一次,具體是根據RTC時鐘和RTC_PRL的值決定)。
當RTC_CNT的值溢出後從0開始,併產生一個溢出中斷(由軟件配置是否使能)。當RTC_CNT等於RTC_CNTRTC_ALR(鬧鐘寄存器)時,產生一個鬧鐘中斷(由軟件配置是否使能,可在用在系統待機模式下喚醒系統)。
RTC_CR(RTC控制寄存器)不在後備區域,所以它的數據會隨系統復位而復位,也就是說當系統下電是,秒中斷、溢出中斷、鬧鐘中斷就不存在了,在下一次系統上電時需要重新初始化。圖中“退出待機模式”有兩種方法:RTC鬧鐘、WKUP引腳。
這裏說一個圖上沒有的知識點,PC.13引腳也就是侵入檢測引腳,它可以用來輸出RTC鬧鐘脈衝、RTC秒脈衝或者是鍾頻率爲 RTC 時鐘除以 64的脈衝,後者在系統下電的情況下無法輸出。
寄存器的配置不作詳細講解,下面是利用標準庫函數進行開發的RTC應用。
4.代碼設計
#include "stm32f10x.h"
#include "stdio.h"
#include "string.h"
static void RTC_Configuration(void);
static void NVIC_Configuration(void);
static void USART1_Config(void);
static void Delay(__IO u32 nCount);
static char *USART_GetString(char *s);
char strbuf[50];
unsigned char HH,MM,SS;
static char cmd_SetTime[]="AT+SETTIME";//設置時間的指令
static char cmd_SetAlarm[]="AT+SETALARM";//設置鬧鐘的指令
int main(void)
{
USART1_Config();//串口1輸出調試信息
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);//使能電源管理時鐘,後備寄存器模塊時鐘
PWR_BackupAccessCmd(ENABLE);//使能RTC和後備寄存器的訪問
if(BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5)//如果在後備數據寄存器1讀取到的值不是0xA5A5 說明後備寄存器(RTC等)未初始化
{
RTC_Configuration(); //配置RTC,時鐘選用LSE(外部低速時鐘),RTC計數器1s自增1
RTC_WaitForLastTask();//等待RTC操作完成
RTC_SetCounter(0); //首次時間設置爲 00:00:00
RTC_WaitForLastTask();//等待RTC操作完成
BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);//後備數據寄存器1寫入0xA5A5,標誌RTC已初始化
}
else //系統復位,而後備寄存器並沒有被複位時,無需再初始化RTC
{
if (RCC_GetFlagStatus(RCC_FLAG_PORRST) != RESET){
printf("POR/PDR 復位\r\n"); //VDD掉電上電
}else if (RCC_GetFlagStatus(RCC_FLAG_PINRST) != RESET){
printf("RESET引腳復位\r\n");
}else if (RCC_GetFlagStatus(RCC_FLAG_SFTRST) != RESET){
printf("軟件復位\r\n");
}else if (RCC_GetFlagStatus(RCC_FLAG_IWDGRST) != RESET){
printf("獨立看門狗復位\r\n");
}else if (RCC_GetFlagStatus(RCC_FLAG_WWDGRST) != RESET){
printf("窗口看門狗復位\r\n");
}else if (RCC_GetFlagStatus(RCC_FLAG_LPWRRST) != RESET){
printf("低功耗復位\r\n");
}
//以上覆位,後備寄存器的數據仍然保持
printf("不需要配置RTC.\r\n");
RTC_WaitForSynchro();//等待 RTC 寄存器與RTC的APB時鐘同步
}
NVIC_Configuration();//配置RTC中斷優先級
//以下三個中斷根據需要開啓,三者觸發時都是進入RTC_IRQHandler中斷函數,通過RTC_GetITStatus判斷具體是哪個中斷觸發
#if 1 //(可選)
RTC_WaitForLastTask();//等待RTC操作完成,下同
RTC_ITConfig(RTC_IT_SEC, ENABLE);//秒中斷使能秒,用來產生一個可編程的週期性中斷信號(最長可達1秒)。
RTC_WaitForLastTask();
#endif
#if 1 //(可選)
RTC_WaitForLastTask();
RTC_ITConfig(RTC_IT_ALR, ENABLE);//鬧鐘中斷使能,用來產生一個軟件可編程的鬧鐘中斷。
RTC_WaitForLastTask();
#endif
#if 1 //(可選)
RTC_WaitForLastTask();
RTC_ITConfig(RTC_IT_OW, ENABLE);//溢出中斷使能,指示內部可編程計數器溢出並回轉爲0的狀態。
RTC_WaitForLastTask();
#endif
#if 1 //(可選)
/*BKP_RTCOutputSource_None 侵入檢測管腳(PC.13)上無 RTC 輸出
BKP_RTCOutputSource_CalibClock 侵入檢測管腳(PC.13)上輸出,其時鐘頻率爲 RTC 時鐘除以 64
BKP_RTCOutputSource_Alarm 侵入檢測管腳(PC.13)上輸出 RTC 鬧鐘脈衝
BKP_RTCOutputSource_Second 侵入檢測管腳(PC.13)上輸出 RTC 秒脈衝*/
BKP_TamperPinCmd(DISABLE);
BKP_RTCOutputConfig(BKP_RTCOutputSource_Second);
#endif
//清除復位標誌
RCC_ClearFlag();
while(1)
{
if(USART_GetString(strbuf)!=NULL){
if(strncmp(strbuf,cmd_SetTime,strlen(cmd_SetTime))==0){
HH = (strbuf[strlen(cmd_SetTime)+1]-'0')*10 +(strbuf[strlen(cmd_SetTime)+2]-'0');
MM = (strbuf[strlen(cmd_SetTime)+4]-'0')*10 +(strbuf[strlen(cmd_SetTime)+5]-'0');
SS = (strbuf[strlen(cmd_SetTime)+7]-'0')*10 +(strbuf[strlen(cmd_SetTime)+8]-'0');
printf("設置RTC時間爲 %d:%d:%d\r\n",HH,MM,SS);
RTC_WaitForLastTask();
RTC_SetCounter(HH*3600+MM*60+SS);//設置RTC當前計數寄存器的值
RTC_WaitForLastTask();
printf("設置RTC時間成功!\r\n");
}
if(strncmp(strbuf,cmd_SetAlarm,strlen(cmd_SetAlarm))==0){
HH = (strbuf[strlen(cmd_SetAlarm)+1]-'0')*10 +(strbuf[strlen(cmd_SetAlarm)+2]-'0');
MM = (strbuf[strlen(cmd_SetAlarm)+4]-'0')*10 +(strbuf[strlen(cmd_SetAlarm)+5]-'0');
SS = (strbuf[strlen(cmd_SetAlarm)+7]-'0')*10 +(strbuf[strlen(cmd_SetAlarm)+8]-'0');
printf("設置鬧鐘時間爲 %d:%d:%d\r\n",HH,MM,SS);
RTC_WaitForLastTask();
RTC_SetAlarm(HH*3600+MM*60+SS);//設置RTC鬧鐘值,記得使能鬧鐘中斷
RTC_WaitForLastTask();
printf("設置鬧鐘時間成功!\r\n");
}
}
}
}
void NVIC_Configuration(void)//配置RTC中斷優先級
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void RTC_Configuration(void)
{
BKP_DeInit();
RCC_LSEConfig(RCC_LSE_ON);
while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET);//等待LSE(外部低速)時鐘穩定
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);//選擇LSE時鐘作爲RTC時鐘,此外還可以選擇:LSI、HSE_Div128
RCC_RTCCLKCmd(ENABLE);//使能RTC時鐘
RTC_WaitForSynchro();//等待 RTC 寄存器與RTC的APB時鐘同步
RTC_SetPrescaler(32767); //RTC period = RTCCLK/RTC_PR = (32.768 KHz)/(32767+1)=1s 這個決定“秒中斷”觸發的週期
RTC_WaitForLastTask();//等待RTC操作完成
}
void USART1_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
//配置串口1(USART1)時鐘
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA |RCC_APB2Periph_GPIOC, ENABLE);
//配置串口1(USART1 Tx (PA.09))
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//配置串口1 USART1 Rx (PA.10)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOC, &GPIO_InitStructure);
//串口1模式(USART1 mode)配置
USART_InitStructure.USART_BaudRate = 9600;//一般設置爲9600;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No ;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &USART_InitStructure);
USART_Cmd(USART1, ENABLE); //使能串口
USART_ClearFlag(USART1,USART_FLAG_TC);
}
int fputc(int ch, FILE *f)//重寫標準庫的fputc函數
{
//將Printf內容發往串口
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)!=SET);
USART_SendData(USART1, (unsigned char) ch);
return (ch);
}
char *USART_GetString(char *s)//從串口阻塞等待一個字符串,遇到0x0d或者0x0a結束
{
char code;
char *str = s;
if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)==RESET){
*s=0;
return NULL;
}
while(1){
if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)!=RESET){
USART_ClearFlag(USART1,USART_FLAG_RXNE);
code=USART_ReceiveData(USART1);
if(code == 0x0D||code ==0x0A){
*str=0;
break;
}else{
*str++ = code;
}
}
}
return s;
}
void Delay(__IO u32 nCount) //簡單的延時函數
{
for(; nCount != 0; nCount--);
}
在stm32f10x_it.c加入:
unsigned int systick;
static unsigned char THH,TMM,TSS;
void RTC_IRQHandler(void)
{
if (RTC_GetITStatus(RTC_IT_SEC) != RESET) //秒中斷
{
RTC_ClearITPendingBit(RTC_IT_SEC);
systick = RTC_GetCounter();
RTC_WaitForLastTask();
systick %= 86400; //24小時 = 86400s
THH = systick / 3600;
TMM = (systick % 3600) / 60;
TSS = (systick % 3600) % 60;
printf("當前系統時間:%.2d:%.2d:%.2d\r\n",THH,TMM,TSS);
}
if (RTC_GetITStatus(RTC_IT_ALR) != RESET) //鬧鐘中斷
{
RTC_ClearITPendingBit(RTC_IT_ALR);
RTC_WaitForLastTask();
printf("鬧鐘中斷觸發.\r\n");
}
if (RTC_GetITStatus(RTC_IT_OW) != RESET) //溢出中斷
{
RTC_ClearITPendingBit(RTC_IT_OW);
RTC_WaitForLastTask();
printf("溢出中斷觸發.\r\n");
}
}
代碼已經加入很多註釋以便理解,這裏不做講解。編譯編譯後,串口線連接到電腦,檢查VBAT引腳是否接了電池,沒有的話接到電源上。
在串口助手上操作:
發送兩條命令進行調試,勾選發送新行:
設置時間:AT+SETTIME 09:50:10
設置鬧鐘時間:AT+SETALARM 09:50:15
如果想用示波器監測PC.13輸出的脈衝,一定要排除電其他連在該引腳的器件,很多板子都會在該引腳接上一個led,這樣會影響示波器檢測到的波形。
後備供電區域功耗很低,一個鈕釦電池可以用很久,而且當VDD上電時會自動切換爲VDD供電。由於晶振的性能會受溫度的影響,當環境溫度改變時會影響計時的準確性,可通過設置RTC 時鐘校準值進行補償。這個校準值需要隨溫度的變化配置不同的值,所以要進行溫度標定實驗,統計數據得出兩者的關係。在板子上需要加入溫度傳感器以獲取當前溫度,再者設置相應的校準值。
代碼還有很多不足,需要完善,這裏重點是RTC功能的使用方法。如若有誤,還望指出,謝謝。
<<人生最迷惘的階段就是,未曾老去,卻不再年輕。>>