stm32之实时时钟RTC(掉电计时保持、秒中断、闹钟中断、溢出中断)
前言: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自增1RTC_WaitForLastTask();//等待RTC操作完成RTC_SetCounter(0); //首次時間設置為 00:00:00RTC_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 時鐘除以 64BKP_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_Div128RCC_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小時 = 86400sTHH = 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功能的使用方法。如若有誤,還望指出,謝謝。
?
?
?
<<人生最迷惘的階段就是,未曾老去,卻不再年輕。>>
總結
以上是生活随笔為你收集整理的stm32之实时时钟RTC(掉电计时保持、秒中断、闹钟中断、溢出中断)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: stm32之PVD可编程电压监测器(掉电
- 下一篇: 基于stm32、spi协议的Fatfs文