STM32标准库——(18)Unix时间戳、BKP备份寄存器、RTC实时时钟

1.Unix时间戳

1.1 简介

32位有符号数所能表示的最大数字是2^32/2-1这个数是21亿多,这其实是有溢出风险的,因为目前到2023年时间戳已经计到16亿了,32位有符号数的时间戳会在2038年的1月19号溢出,64位的时间戳能存储的时间范围非常非常的大,看下手册STM32它核心的计时部分,是一个32位的可编程计数器,这说明我们这款stm32 ,它的时间戳是32位的数据类型,这表示我们这个stm32也会在2038年出现bug吗,实际上并不会啊,因为根据我的研究,这个时间戳在stm32程序中定义的其实是无符号的,要到2106年才会溢出。

1.2 UTC/GMT

1.3 时间戳转换

C语言的time.h模块提供了时间获取和时间戳转换的相关函数,可以方便地进行秒计数器、日期时间和字符串之间的转换

 

1.4 C标准库<Time.h> 

1.4.1 time.h 中定义的变量类型
序号变量 & 描述
1size_t 是无符号整数类型,它是 sizeof 关键字的结果。
2clock_t 这是一个适合存储处理器时间的类型。
3time_t is 这是一个适合存储日历时间类型。
4struct tm 这是一个用来保存时间和日期的结构。
1.4.2 tm结构定义
struct tm {int tm_sec;         /* 秒,范围从 0 到 59        */int tm_min;         /* 分,范围从 0 到 59        */int tm_hour;        /* 小时,范围从 0 到 23        */int tm_mday;        /* 一月中的第几天,范围从 1 到 31    */int tm_mon;         /* 月,范围从 0 到 11        */int tm_year;        /* 自 1900 年起的年数        */int tm_wday;        /* 一周中的第几天,范围从 0 到 6    */int tm_yday;        /* 一年中的第几天,范围从 0 到 365    */int tm_isdst;       /* 夏令时                */
};
1.4.3 相关示例
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
// struct tm {
//     int tm_sec;   /* 秒,范围从0 到59       */
//     int tm_min;   /* 分,范围从0 到59       */
//     int tm_hour; /* 小时,范围从0 到23       */
//     int tm_mday; /* 一月中的第几天,范围从1 到31   */
//     int tm_mon;   /* 月,范围从0 到11       */
//     int tm_year; /* 自1900 年起的年数      */
//     int tm_wday; /* 一周中的第几天,范围从0 到6   */
//     int tm_yday; /* 一年中的第几天,范围从0 到365   */
//     int tm_isdst; /* 夏令时              */
// };
int main(int argc, char const *argv[])
{time_t Time; // 时间戳struct tm_Date;//日期存放结构体char *time_str;//日期字符串// Time = time(NULL);time(&Time);printf("Time = %ld\n", Time); // Time = 1709649653Date = *localtime(&Time);//显示当地时间printf("%d\n", Date.tm_year + 1900);//加1900是因为这里tm_year是从1900后开始记起printf("%d\n", Date.tm_mon + 1);//加1是因为这里tm_mon是从0开始记起printf("%d\n", Date.tm_mday);printf("%d\n", Date.tm_hour);printf("%d\n", Date.tm_min);printf("%d\n", Date.tm_sec);printf("%d\n", Date.tm_wday);Time = mktime(&tm_Data); //将日期时间重新转化为秒计时器printf("%d\n",Time); time_str = ctime(&Time);//将秒计数器转化为字符串printf(time_str);//这里字符串的名字就是首地址tine_str = asctime(&tm_Data);//将日期转化为字符串printf(time_str);char t[50];strftime(t,50,"$H-%M-%S",tm_Data);//前两个参数 传入字符数组和数组长度 第三个参数给定指定的格式字符串 第四个参数为日期结构体 %H是小时 %M是分钟 %S是秒 将字符串格式对应日期结构体中的参数写入字符数组中 再将字符数组打印出来printf(t);return 0;
}

现象如下:表示2024.3.5 22:40:53 星期二

1709649653

2024

3

5

22

40

53

2

1709649653

Tue Mar 05 22:40:53 2024

Tue Mar 05 22:40:53 2024

22-40-53

2.BKP备份寄存器

2.1 简介

  •  TAMPER引脚产生的侵入事件将所有备份寄存器内容清除,TAMPER是一个接到stm32外部的引脚,它的位置可以看一下引脚定义,这个TAMPER是一个安全保障设计,比如如果你做一个安全系数非常高的设备,设备需要有防拆功能,然后bkp里也存储了一些敏感数据,这些数据不能被别人窃取或者篡改,那你就可以使用这个TAMPER引脚的侵入检测功能,设计电路时,TAMPER引脚可以先加一个默认的上拉或者下拉电阻,然后引一根线到你的设备外壳的防拆开关或触点,别人一拆开你的设备触发开关,就会在TAMPER引脚产生上升沿或者下降沿,这样STM32就检测到侵入事件了,这时BKP的数据会自动清零,并且申请中断,你在中断里还可以继续保护设备,比如清除其他存储器数据,然后设备锁死,这样来保障设备的安全,另外主电源断电后,侵入检测仍然有效,这样即使设备关机也能防拆。
  • RTC引脚输出RTC校准时钟、RTC闹钟脉冲或者秒脉冲,RTC引脚刚才看过了,也是在PC13这个位置,这就是RTC时钟输出的功能,RTC的校准时钟,闹钟或者秒脉冲的信号,可以通过RTC引脚输出,其中外部用设备测量RTC校准时钟,可以对内部RTC微小的误差进行校准,然后闹钟脉冲或者秒脉冲可以输出出来,为别的设备提供这些信号,这是RTC时钟输出的功能,因为PC13、temple和RTC这三个引脚共用一个端口,所以这三个功能同一时间只能使用一个

  • BKP中用户数据的存储容量,在中容量和小容量设备里,BKP是20个字节,在大容量和互联型设备里,BKP是84个字节,我们使用的c8t6是中容量设备,所以可以看出BKP的容量其实非常小,一般只能用来存储少量的参数。

2.2 基本结构

  •  看一下BKP的基本结构,这个图中橙色部分我们可以叫做后备区域,BKP处于后备区域,但后备区域不只有BKP,还有RTC的相关电路也位于后备区域,STM32后备区域的特性就是当VDD主电源掉电时,后备区域仍然可以由VBAT的备用电池供电,当VDD主电源上电时,后备区域供电会切换到VDD,主电源有电时VBAT不会用的,这样可以节省电池电量,然后BKP是位于后备区域的,BKP里主要有数据寄存器、控制寄存器、状态寄存器和RTC时钟校准寄存器这些东西,其中数据寄存器是主要部分,用来存储数据的,每个数据寄存器都是16位的,也就是一个数据寄存器可以存两个字节,那对于中容量和小容量的设备,里面有dr1、dr2一直到dr10总共十个数据寄存器,那一个寄存器存两个字节,所以容量是20个字节再往后最多能有42个数据寄存器,容量就是84个字节,对应大容量和互联型
  • BKP还有几个功能,就下面这里的侵入检测,可以从PC13位置的TAMPER引脚引入一个检测信号,当TAMPER产生上升沿或者下降沿时,清除BKP所有的内容以保证安全,时钟输出,可以把RTC的相关时钟,从pc13位置的RTC引脚输出数据供外部使用,其中输出较准时钟时,再配合这个校准寄存器,可以对RTC的误差进行校准。

3.RTC实时时钟

3.1 简介

记住H开头是高速,L开头是低速,E结尾是外部,I结尾是内部,这里高速时钟一般供内部程序运行和主要外设使用,低速时钟一般供RTC看门狗这些东西使用,那对于我们本节的RTC呢,我们可以看到这一块,这里指向的箭头通往RTC,就是RTCCLK,RTCCLK有三个来源,第一个是OSC引脚接的HSE外部高速晶振,这个晶振是主晶振,我们一般都用的8MHZ,通过128分频可以产生RTCCLK信号为什么要先128分频,这是因为这个8MHz的晶振太快了,如果不提前分频,直接给RTCCLK,后续即使再通过RTC的20位分频器,也分不到1Hz这么低的频率。然后中间这一路时钟来源是LSE外部低速晶振,我们在oc32这两个引脚,接上外部低速晶振,这个晶振产生的时钟,可以直接提供给RTCCLK,这个osc32的晶振,是内部RTC的专用时钟,这个晶振的值也不是随便选的,通常跟RTC有关的晶振都是统一的数值,就是32.768KHz,为什么选择这个数值呢,一方面是32.768KHz这个值附近的频率,是晶振工艺比较合适的频率,你要说非要做一个1Hz的晶振,那可能是做不出来或者做出来了但体积很大性能很差,另一方面是32768这是一个二的次方数,2的15次方等于32768,所以32.768KHz,经过一个15位分频器的自然溢出,就能很方便地得到1Hz的频率自然溢出的意思就是设计一个15位的计数器,这个计数器不用设置计数目标,直接从0计到最大值,就计得32767,计满后自然溢出,这个溢出信号就是1Hz自然溢出的好处就是不用再额外设计一个计数目标了,也不用比较计数是不是计到目标值,这样可以简化电路设计,所以目前在RTC电路中,基本都是清一色的32.768KHz的晶振,你只要看到32.768KHz的晶振八成就是提供给rtc的,这是第二路,最后看第三路时钟源,来自于LSI,内部低速rc振荡器LSI固定是40KHz,如果选择LSI当做RTCCLK,后续再经过40k的分频,就能得到1Hz的计数时钟了,当然内部的RC振荡器,一般精度没有外部晶振高,所以LSI给RTCCLK,可以当做一个备选方案,另外LSI还可以提供给看门狗,我们最常用的就是中间这一路,外部32.768KHz的晶振,提供RTCCLK的时钟,第一个原因就是中间这一路,32.768KHz的晶振本身就是专供rtc使用的,上下这两路其实是有各自的任务,上面这一路主要作为系统主时钟,下面这一路主要作为看门狗时钟,他们只是顺带可以备选当做rdc的时钟,另外一个更重要的原因,只有中间这一路的时钟可以通过VBAT备用电池供电,上下两路时钟在主电源断电后是停止运行的,所以要想实现rtc主电源掉电继续走时的功能,必须得选择中间这一路的rtc专用时钟,如果选择的是上下两路时钟,主电源断电后时钟就暂停了,这显然会导致走时出错

3.2 框图

  1. 首先看分频和计数器计数部分,这一块的输入时钟是RTCCLK,这刚才说过RTCCLK的来源需要在RCC里进行配置,可以选择的选项是这三个,我们主要选择中间一路,那因为这三路时钟频率各不相同,而且都远大于我们所需要的1Hz的秒计数频率,所以RTCCLK进来,首先需要经过RTC预分频器进行分频,这个分频器由两个计算器组成,上面这个是重装载寄存器RTC_PRL,下面这个RTC_DIV手册里叫做余数寄存器,但实际上这一块跟我们之前定时器时机单元里的计数器CNT和重装值ARR是一样的,可能是右边已经有一个计数器cnt了,所以这个名字就比较奇怪,叫做余数寄存器,但实际上它还是计数器的作用,分频器其实就是一个计数器,记几个数溢出一次就几分频,所以对于可编程的分频器来说,需要有两个寄存器,一个寄存器用来不断的计数(RTC_DIV),另一个寄存器我们写入一个计数目标值(RTC_PRL),用来配置是几分频,那在这里上面这个PRL就是计数目标,我们写入六那就是七分频,写九那就是十分频,因为计数指包含了零,然后下面这个DIV就是每来一个时钟计一个数的用途了,当然这个DIV计数器是一个自减计数器每来一个输入时钟,DIV的值自减一次,自减到0时再来一个输入时钟,DIV输出一个脉冲产生溢出信号,同时DIV从PRL获取重装值,回到重装值继续自减

  2. 然后看一下计数计时部分,32位可编程计数器,RTC_CNT就是计时最核心的部分,我们可以把这个计数器看作是Unix时间戳的秒计数器,这样借助time.h的函数,就可以很方便地得到年月日时分秒了,然后在下面这里,这个RTC还设计的有一个闹钟寄存器RTC_ALR,这个ALR也是一个32位的寄存器,和上面这个cnt是等宽的,它的作用顾名思义就是设置闹钟,我们可以在ALR写一个秒数设定闹钟,当cnt的值跟ALR设定的闹钟值一样时,也是这里画了等号啊,如果他俩值相等就代表闹钟响了,这时就会产生RTC_Alarm闹钟信号,通往右边的中断系统,在中断函数里你可以执行相应的操作,同时这个闹钟还兼具一个功能,就下面这里的闹钟信号,可以让STM32退出待机模式,这个就可以对应一些用途,比如你设计一个数据采集设备,需要在环境非常恶劣的地方工作,比如海底高原深井这些地方,然后要求是每天中午12点采集一次环境数据,其他时间为了节省电量避免频繁换电池,芯片都必须处于待机模式,这样的话我们就可以用这个rtc自带的闹钟功能,定一个中午12点的闹钟,闹钟一响芯片唤醒采集数据完成后继续待机,另外这个闹钟值是一个定值,只能响一次,所以如果你想实现周期性的闹钟,大家每次闹钟响之后都需要再重新设置一下下一个闹钟时间,就是这个闹钟和闹钟唤醒的一个用途。

  3. 那继续往右看,这是中段部分的,在左边这里有三个信号可以触发中断,第一个是RTC_Second秒中断,它的来源就是cnt的输入时钟,如果开启这个中断,那么程序就会每秒进一次rtc中断,第二个是RTC_Overflow溢出中断,它的来源是cnt的右边,意思就是cnt的32位计数器计满溢出来了,会触发一次中断,所以这个中段一般不会触发,我们上一节说过,这个cnt定义的是无符号数,到2106年才会溢出,所以这个中段在2106年会触发一次,如果你想程序更完善一些,可以开启这个中段,到2106年就是一溢出,为了避免不必要的错误,你可以让芯片罢工,然后提示当前设备过老,请及时更换,但在2106年之后这个stm32的rtc就不太好用了,到时候或许可以通过打补丁的方式继续运行,或者直接淘汰32位的时间戳,这个问题就留给后人解决吧。来继续看下面第三个RTC_Alarm闹钟中断,刚才说过,当计数器和闹钟值相等时触发中断,同时闹钟信号可以把设备从待机模式唤醒,这是这三个中断信号,中断信号到右边这里,这一块就是中断标志位和中段输出控制,这些f结尾的是对应的中断标志位,ie结尾(Interrupt ENABLE)的是中断使能,最后三个信号通过一个或门汇聚到NVIC中断控制器,这个地方是不是漏画了一根线,中间这个应该也是要通过或门的,好这是右边的中断部分,然后上面这部分APB1总线和APB1接口,就是我们程序读写寄存器的地方,读写计算器可以通过APB1总线来完成,另外也可以看出,RTC是APB1总线上的设备,最后下面这一块退出待机模式,还有一个wake up引脚,闹钟信号和wake up引脚都可以唤醒设备,wake up引脚可以看一下接线图,就这里PA0的位置它兼具唤醒的功能,这个我们下一节再学习。

3.3 基本结构

rtc的核心部分如图所示,最左边是RTCCLK时钟来源,这块需要在RCC里配置三个时钟选择一个当做RTCCLK,之后RTCCLK先通过预分频器对时钟进行分频余数寄存器是一个自减计数器,存储当前的计数值,重装寄存器是计数目标,决定分频值,分频之后得到1Hz的秒计数信号通向32位计数器,一秒自增一次,下面还有个32位的闹钟值,可以设定闹钟,如果不需要闹钟的话,下面这一块可以不用管,然后右边有三个信号可以触发中断,分别是秒信号、计数器溢出信号和闹钟信号,三个信号先通过中断输出控制进行中断使能,使能的中断才能通向NVIC,然后向cpu申请中断,在程序中我们配置这个数据选择器,可以选择时钟来源,配置重装寄存器可以选择分频系数,配置32位计数器可以进行日期时间的读写,需要闹钟的话,配置32位闹钟值即可,需要中断的话,先允许中断,再配置nvic,最后写对应的中断函数即可,这是RTC外设的主要内容。

3.4 硬件电路

 

备用电池供电部分,我这里给了两个参考电路,第一个是简单连接,就使用一个3v的电池,负极和系统共地,正极直接接到stm32 的VBAT引脚,这样就行了,这个供电方案非常简单,参考来源是stm32的数据手册,在5.1.6供电方案这里就给出来这个图,图上画的就是直接建一个1.8~3.6v的电池到VBAT就行了,另外也可以看到,在内部是有一个供电开关的,当vdd有电时,开关拨到下面,后备电路由vdd供电,当vdd没电时,开关拨到上面,后备电路由VBAT供电,然后vbat供电的设备,在这里写了,vbat供电的后备电路有32KHz振荡器、rtc、唤醒电路和后备寄存器,那这就是根据数据手册里设计的VBAT供电方案,这个设计非常简单一般来说也没问题,然后我这里还给了第二种方案是推荐连接,这种连接方法是电池通过二极管D1向VBAT供电,另外主电源的3.3V,也通过二极管D2向VBAT供电,最后VBAT再加一个0.1uf的电源滤波电容,这个供电方案的参考来源是stm32的参考手册,在这个4.1.2电池备份区域这一节有这样描述,大家可以都看看,其中手册里有几个建议,一个是在这些这些情况下,电流可能通过vdd和vbat之间的内部二极管注入到vbat,如果与vbat连接的电源或者电池,不能承受这样的注入电流,强烈建议在外部,vbat和电源之间连接一个低压降的二极管,另一个是如果在应用中没有外部电池,建议vbat在外部连接到vdd,并连接一个100nf的陶瓷滤波电容,所以综合这两条建议,我们可以设计出右边的推荐连接,电池和主电源都加一个二极管,防止电流倒灌,vbat加一个0.1uf的电源滤波电容,0.1uf就是100nf,如果没有备用电池,就3.3V的主电源供电,如果接了备用电池,3.3v没电时,就是备用电池供电,这是根据参考手册设计的推荐电路,如果你只是进行实验,那使用左边的简单连接就行了,如果你要画板子设计产品,那还是推荐使用右边的连接,这样更保险,这是vbat供电部分。

3.5 操作注意事项

  1. 正常的外设第一步开启时钟就能用了,但是BKP和RTC这两个外设,开启稍微复杂一些,如果你要使用BKP或者RTC,都要先执行这两步,第一步开启PWR和BKP的时钟,第二步使用PWR使能BKP和RTC的访问
  2. 若在读取RTC寄存器时,RTC的APB1接口曾经处于禁止状态,则软件首先必须等待RTC_CRL寄存器中的RSF位(寄存器同步标志)被硬件置1,这一步对应代码里的一个库函数就是RTC等待同步,一般在刚上电的时候调用一下这个函数就行了,为什么要有这一步呢,可以看看上面的框图,在这里会有两个时钟PCLK1和RTCCLKPCLK1在主电源掉电时会停止,所以为了保证RTC主电源掉电正常工作,RTC里的寄存器,都是在RTCCLK的同步下变更的当我们用PCLK驱动的总线去读取RTCCLK驱动的寄存器时,就会有个时钟不同步的问题,RTC寄存器只有在RTCCLK的上升沿更新,但是PCLK1的频率36MHz远大于RTCCLK的频率32KHz如果我们在APB1刚开启时,就立刻读取RTC寄存器,有可能RTC寄存器还没有更新到APB1总线上,这样我们读到的值就是错误的,通常来说就读取到0,所以这就要求我们在APB1总线刚开机时,要等一下RTCCLK,只要RTCCLK来一个上升沿,rtc把它的寄存器的值同步到APB1总线上,这样之后读取的值就都是没问题的了,这是设计细节的一个问题,当然我们其实也不用管那么多了,只需要在初始化时调用一个等待同步的函数就行了。

  3. 必须设置RTC_CRL寄存器中的CNF位,使RTC进入配置模式后,才能写入RTC_PRL、RTC_CNT、RTC_ALR寄存器,这一条其实比较简单,就是RTC会有一个进入配置模式的标志位把这一位置1才能设置时间,其实这个操作在库函数中,每个写寄存器的函数,它都自动帮我们加上了这个操作,所以我们就不用再单独调用函数进入配置模式了。

  4. 对RTC任何寄存器的写操作,都必须在前一次写操作结束后进行。可以通过查询RTC_CR寄存器中的RTOFF状态位判断RTC寄存器是否处于更新中。仅当RTOFF状态位是1时,才可以写入RTC寄存器,这个操作也是调用一个等待的函数就行了,跟我们之前读写flash芯片是类似的,就写入之前要等待一下,如果上一次的写入还没完成,你就别急着写下一次了,或者说每次写入之后,你要等待RTOFF为1,只有RTOFF为1才表示写完成,为什么要有这个操作呢,其实还是因为这里的PCLK1和RTCCLKk时钟频率不一样,你用PCLK1的频率写入之后,这个值还不能立刻更新到RTC的寄存器里,因为RTC寄存器是由RTCCLK驱动的,所以PCLK1写完之后,得等一下RTCCLK的时钟,RTCCLK来个上升沿,值更新到RTC寄存器里,整个写作过程才算结束了,这个操作了解一下,在代码里也就是调用一个等待函数的事。

4.读写备份寄存器B

4.1 相关API

4.1.1 BKP_ReadBackupRegister
/*** @brief  Reads data from the specified Data Backup Register.* @param  BKP_DR: specifies the Data Backup Register.*   This parameter can be BKP_DRx where x:[1, 42]* @retval The content of the specified Data Backup Register*/
uint16_t BKP_ReadBackupRegister(uint16_t BKP_DR);
功能:从指定的后备寄存器中读出数据
参数:BKP_DR:数据后备寄存器
返回值:指定的后备寄存器中的数据
4.1.2 BKP_WriteBackupRegister
/*** @brief  Writes user data to the specified Data Backup Register.* @param  BKP_DR: specifies the Data Backup Register.*   This parameter can be BKP_DRx where x:[1, 42]* @param  Data: data to write* @retval None*/
void BKP_WriteBackupRegister(uint16_t BKP_DR, uint16_t Data);
功能:向指定的后备寄存器中写入用户程序数据
参数:BKP_DR:数据后备寄存器Data:待写入的数据
返回值:无   
4.1.3 PWR_BackupAccessCmd

/*** @brief  Enables or disables access to the RTC and backup registers.* @param  NewState: new state of the access to the RTC and backup registers.*   This parameter can be: ENABLE or DISABLE.* @retval None*/
void PWR_BackupAccessCmd(FunctionalState NewState);
功能:使能或者失能 RTC 和后备寄存器访问
参数:NewState: RTC 和后备寄存器访问的新状态
返回值:无   

4.2 接线图

4.3 相关代码

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Key.h"uint8_t KeyNum;					//定义用于接收按键键码的变量uint16_t ArrayWrite[] = {0x1234, 0x5678};	//定义要写入数据的测试数组
uint16_t ArrayRead[2];						//定义要读取数据的测试数组int main(void)
{/*模块初始化*/OLED_Init();				//OLED初始化Key_Init();					//按键初始化/*显示静态字符串*/OLED_ShowString(1, 1, "W:");OLED_ShowString(2, 1, "R:");/*开启时钟*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);		//开启PWR的时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);		//开启BKP的时钟/*备份寄存器访问使能*/PWR_BackupAccessCmd(ENABLE);							//使用PWR开启对备份寄存器的访问while (1){KeyNum = Key_GetNum();		//获取按键键码if (KeyNum == 1)			//按键1按下{			BKP_WriteBackupRegister(BKP_DR1, ArrayWrite[0]);	//写入测试数据到备份寄存器BKP_WriteBackupRegister(BKP_DR2, ArrayWrite[1]);OLED_ShowHexNum(1, 3, ArrayWrite[0], 4);		//显示写入的测试数据OLED_ShowHexNum(1, 8, ArrayWrite[1], 4);ArrayWrite[0] ++;		//测试数据自增ArrayWrite[1] ++;}ArrayRead[0] = BKP_ReadBackupRegister(BKP_DR1);		//读取备份寄存器的数据ArrayRead[1] = BKP_ReadBackupRegister(BKP_DR2);OLED_ShowHexNum(2, 3, ArrayRead[0], 4);				//显示读取的备份寄存器数据OLED_ShowHexNum(2, 8, ArrayRead[1], 4);}
}

现象:按下按键OLED显示数组数据 再次按下数据自增 同时拔掉电源 下次通电后还是最后一次显示R的数据 但是拔掉备用电源 数据会消失

5.读写RTC实时时钟

5.1 相关API

5.1.1 RCC_LSEConfig
/*** @brief  Configures the External Low Speed oscillator (LSE).* @param  RCC_LSE: specifies the new state of the LSE.*   This parameter can be one of the following values:*     @arg RCC_LSE_OFF: LSE oscillator OFF*     @arg RCC_LSE_ON: LSE oscillator ON*     @arg RCC_LSE_Bypass: LSE oscillator bypassed with external clock* @retval None*/
void RCC_LSEConfig(uint8_t RCC_LSE);
功能:设置外部低速晶振(LSE)
参数:RCC_LSE: LSE 的新状态
返回值:无
5.1.2 RCC_GetFlagStatus
/*** @brief  Checks whether the specified RCC flag is set or not.* @param  RCC_FLAG: specifies the flag to check.*   *   For @b STM32_Connectivity_line_devices, this parameter can be one of the*   following values:*     @arg RCC_FLAG_HSIRDY: HSI oscillator clock ready*     @arg RCC_FLAG_HSERDY: HSE oscillator clock ready*     @arg RCC_FLAG_PLLRDY: PLL clock ready*     @arg RCC_FLAG_PLL2RDY: PLL2 clock ready      *     @arg RCC_FLAG_PLL3RDY: PLL3 clock ready                           *     @arg RCC_FLAG_LSERDY: LSE oscillator clock ready*     @arg RCC_FLAG_LSIRDY: LSI oscillator clock ready*     @arg RCC_FLAG_PINRST: Pin reset*     @arg RCC_FLAG_PORRST: POR/PDR reset*     @arg RCC_FLAG_SFTRST: Software reset*     @arg RCC_FLAG_IWDGRST: Independent Watchdog reset*     @arg RCC_FLAG_WWDGRST: Window Watchdog reset*     @arg RCC_FLAG_LPWRRST: Low Power reset* *   For @b other_STM32_devices, this parameter can be one of the following values:        *     @arg RCC_FLAG_HSIRDY: HSI oscillator clock ready*     @arg RCC_FLAG_HSERDY: HSE oscillator clock ready*     @arg RCC_FLAG_PLLRDY: PLL clock ready*     @arg RCC_FLAG_LSERDY: LSE oscillator clock ready*     @arg RCC_FLAG_LSIRDY: LSI oscillator clock ready*     @arg RCC_FLAG_PINRST: Pin reset*     @arg RCC_FLAG_PORRST: POR/PDR reset*     @arg RCC_FLAG_SFTRST: Software reset*     @arg RCC_FLAG_IWDGRST: Independent Watchdog reset*     @arg RCC_FLAG_WWDGRST: Window Watchdog reset*     @arg RCC_FLAG_LPWRRST: Low Power reset*   * @retval The new state of RCC_FLAG (SET or RESET).*/
FlagStatus RCC_GetFlagStatus(uint8_t RCC_FLAG);
功能:检查指定的 RCC 标志位设置与否
参数:RCC_FLAG:待检查的 RCC 标志位
返回值:RCC_FLAG 的新状态(SET 或者 RESET)
5.1.3 RCC_RTCCLKConfig
/*** @brief  Configures the RTC clock (RTCCLK).* @note   Once the RTC clock is selected it can't be changed unless the Backup domain is reset.* @param  RCC_RTCCLKSource: specifies the RTC clock source.*   This parameter can be one of the following values:*     @arg RCC_RTCCLKSource_LSE: LSE selected as RTC clock*     @arg RCC_RTCCLKSource_LSI: LSI selected as RTC clock*     @arg RCC_RTCCLKSource_HSE_Div128: HSE clock divided by 128 selected as RTC clock* @retval None*/
void RCC_RTCCLKConfig(uint32_t RCC_RTCCLKSource);
功能:设置 RTC 时钟(RTCCLK)
参数:RCC_RTCCLKSource: 定义 RTCCLK
返回值:无   
 5.1.4 RCC_RTCCLKCmd
/*** @brief  Enables or disables the RTC clock.* @note   This function must be used only after the RTC clock was selected using the RCC_RTCCLKConfig function.* @param  NewState: new state of the RTC clock. This parameter can be: ENABLE or DISABLE.* @retval None*/
void RCC_RTCCLKCmd(FunctionalState NewState);
功能:使能或者失能 RTC 时钟
参数:NewState:RTC 时钟的新状态, 这个参数可以取:ENABLE 或者 DISABLE
返回值:无    
5.1.5 RTC_WaitForSynchro
/*** @brief  Waits until the RTC registers (RTC_CNT, RTC_ALR and RTC_PRL)*   are synchronized with RTC APB clock.* @note   This function must be called before any read operation after an APB reset*   or an APB clock stop.* @param  None* @retval None*/
void RTC_WaitForSynchro(void);
功能:等待最近一次对 RTC 寄存器的写操作完成
参数:无
返回值:无     
5.1.6 RTC_WaitForLastTask
/*** @brief  Waits until last write operation on RTC registers has finished.* @note   This function must be called before any write to RTC registers.* @param  None* @retval None*/
void RTC_WaitForLastTask(void);
功能:等待最近一次对 RTC 寄存器的写操作完成
参数:无
返回值:无        
5.1.7 RTC_SetPrescaler
/*** @brief  Sets the RTC prescaler value.* @param  PrescalerValue: RTC prescaler new value.* @retval None*/
void RTC_SetPrescaler(uint32_t PrescalerValue);
功能:设置 RTC 预分频的值
参数:PrescalerValue:新的 RTC 预分频值
返回值:无        
5.1.8 RTC_SetCounter
/*** @brief  Sets the RTC counter value.* @param  CounterValue: RTC counter new value.* @retval None*/
void RTC_SetCounter(uint32_t CounterValue);
功能:设置 RTC 计数器的值
参数:CounterValue:新的 RTC 计数器值
返回值:无      
5.1.9 RTC_GetCounter
/*** @brief  Gets the RTC counter value.* @param  None* @retval RTC counter value.*/
uint32_t RTC_GetCounter(void);
功能:获取 RTC 计数器的值
参数:无
返回值:RTC 计数器的值    

5.2 接线图

5.3 相关代码

5.3.1 MyRTC.c
#include "stm32f10x.h"                  // Device header
#include <time.h>uint16_t MyRTC_Time[] = {2023, 1, 1, 23, 59, 55};	//定义全局的时间数组,数组内容分别为年、月、日、时、分、秒void MyRTC_SetTime(void);				//函数声明/*** 函    数:RTC初始化* 参    数:无* 返 回 值:无*/
void MyRTC_Init(void)
{/*开启时钟*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);		//开启PWR的时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);		//开启BKP的时钟/*备份寄存器访问使能*/PWR_BackupAccessCmd(ENABLE);							//使用PWR开启对备份寄存器的访问if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5)			//通过写入备份寄存器的标志位,判断RTC是否是第一次配置//if成立则执行第一次的RTC配置{RCC_LSEConfig(RCC_LSE_ON);							//开启LSE时钟while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) != SET);	//等待LSE准备就绪RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);				//选择RTCCLK来源为LSERCC_RTCCLKCmd(ENABLE);								//RTCCLK使能RTC_WaitForSynchro();								//等待同步RTC_WaitForLastTask();								//等待上一次操作完成RTC_SetPrescaler(32768 - 1);						//设置RTC预分频器,预分频后的计数频率为1HzRTC_WaitForLastTask();								//等待上一次操作完成MyRTC_SetTime();									//设置时间,调用此函数,全局数组里时间值刷新到RTC硬件电路BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);			//在备份寄存器写入自己规定的标志位,用于判断RTC是不是第一次执行配置}else													//RTC不是第一次配置{RTC_WaitForSynchro();								//等待同步RTC_WaitForLastTask();								//等待上一次操作完成}
}//如果LSE无法起振导致程序卡死在初始化函数中
//可将初始化函数替换为下述代码,使用LSI当作RTCCLK
//LSI无法由备用电源供电,故主电源掉电时,RTC走时会暂停
/* 
void MyRTC_Init(void)
{RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);PWR_BackupAccessCmd(ENABLE);if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5){RCC_LSICmd(ENABLE);while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) != SET);RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);RCC_RTCCLKCmd(ENABLE);RTC_WaitForSynchro();RTC_WaitForLastTask();RTC_SetPrescaler(40000 - 1);RTC_WaitForLastTask();MyRTC_SetTime();BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);}else{RCC_LSICmd(ENABLE);				//即使不是第一次配置,也需要再次开启LSI时钟while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) != SET);RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);RCC_RTCCLKCmd(ENABLE);RTC_WaitForSynchro();RTC_WaitForLastTask();}
}*//*** 函    数:RTC设置时间* 参    数:无* 返 回 值:无* 说    明:调用此函数后,全局数组里时间值将刷新到RTC硬件电路*/
void MyRTC_SetTime(void)
{time_t time_cnt;		//定义秒计数器数据类型struct tm time_date;	//定义日期时间数据类型time_date.tm_year = MyRTC_Time[0] - 1900;		//将数组的时间赋值给日期时间结构体time_date.tm_mon = MyRTC_Time[1] - 1;time_date.tm_mday = MyRTC_Time[2];time_date.tm_hour = MyRTC_Time[3];time_date.tm_min = MyRTC_Time[4];time_date.tm_sec = MyRTC_Time[5];time_cnt = mktime(&time_date) - 8 * 60 * 60;	//调用mktime函数,将日期时间转换为秒计数器格式//- 8 * 60 * 60为东八区的时区调整RTC_SetCounter(time_cnt);						//将秒计数器写入到RTC的CNT中RTC_WaitForLastTask();							//等待上一次操作完成
}/*** 函    数:RTC读取时间* 参    数:无* 返 回 值:无* 说    明:调用此函数后,RTC硬件电路里时间值将刷新到全局数组*/
void MyRTC_ReadTime(void)
{time_t time_cnt;		//定义秒计数器数据类型struct tm time_date;	//定义日期时间数据类型time_cnt = RTC_GetCounter() + 8 * 60 * 60;		//读取RTC的CNT,获取当前的秒计数器//+ 8 * 60 * 60为东八区的时区调整time_date = *localtime(&time_cnt);				//使用localtime函数,将秒计数器转换为日期时间格式MyRTC_Time[0] = time_date.tm_year + 1900;		//将日期时间结构体赋值给数组的时间MyRTC_Time[1] = time_date.tm_mon + 1;MyRTC_Time[2] = time_date.tm_mday;MyRTC_Time[3] = time_date.tm_hour;MyRTC_Time[4] = time_date.tm_min;MyRTC_Time[5] = time_date.tm_sec;
}
5.3.2 MyRTC.h
#ifndef __MYRTC_H
#define __MYRTC_Hextern uint16_t MyRTC_Time[];void MyRTC_Init(void);
void MyRTC_SetTime(void);
void MyRTC_ReadTime(void);#endif
5.3.3 main.c
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyRTC.h"int main(void)
{/*模块初始化*/OLED_Init();		//OLED初始化MyRTC_Init();		//RTC初始化/*显示静态字符串*/OLED_ShowString(1, 1, "Date:XXXX-XX-XX");OLED_ShowString(2, 1, "Time:XX:XX:XX");OLED_ShowString(3, 1, "CNT :");OLED_ShowString(4, 1, "DIV :");while (1){MyRTC_ReadTime();							//RTC读取时间,最新的时间存储到MyRTC_Time数组中OLED_ShowNum(1, 6, MyRTC_Time[0], 4);		//显示MyRTC_Time数组中的时间值,年OLED_ShowNum(1, 11, MyRTC_Time[1], 2);		//月OLED_ShowNum(1, 14, MyRTC_Time[2], 2);		//日OLED_ShowNum(2, 6, MyRTC_Time[3], 2);		//时OLED_ShowNum(2, 9, MyRTC_Time[4], 2);		//分OLED_ShowNum(2, 12, MyRTC_Time[5], 2);		//秒OLED_ShowNum(3, 6, RTC_GetCounter(), 10);	//显示32位的秒计数器OLED_ShowNum(4, 6, RTC_GetDivider(), 10);	//显示余数寄存器}
}

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

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

相关文章

C++对象模型剖析(六)一一Data语义学(三)

Data 语义学&#xff08;三&#xff09; “继承” 与 Data member 上期的这个继承的模块我们还剩下一个虚拟继承&#xff08;virtual inheritance&#xff09;没有讲&#xff0c;现在我们就来看看吧。 虚拟继承&#xff08;Virtual Inheritance&#xff09; 虚拟继承本质就是…

Linux笔记--make

使用上一节的 main.c、add.c、sub.c文件进行编译&#xff0c;编译的过程有很多步骤&#xff0c;如果要重新编译&#xff0c;还需要再重来一遍&#xff0c;能不能一步完成这些步骤?将这些步骤写到makefile文件中&#xff0c;通过make工具进行编译 一个工程中的源文件不计其数&a…

java 获取项目内的资源/配置文件

【getResourceAsStream】是java中用于获取项目内资源的常用方法&#xff0c;能够返回一个数据流&#xff0c;从而允许我们读取指定路径下的资源文件。这个方法可以用来读取各种类型的资源文件&#xff0c;包括但不限于文本文件、图像文件、配置文件等。 要使用getResourceAsStr…

高端相亲婚恋平台有哪些?分享五款高端靠谱相亲交友软件

如今市场上的相亲软件越来越多&#xff0c;但很少有人能找到自己心仪的相亲软件。在选择相亲软件时&#xff0c;大家最看重的就是安全性和真实性&#xff0c;因此我想向大家分享几款我用过且觉得可靠的高端相亲软件&#xff0c;希望能得到你们的认可。 1、丛丛 这是我用的最久的…

【[STM32]标准库-自定义BootLoader】

[STM32]标准库-自定义BootLoader BootloaderBootloader的实现BOOTloader工程APP工程 Bootloader bootloader其实就是一段启动程序&#xff0c;它在芯片启动的时候最先被执行&#xff0c;可以用来做一些硬件的初始化或者用作固件热更新&#xff0c;当初始化完成之后跳转到对应的…

LeetCode 热题 100 | 图论(二)

目录 1 基础知识 1.1 什么是拓扑排序 1.2 如何进行拓扑排序 1.3 拓扑排序举例 2 207. 课程表 3 210. 课程表 II 菜鸟做题&#xff0c;语言是 C 1 基础知识 1.1 什么是拓扑排序 含义&#xff1a;根据节点之间的依赖关系来生成一个有序的序列。 应用&#xff1a…

12:Logstash|Web日志实时分析

Logstash|Web日志实时分析 logstashlogstash工作结构安装Logstash编写logstash配置文件步骤一:codec类插件插件帮助手册Logstash input插件步骤一:file模块插件filter grok插件Web日志实时分析部署beats与filebeat步骤一:filter grok模块插件logstash 一个数据采集、加工处…

Ubuntu22.04系统 安装cAdvisor提示找不到 CPU 的挂载点错误解决办法。

如果我们在安装cAdvisor时容器启动不起来 查看日志如下图所示 1、查看cgroup文件系统是v2 还是 v1 mount | grep cgroup 如图所示我的是v2 &#xff0c; cAdvisor 目前的最新版本&#xff08;v0.39.0&#xff09;仍然只支持 cgroup v1&#xff0c;不支持 cgroup v2。因此&#…

闫震海:腾讯音乐空间音频技术的发展和应用 | 演讲嘉宾公布

一、3D 音频 3D 音频分论坛将于3月27日同期举办&#xff01; 3D音频技术不仅能够提供更加真实、沉浸的虚拟世界体验&#xff0c;跨越时空的限制&#xff0c;探索未知的世界。同时&#xff0c;提供更加丰富、立体的情感表达和交流方式&#xff0c;让人类能够更加深入地理解彼此&…

做抖店月入百万还是会亏损?珠珠来告诉你,做抖店水到底有多深?

我是电商珠珠 抖店的热度一直只高不低&#xff0c;所以很多想要做的新手不知道抖店水的深浅&#xff0c;就一股脑的去做了。结果又是被扣保证金&#xff0c;又是被判定无货源违规的&#xff0c;最后灰头土脸的关了店。那些说做了十万十几万的&#xff0c;几百万的难道都是假的…

在三个el-form-item中的el-radio的值中取一个发送给后端怎么获取

问: 请问,这段代码怎么获取:无策略,策略1,策略2的值? 回答: 问: 三个里面只可以选中一个吗? 回答:

应对求职高峰:金三银四必备问答与大厂模板,成功职场攻略!

三四月是求职的黄金季节&#xff0c;很多设计师会选择在这个时候准备作品集。一个视觉精美、有条不紊的作品集&#xff0c;无疑是走向大厂的敲门砖。但是我问了一圈优秀社区的朋友&#xff0c;发现大家或多或少都遇到过问题。今天我整理了群里的高频问题&#xff0c;附上了解决…

[GXYCTF2019]BabyUpload1 -- 题目分析与详解

目录 一、题目分析 1、判断题目类型&#xff1a; 2、上传不同类型的文件进行测试&#xff1a; 二、题目详解 1、写出.htaccess文件&#xff1a; 2、.htaccess 文件配合 .jpg 上传&#xff1a; 3、利用 中国蚁剑/中国菜刀 获取flag&#xff1a; 一、题目分析 1、判断题目…

信奥一本通:1082:求小数的某一位

分数转换为小数就是分子除分母&#xff0c;但是他要求指定的n项小数&#xff0c;n大于1小于10000&#xff0c;如果是10000的话就太大了&#xff0c;用传统的方式无法找出指定的位数。 方式就是&#xff1a;例如求2分之1&#xff0c;我们先用除法把具体的值求出来&#xff0c;然…

图机器学习(1)--导论

0 CS224W概况 斯坦福大学CS224W课程&#xff1a;http://cs224w.stanford.edu/ 图机器学习的库&#xff1a; 为什么是图&#xff1f;图是描述和分析具有关系/交互的实体的通用语言。 1 图数据举例 复杂域具有丰富的关系结构&#xff0c;可以表示为关系图。 通过显式地建模关…

【论文阅读】Generative Pretraining from Pixels

Generative Pretraining From Pixels 引用&#xff1a; Chen M, Radford A, Child R, et al. Generative pretraining from pixels[C]//International conference on machine learning. PMLR, 2020: 1691-1703. 论文链接&#xff1a; http://proceedings.mlr.press/v119/chen…

LeetCode --- 三数之和

题目描述 三数之和 代码解析 暴力 在做这一道题的时候&#xff0c;脑海里先想出来的是暴力方法&#xff0c;一次排序&#xff0c;将这个数组变为有序的&#xff0c;再通过三次for循环来寻找满足条件的数字&#xff0c;然后将符合条件的数组与之前符合条件的数组进行一一对比…

2024.3.6补题

1.关鸡 对于这一道题&#xff0c;我们先按照题意进行分析&#xff1a;首先鸡自己的初始位置&#xff0c;如果着火点在鸡一开始的左右下各有一个那么就可以达到题目效果&#xff0c;也就是说不需要添加着火点&#xff0c;同时最多需要添加的着火点其实也就是它初始位置身边所有的…

Optional 详解

Optional 详解 1、Optional 介绍2、创建 Optional 对象3、Optional 常用方法1. 判断值是否存在 — isPresent()2. 非空表达式 — ifPresent()3. 设置(获取)默认值 — orElse()、orElseGet()4. 获取值 — get()5. 过滤值 — filter()6. 转换值 — map() 作为一名 Java 程序员&am…

电子电器架构刷写策略 —— 队列刷写

电子电器架构刷写策略 —— 队列刷写 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明自己…