stm32之實時時鐘RTC(掉電計時保持、秒中斷、鬧鐘中斷、溢出中斷)

前言:stm32系列產品普遍都有實時時鐘RTC模塊,它提供一個掉電保持計時功能,掉電後由後備供電區域供電。除了提供時間和日期之外,還可以設置鬧鐘提醒,且可以在待機模式下設置鬧鐘喚醒系統。在一些小容量、中容量產品中,只有一個32位的計數寄存器,如果該計數寄存器自增1週期設置爲1s,那麼軟件可以根據該計數寄存器的值算出當前的日期和時、分、秒。在一些大容量的產品中,年、月、日、時、分、秒都是獨立的寄存器,可直接讀出需要的值。

1.RTC簡介

實時時鐘是一個獨立的定時器。RTC模塊擁有一組連續計數的計數器,在相應軟件配置下,可提供時鐘日曆的功能。修改計數器的值可以重新設置系統當前的時間和日期。

RTC模塊和時鐘配置系統(RCC_BDCR寄存器)處於後備區域,即在系統復位或從待機模式喚醒後,RTC的設置和時間維持不變。

2.RTC特性

     ● 可編程的預分頻係數:分頻係數最高爲2^{20}

     ● 32位的可編程計數器,可用於較長時間段的測量。(若最小單位爲秒:2^{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功能的使用方法。如若有誤,還望指出,謝謝。

 

 

 

<<人生最迷惘的階段就是,未曾老去,卻不再年輕。>>

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章