第二十八章 RTC——实时时钟

第二十八章 RTC------实时时钟​​​​​​​

目录

[第二十八章 RTC------实时时钟](#第二十八章 RTC——实时时钟)

[1 RTC实时时钟简介](#1 RTC实时时钟简介)

[2 RTC外设框图剖析](#2 RTC外设框图剖析)

[3 UNIX时间戳](#3 UNIX时间戳)

[4 与RTC控制相关的库函数](#4 与RTC控制相关的库函数)

[4.1 等待时钟同步和操作完成](#4.1 等待时钟同步和操作完成)

[4.2 使能备份域涉及RTC配置](#4.2 使能备份域涉及RTC配置)

[4.3 设置RTC时钟分频](#4.3 设置RTC时钟分频)

[4.4 设置、获取RTC计数器及闹钟](#4.4 设置、获取RTC计数器及闹钟)

[5 实时时钟](#5 实时时钟)

[5.1 代码解析](#5.1 代码解析)

[5.2 下载验证](#5.2 下载验证)

[6 RTC_LSICalib](#6 RTC_LSICalib)

[6.1 代码解析](#6.1 代码解析)

[6.2 下载验证](#6.2 下载验证)


本章参考资料:《W55MH32数据手册》、《W55MH32参考手册》的《电源控制PWR》及《实时时钟RTC》章节。

1 RTC实时时钟简介

W55MH32的RTC外设(Real Time Clock),实质是一个掉电后还继续运行的定时器。从定时器的角度来说,相对于通用定时器TIM外设,它十分简单, 只有很纯粹的计时和触发中断的功能;但从掉电还继续运行的角度来说,它却是W55MH32中唯一一个具有如此强大功能的外设。 所以RTC外设的复杂之处并不在于它的定时功能,而在于它掉电还继续运行的特性。

以上所说的掉电,是指主电源VDD断开的情况,为了RTC外设掉电继续运行,必须接上锂电池给W55MH32的RTC、 备份发卡通过VBAT引脚供电。当主电源VDD有效时,由VDD给RTC外设供电; 而当VDD掉电后,由VBAT给RTC外设供电。但无论由什么电源供电,RTC中的数据都保存在属于RTC的备份域中, 若主电源VDD和VBAT都掉电,那么备份域中保存的所有数据将丢失。备份域除了RTC模块的寄存器, 还有42个16位的寄存器可以在VDD掉电的情况下保存用户程序的数据,系统复位或电源复位时,这些数据也不会被复位。

从RTC的定时器特性来说,它是一个32位的计数器,只能向上计数。它使用的时钟源有三种,分别为高速外部时钟的128分频(HSE/128)、 低速内部时钟LSI以及低速外部时钟LSE;使HSE分频时钟或LSI的话,在主电源VDD掉电的情况下,这两个时钟来源都会受到影响, 因此没法保证RTC正常工作。因此RTC一般使用低速外部时钟LSE,在设计中,频率通常为实时时钟模块中常用的32.768KHz, 这是因为32768 = 2的15次方,分频容易实现,所以它被广泛应用到RTC模块。在主电源VDD有效的情况下(待机), RTC还可以配置闹钟事件使W55MH32退出待机模式。

2 RTC外设框图剖析

RTC外设框图如下:

框图中浅灰色的部分都是属于备份域的,在VDD掉电时可在VBAT的驱动下继续运行。 这部分仅包括RTC的分频器,计数器,和闹钟控制器。若VDD电源有效,RTC可以触发RTC_Second(秒中断)、 RTC_Overflow(溢出事件)和RTC_Alarm(闹钟中断)。从结构图可以分析到,其中的定时器溢出事件无法被配置为中断。 若W55MH32原本处于待机状态,可由闹钟事件或WKUP事件(外部唤醒事件,属于EXTI模块,不属于RTC)使它退出待机模式。 闹钟事件是在计数器RTC_CNT的值等于闹钟寄存器RTC_ALR的值时触发的。

在备份域中所有寄存器都是16位的, RTC控制相关的寄存器也不例外。它的计数器RTC_CNT的32位由RTC_CNTL和RTC_CNTH两个寄存器组成,分别保存定时计数值的低16位和高16位。 在配置RTC模块的时钟时,通常把输入的32768Hz的RTCCLK进行32768分频得到实际驱动计数器的时钟 TR_CLK =RTCCLK/32768= 1 Hz, 计时周期为1秒,计时器在TR_CLK的驱动下计数,即每秒计数器RTC_CNT的值加1。

由于备份域的存在,使得RTC核具有了完全独立于APB1接口的特性, 也因此对RTC寄存器的访问要遵守一定的规则。

系统复位后,默认禁止访问后备寄存器和RTC,防止对后备区域(BKP)的意外写操作。 执行以下操作使能对后备寄存器和RTC的访问:

设置RCC_APB1ENR寄存器的PWREN和BKPEN位来使能电源和后备接口时钟。

设置PWR_CR寄存器的DBP位使能对后备寄存器和RTC的访问。

设置后备寄存器为可访问后,在第一次通过APB1接口访问RTC时,因为时钟频率的差异,所以必须等待APB1与RTC外设同步, 确保被读取出来的RTC寄存器值是正确的。若在同步之后,一直没有关闭APB1的RTC外设接口,就不需要再次同步了。

如果内核要对RTC寄存器进行任何的写操作,在内核发出写指令后,RTC模块在3个RTCCLK时钟之后,才开始正式的写RTC寄存器操作。 由于RTCCLK的频率比内核主频低得多,所以每次操作后必须要检查RTC关闭操作标志位RTOFF,当这个标志被置1时,写操作才正式完成。

当然,以上的操作都具有库函数,读者不必具体地查阅寄存器。

3 UNIX时间戳

在使用RTC外设前,还需要引入UNIX时间戳的概念。

如果从现在起,把计数器RTC_CNT的计数值置0,然后每秒加1, RTC_CNT什么时候会溢出呢?由于RTC_CNT是32位寄存器, 可存储的最大值为(232-1),即这样计时的话,在232秒后溢出,即它将在今后的136年时溢出:

N = 232/365/24/60/60 ≈136年

假如某个时刻读取到计数器的数值为X = 60*60*24*2,即两天时间的秒数,而假设又知道计数器是在2011年1月1日的0时0分0秒置0的, 那么就可以根据计数器的这个相对时间数值,计算得这个X时刻是2011年1月3日的0时0分0秒了。而计数器则会在(2011+136)年左右溢出, 也就是说到了(2011+136)年时,如果我们还在使用这个计数器提供时间的话就会出现问题。在这个例子中,定时器被置0的这个时间被称为计时元年, 相对计时元年经过的秒数称为时间戳,也就是计数器中的值。

大多数操作系统都是利用时间戳和计时元年来计算当前时间的,而这个时间戳和计时元年大家都取了同一个标准------UNIX时间戳和UNIX计时元年。 UNIX计时元年被设置为格林威治时间1970年1月1日0时0分0秒,大概是为了纪念UNIX的诞生的时代吧, 而UNIX时间戳即为当前时间相对于UNIX计时元年经过的秒数。因为unix时间戳主要用来表示当前时间或者和电脑有关的日志时间(如文件创立时间,log发生时间等), 考虑到所有电脑文件不可能在1970年前创立,所以用unix时间戳很少用来表示1970前的时间。

在这个计时系统中,使用的是有符号的32位整型变量来保存UNIX时间戳的,即实际可用计数位数比我们上面例子中的少了一位, 少了这一位,UNIX计时元年也相对提前了,这个计时方法在2038年1月19日03时14分07秒将会发生溢出,这个时间离我们并不远。 由于UNIX时间戳被广泛应用到各种系统中,溢出可能会导致系统发生严重错误,届时,很可能会重演一次"千年虫"的问题,所以在设计预期寿命较长的设备需要注意。

在网络上搜索"UNIX时间戳"可找到一些网站提供当前实时的UNIX时间戳,见下图某网站显示的实时UNIX时间戳:

4 与RTC控制相关的库函数

W55MH32标准库对RTC控制提供了完善的函数,使用它们可以方便地进行控制,本小节对这些内容进行讲解。

4.1 等待时钟同步和操作完成

RTC区域的时钟比APB时钟慢,访问前需要进行时钟同步,只要调用库函数RTC_WaitForSynchro()即可,而如果修改了RTC的寄存器, 又需要调用RTC_WaitForLastTask()函数确保数据已写入,见代码清单:RTC-1 :

代码清单:RTC-1 等待时钟同步和操作完成

复制代码
/**`
`* @brief  等待RTC寄存器与APB时钟同步 (RTC_CNT, RTC_ALR and RTC_PRL)`
`* @note   在APB时钟复位或停止后,在对RTC寄存器的任何操作前,必须调用本函数`
`* @param  None`
`* @retval None`
`*/`
`void RTC_WaitForSynchro(void)`
`{`
`    /* 清除 RSF 寄存器位 */`
`    RTC->CRL &= (uint16_t)~RTC_FLAG_RSF;`
`    /* 等待至 RSF 寄存器位为SET */`
`    while ((RTC->CRL & RTC_FLAG_RSF) == (uint16_t)RESET) {`
`    }`
`}`

`/**`
`* @brief  等待上一次对 RTC寄存器的操作完成`
`* @note   修改RTC寄存器后,必须调用本函数`
`* @param  None`
`* @retval None`
`*/`
`void RTC_WaitForLastTask(void)`
`{`
`    /* 等待至 RTOFF 寄存器位为SET*/`
`    while ((RTC->CRL & RTC_FLAG_RTOFF) == (uint16_t)RESET) {`
`    }`
`}

这两个库函数主要通过while循环检测RTC控制寄存器的RSF和RTOFF位实现等待功能。

4.2 使能备份域涉及RTC配置

默认情况下,RTC所属的备份域禁止访问,可使用库函数PWR_BackupAccessCmd()使能访问,见代码清单:RTC-2 :

代码清单:RTC-2 使能备份域访问

复制代码
/**`
`* @brief  使能对 RTC 和 backup 寄存器的访问.`
`* @param   ENABLE 或 DISABLE.`
`* @retval None`
`*/`
`void PWR_BackupAccessCmd(FunctionalState NewState)`
`{`
`    *(__IO uint32_t *) CR_DBP_BB = (uint32_t)NewState;`
`}

该函数通过PWR_CR寄存器的DBP位使能访问,使能后才可以访问RTC相关的寄存器,然而若希望修改RTC的寄存器, 还需要进一步使能RTC控制寄存器的CNF位使能寄存器配置,见代码清单:RTC-3:

代码清单:RTC-3 进入和退出RTC配置模式

复制代码
/**`
`* @brief  进入 RTC 配置模式 .`
`* @param  None`
`* @retval None`
`*/`
`void RTC_EnterConfigMode(void)`
`{`
`    /* 设置 CNF 位进入配置模式 */`
`    RTC->CRL |= RTC_CRL_CNF;`
`}`

`/**`
`* @brief  退出 RTC 配置模式 .`
`* @param  None`
`* @retval None`
`*/`
`void RTC_ExitConfigMode(void)`
`{`
`    /* 清空  CNF 位退出配置模式 */`
`    RTC->CRL &= (uint16_t)~((uint16_t)RTC_CRL_CNF);`
`}

这两个库函数分别提供了进入和退出RTC寄存器的配置模式,一般情况下它们由库函数调用。

4.3 设置RTC时钟分频

使用RCC相关的库函数选择RTC使用的时钟后,可以使用库函数RTC_SetPrescaler()进行分频, 一般会把RTC时钟分频得到1Hz的时钟,见代码清单:RTC-4:

代码清单:RTC-4 设置RTC时钟分频

复制代码
/**`
`* @brief  设置RTC分频配置`
`* @param  PrescalerValue: RTC 分频值.`
`* @retval None`
`*/`
`void RTC_SetPrescaler(uint32_t PrescalerValue)`
`{`
`    RTC_EnterConfigMode();`
`    /* 设置 RTC 分频值的 MSB  */`
`    RTC->PRLH = (PrescalerValue & PRLH_MSB_MASK) >> 16;`
`    /* 设置 RTC 分频值的 LSB  */`
`    RTC->PRLL = (PrescalerValue & RTC_LSB_MASK);`
`    RTC_ExitConfigMode();`
`}

在函数中,使用RTC_EnterConfigMode()和RTC_ExitConfigMode()进入和退出RTC寄存器配置模式, 配置时把函数参数PrescalerValue写入到RTC的PRLH和PRLL寄存器中。

4.4 设置、获取RTC计数器及闹钟

RTC外设中最重要的就是计数器以及闹钟寄存器了,它们可以使用RTC_SetCounter()、RTC_GetCounter()以及RTC_SetAlarm()库函数操作,见代码清单:RTC-5:

代码清单:RTC-5 设置RTC计数器及闹钟

复制代码
/**`
`* @brief  设置 RTC 计数器的值 .`
`* @param  CounterValue: 要设置的RTC计数器值.`
`* @retval None`
`*/`
`void RTC_SetCounter(uint32_t CounterValue)`
`{`
`    RTC_EnterConfigMode();`
`    /* 设置 RTC 计数器的 MSB  */`
`    RTC->CNTH = CounterValue >> 16;`
`    /* 设置 RTC 计数器的 LSB  */`
`    RTC->CNTL = (CounterValue & RTC_LSB_MASK);`
`    RTC_ExitConfigMode();`
`}`

`/**`
`* @brief  获取 RTC 计数器的值 .`
`* @param  None`
`* @retval 返回RTC计数器的值`
`*/`
`uint32_t RTC_GetCounter(void)`
`{`
`    uint16_t tmp = 0;`
`    tmp = RTC->CNTL;`
`    return (((uint32_t)RTC->CNTH << 16 ) | tmp) ;`
`}`

`/**`
`* @brief  设置 RTC 闹钟的值 .`
`* @param  AlarmValue: 要设置的RTC闹钟值.`
`* @retval None`
`*/`
`void RTC_SetAlarm(uint32_t AlarmValue)`
`{`
`    RTC_EnterConfigMode();`
`    /* 设置 RTC 闹钟的 MSB  */`
`    RTC->ALRH = AlarmValue >> 16;`
`    /* 设置 RTC 闹钟的 LSB  */`
`    RTC->ALRL = (AlarmValue & RTC_LSB_MASK);`
`    RTC_ExitConfigMode();`
`}

利用RTC_SetCounter()可以向RTC的计数器写入新数值,通常这些数值被设置为时间戳以更新时间。

RTC_GetCounter()函数则用于在RTC正常运行时获取当前计数器的值以获取当前时间。

RTC_SetAlarm()函数用于配置闹钟时间,当计数器的值与闹钟寄存器的值相等时, 可产生闹钟事件或中断,该事件可以把睡眠、停止和待机模式的W55MH32芯片唤醒。

5 实时时钟

5.1 代码解析

1.头文件包含

复制代码
#include <stdlib.h>`
`#include <string.h>`
`#include <stdio.h>`
`#include "delay.h"`
`#include "w55mh32.h"

这里包含了标准库的头文件stdlib.h、string.h和stdio.h,以及自定义的头文件delay.h和w55mh32.h。

2.全局变量和函数声明

复制代码
USART_TypeDef *USART_TEST = USART1;`

`void UART_Configuration(uint32_t bound);`
`void NVIC_Configuration(void);`
`void RCC_ClkConfiguration(void);`
`void RTC_Configuration(void);`
`void Time_Adjust(void);`
`void Time_Show(void);`

`__IO uint32_t TimeDisplay = 0;

**USART_TEST:**指定使用的串口为USART1。

声明了一系列函数,用于串口配置、中断向量表配置、时钟配置、RTC 配置、时间调整和显示。

**TimeDisplay:**一个易变的全局变量,用于标记是否需要显示时间。

3.main()函数

复制代码
int main(void)`
`{`
`    RCC_ClocksTypeDef clocks;`

`    delay_init();`

`    RCC_ClkConfiguration();`

`    UART_Configuration(115200);`
`    printf("RTC Calendar Test.\n");`
`    RCC_GetClocksFreq(&clocks);`

`    printf("\n");`
`    printf("SYSCLK: %3.1fMhz, HCLK: %3.1fMhz, PCLK1: %3.1fMhz, PCLK2: %3.1fMhz, ADCCLK: %3.1fMhz\n",`
`           (float)clocks.SYSCLK_Frequency / 1000000, (float)clocks.HCLK_Frequency / 1000000,`
`           (float)clocks.PCLK1_Frequency / 1000000, (float)clocks.PCLK2_Frequency / 1000000, (float)clocks.ADCCLK_Frequency / 1000000);`

`    NVIC_Configuration();`
`    if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5)`
`    {`
`        printf("\rRTC not yet configured....\n");`
`        RTC_Configuration();`

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

`        Time_Adjust();`
`        BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);`
`    }`
`    else`
`    {`
`        if (RCC_GetFlagStatus(RCC_FLAG_PORRST) != RESET)`
`        {`
`            printf("Power On Reset occurred....\n");`
`        }`
`        else if (RCC_GetFlagStatus(RCC_FLAG_PINRST) != RESET)`
`        {`
`            printf("External Reset occurred....\n");`
`        }`

`        printf("No need to configure RTC....\n");`
`        RTC_WaitForSynchro();`

`        RTC_ITConfig(RTC_IT_SEC, ENABLE);`
`        RTC_WaitForLastTask();`
`    }`

`    RCC_ClearFlag();`

`    Time_Show();`

`    while (1);`
`}

初始化延时函数delay_init()。

配置系统时钟RCC_ClkConfiguration()。

配置串口UART_Configuration(115200),并输出测试信息。

获取系统时钟频率并输出。

配置中断向量表NVIC_Configuration()。

检查备份寄存器BKP_DR1的值,如果不等于0xA5A5,则进行 RTC 配置和时间调整;否则,根据复位标志输出相应信息,并使能 RTC 秒中断。

清除 RCC 标志位。

进入Time_Show()函数,循环显示时间。

最后进入无限循环。

4.Time_Display()函数

复制代码
void Time_Display(uint32_t TimeVar)`
`{`
`    uint32_t THH = 0, TMM = 0, TSS = 0;`

`    if (RTC_GetCounter() == 0x0001517F)`
`    {`
`        RTC_SetCounter(0x0);`
`        RTC_WaitForLastTask();`
`    }`

`    THH = TimeVar / 3600;`
`    TMM = (TimeVar % 3600) / 60;`
`    TSS = (TimeVar % 3600) % 60;`

`    printf("Time: %0.2d:%0.2d:%0.2d\n", THH, TMM, TSS);`
`}

该函数用于将秒数转换为小时、分钟和秒,并输出当前时间。如果 RTC 计数器达到0x0001517F,则将其重置为0。

5.Time_Show()函数

复制代码
void Time_Show(void)`
`{`
`    printf("\n\r");`
`    while (1)`
`    {`
`        if (TimeDisplay == 1)`
`        {`
`            Time_Display(RTC_GetCounter());`
`            TimeDisplay = 0;`
`        }`
`    }`
`}

该函数进入一个无限循环,当TimeDisplay为1时,调用Time_Display()函数显示当前时间,并将TimeDisplay重置为0。

6.RTC_Configuration()函数

复制代码
void RTC_Configuration(void)`
`{`
`    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);`

`    PWR_BackupAccessCmd(ENABLE);`

`    BKP_DeInit();`

`    RCC_LSICmd(ENABLE);`
`    while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET)`
`    {`
`    }`

`    RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);`

`    RCC_RTCCLKCmd(ENABLE);`

`    RTC_WaitForSynchro();`

`    RTC_WaitForLastTask();`

`    RTC_ITConfig(RTC_IT_SEC, ENABLE);`

`    RTC_WaitForLastTask();`

`    RTC_SetPrescaler(32767); /* RTC period = RTCCLK/RTC_PR = (32.768 KHz)/(32767+1) */`

`    RTC_WaitForLastTask();`
`}

该函数用于配置 RTC,包括使能电源和备份域时钟、允许访问备份域、复位备份寄存器、使能低速内部时钟(LSI)、选择 RTC 时钟源、使能 RTC 时钟、等待 RTC 同步、使能 RTC 秒中断和设置 RTC 预分频器。

7.USART_Scanf()函数

复制代码
uint8_t USART_Scanf(uint32_t value)`
`{`
`    uint32_t index  = 0;`
`    uint32_t tmp[2] = {0, 0};`

`    while (index < 2)`
`    {`
`        while (USART_GetFlagStatus(USART_TEST, USART_FLAG_RXNE) == RESET)`
`        {`
`        }`
`        tmp[index++] = (USART_ReceiveData(USART_TEST));`
`        if ((tmp[index - 1] < 0x30) || (tmp[index - 1] > 0x39))`
`        {`
`            printf("\n\rPlease enter valid number between 0 and 9");`
`            index--;`
`        }`
`    }`
`    index = (tmp[1] - 0x30) + ((tmp[0] - 0x30) * 10);`

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

该函数用于从串口读取两个数字字符,并将其转换为一个两位数的整数。如果输入的字符不是数字或超出了指定范围,则提示用户重新输入。​​​​​​​

8.Time_Regulate()函数

复制代码
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);`

`    return ((Tmp_HH * 3600 + Tmp_MM * 60 + Tmp_SS));`
`}

该函数用于通过串口与用户交互,让用户设置小时、分钟和秒,并将其转换为秒数返回。

9.Time_Adjust()函数

复制代码
void Time_Adjust(void)`
`{`
`    RTC_WaitForLastTask();`
`    RTC_SetCounter(Time_Regulate());`
`    RTC_WaitForLastTask();`
`}

该函数用于调整 RTC 计数器的值,调用Time_Regulate()函数获取用户设置的时间,并将其设置到 RTC 计数器中。

10.NVIC_Configuration()函数

复制代码
void NVIC_Configuration(void)`
`{`
`    NVIC_InitTypeDef NVIC_InitStructure;`

`    /* Configure one bit for preemption priority */`
`    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);`

`    /* Enable the RTC Interrupt */`
`    NVIC_InitStructure.NVIC_IRQChannel                   = RTC_IRQn;`
`    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;`
`    NVIC_InitStructure.NVIC_IRQChannelSubPriority        = 0;`
`    NVIC_InitStructure.NVIC_IRQChannelCmd                = ENABLE;`
`    NVIC_Init(&NVIC_InitStructure);`
`}

该函数用于配置中断向量表,设置中断优先级分组为NVIC_PriorityGroup_1,并使能 RTC 中断。

11.UART_Configuration()函数

复制代码
void UART_Configuration(uint32_t bound)`
`{`
`    GPIO_InitTypeDef  GPIO_InitStructure;`
`    USART_InitTypeDef USART_InitStructure;`

`    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);`
`    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);`

`    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_9;`
`    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;`
`    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_PP;`
`    GPIO_Init(GPIOA, &GPIO_InitStructure);`

`    GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_10;`
`    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;`
`    GPIO_Init(GPIOA, &GPIO_InitStructure);`

`    USART_InitStructure.USART_BaudRate            = bound;`
`    USART_InitStructure.USART_WordLength          = USART_WordLength_8b;`
`    USART_InitStructure.USART_StopBits            = USART_StopBits_1;`
`    USART_InitStructure.USART_Parity              = USART_Parity_No;`
`    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;`
`    USART_InitStructure.USART_Mode                = USART_Mode_Rx | USART_Mode_Tx;`

`    USART_Init(USART_TEST, &USART_InitStructure);`
`    USART_Cmd(USART_TEST, ENABLE);`
`}

该函数用于配置串口USART1,包括使能 USART1 和 GPIOA 时钟、配置 GPIO 引脚、设置串口参数(波特率、数据位、停止位、奇偶校验等),并使能串口。

12.SER_PutChar()和fputc()函数

复制代码
int SER_PutChar(int ch)`
`{`
`    while (!USART_GetFlagStatus(USART_TEST, USART_FLAG_TC));`
`    USART_SendData(USART_TEST, (uint8_t)ch);`

`    return ch;`
`}`

`int fputc(int c, FILE *f)`
`{`
`    /* Place your implementation of fputc here */`
`    /* e.g. write a character to the USART */`
`    if (c == '\n')`
`    {`
`        SER_PutChar('\r');`
`    }`
`    return (SER_PutChar(c));`
`}

SER_PutChar()函数用于向串口发送一个字符。

fputc()函数是标准库中用于输出字符的函数,这里将其重定向到串口输出,并且在输出换行符时自动添加回车符。​​​​​​​

5.2 下载验证

6 RTC_LSICalib

6.1 代码解析

**1. 主函数 main()**​​​​​​​

复制代码
int main(void) {`
`    // 初始化串口,打印系统时钟信息`
`    UART_Configuration(115200);`
`    printf("RTC LSI Calib Test.\n");`
`    // 配置RTC、TIM5、NVIC`
`    RTC_Configuration();`
`    TIM_Configuration();`
`    NVIC_Configuration();`
`    // 等待TIM5测量完成`
`    while (OperationComplete != 2); `
`    // 计算LSI频率并设置RTC预分频`
`    if (PeriodValue != 0) {`
`        LsiFreq = (uint32_t)((uint32_t)(clocks.PCLK1_Frequency * 2) / (uint32_t)PeriodValue);`
`    }`
`    printf("LsiFreq: %d Hz\n", LsiFreq);`
`    RTC_SetPrescaler(LsiFreq - 1);`
`    while (1);`
`}`
`

**流程:**初始化串口后,配置 RTC、TIM5 和中断,测量 LSI 频率,最后设置 RTC 预分频。

2. TIM5 配置(TIM_Configuration)

复制代码
void TIM_Configuration(void) {`
`    // 使能时钟,重映射LSI到TIM5_CH4`
`    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);`
`    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE);`
`    GPIO_PinRemapConfig(GPIO_Remap_TIM5CH4_LSI, ENABLE);`
`    // 配置TIM5时基:不分频,向上计数,周期0xFFFF`
`    TIM_TimeBaseStructure.TIM_Prescaler = 0;`
`    TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure);`
`    // 配置输入捕获:通道4,上升沿捕获`
`    TIM_ICInitStructure.TIM_Channel = TIM_Channel_4;`
`    TIM_ICInit(TIM5, &TIM_ICInitStructure);`
`    TIM_Cmd(TIM5, ENABLE);`
`    TIM_ITConfig(TIM5, TIM_IT_CC4, ENABLE);`
`}`
`​​​​​​​

**作用:**将 LSI 信号连接到 TIM5_CH4,配置 TIM5 为输入捕获模式,测量 LSI 的周期。

**3. RTC 配置(RTC_Configuration)**​​​​​​​

复制代码
void RTC_Configuration(void) {`
`    // 使能电源和备份域时钟,允许访问备份域`
`    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);`
`    PWR_BackupAccessCmd(ENABLE);`
`    // 选择LSI作为RTC时钟源`
`    RCC_LSICmd(ENABLE);`
`    RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);`
`    RCC_RTCCLKCmd(ENABLE);`
`    // 配置RTC预分频:根据测量的LSI频率设置`
`    RTC_SetPrescaler(40000); `
`    BKP_RTCOutputConfig(BKP_RTCOutputSource_Second);`
`}`
`

**作用:**使能 LSI,将其作为 RTC 时钟源,配置 RTC 预分频器,输出秒信号。

4. NVIC 配置(NVIC_Configuration)

复制代码
void NVIC_Configuration(void) {`
`    // 设置中断优先级分组`
`    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);`
`    // 配置RTC中断:最高优先级`
`    NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;`
`    NVIC_Init(&NVIC_InitStructure);`
`    // 配置TIM5中断:子优先级2`
`    NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn;`
`    NVIC_Init(&NVIC_InitStructure);`
`}

**作用:**设置 RTC 和 TIM5 的中断优先级,确保中断正确响应。

这段代码通过 TIM5 测量 LSI 频率,动态配置 RTC 预分频,确保 RTC 计时精度,适用于需要校准 LSI 的嵌入式场景,如 RTC 时钟源校准。

6.2 下载验证

相关推荐
xxy.c1 分钟前
基于IMX6ULL的时钟,定时器(EPIT,GPT)
单片机·嵌入式硬件·fpga开发
happygrilclh1 小时前
stm32L496 flash 分配
stm32·单片机·嵌入式硬件
古译汉书2 小时前
嵌入式铁头山羊STM32-各章节详细笔记-查阅传送门
数据结构·笔记·stm32·单片机·嵌入式硬件·个人开发
自由的好好干活3 小时前
从0开始使用LabVIEW操作数据采集卡-概述和新建新建项目
嵌入式硬件·labview
一枚码农~5 小时前
STM32红外与LED控制实战
单片机·嵌入式硬件
路过羊圈的狼7 小时前
STM32的HAL库驱动ADS124S08进行PT100温度采集
stm32·嵌入式硬件·mongodb
李永奉7 小时前
51单片机-实现红外遥控模块教程
单片机·嵌入式硬件·51单片机
辛集电子8 小时前
【STM32】位带操作
stm32·单片机·嵌入式硬件
wei-dong-183797540088 小时前
嵌入式硬件工程师每日提问
嵌入式硬件·电源拓扑结构
MOS管-冠华伟业9 小时前
微硕WSF4012 N+P双沟MOS管,驱动汽车智能座椅“无感”升降气泵
单片机·嵌入式硬件