[单片机芯片] CH32L103 RTC应用

[复制链接]
3758|6
 楼主| L-MCU 发表于 2024-6-25 14:19 | 显示全部楼层 |阅读模式
ar, se, TI, tc, RTC
本帖最后由 L-MCU 于 2024-6-25 14:38 编辑

1、实时时钟(RTC)
关于CH32L103 RTC具体介绍,可看CH32L103应用手册。
关于RTC的预分频系数,最高可设置为2的20次方,但一般根据所用RTC时钟源设置。如LSE频率为32.768KHz,设置预分频器重装值寄存器(RTC_PSCRL)值为0x7FFF,则RTC就1s计数一次。寄存器具体介绍如下。
9576667a610536d1c.png
关于RTC的时钟源,可选为LSE或LSI或HSE128分频,如下图。一般选择LSE或LSI。
42621667a611f696d9.png
关于RTC的复位,其中预分频,预分频重装值,主计数器和闹钟,只能通过后备域的复位信号复位,而RTC控制寄存器由系统复位或电源复位控制。


2、RTC应用
CH32L103 EVT提供了RTC日历例程,程序中,使用TIM1进行RTC校准,TIM定时器设置1us计数一次,重装载值设置为65535,即65536us进一次中断,进一次更新中断计数加1。RTC秒中断中,对TIM计数器的值进行判断,理论上1s=1000000us,将TIM计数器的值与1000000进行比较,根据这之间的差值进行校准。具体的校准函数可参考RTC例程。
RTC例程配置使用LSE作为时钟源,此外还可以配置LSI或HSE/128作为时钟源,注意使用HSE/128作为时钟源时,要配置时钟控制寄存器(RCC_CTLR)使能开启HSE振荡器,可以使用HSE配置系统主频。
下代码为在RTC日历例程基础上修改的可配置使用LSI或HSE/128作为RTC时钟源,完整代码可见附件例程。
  1. /********************************** (C) COPYRIGHT *******************************
  2. * File Name          : main.c
  3. * Author             : WCH
  4. * Version            : V1.0.0
  5. * Date               : 2024/02/21
  6. * Description        : Main program body.
  7. *********************************************************************************
  8. * Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd.
  9. * Attention: This software (modified or not) and binary are used for
  10. * microcontroller manufactured by Nanjing Qinheng Microelectronics.
  11. *******************************************************************************/


  12. /**
  13. * @note
  14. * The code initializes the TIM1 timer and the RTC module, and then performs calibration on the RTC
  15. * based on a calibration value.
  16. *
  17. * this code block is checking if the `CalibrationVal` is less than 1000000. If it is, it
  18. * calculates the `FastSecPer30days` value by subtracting `CalibrationVal` from 1000000 and then
  19. * multiplying it by the number of seconds in 30 days (3600 seconds * 24 hours * 30 days) divided
  20. * by 1000000. This `FastSecPer30days` value is then passed to the `RTC_Calibration` function for
  21. * further processing.
  22. */
  23. #include "debug.h"

  24. #define  RTC_LSE        1
  25. #define  RTC_LSI        2
  26. #define  RTC_HSE_128    3

  27. #define  Mode     RTC_HSE_128

  28. /* Global define */
  29. #define PPM_PER_STEP 0.9536743 // 10^6/2^20.
  30. #define PPM_PER_SEC 0.3858025  // 10^6/(30d*24h*3600s).
  31. /* Global Variable */
  32. volatile uint8_t CalibrationFlag = 0;
  33. volatile uint32_t CalibrationTIMCir = 0, CalibrationVal = 0;

  34. typedef struct
  35. {
  36.     vu8 hour;
  37.     vu8 min;
  38.     vu8 sec;

  39.     vu16 w_year;
  40.     vu8 w_month;
  41.     vu8 w_date;
  42.     vu8 week;
  43. } _calendar_obj;

  44. _calendar_obj calendar;

  45. u8 const table_week[12] = {0, 3, 3, 6, 1, 4, 6, 2, 5, 0, 3, 5};
  46. const u8 mon_table[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

  47. /* Exported_Functions */
  48. u8 RTC_Init(void);
  49. u8 Is_Leap_Year(u16 year);
  50. u8 RTC_Alarm_Set(u16 syear, u8 smon, u8 sday, u8 hour, u8 min, u8 sec);
  51. u8 RTC_Get(void);
  52. u8 RTC_Get_Week(u16 year, u8 month, u8 day);
  53. u8 RTC_Set(u16 syear, u8 smon, u8 sday, u8 hour, u8 min, u8 sec);

  54. volatile uint8_t Calibration_STA = 0;

  55. void RTC_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
  56. void TIM1_UP_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
  57. /*********************************************************************
  58. * @fn      RTC_IRQHandler
  59. *
  60. * [url=home.php?mod=space&uid=247401]@brief[/url]   This function handles RTC Handler.
  61. *
  62. * [url=home.php?mod=space&uid=266161]@return[/url]  none
  63. */
  64. void RTC_IRQHandler(void)
  65. {
  66.     if (CalibrationFlag)
  67.     {
  68.         if (RTC_GetITStatus(RTC_IT_SEC) != RESET) /* Seconds interrupt */
  69.         {
  70.             RTC_Get();
  71.         }
  72.         if (RTC_GetITStatus(RTC_IT_ALR) != RESET) /* Alarm clock interrupt */
  73.         {
  74.             RTC_ClearITPendingBit(RTC_IT_ALR);
  75.             RTC_Get();
  76.         }
  77.         printf("year/month/day/week/hour/min/sec:\r\n");
  78.         printf("%d-%d-%d  %d  %d:%d:%d\r\n", calendar.w_year, calendar.w_month, calendar.w_date,
  79.                calendar.week, calendar.hour, calendar.min, calendar.sec);
  80.     }
  81.     else
  82.     {
  83.         if (RTC_GetITStatus(RTC_IT_SEC) != RESET)
  84.         {
  85.             if (Calibration_STA == 0)
  86.             {
  87.                 CalibrationTIMCir = 0;
  88.                 TIM1->CNT = 0;
  89.                 TIM1->CTLR1 |= TIM_CEN;
  90.                 Calibration_STA = 1;
  91.             }
  92.             else if (Calibration_STA == 1)
  93.             {

  94.                 TIM1->CTLR1 &= ~TIM_CEN;
  95.                 CalibrationVal = TIM1->CNT + CalibrationTIMCir * 65536;
  96.                 CalibrationVal < 1000000 ? printf("Calibration val = %ld\n", 1000000 - CalibrationVal) : printf("Calibration val = %ld\n", CalibrationVal - 1000000);
  97.                 ;

  98.                 TIM1->CNT = 0;
  99.                 Calibration_STA = 0;
  100.                 CalibrationFlag = 1;
  101.             }
  102.         }
  103.     }
  104.     RTC_ClearITPendingBit(RTC_IT_SEC | RTC_IT_OW);
  105.     RTC_WaitForLastTask();
  106. }

  107. void TIM1_UP_IRQHandler()
  108. {
  109.     if (!CalibrationFlag)
  110.         CalibrationTIMCir += 1;
  111.     TIM_ClearITPendingBit(TIM1, TIM_IT_Update);
  112. }


  113. /*********************************************************************
  114. * @fn      TIM1_OutCompare_Init
  115. *
  116. * @brief   Initializes TIM1 output compare.
  117. *
  118. * @param   arr - the period value.
  119. *          psc - the prescaler value.
  120. *          ccp - the pulse value.
  121. *
  122. * @return  none
  123. */
  124. void TIM1_Base_Init(u16 arr, u16 psc)
  125. {
  126.     TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure = {0};
  127.     NVIC_InitTypeDef NVIC_InitStructure = {0};

  128.     RCC_PB2PeriphClockCmd(RCC_PB2Periph_TIM1, ENABLE);

  129.     TIM_TimeBaseInitStructure.TIM_Period = arr;
  130.     TIM_TimeBaseInitStructure.TIM_Prescaler = psc;
  131.     TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
  132.     TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
  133.     TIM_TimeBaseInit(TIM1, &TIM_TimeBaseInitStructure);

  134.     NVIC_InitStructure.NVIC_IRQChannel = TIM1_UP_IRQn;
  135.     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  136.     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
  137.     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
  138.     NVIC_Init(&NVIC_InitStructure);

  139.     TIM_ITConfig(TIM1, TIM_IT_Update, ENABLE);
  140.     TIM1->CNT = 0;
  141. }

  142. /*********************************************************************
  143. * @fn      RTC_NVIC_Config
  144. *
  145. * @brief   Initializes RTC Int.
  146. *
  147. * @return  none
  148. */
  149. static void RTC_NVIC_Config(void)
  150. {
  151.     NVIC_InitTypeDef NVIC_InitStructure = {0};
  152.     NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;
  153.     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  154.     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
  155.     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  156.     NVIC_Init(&NVIC_InitStructure);
  157. }

  158. /*********************************************************************
  159. * @fn      RTC_Init
  160. *
  161. * @brief   Initializes RTC collection.
  162. *
  163. * @return  1 - Init Fail
  164. *          0 - Init Success
  165. */
  166. u8 RTC_Init(void)
  167. {
  168.     u8 temp = 0;
  169.     RCC_PB1PeriphClockCmd(RCC_PB1Periph_PWR | RCC_PB1Periph_BKP, ENABLE);
  170.     PWR_BackupAccessCmd(ENABLE);

  171.     /* Is it the first configuration */
  172.     if (BKP_ReadBackupRegister(BKP_DR1) != 0xA1A1)//从指定的后备寄存器中读出数据:读出了与写入的指定数据不相乎
  173.     {
  174.         BKP_DeInit();


  175. #if(Mode==RTC_LSE)
  176.         RCC_LSEConfig(RCC_LSE_ON);
  177.         while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET && temp < 250)
  178.         {
  179.             temp++;
  180.             Delay_Ms(20);
  181.         }
  182.         if (temp >= 250)
  183.             return 1;
  184.         RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
  185. #elif(Mode==RTC_LSI)
  186.         RCC_LSICmd(ENABLE);
  187.         while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET && temp < 250)
  188.         {
  189.             temp++;
  190.             Delay_Ms(20);
  191.         }
  192.         if (temp >= 250)
  193.             return 1;
  194.         RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);
  195. #elif(Mode==RTC_HSE_128)
  196.         RCC_RTCCLKConfig(RCC_RTCCLKSource_HSE_Div128);
  197. #endif


  198.         RCC_RTCCLKCmd(ENABLE);
  199.         RTC_WaitForLastTask();
  200.         RTC_WaitForSynchro();
  201.         RTC_ITConfig(RTC_IT_SEC, ENABLE);
  202.         RTC_WaitForLastTask();
  203.         RTC_EnterConfigMode();
  204.         /*Deliberately speeding up the clock*/
  205. #if(Mode==RTC_HSE_128)
  206.         RTC_SetPrescaler(62500);
  207. #elif(Mode==RTC_LSE||Mode==RTC_LSI)
  208.         RTC_SetPrescaler(32766);
  209. #endif

  210.         RTC_WaitForLastTask();
  211.         RTC_Set(2019, 10, 8, 13, 58, 55); /* Setup Time */
  212.         RTC_ExitConfigMode();
  213.         BKP_WriteBackupRegister(BKP_DR1, 0XA1A1);
  214.     }
  215.     else
  216.     {
  217.         RTC_WaitForSynchro(); //等待最近一次对RTC寄存器的写操作完成
  218.         RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能RTC秒中断
  219.         RTC_WaitForLastTask();//等待最近一次对RTC寄存器的写操作完成
  220.     }

  221.     RTC_NVIC_Config();
  222.     RTC_Get();

  223.     return 0;
  224. }

  225. /*********************************************************************
  226. * @fn      Is_Leap_Year
  227. *
  228. * @brief   Judging whether it is a leap year.
  229. *
  230. * @param   year
  231. *
  232. * @return  1 - Yes
  233. *          0 - No
  234. */
  235. u8 Is_Leap_Year(u16 year)
  236. {
  237.     if (year % 4 == 0)
  238.     {
  239.         if (year % 100 == 0)
  240.         {
  241.             if (year % 400 == 0)
  242.                 return 1;
  243.             else
  244.                 return 0;
  245.         }
  246.         else
  247.             return 1;
  248.     }
  249.     else
  250.         return 0;
  251. }

  252. /*********************************************************************
  253. * @fn      RTC_Set
  254. *
  255. * @brief   Set Time.
  256. *
  257. * @param   Struct of _calendar_obj
  258. *
  259. * @return  1 - error
  260. *          0 - success
  261. */
  262. u8 RTC_Set(u16 syear, u8 smon, u8 sday, u8 hour, u8 min, u8 sec)
  263. {
  264.     u16 t;
  265.     u32 seccount = 0;
  266.     if (syear < 1970 || syear > 2099)
  267.         return 1;
  268.     for (t = 1970; t < syear; t++)
  269.     {
  270.         if (Is_Leap_Year(t))
  271.             seccount += 31622400;
  272.         else
  273.             seccount += 31536000;
  274.     }
  275.     smon -= 1;
  276.     for (t = 0; t < smon; t++)
  277.     {
  278.         seccount += (u32)mon_table[t] * 86400;
  279.         if (Is_Leap_Year(syear) && t == 1)
  280.             seccount += 86400;
  281.     }
  282.     seccount += (u32)(sday - 1) * 86400;
  283.     seccount += (u32)hour * 3600;
  284.     seccount += (u32)min * 60;
  285.     seccount += sec;

  286.     RCC_PB1PeriphClockCmd(RCC_PB1Periph_PWR | RCC_PB1Periph_BKP, ENABLE);
  287.     PWR_BackupAccessCmd(ENABLE);
  288.     RTC_SetCounter(seccount);
  289.     RTC_WaitForLastTask();
  290.     return 0;
  291. }

  292. /*********************************************************************
  293. * @fn      RTC_Alarm_Set
  294. *
  295. * @brief   Set Alarm Time.
  296. *
  297. * @param   Struct of _calendar_obj
  298. *
  299. * @return  1 - error
  300. *          0 - success
  301. */
  302. u8 RTC_Alarm_Set(u16 syear, u8 smon, u8 sday, u8 hour, u8 min, u8 sec)
  303. {
  304.     u16 t;
  305.     u32 seccount = 0;
  306.     if (syear < 1970 || syear > 2099)
  307.         return 1;
  308.     for (t = 1970; t < syear; t++)
  309.     {
  310.         if (Is_Leap_Year(t))
  311.             seccount += 31622400;
  312.         else
  313.             seccount += 31536000;
  314.     }
  315.     smon -= 1;
  316.     for (t = 0; t < smon; t++)
  317.     {
  318.         seccount += (u32)mon_table[t] * 86400;
  319.         if (Is_Leap_Year(syear) && t == 1)
  320.             seccount += 86400;
  321.     }
  322.     seccount += (u32)(sday - 1) * 86400;
  323.     seccount += (u32)hour * 3600;
  324.     seccount += (u32)min * 60;
  325.     seccount += sec;

  326.     RCC_PB1PeriphClockCmd(RCC_PB1Periph_PWR | RCC_PB1Periph_BKP, ENABLE);
  327.     PWR_BackupAccessCmd(ENABLE);
  328.     RTC_SetAlarm(seccount);
  329.     RTC_WaitForLastTask();

  330.     return 0;
  331. }

  332. /*********************************************************************
  333. * @fn      RTC_Get
  334. *
  335. * @brief   Get current time.
  336. *
  337. * @return  1 - error
  338. *          0 - success
  339. */
  340. u8 RTC_Get(void)
  341. {
  342.     static u16 daycnt = 0;
  343.     u32 timecount = 0;
  344.     u32 temp = 0;
  345.     u16 temp1 = 0;
  346.     timecount = RTC_GetCounter();
  347.     temp = timecount / 86400;
  348.     if (daycnt != temp)
  349.     {
  350.         daycnt = temp;
  351.         temp1 = 1970;
  352.         while (temp >= 365)
  353.         {
  354.             if (Is_Leap_Year(temp1))
  355.             {
  356.                 if (temp >= 366)
  357.                     temp -= 366;
  358.                 else
  359.                 {
  360.                     temp1++;
  361.                     break;
  362.                 }
  363.             }
  364.             else
  365.                 temp -= 365;
  366.             temp1++;
  367.         }
  368.         calendar.w_year = temp1;
  369.         temp1 = 0;
  370.         while (temp >= 28)
  371.         {
  372.             if (Is_Leap_Year(calendar.w_year) && temp1 == 1)
  373.             {
  374.                 if (temp >= 29)
  375.                     temp -= 29;
  376.                 else
  377.                     break;
  378.             }
  379.             else
  380.             {
  381.                 if (temp >= mon_table[temp1])
  382.                     temp -= mon_table[temp1];
  383.                 else
  384.                     break;
  385.             }
  386.             temp1++;
  387.         }
  388.         calendar.w_month = temp1 + 1;
  389.         calendar.w_date = temp + 1;
  390.     }
  391.     temp = timecount % 86400;
  392.     calendar.hour = temp / 3600;
  393.     calendar.min = (temp % 3600) / 60;
  394.     calendar.sec = (temp % 3600) % 60;
  395.     calendar.week = RTC_Get_Week(calendar.w_year, calendar.w_month, calendar.w_date);
  396.     return 0;
  397. }

  398. /*********************************************************************
  399. * @fn      RTC_Get_Week
  400. *
  401. * @brief   Get the current day of the week.
  402. *
  403. * @param   year/month/day
  404. *
  405. * @return  week
  406. */
  407. u8 RTC_Get_Week(u16 year, u8 month, u8 day)
  408. {
  409.     u16 temp2;
  410.     u8 yearH, yearL;

  411.     yearH = year / 100;
  412.     yearL = year % 100;
  413.     if (yearH > 19)
  414.         yearL += 100;
  415.     temp2 = yearL + yearL / 4;
  416.     temp2 = temp2 % 7;
  417.     temp2 = temp2 + day + table_week[month - 1];
  418.     if (yearL % 4 == 0 && month < 3)
  419.         temp2--;
  420.     return (temp2 % 7);
  421. }

  422. /*********************************************************************
  423. * @fn      RTC_Calibration
  424. *
  425. * @brief   The function `RTC_Calibration` calculates a calibration step value based on a given fast seconds per
  426. *        30 days value.
  427. *
  428. * @param   FastSecPer30days The `FastSecPer30days` parameter represents the number of fast seconds in a
  429. *        30-day period. This value is used to calculate the calibration step for the RTC (Real-Time Clock)
  430. *        based on the deviation from the ideal timekeeping.
  431. *
  432. * @return  none
  433. */
  434. void RTC_Calibration(uint16_t FastSecPer30days)
  435. {
  436.     float Deviation = 0.0;
  437.     u8 CalibStep = 0;

  438.     Deviation = FastSecPer30days * PPM_PER_SEC;
  439.     Deviation /= PPM_PER_STEP;
  440.     CalibStep = (u8)Deviation;
  441.     if (Deviation >= (CalibStep + 0.5))
  442.         CalibStep += 1;
  443.     if (CalibStep > 127)
  444.         CalibStep = 127;

  445.     BKP_SetRTCCalibrationValue(CalibStep);
  446.     printf("Calibration cab: %d\n", CalibStep);
  447. }

  448. /*********************************************************************
  449. * @fn      main
  450. *
  451. * @brief   Main program.
  452. *
  453. * @return  none
  454. */
  455. int main(void)
  456. {
  457.     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
  458.     SystemCoreClockUpdate();
  459.     Delay_Init();

  460.     USART_Printf_Init(115200);
  461.     printf("SystemClk:%d\r\n", SystemCoreClock);
  462.     printf("ChipID:%08x\r\n", DBGMCU_GetCHIPID());
  463.     printf("RTC Calibration Test\r\n");
  464.     /* The code is initializing the TIM1 timer and the RTC (Real-Time Clock) module. */
  465.     TIM1_Base_Init(65535, SystemCoreClock / 1000000 - 1);
  466.     RTC_Init();

  467.     while (CalibrationFlag == 0)
  468.         ;
  469.    
  470.     printf("1CalibrationVal:%d\r\n", CalibrationVal);
  471.     if (CalibrationVal < 1000000)
  472.     {
  473.         printf("CalibrationVal:%d\r\n", CalibrationVal);
  474.         uint16_t FastSecPer30days = (1000000 - CalibrationVal)*3600*24*30/1000000;
  475.         printf("FastSecPer30days:%d\r\n", FastSecPer30days);
  476.         RTC_Calibration(FastSecPer30days);
  477.     }
  478.     while (1)
  479.     {
  480.         Delay_Ms(100);
  481.     }
  482. }
注意EVT例程在MCU复位后时间会重置,重新从初始化配置时间开始运行,若不想MCU复位之后时间重置,可进行如下操作:在RTC初始化时,会有一个对后备数据寄存器的写入,后备数据寄存器可在VDD掉电后靠VBAT电源保存数据。可在RTC初始化时候加一个对后备数据寄存器读出数据的判断。若读出数据与写入数据一致,则时间不重置,继续RTC计数,若不一致,则重置重新开始计数。具体代码见上,如下图为RTC继续计数打印结果:
2196667a65755dba5.png

  

CH32L103 RTC.zip

720.28 KB, 下载次数: 2

tpgf 发表于 2024-7-1 09:17 | 显示全部楼层
其精度就是2的20次方分之一是吗
磨砂 发表于 2024-7-1 10:27 | 显示全部楼层
rtc时钟能达到的精度是多少呢
观海 发表于 2024-7-1 11:31 | 显示全部楼层
预分频寄存器的位数越多  精度就越高是吗
guanjiaer 发表于 2024-7-1 22:52 | 显示全部楼层
长时间运行的时候如何进行时间上的修正呢
晓伍 发表于 2024-7-1 23:54 | 显示全部楼层
rtc时钟的时钟源只能是低速时钟吗?
您需要登录后才可以回帖 登录 | 注册

本版积分规则

17

主题

39

帖子

1

粉丝
快速回复 在线客服 返回列表 返回顶部