首页 > 其他分享 >STM32学习记录(九):RTC

STM32学习记录(九):RTC

时间:2024-08-11 16:16:27浏览次数:15  
标签:Tmp RTC 记录 -- 串口 STM32 BKP 寄存器

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

标签:Tmp,RTC,记录,--,串口,STM32,BKP,寄存器
From: https://www.cnblogs.com/qianxiaohan/p/18353552

相关文章

  • Java学习记录(二)
    方法方法分为有参方法和无参方法方法就是行为就是功能无参方法:有参方法:返回值返回值可以分为有返回值和无返回值void表示无返回值return表示返回返回值return不具有打印输出功能例如:重载重载就是方法名一致但是参数列表不一致,重载之后的方法名可以重名使用比如......
  • 在线仿真平台+C语言实现:STM32驱动0.96寸OLED屏幕显示DHT11温湿度传感器测量值
    这里推荐一款由深圳航天科技创新研究院推出的在线电路仿真软件,该软件不仅具备原理图绘制与代码编写功能,还支持在线编译代码、上传自定义代码以及进行仿真模拟,此外还能在线生成并允许下载.bin和.hex文件。官网地址如下:Document进入网页后先注册一个账号。  注册完账号后即可......
  • 一些做题记录
    图论P1993小K的农场复习差分约束QAQ。设农场\(i\)种植的作文有\(x_i\)个单位,则题目中的三个条件就是:\[\begin{cases}x_a\gex_b+c\\x_a\lex_b+c\\x_a=x_b\end{cases}\]其中第一个不等式可以转化成\[\begin{cases}x_b\lex_a-c\end{cases}\]注意......
  • Codeforces Round 965 (Div. 2) 补题记录(A,B,D,E1)
    speedforcesagain~A<E1<<B<D<<CA若\(k\equiv1(\bmod2)\),则构造\((x,y)\),\((x-1,y)\),\((x+1,y)\),\((x-2,y)\),\((x+2,y)\),\(\ldots\)。否则构造\((x-1,y)\),\((x+1,y)\),\((x-2,y)\),\((x+2,y)\),\(\ldots\)。#pra......
  • 【STM32】ADC模拟数字转换-规则组单通道
    本篇博客重点在于标准库函数的理解与使用,搭建一个框架便于快速开发 目录 ADC简介ADC时钟配置引脚模拟输入模式规则组通道选择ADC初始化 工作模式数据对齐 触发转换方式连续与单次转换模式扫描模式组内的通道个数ADC初始化框架ADC上电ADC校验 获取转换数......
  • [考试记录] 2024.8.10 csp-s 模拟赛18
    80+20+0+70=170第三题应该有10分暴力的,但我没打。T1星际旅行题面翻译总共有n个节点,m条路径,要求其中m-2条路径走两遍,剩下2条路径仅走一遍,问不同的路径总数有多少,如果仅走一遍的两条边不同则将这两条路径视为不同。样例#1样例输入#15412131415样例输......
  • 从零开始制作一个简易数字示波器,超详细看完必懂 举例基于stm32f103标准库
    为了实现使用STM32f103制作一个简易数值示波器,大体流程如下:1-模拟信号(输入)2-ADC(模数转换)(适当采样率和分辨率配置)3-DMA(直接存储器访问)(DMA搬运,减少CPU负担)4-内存缓冲区(数据转入缓冲区)5-FFT处理(频域数据)(FFT处理(可选,用于频域显示))6-显示处理(像素转换)(数据映射为......
  • 1410、proteus仿真-STM32单片机血压体温血氧心率检测阈值报警加远程监控设计(程序+pro
    毕设帮助、开题指导、技术解答(有偿)见文未 目录方案选择单片机的选择一、设计功能二、proteus仿真图三、程序源码资料包括:需要完整的资料可以点击下面的名片加下我,找我要资源压缩包的百度网盘下载地址及提取码。方案选择单片机的选择方案一:AT89C51是美国ATMEL公......
  • 记录5:ESP32S3的usb使用
    0、前期准备1、会使用idf开发环境2、懂得kconfig1、知识储备1.1概述​TingUSB是一个开源的跨平台的USB主机/设备的usb协议栈,常用在mcu开发平台,由于不采用动态分配内存以及阻塞所有中断事件,将中断事件要处理的事情都放在,非中断函数中处理,因此该usb栈内存设计非常安全......
  • 记录3:ESP32-C3的串口使用
    0、前期准备1、参考首篇文章搭建好esp32环境2、准备好一块esp32开发开发板(本作者使用了esp32c3作为开发平台)1、知识储备1.1概述​UART称为通用异步收发器,可以进行全双工/半双工数据通讯数据通讯,通讯距离取决于上拉驱动能力、波特率,一般只在电路板上使用,如果需要长距......