STM32學習記錄(九):RTC

记录学习的Lyx發表於2024-08-11

RTC框圖

實時時鐘(Real-time clock: RTC)是一個獨立的計時器。RTC提供一組連續執行的計數器,可以與合適的軟體一起使用,以提供時鐘日曆功能。可以寫入計數器值以設定系統的當前時間/日期。

9-1.png

可以選擇以下三種作為RTC時鐘源:

  • HSE時鐘進行128分頻
  • LSE振盪器時鐘
  • LSI振盪器時鐘

有關時鐘的更多資訊,參考STM32時鐘樹
RTC核心部分由2個部分構成:

  • RTC分頻器RTC_PRLRTC_DIV構成:
    • RTC_PRL是預分頻裝載暫存器,用來儲存RTC預分頻器的週期計數值。
    • RTC_DIV是預分頻器計數暫存器(只讀)。在TR_CLK的每個週期裡,RTC預分頻器中計數器的值都會被重新設定為RTC_PRL暫存器的值。使用者可透過讀取RTC_DIV暫存器,以獲得預分頻計數器的當前值,而不停止分頻計數器的工作,從而獲得精確的時間測量。當RTC_DIV的值等於RTC_PRL的值,可生成最長為1s的時間基準TR_CLK
  • RTC32位可程式設計計數器RTC_CNTRTC_ALR構成
    • RTC_CNT是32位計數暫存器,存放RTC當前計數值,計數的速率取決於TR_CLK。分為兩個16位暫存器RTC_CNTHRTC_CNTL
    • RTC_ALR是鬧鐘(alarm)暫存器,當可程式設計計數器(RTC_CNT)的值與RTC_ALR中的32位值相等時,觸發一個鬧鐘事件,並且產生RTC鬧鐘中斷。

系統復位後,對後備暫存器和RTC的訪問被禁止,這是為了防止對後備區域(BKP)的意外寫操作。執行以下操作使能對(Backup)後備暫存器和RTC的訪問:

  • 透過設定RCC_APB1ENR暫存器的PWREN位和BKPEN位,來開啟POWER和BACKUP的時鐘
  • 透過設定電源控制暫存器(PWR_CR)的DBP位,允許對RTC和Backup暫存器的訪問

PWR

電源控制(Power control:PWR),電源框圖如下。STM32的工作電壓(VDD)為2.0~3.6V。透過內建的電壓調節器提供所需的1.8V電源。 當主電源VDD掉電後,可透過VBAT腳為實時時鐘(RTC)和備份暫存器提供電源。
備份域(Backup domain)有以下幾個部分構成:

  • 頻率為32K的LSE振盪器
  • BKP(備份)暫存器
  • RCC_BDCR(備份域控制)暫存器
  • RTC
    9-2.png

BKP

備份暫存器(Backup Register)是42個16位的暫存器,可用來儲存84個位元組的使用者應用程式資料。他們處在備份域裡,當VDD電源被切斷,他們仍然由VBAT維持供電,資料不會丟失。當系統在待機模式下被喚醒,或系統復位或電源復位時,他們也不會被複位。BKP可用於RTC校準功能。

RTC顯示實時時間

透過串列埠配置修改將RTC計數暫存器修改為當前時間,開啟RTC秒中斷(Second Interrupt),1S產生一次RTC中斷,並透過串列埠助手實時顯示時間。備份暫存器在這個例程中的作用就是檢查RTC是否被配置了。由於使用的最小系統板VBAT引腳沒有接電池,所以需要單獨供電,斷電後BKP的內容會被清空。

整體流程圖

透過串列埠修改RTC外設暫存器的值設為當前時間,配置完成後微控制器向串列埠助手傳送資料,使用串列埠助手顯示當前時間。BKP在本例程中的作用就是用於檢測RTC是否被配置,透過向BKP_DR1暫存器寫入一個任意的值,來判斷RTC是否被配置。整體流程圖如下:

flowchart TD id0["配置NVIC"] --> id["上電檢測BKP暫存器"] --> id1["BKP暫存器是否為0x1234?"] -->|NO| id2["配置RTC並向BKP暫存器寫入0x1234"] --> id4["透過串列埠助手設定RTC的時間"] --> id6 id1 -->|YES| id3["等待RTC的暫存器同步"] --> id5["使能RTC秒中斷"] --> id6["清除復位標誌位"] --> id7["向串列埠顯示當前時間"]
void MyRTC_Init(void)
{
    NVIC_Configuration();
    if(BKP_ReadBackupRegister(BKP_DR1) != 0x1234)
    {
        printf("\r\n\n RTC not yet configured....");

        RTC_Configuration();    //配置RTC

        printf("\r\n RTC configured....");

        Time_Adjust();

        BKP_WriteBackupRegister(BKP_DR1, 0x1234);
    }
    else
    {
        /* 檢查電源復位標誌位是否被設定 */
        if (RCC_GetFlagStatus(RCC_FLAG_PORRST) != RESET)
        {
            printf("\r\n\n Power On Reset occurred....");
        }
        /* 檢查引腳復位標誌位是否被設定 */
        else if (RCC_GetFlagStatus(RCC_FLAG_PINRST) != RESET)
        {
            printf("\r\n\n External Reset occurred....");
        }

        printf("\r\n No need to configure RTC....");
        /* 等待RTC同步 */
        RTC_WaitForSynchro();

        /* 使能RTC秒中斷 */
        RTC_ITConfig(RTC_IT_SEC, ENABLE);
 
        /* 等待RTC暫存器上的最後一次寫入操作完成 */
        RTC_WaitForLastTask();
    }

    /* 清除復位標誌位 */
    RCC_ClearFlag();
}

配置RTC

系統復位後,執行以下操作才能實現對(Backup)備份暫存器和RTC的訪問:

  • 開啟PWR、BKP時鐘
  • 允許訪問BACKUP暫存器、RTC外設
flowchart TD id["開啟PWR、BKP時鐘"] --> id1["允許訪問BACKUP暫存器、RTC外設"] --> id2["復位Backup暫存器"] --> id3["使能LSE並選擇LSE為RTC時鐘"] --> id4["使能RTC時鐘"] --> id5["等待RTC的暫存器與APB時鐘同步"] --> id6["開啟RTC秒中斷"] --> id7["設定RTC時鐘預分頻係數"]
{ signal: [
  { name: "RTCCLK",  wave: "p..........", period: 2 },
  { name: "RTC_DIV",  wave: "=.=.=.=.=.=.=.=.=.=.=.", data: ["0x00", "0x03", "0x02", "0x01", "0x00","0x03", "0x02", "0x01", "0x00", "0x03", "0x02"] },
  { name: "TR_CLK", wave: "0.1.0.....1.0.....1.0.."},
  { name: "RTC_Second", wave: "0.1.0.....1.0.....1.0.."},
  { name: "RTC_CNT", wave: "=...=.......=.......=...", data: ["FFFFFFFD", "FFFFFFFFE", "FFFFFFFFF", "00000000"]  },
  { name: "RTC_Overflow", wave:"0.................1.0.."}
  ],
  head:{
   text:'RTC_PRL=0x03,計數溢位的波形圖'
 }
}
  • RTCCLK的時鐘是來自LSE振盪器的時鐘,\(f_{LSE}\)=32.728KHz。由參考手冊可得\(f_{TR\_CLK}=\frac{f_{RTCCLK}}{RTC\_PRL[19:0]+1}\),時間基準TR_CLK最大可計時1S,即RTC_Second的時鐘週期最長可為1S。要獲得1S的時間基準,RTC_PRL暫存器應該寫入32767。
  • RTC_DIV存放分頻計數器當前值,當減為0時,在TR_CLK的上升沿時發生重灌載(reload),RTC_DIV的值被重灌載為RTC_PRL的值
  • RTC_CNT暫存器的值在每個TR_CLK的時鐘週期下,值都會遞增。RTC_CNT暫存器用於存放系統的當前時間
  • RTC_Overflow:當32位的RTC_CNT暫存器計數滿了之後,在下個TR_CLK上升沿時,產生一次溢位訊號

RTC配置程式程式碼:

void RTC_Configuration(void)
{
    /* 開啟PWR、BKP時鐘 */
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);

    /* 允許訪問Backup和RTC暫存器 */
    PWR_BackupAccessCmd(ENABLE);

    /* 復位BKP */
    BKP_DeInit();

    /* 使能LSE */
    RCC_LSEConfig(RCC_LSE_ON);

    /* 等待LSE準備好 */
    while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET)
    {
    }

    /* 選擇LSE做為RTC時鐘源 */
    RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);

    /* 使能RTC時鐘 */
    RCC_RTCCLKCmd(ENABLE);

    /* 等待RTC暫存器同步 */
    RTC_WaitForSynchro();

    /* RTC寫暫存器操作必須要這段語句,等待上次對RTC暫存器的寫操作完成 */
    RTC_WaitForLastTask();

    /* 每秒產生一次中斷 */
    RTC_ITConfig(RTC_IT_SEC, ENABLE);

    /* 等待上次對RTC暫存器的寫操作完成 */
    RTC_WaitForLastTask();

    /* 設定預分頻 */
    RTC_SetPrescaler(32767);    //RTC時鐘週期 = (32.768KHz) / (32767 + 1) = 1Hz = 1s

    /* 等待上次對RTC暫存器的寫操作完成 */
    RTC_WaitForLastTask();
}

串列埠修改RTC

透過串列埠設定RTC計數暫存器的值,printf()向串列埠助手傳送資訊,USART_Scanf()函式接收來自串列埠助手的訊息。USART的資料幀使用8位有效資料。

flowchart TD id["調整時間Time_Adjust()"] -->|呼叫| id1["設定RTC計數暫存器設RTC_SetCounter()"] -->|呼叫| id2["獲取來自串列埠的資料USART_Scanf()"]

串列埠修改RTC程式程式碼:

/**
  * @brief  調整時間
  * @param  none
  * @retval none
  */
void Time_Adjust(void)
{
    /* 等待上次對RTC暫存器的寫操作完成 */
    RTC_WaitForLastTask();

    /* 設定RTC計數器的值 */
    RTC_SetCounter(Time_Regulate());

    /* 等待上次對RTC暫存器的寫操作完成 */
    RTC_WaitForLastTask();
}

/**
  * @brief  透過串列埠設定時間
  * @param  none
  * @retval 用秒錶示的時間
  */
uint32_t Time_Regulate(void)
{
    uint32_t Tmp_HH = 0xFF, Tmp_MM = 0xFF, Tmp_SS = 0xFF;

    printf("\r\n==============Time Settings=====================================");
    printf("\r\n  Please Set Hours");
    
    while(Tmp_HH == 0xFF)
    {
        Tmp_HH = USART_Scanf(23);
    }
    printf(":  %d", Tmp_HH);
    printf("\r\n  Please Set Minutes");
    while(Tmp_MM == 0xFF)
    {
        Tmp_MM = USART_Scanf(59);
    }
    printf(":  %d", Tmp_MM);
    printf("\r\n  Please Set Seconds");
    while(Tmp_SS == 0xFF)
    {
        Tmp_SS = USART_Scanf(59);
    }
    printf(":  %d", Tmp_SS);

    /* 將輸入的值轉換為Counter CNT */
    return (Tmp_HH * 60 * 60 + Tmp_MM * 60 + Tmp_SS);
}

/**
  * @brief  獲取來自串列埠的資料
  * @param  value 允許串列埠輸入的最大值
  * @retval 轉換後的串列埠的資料
  */
uint8_t USART_Scanf(uint32_t value)
{
    uint32_t index = 0;
    uint8_t rev[2] = {0, 0};
    
    while(index < 2)
    {
        /* 等待RXNE = 1 */
        while(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET)
        {
        }
        rev[index++] = USART_ReceiveData(USART1);
        if(rev[index - 1] < 0x30 || rev[index - 1] > 0x39)
        {
            printf("\n\rPlease enter valid number between 0 and 9");
            index--;
        }
    }
    
    index = ((rev[0] - 0x30) * 10) + (rev[1] - 0x30);     //獲取輸入的值

    /* 檢驗 */
    if(index > value)
    {
        printf("\n\rPlease enter valid number between 0 and %d", value);
        return 0xFF;
    }
 
    return index;
}

顯示實時時間

/**
  * @brief  將時間按照指定格式顯示
  * @param  value RTC計數暫存器的值
  * @retval none
  */
void Time_Display(uint32_t value)
{
    /* 當前時間為23:59:59時,復位RTC */
    if(RTC_GetCounter() == 0x0001517F)
    {
        RTC_SetCounter(0x00);
         /* Waits until last write operation on RTC registers has finished. */
        RTC_WaitForLastTask();
    }
    uint32_t Tmp_HH = 0x00, Tmp_MM = 0x00, Tmp_SS = 0x00;
    Tmp_HH = value / 3600;  //小時
    Tmp_MM = (value % 3600) / 60;   //分鐘
    Tmp_SS = (value % 3600) % 60; //秒

    printf("Time: %0.2d:%0.2d:%0.2d\r", Tmp_HH, Tmp_MM, Tmp_SS);
}

/**
  * @brief  顯示實時時間
  * @param  none
  * @retval none
  */
void Time_Show(void)
{
    while(1)
    {
        if(time_display == 1)
        {
            Time_Display(RTC_GetCounter());
            time_display = 0;   //透過RTC中斷來置1
        }
    }
}

RTC中斷處理函式

void RTC_IRQHandler(void)
{
    if(RTC_GetITStatus(RTC_IT_SEC) != RESET)
    {
        time_display = 1;
        RTC_ClearITPendingBit(RTC_IT_SEC);
        /* Waits until last write operation on RTC registers has finished. */
        RTC_WaitForLastTask();
    }
}

主函式


int main(void)
{
    COM_Init();			//串列埠初始化,取樣資料
    MyRTC_Init();		//初始化RTC
    Time_Show();		//在無限迴圈中顯示時間
}

演示結果

使用串列埠助手手動調節RTC來顯示當前時間
9-3.gif

完整例程

9-1 RTC

相關文章