Loading

STM32学习记录(九):RTC

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时钟预分频系数"]

image

  • 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

posted @ 2024-08-11 16:19  记录学习的Lyx  阅读(256)  评论(0编辑  收藏  举报