本帖最后由 onemoren 于 2025-9-12 17:27 编辑
#技术资源# #有奖活动# #申请原创# @21小跑堂
问题: 使用APM3E103XE芯片内部LSI时钟驱动内部RTC,每天误差超过1小时。是否可以通过外部时钟HSE 8MHz进行LSI校准,使RTC一天内的偏差不超过5分钟?
答案: 可以。AMP32E103XE可通过HSE作为参考,对LSI进行校准(RTC 24小时最多偏差86.4秒)。
实验效果: 实验思路:PCBA上有外部HSE晶振,可以使用HSE晶振校准内部LSI。具体思路是使用内核SYSTICK定时器做计数校准。实验使用E103RET6芯片+DEMO板+SDK例程验证。 实验结果:在极海官网SDK例程的基础上修改程序后验证2PCS,确认不同芯片+不同主频情况下,LSI校准后RTC秒中断每秒最多偏差1ms,RTC 24小时最多偏差1ms*60*60*24=86.4秒。 校准方法、原理、步骤、代码: 1、方法:main函数代码本文后面有提供,直接将提供的代码替换到极海APM32E103XE SDK的RTC例程中的main.c就可以验证。烧录后可以通过串口打印出秒中断时间。 2、校准原理: LSI是内部低速RC振荡器(30k-60kHz,典型值40kHz),芯片间的一致性不高。校准的核心是以高精度的HSE作为参考时钟,通过内核自带的systick定时器获取基于LSI信号的秒中断时间,计算出其实际频率,再通过软件调整RTC预分频器参数实现正确的分频。 3、详细步骤:具体如下,主要分4步: ① 配置SYSTICK,实现每1ms中断一次并统计系统时间。(实际使用中如果有RTOS,此步骤可跳过,直接获取RTOS系统时间戳使用) ② 配置串口,用于打印信息。(实际使用中此步骤可跳过) ③ 获取LSI实测频率。 ④ 验证基于实测频率下的秒中断时间(实际使用中此步骤可跳过,直接按照实测频率配置RTC分频即可) 4、实际代码与注解: /************************************* Copyright(C)****************************** ** @FileName : main.c ** @brief : 使用SYSTICK校准LSI ** @author : ** @version : V1.0 ** @date : ********************************************************************************/ /* Includes */ #include "main.h" #include "stdio.h" volatile uint32_t sys_ms=0; //全局变量,系统时间 volatile uint32_t test_time; //测试用时间 /****************************** BEGIN ******************************** **@name : USART1_Init **@Brief : 配置串口,方便打印信息 **@Author : **@Version : V1.0 **@time : ******************************** END *********************************/ void USART1_Init(void) { USART_Config_T USART_ConfigStruct; RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_USART1); USART_ConfigStruct.baudRate = 115200; USART_ConfigStruct.hardwareFlow = USART_HARDWARE_FLOW_NONE; USART_ConfigStruct.mode = USART_MODE_TX_RX; USART_ConfigStruct.parity = USART_PARITY_NONE; USART_ConfigStruct.stopBits = USART_STOP_BIT_1; USART_ConfigStruct.wordLength = USART_WORD_LEN_8B; USART_Config(USART1, &USART_ConfigStruct); GPIO_Config_T GPIO_configStruct; RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_GPIOA); GPIO_configStruct.mode = GPIO_MODE_AF_PP; GPIO_configStruct.pin = GPIO_PIN_9; GPIO_configStruct.speed = GPIO_SPEED_50MHz; GPIO_Config(GPIOA, &GPIO_configStruct); GPIO_configStruct.mode = GPIO_MODE_IN_FLOATING; GPIO_configStruct.pin = GPIO_PIN_10; GPIO_Config(GPIOA, &GPIO_configStruct); USART_Enable(USART1); } /****************************** BEGIN ******************************** **@Name : LSI_Calibration **@Brief : LSI校准,返回LSI频率 **@Author : **@Version : V1.0 **@Time : ******************************** END *********************************/ uint32_t LSI_Calibration(void) { uint32_t lsi_freq=32767; uint32_t time; //LSI RTC配置 RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_PMU); PMU_EnableBackupAccess(); RCM_EnableLSI(); while (RCM_ReadStatusFlag(RCM_FLAG_LSIRDY) == RESET); RCM_ConfigRTCCLK(RCM_RTCCLK_LSI); RCM_EnableRTCCLK(); RTC_WaitForSynchro(); RTC_WaitForLastTask(); RTC_ConfigPrescaler(lsi_freq); RTC_WaitForLastTask(); RTC_ConfigCounter(0); RTC_WaitForLastTask(); //获取1000ms实际计数 time=sys_ms; RTC_ClearStatusFlag(RTC_FLAG_SEC); RTC_WaitForLastTask(); while (RTC_ReadStatusFlag(RTC_FLAG_SEC)==0); time=sys_ms-time; printf("before_calibration_time: %dms\r\n", time); lsi_freq=lsi_freq*1000/time; printf("lsi_freq: %d\r\n", lsi_freq); //RTC disable RTC_ClearStatusFlag(RTC_FLAG_SEC); RTC_WaitForLastTask(); RCM_DisableLSI(); RCM_DisableAPB1PeriphClock((RCM_APB1_PERIPH_T)RCM_APB1_PERIPH_PMU); return lsi_freq; } /****************************** BEGIN ******************************** **@Name : LSI_Verify **@Brief : LSI按照输入频率分频后验证秒中断时间 **@Author : **@Version : V1.0 **@Time : ******************************** END *********************************/ void LSI_Verify(uint32_t prescaler) { RCM_EnableAPB1PeriphClock((RCM_APB1_PERIPH_T)RCM_APB1_PERIPH_PMU); PMU_EnableBackupAccess(); RCM_EnableLSI(); while (RCM_ReadStatusFlag(RCM_FLAG_LSIRDY) == RESET); RCM_ConfigRTCCLK(RCM_RTCCLK_LSI); RCM_EnableRTCCLK(); RTC_WaitForSynchro(); RTC_WaitForLastTask(); RTC_ConfigPrescaler(prescaler); RTC_WaitForLastTask(); RTC_ConfigCounter(0); RTC_WaitForLastTask(); test_time=sys_ms; //开启中断 RTC_EnableInterrupt(RTC_INT_SEC); RTC_WaitForLastTask(); NVIC_EnableIRQRequest(RTC_IRQn, 0, 0); } int main(void) { uint32_t lsi_freq; SysTick_Config(RCM_ReadSYSCLKFreq()/ 1000); //配置systick,1ms中断一次 USART1_Init(); printf("LSI_CAL_TEST\r\n"); printf("SYS CLK: %d MHZ\r\n", RCM_ReadSYSCLKFreq()/1000000); lsi_freq=LSI_Calibration(); LSI_Verify(lsi_freq); while (1) { } } void SysTick_Handler(void) { sys_ms++; } void RTC_IRQHandler(void) { if (RTC_ReadIntFlag(RTC_INT_SEC) != RESET) { printf("after_calibration_time: %dms\r\n", sys_ms-test_time); test_time=sys_ms; RTC_ClearIntFlag(RTC_INT_SEC); RTC_WaitForLastTask(); } } int fputc(int ch, FILE* f) { /* send a byte of data to the serial port */ USART_TxData(USART1, (uint8_t)ch); /* wait for the data to be send */ while (USART_ReadStatusFlag(USART1, USART_FLAG_TXBE) == RESET); return (ch); }
|