[应用相关] RTC实时时钟(秒中断)

[复制链接]
3833|8
 楼主| antusheng 发表于 2017-10-18 11:06 | 显示全部楼层 |阅读模式
STM32 的实时时钟( RTC)是一个独立的定时器。 STM32 的 RTC 模块拥有一组连续计数的计数器,在相应软件配置下,可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。
由于时钟只需要配置一次,下次开机不需要重新配置(开发板有电池的情况下),所以需要用到备份区域(BKP)来标记是否配置过时钟
简单介绍BKP:备份寄存器是 42 个 16 位的寄存器( Mini 开发板就是大容量的),可用来存储 84 个字节的用户应用程序数据。他们处在备份域里, 当 VDD 电源被切断,他们仍然由 VBAT 维持供电。即使系统在待机模式下被唤醒,或系统复位或电源复位时,他们也不会被复位。此外, BKP 控制寄存器用来管理侵入检测和 RTC 校准功能。
简单说一下我对时钟工作原理的理解:一个32位的计数器,从0向上计数的话,假设每加一就是1秒,那么一个32位的计数器跑到溢出需要100多年。。已经很长了,这里时钟自带一个秒中断,当每加一的时候就会触发一次秒中断,我们通过往秒中断里写更新时间的函数来达到时间同步的效果
由于rtc.c里函数很多。。我就贴一下说几个比较重要的吧。。
  1. #include "rtc.h"
  2. _calendar_obj calendar;//时钟结构体
  3. const u8 mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31};
  4. const u8  table_week[12]={0,3,3,6,1,4,6,2,5,0,3,5}; //月修正数据表
  5. u8 Is_LeapYear(u16 year)
  6. {
  7.         return (year%4==0&&year%100!=0)||year%400==0;
  8. }

  9. //获得现在是星期几
  10. //功能描述:输入公历日期得到星期(只允许1901-2099年)
  11. //输入参数:公历年月日
  12. //返回值:星期号
  13. u8 RTC_Get_Week(u16 year,u8 month,u8 day)
  14. {
  15.         u16 temp2;
  16.         u8 yearH,yearL;
  17.        
  18.         yearH=year/100;        yearL=year%100;
  19.         // 如果为21世纪,年份数加100  
  20.         if (yearH>19)yearL+=100;
  21.         // 所过闰年数只算1900年之后的  
  22.         temp2=yearL+yearL/4;
  23.         temp2=temp2%7;
  24.         temp2=temp2+day+table_week[month-1];
  25.         if (yearL%4==0&&month<3)temp2--;
  26.         return(temp2%7);
  27. }
  28. static void RTC_NVIC_Config(void)
  29. {       
  30.   NVIC_InitTypeDef NVIC_InitStructure;
  31.         NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;                //RTC全局中断
  32.         NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;        //先占优先级1位,从优先级3位
  33.         NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;        //先占优先级0位,从优先级4位
  34.         NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                //使能该通道中断
  35.         NVIC_Init(&NVIC_InitStructure);                //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
  36. }
  37. //得到当前的时间,结果保存在calendar结构体里面
  38. //返回值:0,成功;其他:错误代码.
  39. u8 RTC_Get(void)
  40. {
  41.         static u16 daycnt=0;
  42.         u32 timecount=RTC_GetCounter();
  43.         u32 daynum=timecount/86400;
  44.         u16 tem=0;
  45.         if(daycnt!=daynum)//大于1天
  46.         {
  47.                         daycnt=daynum;
  48.                         tem=1970;
  49.                         while(daynum>=365)
  50.                         {
  51.                                 if(Is_LeapYear(tem))
  52.                                 {
  53.                                         if(daynum>=366)daynum-=366;
  54.                                         else break;
  55.                                 }
  56.                                 else daynum-=365;
  57.                                 tem++;
  58.                          }
  59.                         calendar.w_year=tem;//年
  60.                         tem=0;
  61.                         while(daynum>=28)
  62.                         {
  63.                                 if(Is_LeapYear(calendar.w_year)&&tem==1)
  64.                                 {
  65.                                         if(daynum>=29)daynum-=29;
  66.                                         else break;
  67.                                 }
  68.                                 else
  69.                                 {
  70.                                         if(daynum>=mon_table[tem])daynum-=mon_table[tem];
  71.                                         else break;
  72.                                 }
  73.                                 tem++;
  74.                         }
  75.                         calendar.w_month=tem+1;//月
  76.                         calendar.w_date=daynum+1;//日
  77.         }
  78.           daynum=timecount%86400;
  79.                 calendar.hour=daynum/3600;//时
  80.                 calendar.min=(daynum%3600)/60;//分
  81.                 calendar.sec=(daynum%3600)%60;//秒
  82.                 calendar.week=RTC_Get_Week(calendar.w_year,calendar.w_month,calendar.w_date);
  83.     return 0;
  84. }


  85. //设置时钟
  86. //把输入的时钟转换为秒钟
  87. //以 1970 年 1 月 1 日为基准
  88. //1970~2099 年为合法年份
  89. //返回值:0,成功;其他:错误代码.
  90. //平年的月份日期表
  91. //year,mon,day,hour,min,sec:年月日时分秒
  92. //返回值:设置结果。 0,成功; 1,失败。
  93. u8 RTC_Set(u16 year,u8 mon,u8 day,u8 hour,u8 min,u8 sec)
  94. {
  95.         u16 i;u32 seccnt=0;
  96.         if(year<1970||year>2099)return 1;
  97.         for(i=1970;i<year;i++)
  98.         {
  99.                 if(Is_LeapYear(i))seccnt+=31622400;
  100.                 else seccnt+=31536000;
  101.         }
  102.         mon-=1;
  103.         for(i=0;i<mon;i++)
  104.         {
  105.                 seccnt+=(u32)mon_table[i]*86400;
  106.                 if(Is_LeapYear(year)&&i==1)
  107.                         seccnt+=86400;
  108.         }
  109.         seccnt+=(u32)(day-1)*86400;
  110.         seccnt+=(u32)hour*3600;
  111.         seccnt+=(u32)min*60;
  112.         seccnt+=(u32)sec;
  113.         //使能 PWR 和 BKP 外设时钟
  114.         RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR|RCC_APB1Periph_BKP, ENABLE);  
  115.   PWR_BackupAccessCmd(ENABLE); //使能 RTC 和后备寄存器访问
  116.   RTC_SetCounter(seccnt);  //设置 RTC 计数器的值
  117.   RTC_WaitForLastTask();  //等待最近一次对 RTC 寄存器的写操作完成
  118.   return 0;
  119. }
  120. //初始化RTC
  121. u8 RTC_Init(void)
  122. {
  123.         u8 tem=0;
  124.         if(BKP_ReadBackupRegister(BKP_DR1)!=0x5050)//如果没配置过
  125.         {
  126.                 //使能 PWR 和 BKP外设时钟
  127.                 RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR|RCC_APB1Periph_BKP, ENABLE);  
  128.                 PWR_BackupAccessCmd(ENABLE);//使能后备寄存器访问
  129.                 BKP_DeInit();//③复位备份区域
  130.                 RCC_LSEConfig(RCC_LSE_ON);//设置外部低速晶振(LSE)
  131.                 //检查指定的RCC标志位设置与否,等待低速晶振就绪
  132.                 while(RCC_GetFlagStatus(RCC_FLAG_LSERDY)==RESET)
  133.                 {
  134.                         tem++;
  135.                         delay_ms(10);
  136.                 }
  137.                 if(tem>=250)return 1;//初始化时钟失败,晶振有问题
  138.                 RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);//设置RTC时钟
  139.                 RCC_RTCCLKCmd(ENABLE);//使能 RTC 时钟
  140.                 RTC_WaitForLastTask();//等待最近一次对 RTC 寄存器的写操作完成
  141.                 RTC_WaitForSynchro();//等待 RTC 寄存器同步
  142.                 RTC_ITConfig(RTC_IT_SEC,ENABLE);//使能 RTC 秒中断
  143.                 RTC_WaitForLastTask(); //等待最近一次对 RTC 寄存器的写操作完成
  144.     RTC_SetPrescaler(32767);  //设置 RTC 预分频的值
  145.     RTC_WaitForLastTask(); //等待最近一次对 RTC 寄存器的写操作完成
  146.     RTC_Set(2009,12,2,10,0,55); //设置时间
  147.     RTC_ExitConfigMode();  //退出配置模式
  148.                 //向指定的后备寄存器中写入用户程序数据 0x5050
  149.     BKP_WriteBackupRegister(BKP_DR1,0X5050);
  150.         }
  151.         else
  152.         {
  153.                 RTC_WaitForSynchro(); //等待最近一次对 RTC 寄存器的写操作完成
  154.     RTC_ITConfig(RTC_IT_SEC,ENABLE); //使能 RTC 秒中断
  155.     RTC_WaitForLastTask(); //等待最近一次对 RTC 寄存器的写操作完成
  156.         }
  157.         RTC_NVIC_Config(); //RCT 中断分组设置  
  158.   RTC_Get(); //更新时间
  159.   return 0; //ok
  160. }
  161. void RTC_IRQHandler(void)
  162. {
  163.         if(RTC_GetITStatus(RTC_IT_SEC)!=RESET)//秒中断
  164.         {
  165.                 RTC_Get();//更新时间
  166.         }
  167.         if(RTC_GetITStatus(RTC_IT_ALR)!=RESET)//闹钟中断
  168.         {
  169.                 RTC_ClearITPendingBit(RTC_IT_ALR);//清闹钟中断
  170.         }
  171.         RTC_ClearITPendingBit(RTC_IT_SEC|RTC_IT_OW);//清中断
  172.         RTC_WaitForLastTask();
  173. }
我们是以1970年为基准,往后计时的,即当1970年一月一日00点00分00秒时,计数器从0开始计时。。
说一个比较粗心的地方(导致调试了一晚上都没调好),在写RTC_Get()函数(更新时间)的时候不小心将一个16位的变量当成32的用了。。结果是一晚上都忙的热火朝天还没找到是哪错了,早晨来到一直到中午才弄好。。sad

 楼主| antusheng 发表于 2017-10-18 11:07 | 显示全部楼层
rtc.h
  1. #ifndef _RTC_H
  2. #define _RTC_H
  3. #include "sys.h"
  4. #include "delay.h"
  5. #include "usart.h"
  6. typedef struct
  7. {
  8.         vu8 hour;
  9.   vu8 min;
  10.   vu8 sec;
  11.   //公历日月年周
  12.   vu16 w_year;
  13.   vu8 w_month;
  14.         vu8 w_date;
  15.         vu8 week;
  16. }_calendar_obj;
  17. extern _calendar_obj calendar;//日历结构体
  18. //void Disp_Time(u8 x,u8 y,u8 size); //在制定位置开始显示时间
  19. //void Disp_Week(u8 x,u8 y,u8 size,u8 lang);//在指定位置显示星期
  20. u8 RTC_Init(void); //初始化 RTC,返回 0,失败;1,成功;
  21. u8 Is_LeapYear(u16 year); //平年,闰年判断
  22. u8 RTC_Get(void); //更新时间
  23. u8 RTC_Get_Week(u16 year,u8 month,u8 day);
  24. u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec);//设置时间
  25. #endif




 楼主| antusheng 发表于 2017-10-18 11:07 | 显示全部楼层
主函数
  1. #include "led.h"
  2. #include "delay.h"
  3. #include "sys.h"
  4. #include "usart.h"
  5. #include "lcd.h"
  6. #include "usmart.h"
  7. #include "rtc.h"

  8. void init(void)
  9. {
  10.         NVIC_Configuration();
  11.         delay_init();
  12.         uart_init(9600);
  13.         LED_Init();
  14.         LCD_Init();
  15.         usmart_dev.init(72);//初始化SMART组件
  16. }
  17. int main(void)
  18. {
  19.         u8 t;
  20.         init();
  21.   POINT_COLOR=RED;
  22.         while(RTC_Init())                //RTC初始化        ,一定要初始化成功
  23.         {
  24.                 LCD_ShowString(60,130,200,16,16,"RTC ERROR!   ");       
  25.                 delay_ms(800);
  26.                 LCD_ShowString(60,130,200,16,16,"RTC Trying...");       
  27.         }
  28.         //显示时间
  29.         POINT_COLOR=BLUE;//设置字体为蓝色                                         
  30.         LCD_ShowString(60,130,200,16,16,"    -  -     ");          
  31.         LCD_ShowString(60,162,200,16,16,"  :  :  ");                             
  32.         while(1)
  33.         {                                                                    
  34.                 if(t!=calendar.sec)
  35.                 {
  36.                         t=calendar.sec;
  37.                         LCD_ShowNum(60,130,calendar.w_year,4,16);                                                                          
  38.                         LCD_ShowNum(100,130,calendar.w_month,1,16);                                                                          
  39.                         LCD_ShowNum(124,130,calendar.w_date,2,16);         
  40.                         switch(calendar.week)
  41.                         {
  42.                                 case 0:
  43.                                         LCD_ShowString(60,148,200,16,16,"Sunday   ");
  44.                                         break;
  45.                                 case 1:
  46.                                         LCD_ShowString(60,148,200,16,16,"Monday   ");
  47.                                         break;
  48.                                 case 2:
  49.                                         LCD_ShowString(60,148,200,16,16,"Tuesday  ");
  50.                                         break;
  51.                                 case 3:
  52.                                         LCD_ShowString(60,148,200,16,16,"Wednesday");
  53.                                         break;
  54.                                 case 4:
  55.                                         LCD_ShowString(60,148,200,16,16,"Thursday ");
  56.                                         break;
  57.                                 case 5:
  58.                                         LCD_ShowString(60,148,200,16,16,"Friday   ");
  59.                                         break;
  60.                                 case 6:
  61.                                         LCD_ShowString(60,148,200,16,16,"Saturday ");
  62.                                         break;  
  63.                         }
  64.                         LCD_ShowNum(60,162,calendar.hour,2,16);                                                                          
  65.                         LCD_ShowNum(84,162,calendar.min,2,16);                                                                          
  66.                         LCD_ShowNum(108,162,calendar.sec,2,16);
  67.                         LED0=!LED0;
  68.                 }       
  69.                 delay_ms(10);                                                                  
  70.         }                                                                                         
  71. }


通过USMART就可以设置任意时间啦。。
 楼主| antusheng 发表于 2017-10-18 11:08 | 显示全部楼层
以上内容转载于网络,感觉很有参考价值。
一路向北lm 发表于 2017-10-18 13:30 | 显示全部楼层
这种参考代码很多啊,原子哥的很详细。
 楼主| antusheng 发表于 2017-10-18 17:00 | 显示全部楼层
一路向北lm 发表于 2017-10-18 13:30
这种参考代码很多啊,原子哥的很详细。

是的,好多个哥的都很牛叉,要不怎么一战成名
wanduzi 发表于 2017-10-18 20:13 | 显示全部楼层
非常好的参考,以前没有做过这个方方面面,没想到计时器是这么工作的。
zhuomuniao110 发表于 2017-10-18 20:41 | 显示全部楼层
官方的库函数应该有RTC相关的,可以看看能否直接调用。
mmuuss586 发表于 2017-10-19 09:20 | 显示全部楼层
哦,谢谢分享;
您需要登录后才可以回帖 登录 | 注册

本版积分规则

86

主题

1521

帖子

5

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