[其他ST产品] stm32之实时时钟RTC(掉电计时保持、秒中断、闹钟中断、溢出中断)

[复制链接]
2552|10
 楼主| 梅花香自123 发表于 2022-12-24 15:23 | 显示全部楼层 |阅读模式
stm32系列产品普遍都有实时时钟RTC模块,它提供一个掉电保持计时功能,掉电后由后备供电区域供电。除了提供时间和日期之外,还可以设置闹钟提醒,且可以在待机模式下设置闹钟唤醒系统。在一些小容量、中容量产品中,只有一个32位的计数寄存器,如果该计数寄存器自增1周期设置为1s,那么软件可以根据该计数寄存器的值算出当前的日期和时、分、秒。在一些大容量的产品中,年、月、日、时、分、秒都是独立的寄存器,可直接读出需要的值。
RTC简介
实时时钟是一个独立的定时器。RTC模块拥有一组连续计数的计数器,在相应软件配置下,可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。

RTC模块和时钟配置系统(RCC_BDCR寄存器)处于后备区域,即在系统复位或从待机模式唤醒后,RTC的设置和时间维持不变。

 楼主| 梅花香自123 发表于 2022-12-24 15:24 | 显示全部楼层
RTC特性
     ● 可编程的预分频系数:分频系数最高为220220

     ● 32位的可编程计数器,可用于较长时间段的测量。(若最小单位为秒:232232=4,294,967,295秒=49,710天  大概136年。)

     ● 2个分离的时钟:用于APB1接口的PCLK1和RTC时钟(RTC时钟的频率必须小于PCLK1时钟频率的四分之一以上)。

     ● 可以选择以下三种RTC的时钟源:

             ─ HSE时钟除以128;

             ─ LSE振荡器时钟;(常用的是外部低速,稳定精准,重要的是VDD掉电后可有后备供电区域给它供电)

             ─ LSI振荡器时钟。

     ● 2个独立的复位类型:

             ─ APB1接口由系统复位;

             ─ RTC核心(预分频器、闹钟、计数器和分频器)只能由后备域复位。(可导致后备区域复位:侵入事件、软件复位、VBAT掉电)

     ● 3个专门的可屏蔽中断:

            ─ 闹钟中断,用来产生一个软件可编程的闹钟中断。

            ─ 秒中断,用来产生一个可编程的周期性中断信号(最长可达1秒)。

            ─ 溢出中断,指示内部可编程计数器溢出并回转为0的状态。
 楼主| 梅花香自123 发表于 2022-12-24 15:25 | 显示全部楼层
RTC功能框图

4343863a6a92a2b3af.png
 楼主| 梅花香自123 发表于 2022-12-24 15:26 | 显示全部楼层
从左上角开始到右下角看图理解,系统通过APB1总线对后备区域的RTC进行通信,那三斜杠的意思是可断开,因为在系统掉电的时候RTC是独立的,只有在系统运行时才有可能会相通。RTCCLK(RTC时钟输入)必须小于PCLK1(低速AHB时钟)的三分之一以上。

RTC_PRL(预分频装载寄存器)的值决定TR_CLK脉冲产生的周期,RTC_DIV(预分频器余数寄存器)可读不可写,当RTCCLK的一个上升沿到来,RTC_DIV的值减1,减到0后硬件重载为RTC_PRL的值同时产生一个TR_CLK脉冲,一个TR_CLK脉冲的到来会使RTC_CNT(计数器寄存器)的值加1,同时产生一个RTC_Second中断(由软件配置是否使能,“秒中断”并不一定是一秒触发一次,具体是根据RTC时钟和RTC_PRL的值决定)。
 楼主| 梅花香自123 发表于 2022-12-24 15:28 | 显示全部楼层
当RTC_CNT的值溢出后从0开始,并产生一个溢出中断(由软件配置是否使能)。当RTC_CNT等于RTC_CNTRTC_ALR(闹钟寄存器)时,产生一个闹钟中断(由软件配置是否使能,可在用在系统待机模式下唤醒系统)。

RTC_CR(RTC控制寄存器)不在后备区域,所以它的数据会随系统复位而复位,也就是说当系统下电是,秒中断、溢出中断、闹钟中断就不存在了,在下一次系统上电时需要重新初始化。图中“退出待机模式”有两种方法:RTC闹钟、WKUP引脚。

这里说一个图上没有的知识点,PC.13引脚也就是侵入检测引脚,它可以用来输出RTC闹钟脉冲、RTC秒脉冲或者是钟频率为 RTC 时钟除以 64的脉冲,后者在系统下电的情况下无法输出。
 楼主| 梅花香自123 发表于 2022-12-24 15:28 | 显示全部楼层
寄存器的配置不作详细讲解,下面是利用标准库函数进行开发的RTC应用。
代码设计
  1. #include "stm32f10x.h"
  2. #include "stdio.h"
  3. #include "string.h"

  4. static void RTC_Configuration(void);
  5. static void NVIC_Configuration(void);
  6. static void USART1_Config(void);
  7. static void Delay(__IO u32 nCount);
  8. static char *USART_GetString(char *s);

  9. char strbuf[50];
  10. unsigned char HH,MM,SS;
  11. static char cmd_SetTime[]="AT+SETTIME";//设置时间的指令
  12. static char cmd_SetAlarm[]="AT+SETALARM";//设置闹钟的指令

  13. int main(void)
  14. {       
  15.     USART1_Config();//串口1输出调试信息
  16.        
  17.     RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);//使能电源管理时钟,后备寄存器模块时钟
  18.     PWR_BackupAccessCmd(ENABLE);//使能RTC和后备寄存器的访问
  19.        
  20.     if(BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5)//如果在后备数据寄存器1读取到的值不是0xA5A5 说明后备寄存器(RTC等)未初始化
  21.    {
  22.         RTC_Configuration();  //配置RTC,时钟选用LSE(外部低速时钟),RTC计数器1s自增1
  23.         RTC_WaitForLastTask();//等待RTC操作完成
  24.         RTC_SetCounter(0);    //首次时间设置为 00:00:00
  25.         RTC_WaitForLastTask();//等待RTC操作完成
  26.         BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);//后备数据寄存器1写入0xA5A5,标志RTC已初始化
  27.    }
  28.   else //系统复位,而后备寄存器并没有被复位时,无需再初始化RTC
  29.   {
  30.         if (RCC_GetFlagStatus(RCC_FLAG_PORRST) != RESET){
  31.                 printf("POR/PDR 复位\r\n"); //VDD掉电上电
  32.         }else if (RCC_GetFlagStatus(RCC_FLAG_PINRST) != RESET){
  33.                 printf("RESET引脚复位\r\n");
  34.         }else if (RCC_GetFlagStatus(RCC_FLAG_SFTRST) != RESET){
  35.                 printf("软件复位\r\n");
  36.         }else if (RCC_GetFlagStatus(RCC_FLAG_IWDGRST) != RESET){
  37.                 printf("独立看门狗复位\r\n");
  38.         }else if (RCC_GetFlagStatus(RCC_FLAG_WWDGRST) != RESET){
  39.                 printf("窗口看门狗复位\r\n");
  40.         }else if (RCC_GetFlagStatus(RCC_FLAG_LPWRRST) != RESET){
  41.                 printf("低功耗复位\r\n");
  42.         }
  43.         //以上复位,后备寄存器的数据仍然保持
  44.         printf("不需要配置RTC.\r\n");
  45.         RTC_WaitForSynchro();//等待 RTC 寄存器与RTC的APB时钟同步
  46.   }
  47.        
  48.         NVIC_Configuration();//配置RTC中断优先级
  49.        
  50. //以下三个中断根据需要开启,三者触发时都是进入RTC_IRQHandler中断函数,通过RTC_GetITStatus判断具体是哪个中断触发
  51. #if 1        //(可选)
  52.   RTC_WaitForLastTask();//等待RTC操作完成,下同
  53.   RTC_ITConfig(RTC_IT_SEC, ENABLE);//秒中断使能秒,用来产生一个可编程的周期性中断信号(最长可达1秒)。
  54.   RTC_WaitForLastTask();
  55. #endif
  56.        
  57. #if 1        //(可选)
  58.   RTC_WaitForLastTask();
  59.   RTC_ITConfig(RTC_IT_ALR, ENABLE);//闹钟中断使能,用来产生一个软件可编程的闹钟中断。
  60.   RTC_WaitForLastTask();
  61. #endif
  62.        
  63. #if 1        //(可选)
  64.   RTC_WaitForLastTask();
  65.   RTC_ITConfig(RTC_IT_OW, ENABLE);//溢出中断使能,指示内部可编程计数器溢出并回转为0的状态。
  66.   RTC_WaitForLastTask();
  67. #endif


  68. #if 1 //(可选)
  69. /*BKP_RTCOutputSource_None         侵入检测管脚(PC.13)上无 RTC 输出
  70.   BKP_RTCOutputSource_CalibClock     侵入检测管脚(PC.13)上输出,其时钟频率为 RTC 时钟除以 64
  71.   BKP_RTCOutputSource_Alarm         侵入检测管脚(PC.13)上输出 RTC 闹钟脉冲
  72.   BKP_RTCOutputSource_Second    侵入检测管脚(PC.13)上输出 RTC 秒脉冲*/
  73.   BKP_TamperPinCmd(DISABLE);
  74.   BKP_RTCOutputConfig(BKP_RTCOutputSource_Second);
  75. #endif       
  76.        
  77.     //清除复位标志
  78.     RCC_ClearFlag();
  79.        
  80.     while(1)
  81.     {
  82.         if(USART_GetString(strbuf)!=NULL){
  83.             if(strncmp(strbuf,cmd_SetTime,strlen(cmd_SetTime))==0){
  84.                 HH = (strbuf[strlen(cmd_SetTime)+1]-'0')*10 +(strbuf[strlen(cmd_SetTime)+2]-'0');
  85.                 MM = (strbuf[strlen(cmd_SetTime)+4]-'0')*10 +(strbuf[strlen(cmd_SetTime)+5]-'0');
  86.                 SS = (strbuf[strlen(cmd_SetTime)+7]-'0')*10 +(strbuf[strlen(cmd_SetTime)+8]-'0');
  87.                 printf("设置RTC时间为 %d:%d:%d\r\n",HH,MM,SS);
  88.                 RTC_WaitForLastTask();
  89.                 RTC_SetCounter(HH*3600+MM*60+SS);//设置RTC当前计数寄存器的值
  90.                 RTC_WaitForLastTask();
  91.                 printf("设置RTC时间成功!\r\n");
  92.             }
  93.                        
  94.         if(strncmp(strbuf,cmd_SetAlarm,strlen(cmd_SetAlarm))==0){
  95.                 HH = (strbuf[strlen(cmd_SetAlarm)+1]-'0')*10 +(strbuf[strlen(cmd_SetAlarm)+2]-'0');
  96.                 MM = (strbuf[strlen(cmd_SetAlarm)+4]-'0')*10 +(strbuf[strlen(cmd_SetAlarm)+5]-'0');
  97.                 SS = (strbuf[strlen(cmd_SetAlarm)+7]-'0')*10 +(strbuf[strlen(cmd_SetAlarm)+8]-'0');
  98.                 printf("设置闹钟时间为 %d:%d:%d\r\n",HH,MM,SS);
  99.                 RTC_WaitForLastTask();
  100.                 RTC_SetAlarm(HH*3600+MM*60+SS);//设置RTC闹钟值,记得使能闹钟中断
  101.                 RTC_WaitForLastTask();
  102.                 printf("设置闹钟时间成功!\r\n");
  103.             }
  104.         }
  105.       }
  106. }
  107. void NVIC_Configuration(void)//配置RTC中断优先级
  108. {
  109.   NVIC_InitTypeDef NVIC_InitStructure;

  110.   NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
  111.   NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;
  112.   NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
  113.   NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
  114.   NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  115.   NVIC_Init(&NVIC_InitStructure);
  116. }

  117. void RTC_Configuration(void)
  118. {
  119.   BKP_DeInit();
  120.        
  121.   RCC_LSEConfig(RCC_LSE_ON);

  122.   while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET);//等待LSE(外部低速)时钟稳定

  123.   RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);//选择LSE时钟作为RTC时钟,此外还可以选择:LSI、HSE_Div128

  124.   RCC_RTCCLKCmd(ENABLE);//使能RTC时钟

  125.   RTC_WaitForSynchro();//等待 RTC 寄存器与RTC的APB时钟同步

  126.   RTC_SetPrescaler(32767); //RTC period = RTCCLK/RTC_PR = (32.768 KHz)/(32767+1)=1s  这个决定“秒中断”触发的周期

  127.   RTC_WaitForLastTask();//等待RTC操作完成
  128. }

  129. void USART1_Config(void)
  130. {
  131.         GPIO_InitTypeDef GPIO_InitStructure;
  132.         USART_InitTypeDef USART_InitStructure;
  133.                 NVIC_InitTypeDef NVIC_InitStructure;
  134.        
  135.         //配置串口1(USART1)时钟
  136.         RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA |RCC_APB2Periph_GPIOC, ENABLE);
  137.        
  138.         //配置串口1(USART1 Tx (PA.09))
  139.         GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
  140.         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  141.         GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  142.         GPIO_Init(GPIOA, &GPIO_InitStructure);
  143.   
  144.         //配置串口1 USART1 Rx (PA.10)
  145.         GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
  146.         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  147.         GPIO_Init(GPIOA, &GPIO_InitStructure);
  148.        
  149.          GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
  150.         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  151.         GPIO_Init(GPIOC, &GPIO_InitStructure);
  152.        
  153.         //串口1模式(USART1 mode)配置
  154.         USART_InitStructure.USART_BaudRate = 9600;//一般设置为9600;
  155.         USART_InitStructure.USART_WordLength = USART_WordLength_8b;
  156.         USART_InitStructure.USART_StopBits = USART_StopBits_1;
  157.         USART_InitStructure.USART_Parity = USART_Parity_No ;
  158.         USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
  159.         USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
  160.         USART_Init(USART1, &USART_InitStructure);
  161.        
  162.         USART_Cmd(USART1, ENABLE); //使能串口
  163.         USART_ClearFlag(USART1,USART_FLAG_TC);              
  164. }

  165. int fputc(int ch, FILE *f)//重写标准库的fputc函数
  166. {
  167.         //将Printf内容发往串口
  168.         while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)!=SET);
  169.         USART_SendData(USART1, (unsigned char) ch);       
  170.         return (ch);
  171. }

  172. char *USART_GetString(char *s)//从串口阻塞等待一个字符串,遇到0x0d或者0x0a结束
  173. {
  174.         char code;
  175.         char *str = s;
  176.         if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)==RESET){
  177.             *s=0;
  178.             return NULL;
  179.         }
  180.        
  181.         while(1){
  182.             if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)!=RESET){
  183.                 USART_ClearFlag(USART1,USART_FLAG_RXNE);
  184.                 code=USART_ReceiveData(USART1);
  185.                 if(code == 0x0D||code ==0x0A){
  186.                         *str=0;
  187.                         break;
  188.                 }else{
  189.                         *str++ = code;
  190.                 }
  191.             }
  192.         }
  193.         return s;
  194. }

  195. void Delay(__IO u32 nCount)         //简单的延时函数
  196. {
  197.         for(; nCount != 0; nCount--);
  198. }
 楼主| 梅花香自123 发表于 2022-12-24 15:32 | 显示全部楼层
在stm32f10x_it.c加入:
  1. unsigned int systick;
  2. static unsigned char THH,TMM,TSS;
  3. void RTC_IRQHandler(void)
  4. {
  5.   if (RTC_GetITStatus(RTC_IT_SEC) != RESET) //秒中断
  6.   {
  7.         RTC_ClearITPendingBit(RTC_IT_SEC);
  8.         systick = RTC_GetCounter();
  9.         RTC_WaitForLastTask();
  10.                
  11.         systick %= 86400;   //24小时 = 86400s
  12.         THH = systick / 3600;
  13.         TMM = (systick % 3600) / 60;
  14.         TSS = (systick % 3600) % 60;
  15.         printf("当前系统时间:%.2d:%.2d:%.2d\r\n",THH,TMM,TSS);
  16.   }

  17.   if (RTC_GetITStatus(RTC_IT_ALR) != RESET) //闹钟中断
  18.   {
  19.         RTC_ClearITPendingBit(RTC_IT_ALR);
  20.         RTC_WaitForLastTask();
  21.                
  22.         printf("闹钟中断触发.\r\n");
  23.   }
  24.        
  25.   if (RTC_GetITStatus(RTC_IT_OW) != RESET) //溢出中断
  26.   {
  27.         RTC_ClearITPendingBit(RTC_IT_OW);
  28.         RTC_WaitForLastTask();
  29.                
  30.         printf("溢出中断触发.\r\n");
  31.   }
  32. }
 楼主| 梅花香自123 发表于 2022-12-24 15:33 | 显示全部楼层
代码已经加入很多注释以便理解,这里不做讲解。编译编译后,串口线连接到电脑,检查VBAT引脚是否接了电池,没有的话接到电源上。
 楼主| 梅花香自123 发表于 2022-12-24 15:33 | 显示全部楼层
在串口助手上操作:
758363a6ab5db6d41.png
 楼主| 梅花香自123 发表于 2022-12-24 15:35 | 显示全部楼层
发送两条命令进行调试,勾选发送新行:

设置时间:AT+SETTIME 09:50:10

设置闹钟时间:AT+SETALARM 09:50:15

如果想用示波器监测PC.13输出的脉冲,一定要排除电其他连在该引脚的器件,很多板子都会在该引脚接上一个led,这样会影响示波器检测到的波形。
 楼主| 梅花香自123 发表于 2022-12-24 15:36 | 显示全部楼层
后备供电区域功耗很低,一个纽扣电池可以用很久,而且当VDD上电时会自动切换为VDD供电。由于晶振的性能会受温度的影响,当环境温度改变时会影响计时的准确性,可通过设置RTC 时钟校准值进行补偿。这个校准值需要随温度的变化配置不同的值,所以要进行温度标定实验,统计数据得出两者的关系。在板子上需要加入温度传感器以获取当前温度,再者设置相应的校准值。

代码还有很多不足,需要完善,这里重点是RTC功能的使用方法。如若有误,还望指出,谢谢。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

102

主题

1216

帖子

0

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