STM32学习记录(九):RTC

news/2024/9/21 7:51:56/文章来源:https://www.cnblogs.com/qianxiaohan/p/18353552

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 TDid0["配置NVIC"] --> id["上电检测BKP寄存器"] --> id1["BKP寄存器是否为0x1234?"]-->|NO| id2["配置RTC并向BKP寄存器写入0x1234"] --> id4["通过串口助手设置RTC的时间"] --> id6id1 -->|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();    //配置RTCprintf("\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 TDid["开启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 TDid["调整时间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();		//初始化RTCTime_Show();		//在无限循环中显示时间
}

演示结果

使用串口助手手动调节RTC来显示当前时间
9-3.gif

完整例程

9-1 RTC

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/781311.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

炒鸡好用的Markdown语法

简介 Markdown是一种轻量级标记语言,它最初由John Gruber和Aaron Swartz在2004年共同创建,可以通过简单、纯文本的语法,快速构建格式化、排版精美的文档。其可与HTML混编,可导出为HTML、PDF、Word等格式的文件 Markdown可以让作者更多地关注内容本身而非格式排版。同样的内…

Arweave区块链私有化部署

Arweave区块链私有化部署Arweave区块链主打数据永久存储,即保存在区块链的数据永久存在、不可篡改。公链主网络arweave.N.1在2024年8月11日累计产生了148万个区块(见下截图所示),区块还在不断产生,大约2分钟产生一个区块。Arweave区块链可以在Ubuntu 22.04LTS或Ubuntu 24.04…

多线程复习总结

1基本概念 1什么是进程什么是线程 进程:是程序执行一次的过程,他是动态的概念,是资源分配的基本单位。一个应用程序(1个进程是一个软件)。 线程:一个进程可以有多个线程,线程是cpu调度的单位,一个进程中的执行场景/执行单元。 对于java程序来说,当在DOS命令窗口中输入…

kubernetes-POD的基本原理

目录什么是POD?POD有以下特点:为什么使用POD作为最小单元,而不是container为什么允许一个POD里有多个容器POD中如何管理多个容器POD的yaml格式定义配置文件说明如何使用PodPOD的持久性和终止Pause我们首先在节点上运行一个pause容器然后再运行一个nginx容器,nginx将为localh…

【投资认知】- 2024Q1的英伟达NVIDIA

来自关注的Twitter博主@ZeevyInvesting 💡 Investing visuals | 📜 One-pager analysis | 📈 Tech portfolio updates | 🥊 Business Battles来源:https://twitter.com/ZeevyInvesting/status/1801691822705512947名词解释CAGR:复合年增长率(Compound Annual Grow…

【1.0版】【MYSQL安全】sql注入系列:基于报错的 SQL 盲注

主题 sql注入:基于报错的 SQL 盲注一、Floor报错注入1.1 floor 函数1.2 rand函数 1.3 count(*) 1.4 floor函数实际利用二、extractvalue函数三、updatexml函数:同extractvalue本来网页是不显示信息的,但是我们可以构造 payload 让信息通过错误提示回显出来一、Floor报错注入…

CISC和RISC

CISC的特点RISC的特点CISC和RISC的比较如何分辨CISC和RISCCISC的指令不定长,RISC的指令都是定长的 RISC中只有Load\Store类指令可以访问主存

P1502 窗口的星星 题解

题目传送门。 思路 扫描线 扫描线 首先,将题目中给出的条件和问题进行转化: 首先先不考虑边框上的点不算在内的限制,考虑一个点可以对那些矩形产生贡献。 只考虑矩形的右上角,容易发现,每个星星的亮度只对右上角在以星星为左下角的长为 \(W\),高为 \(H\)的矩形有贡献。 如…

bugbountyhunter scope BARKER:第7滴血 存储型 XSS 编码测试和多处引用 报告

注册后,来到UI Display Name处直接点击更新之后,发现反射值的存在尝试一些编码,发现没有任何转换。编码测试更简单,语义一把梭:比如各种华丽花哨的编码到落地并没有被还原成 <>"等语义,此处没有漏洞 https://github.com/swisskyrepo/PayloadsAllTheThings/tre…

centos7中安装了centos-release-scl后,之前的yum源变为不可用 解决方案

centos7中安装了centos-release-scl后,之前的yum源变为不可用 解决方案 1. 前言 今天遇到了一个奇奇怪怪的事情,我自己在自己的服务器(centos7)上安装了centos-release-scl后,之前运行正常的yum命令竟然变得不可用。 2. 场景重现 执行下面这条命令后,再次使用yum报错。 yum …

grpc-gateway:grpc转换为http协议对外提供服务

使用grpc的优点很多,二进制的数据可以加快传输速度,基于http2的多路复用可以减少服务之间的连接次数,和函数一样的调用方式也有效的提升了开发效率。 不过使用grpc也会面临一个问题,我们的微服务对外一定是要提供Restful接口的,如果内部调用使用grpc,在某些情况下要同时提…