第二十八章 RTC——实时时钟
第二十八章 RTC——实时时钟
目录
第二十八章 RTC——实时时钟
1 RTC实时时钟简介
2 RTC外设框图剖析
3 UNIX时间戳
4 与RTC控制相关的库函数
4.1 等待时钟同步和操作完成
4.2 使能备份域涉及RTC配置
4.3 设置RTC时钟分频
4.4 设置、获取RTC计数器及闹钟
5 实时时钟
5.1 代码解析
5.2 下载验证
6 RTC_LSICalib
6.1 代码解析
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 下载验证
相关文章:

第二十八章 RTC——实时时钟
第二十八章 RTC——实时时钟 目录 第二十八章 RTC——实时时钟 1 RTC实时时钟简介 2 RTC外设框图剖析 3 UNIX时间戳 4 与RTC控制相关的库函数 4.1 等待时钟同步和操作完成 4.2 使能备份域涉及RTC配置 4.3 设置RTC时钟分频 4.4 设置、获取RTC计数器及闹钟 5 实时时…...

使用 DuckLake 和 DuckDB 构建 S3 数据湖实战指南
本文介绍了由 DuckDB 和 DuckLake 组成的轻量级数据湖方案,旨在解决传统数据湖(如HadoopHive)元数据管理复杂、查询性能低及厂商锁定等问题。该方案为中小规模数据湖场景提供了简单、高性能且无厂商锁定的替代选择。 1. 什么是 DuckLake 和 D…...

大语言模型提示词(LLM Prompt)工程系统性学习指南:从理论基础到实战应用的完整体系
文章目录 前言:为什么提示词工程成为AI时代的核心技能一、提示词的本质探源:认知科学与逻辑学的理论基础1.1 认知科学视角下的提示词本质信息处理理论的深层机制图式理论的实际应用认知负荷理论的优化策略 1.2 逻辑学框架下的提示词架构形式逻辑的三段论…...

如何基于Mihomo Party http端口配置git与bash命令行代理
如何基于Mihomo Party http端口配置git与bash命令行代理 1. 确定Mihomo Party http端口配置 点击内核设置后即可查看 默认7892端口,开启允许局域网连接 2. 配置git代理 配置本机代理可以使用 127.0.0.1 配置局域网内其它机代理需要使用本机的非回环地址 IP&am…...
CMake 为 Debug 版本的库或可执行文件添加 d 后缀
在使用 CMake 构建项目时,我们经常需要区分 Debug 和 Release 构建版本。一个常见的做法是为 Debug 版本的库或可执行文件添加后缀(如 d),例如 libmylibd.so 或 myappd.exe。 本文将介绍几种在 CMake 中实现为 Debug 版本自动添加 d 后缀的方法。 方法一:使用 CMAKE_DEBU…...
Linux 特殊权限位详解:SetUID, SetGID, Sticky Bit
Linux 特殊权限位详解:SetUID, SetGID, Sticky Bit 在Linux权限系统中,除了基本的读、写(w)、执行(x)权限外,还有三个特殊权限位:SetUID、SetGID和Sticky Bit。这些权限位提供了更精细的权限控制机制,尤其在需要临时提升权限或管理共享资源时非常有用。 一、SetUID (s位…...

埃文科技智能数据引擎产品入选《中国网络安全细分领域产品名录》
嘶吼安全产业研究院发布《中国网络安全细分领域产品名录》,埃文科技智能数据引擎产品成功入选数据分级分类产品名录。 在数字化转型加速的今天,网络安全已成为企业生存与发展的核心基石,为了解这一蓬勃发展的产业格局,嘶吼安全产业…...
使用VTK还是OpenGL集成到qt程序里哪个好?
在Qt程序中集成VTK与OpenGL:选择哪个更好? 在Qt程序中实现三维可视化时,开发者常常面临一个选择:是使用VTK(Visualization Toolkit)还是OpenGL(Open Graphics Library)。这两种技术…...
Java-IO流之打印流详解
Java-IO流之打印流详解 一、打印流概述1.1 什么是打印流1.2 打印流的特点1.3 打印流的应用场景 二、PrintStream详解2.1 基本概念2.2 构造函数2.3 核心方法2.4 使用示例 三、PrintWriter详解3.1 基本概念3.2 构造函数3.3 核心方法3.4 使用示例 四、PrintStream与PrintWriter的比…...
高效图像处理:使用 Pillow 进行格式转换与优化
高效图像处理:使用 Pillow 进行格式转换与优化 1. 背景引入 在图像处理应用中,格式转换、裁剪、压缩等操作是常见需求。Python 的 Pillow 库基于 PIL(Python Imaging Library),提供 轻量、强大 的图像处理能力,广泛用于 Web 开发、数据分析、机器学习 等领域。 本文将…...
Github 2025-06-06 Java开源项目日报Top10
根据Github Trendings的统计,今日(2025-06-06统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Java项目10TypeScript项目1Java实现的算法集合:使用Gitpod.io进行编辑和贡献 创建周期:2883 天开发语言:Java协议类型:MIT LicenseStar数量…...
使用 Ansible 在 Windows 服务器上安装 SSL 证书
在本教程中,我将向您展示如何使用 Ansible 在 Windows 服务器上安装 SSL 证书。使用 Ansible 自动化 SSL 证书安装过程可以提高 IT 运营的效率、一致性和协作性。我将介绍以下步骤: 将 SSL 证书文件复制到服务器将 PFX 证书导入指定的存储区获取导入的证…...
厂区能源监控系统:网关赋能下的高效能源管理与环保监测
在现代工业生产领域,能源的有效利用与环境保护是企业实现可持续发展的两大关键要素。厂区能源监控系统借助先进的信息技术与自动化控制手段,对厂区内能源消耗及污水处理等核心环节展开实时监控与精细化管理。其中,御控网关作为系统关键枢纽&a…...
CentOS 7 如何安装llvm-project-10.0.0?
CentOS 7 如何安装llvm-project-10.0.0? 需要先升级gcc至7.5版本,详见CentOS 7如何编译安装升级gcc版本?一文 # 备份之前的yum .repo文件至 /tmp/repo_bak 目录 mkdir -p /tmp/repo_bak && cd /etc/yum.repo.d && /bin/mv ./*.repo …...
Cursor 1.0 的核心功能亮点及技术价值分析
Cursor 1.0 的核心功能亮点及技术价值分析 结合官方更新和开发者实测整理: 🛠️ 一、BugBot:智能自动化代码审查 功能亮点:深度集成 GitHub,自动扫描 Pull Request(PR)中的潜在 Bug(…...
软考 系统架构设计师系列知识点之杂项集萃(83)
接前一篇文章:软考 系统架构设计师系列知识点之杂项集萃(82) 第150题 体系结构权衡分析方法(Architecture Tradeoff Analysis Method,ATAM)是一种常见的系统架构评估框架,该框架主要关注系统的…...

NLP学习路线图(二十六):自注意力机制
一、为何需要你?序列建模的困境 在你出现之前,循环神经网络(RNN)及其变种LSTM、GRU是处理序列数据(如文本、语音、时间序列)的主流工具。它们按顺序逐个处理输入元素,将历史信息压缩在一个隐藏…...

Unity3D仿星露谷物语开发60之定制角色其他部位
1、目标 上一篇中定制了角色的衬衫、手臂。 本篇中将定制角色其他部位的图形,包括:裤子、发型、皮肤、帽子等。 2、定制裤子 (1)修改ApplyCharacterCustomisation.cs脚本 我们需要设置一个输入框选择裤子的颜色。 // Select …...
C++动态链接库封装,供C#/C++ 等编程语言使用——C++动态链接库概述(总)
目录: 一、前言及背景1.1需求描述1.2常见编程语言对比1.3应用背景 二、C对外接口2.1C对外封装2.2基于目标平台封装接口形式 三、系列文章汇总 一、前言及背景 1.1需求描述 不同的编程语言,具有不同的编程生态环境,对于项目应用来说ÿ…...

Google机器学习实践指南(机器学习模型泛化能力)
🔥 Google机器学习(14)-机器学习模型泛化能力解析 Google机器学习(14)-机器学习模型泛化原理与优化(约10分钟) 一、泛化问题引入 ▲ 模型表现对比: 假设森林中树木健康状况预测模型: 图1:初始模型表现 …...

MySQL性能调优:Mysql8高频面试题汇总
1,主键和唯一键有什么区别? 主键不能重复,不能为空,唯一键不能重复,可以为空。 建立主键的目的是让外键来引用。 一个表最多只有一个主键,但可以有很多唯一键 2,MySQL常用的存储引擎有哪些&…...
Neo4j 数据建模:原理、技术与实践指南
Neo4j 作为领先的图数据库,其核心优势在于利用图结构直观地表达和高效地查询复杂关系。其数据建模理念与传统关系型数据库截然不同,专注于实体(节点)及其连接(关系)。以下基于官方文档,系统阐述其建模原理、关键技术、实用技巧及最佳实践: 一、 核心原理:以关系为中心…...
【数据结构知识分享】顺序表详解
一、存储结构 物理相邻性: 若元素 a 和 b 逻辑相邻,则它们在内存中的地址也连续(如 &a[i1] &a[i] sizeof(ElemType))。 内存布局x: 基地址 索引 元素大小,通过首地址直接计算任意位置地址。 …...

vue+elementUI+springboot实现文件合并前端展示文件类型
项目场景: element的table上传文件并渲染出文件名称点击所属行可以查看文件,并且可以导出合并文件,此文章是记录合并文档前端展示的帖子 解决方案: 后端定义三个工具类 分别是pdf,doc和word的excle的目前我没整 word的工具类 package com.sc.modules…...

高效绘制业务流程图!专业模板免费下载
在复杂的业务流程管理中,可视化工具已成为提升效能的核心基础设施。为助力开发者、项目经理及业务架构师高效落地流程标准化,本文将为你精选5套开箱即用的专业流程图模板。这些模板覆盖跨部门协作、电商订单、客户服务等高频场景,具备以下核心…...

Spring Boot + Prometheus 实现应用监控(基于 Actuator 和 Micrometer)
文章目录 Spring Boot Prometheus 实现应用监控(基于 Actuator 和 Micrometer)环境准备示例结构启动和验证验证 Spring Boot 应用Prometheus 抓取配置(静态方式)Grafana 面板配置总结 Spring Boot Prometheus 实现应用监控&…...

PowerBI企业运营分析—列互换式中国式报表分析
PowerBI企业运营分析—列互换式中国式报表分析 欢迎来到Powerbi小课堂,在竞争激烈的市场环境中,企业运营分析平台成为提升竞争力的核心工具。 该平台通过高效整合多源数据,并实时监控关键指标,能够迅速揭示业务表现的全貌&#…...

BugKu Web渗透之需要管理员
启动场景,打开网页,显示如下: 一般没有上面头绪的时候,就是两步:右键查看源代码 和 扫描网站目录。 步骤一: 右键查看源代码 和 扫描网站目录。 右键查看源代码没有发现异常。 于是扫描网站目录&…...
Java集合初始化:Lists.newArrayList vs new ArrayList()
文章目录 前言一、核心区别全景图二、代码实现深度对比1. 初始化方式对比2. 容量预分配机制 三、性能与底层原理1. 内存分配策略2. 基准测试数据(JMH) 四、Guava的进阶功能生态1. 集合转换2. 集合分片3. 不可变集合创建 五、最佳实践指南六、源码级实现解…...
VBA清空数据
列数转字母 Function CNtoW(ByVal num As Long) As String CNtoW Replace(Cells(1, num).Address(False, False), "1", "") End Function 字母转列数 Function CWtoN(ByVal AB As String) As Long CWtoN Range("a1:" & AB & &…...