第十九章 ADC——电压采集
单芯片解决方案,开启全新体验——W55MH32 高性能以太网单片机 W55MH32是WIZnet重磅推出的高性能以...
单芯片解决方案,开启全新体验——W55MH32 高性能以太网单片机
W55MH32是WIZnet重磅推出的高性能以太网单片机,它为用户带来前所未有的集成化体验。这颗芯片将强大的组件集于一身,具体来说,一颗W55MH32内置高性能Arm® Cortex-M3核心,其主频最高可达216MHz;配备1024KB FLASH与96KB SRAM,满足存储与数据处理需求;集成TOE引擎,包含WIZnet全硬件TCP/IP协议栈、内置MAC以及PHY,拥有独立的32KB以太网收发缓存,可供8个独立硬件socket使用。如此配置,真正实现了All-in-One解决方案,为开发者提供极大便利。
在封装规格上,W55MH32 提供了两种选择:QFN100和QFN68。
W55MH32L采用QFN100封装版本,尺寸为12x12mm,其资源丰富,专为各种复杂工控场景设计。它拥有66个GPIO、3个ADC、12通道DMA、17个定时器、2个I2C、5个串口、2个SPI接口(其中1个带I2S接口复用)、1个CAN、1个USB2.0以及1个SDIO接口。如此丰富的外设资源,能够轻松应对工业控制中多样化的连接需求,无论是与各类传感器、执行器的通信,还是对复杂工业协议的支持,都能游刃有余,成为复杂工控领域的理想选择。 同系列还有QFN68封装的W55MH32Q版本,该版本体积更小,仅为8x8mm,成本低,适合集成度高的网关模组等场景,软件使用方法一致。更多信息和资料请进入网站或者私信获取。
此外,本W55MH32支持硬件加密算法单元,WIZnet还推出TOE+SSL应用,涵盖TCP SSL、HTTP SSL以及 MQTT SSL等,为网络通信安全再添保障。
为助力开发者快速上手与深入开发,基于W55MH32L这颗芯片,WIZnet精心打造了配套开发板。开发板集成WIZ-Link芯片,借助一根USB C口数据线,就能轻松实现调试、下载以及串口打印日志等功能。开发板将所有外设全部引出,拓展功能也大幅提升,便于开发者全面评估芯片性能。
若您想获取芯片和开发板的更多详细信息,包括产品特性、技术参数以及价格等,欢迎访问官方网页,我们期待与您共同探索W55MH32的无限可能。
第二十八章 RTC——实时时钟
本章参考资料:《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.1fMhzn", (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.2dn", THH, TMM, TSS); }
该函数用于将秒数转换为小时、分钟和秒,并输出当前时间。如果 RTC 计数器达到0x0001517F,则将其重置为0。
5.Time_Show()函数
void Time_Show(void) { printf("nr"); 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("nrPlease enter valid number between 0 and 9"); index--; } } index = (tmp[1] - 0x30) + ((tmp[0] - 0x30) * 10); if (index > value) { printf("nrPlease 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("rn==============Time Settings====================================="); printf("rn Please Set Hours"); while (Tmp_HH == 0xFF) { Tmp_HH = USART_Scanf(23); } printf(": %d", Tmp_HH); printf("rn Please Set Minutes"); while (Tmp_MM == 0xFF) { Tmp_MM = USART_Scanf(59); } printf(": %d", Tmp_MM); printf("rn 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 Hzn", 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 下载验证
当前非电脑浏览器正常宽度,请使用移动设备访问本站!