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

news2025/7/28 22:59:24

第二十八章 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 下载验证

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

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

相关文章

使用 DuckLake 和 DuckDB 构建 S3 数据湖实战指南

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

大语言模型提示词(LLM Prompt)工程系统性学习指南:从理论基础到实战应用的完整体系

文章目录 前言&#xff1a;为什么提示词工程成为AI时代的核心技能一、提示词的本质探源&#xff1a;认知科学与逻辑学的理论基础1.1 认知科学视角下的提示词本质信息处理理论的深层机制图式理论的实际应用认知负荷理论的优化策略 1.2 逻辑学框架下的提示词架构形式逻辑的三段论…

如何基于Mihomo Party http端口配置git与bash命令行代理

如何基于Mihomo Party http端口配置git与bash命令行代理 1. 确定Mihomo Party http端口配置 点击内核设置后即可查看 默认7892端口&#xff0c;开启允许局域网连接 2. 配置git代理 配置本机代理可以使用 127.0.0.1 配置局域网内其它机代理需要使用本机的非回环地址 IP&am…

埃文科技智能数据引擎产品入选《中国网络安全细分领域产品名录》

嘶吼安全产业研究院发布《中国网络安全细分领域产品名录》&#xff0c;埃文科技智能数据引擎产品成功入选数据分级分类产品名录。 在数字化转型加速的今天&#xff0c;网络安全已成为企业生存与发展的核心基石&#xff0c;为了解这一蓬勃发展的产业格局&#xff0c;嘶吼安全产业…

NLP学习路线图(二十六):自注意力机制

一、为何需要你&#xff1f;序列建模的困境 在你出现之前&#xff0c;循环神经网络&#xff08;RNN&#xff09;及其变种LSTM、GRU是处理序列数据&#xff08;如文本、语音、时间序列&#xff09;的主流工具。它们按顺序逐个处理输入元素&#xff0c;将历史信息压缩在一个隐藏…

Unity3D仿星露谷物语开发60之定制角色其他部位

1、目标 上一篇中定制了角色的衬衫、手臂。 本篇中将定制角色其他部位的图形&#xff0c;包括&#xff1a;裤子、发型、皮肤、帽子等。 2、定制裤子 &#xff08;1&#xff09;修改ApplyCharacterCustomisation.cs脚本 我们需要设置一个输入框选择裤子的颜色。 // Select …

Google机器学习实践指南(机器学习模型泛化能力)

&#x1f525; Google机器学习(14)-机器学习模型泛化能力解析 Google机器学习(14)-机器学习模型泛化原理与优化&#xff08;约10分钟&#xff09; 一、泛化问题引入 ▲ 模型表现对比&#xff1a; 假设森林中树木健康状况预测模型&#xff1a; 图1&#xff1a;初始模型表现 …

MySQL性能调优:Mysql8高频面试题汇总

1&#xff0c;主键和唯一键有什么区别&#xff1f; 主键不能重复&#xff0c;不能为空&#xff0c;唯一键不能重复&#xff0c;可以为空。 建立主键的目的是让外键来引用。 一个表最多只有一个主键&#xff0c;但可以有很多唯一键 2&#xff0c;MySQL常用的存储引擎有哪些&…

vue+elementUI+springboot实现文件合并前端展示文件类型

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

高效绘制业务流程图!专业模板免费下载

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

Spring Boot + Prometheus 实现应用监控(基于 Actuator 和 Micrometer)

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

PowerBI企业运营分析—列互换式中国式报表分析

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

BugKu Web渗透之需要管理员

启动场景&#xff0c;打开网页&#xff0c;显示如下&#xff1a; 一般没有上面头绪的时候&#xff0c;就是两步&#xff1a;右键查看源代码 和 扫描网站目录。 步骤一&#xff1a; 右键查看源代码 和 扫描网站目录。 右键查看源代码没有发现异常。 于是扫描网站目录&…

TDengine 开发指南—— UDF函数

UDF 简介 在某些应用场景中&#xff0c;应用逻辑需要的查询功能无法直接使用内置函数来实现&#xff0c;TDengine 允许编写用户自定义函数&#xff08;UDF&#xff09;&#xff0c;以便解决特殊应用场景中的使用需求。UDF 在集群中注册成功后&#xff0c;可以像系统内置函数一…

使用vsftpd搭建FTP服务器(TLS/SSL显式加密)

安装vsftpd服务 使用vsftpd RPM安装包安装即可&#xff0c;如果可以访问YUM镜像源&#xff0c;通过dnf或者yum工具更加方便。 yum -y install vsftpd 启动vsftpd、查看服务状态 systemctl enable vsftpd systemctl start vsftpd systemctl status vsftpd 备份配置文件并进…

1.1Nodejs和浏览器中的二进制处理

Buffer 在 Node.js 中&#xff0c;Buffer 类用于处理二进制数据。由于 JavaScript 在浏览器环境中主要用于处理字符串和数字等类型的数据&#xff0c;对二进制数据的处理能力较弱&#xff0c;因此 Node.js 引入了 Buffer 类来弥补这一不足&#xff0c;特别是在处理文件系统操作…

入门AJAX——XMLHttpRequest(Post)

一、前言 在上篇文章中&#xff0c;我们已经介绍了 HMLHttpRequest 的GET 请求的基本用法&#xff0c;并基于我提供的接口练习了两个简单的例子。如果你还没有看过第一篇文章&#xff0c;强烈建议你在学习完上篇文章后再学习本篇文章&#xff1a; &#x1f517;入门AJAX——XM…

Qt(part1)Qpushbutton,信号与槽,对象树,自定义信号与槽,lamda表达式。

1、创建Qt程序 2、命名规范及快捷键 3、Qpushbutton按钮创建 4、对象树概念 5、信号与槽 6、自定义信号与槽 7、当自定义信号和槽发生重载时 8、信号可以连接信号&#xff0c;信号也可以断开。 9、lamda表达式

西北某省级联通公司:3D动环模块如何实现机房“一屏统管”?

一、运营商机房监控痛点凸显 在通信行业快速发展的当下&#xff0c;西北某省级联通公司肩负着保障区域通信畅通的重任。然而&#xff0c;公司分布广泛的机房面临着诸多监控难题&#xff0c;尤其是偏远机房环境风险无法实时感知这一痛点&#xff0c;严重影响了机房的稳定运行和通…

视觉分析在人员行为属性检测中的应用

基于视觉分析的人员行为属性检测方案 一、背景与需求分析 在工业生产、建筑施工、公共安全等领域&#xff0c;人员行为属性的合规性检测是保障安全生产的关键环节。例如&#xff0c;工地工人未佩戴安全帽、厨房人员未佩戴手套、作业现场人员使用手机等行为&#xff0c;均可能…