ST MCU Finder
安装免费手机应用,
寻找理想的ST MCU

[应用相关] stm32——RTC实时时钟

[复制链接]
97|19
 楼主 | 2019-8-17 17:06 | 显示全部楼层 |阅读模式
一、关于时间
  2038年问题
  在计算机应用上,2038年问题可能会导致某些软件在2038年无法正常工作。所有使用UNIX时间表示时间的程序都将将受其影响,因为它们以自1970年1月1日经过的秒数(忽略闰秒)来表示时间。这种时间表示法在类Unix(Unix-like)操作系统上是一个标准,并会影响以其C编程语言开发给其他大部份操作系统使用的软件。
  在大部份的32位操作系统上,此“time_t”数据模式使用一个有正负号的32位元整数(signedint32)存储计算的秒数。也就是说最大可以计数的秒数为 2^31次方 可以算得:
                2^31/3600/24/365 ≈ 68年
  所以依照此“time_t”标准,在此格式能被表示的最后时间是2038年1月19日03:14:07,星期二(UTC)。超过此一瞬间,时间将会被掩盖(wrap around)且在内部被表示为一个负数,并造成程序无法工作,因为它们无法将此时间识别为2038年,而可能会依个别实作而跳回1970年或1901年。
  对于PC机来说,时间开始于1980年1月1日,并以无正负符号的32位整数的形式按秒递增,这与UNIX时间非常类似。可以算得:
                 2^32/3600/24/365 ≈ 136年
  到2116年,这个整数将溢出。
    Windows NT使用64位整数来计时。但是,它使用100纳秒作为增量单位,且时间开始于1601年1月1日,所以NT将遇到2184年问题。
  苹果公司声明,Mac在29,940年之前不会出现时间问题!



使用特权

评论回复
 楼主 | 2019-8-17 17:07 | 显示全部楼层
二、RTC使用说明
  "RTC"是Real Time Clock 的简称,意为实时时钟。stm32提供了一个秒中断源和一个闹钟中断源,修改计数器的值可以重新设置系统当前的时间和日期。

  RTC模块之所以具有实时时钟功能,是因为它内部维持了一个独立的定时器,通过配置,可以让它准确地每秒钟中断一次。但实际上,RTC就只是一个定时器而已,掉电之后所有信息都会丢失,因此我们需要找一个地方来存储这些信息,于是就找到了备份寄存器。其在掉电后仍然可以通过纽扣电池供电,所以能时刻保存这些数据。

使用特权

评论回复
 楼主 | 2019-8-17 17:07 | 显示全部楼层
  配置RTC前须知:

  BKP:

  RTC模块和时钟配置系统的寄存器是在后备区域的(即BKP),通过BKP后备区域来存储RTC配置的数据可以让其在系统复位或待机模式下唤醒后,RTC里面配置的数据维持不变。

  PWR:

  PWR为电源的寄存器,我们需要用到的是电源控制寄存器(PWR_CR),通过使能PWR_CR的DBP位来取消后备区域BKP的写保护。

  RTC:

  由一组可编程计数器组成,分成两个模块。第一个模块是RTC的预分频模块,它可编程产生最长为1秒的RTC时间基准TR_CLK。RTC的预分频模块包含了一个20位的可编程分频器(RTC)TR_CLK 周期中RTC产生一个中断(秒中断)。第二个模块是一个32位的可编程计数器,可被初始化为当前的系统时间。系统时间按TR_CLK周期累加并与存储在RTC_ALR寄存器中的可编程时间相比较,如果RTC_CR控制寄存器中设置了相应允许位,比较匹配时,将产生一个闹钟中断。

58705d57c3ee21b77.png

使用特权

评论回复
 楼主 | 2019-8-17 17:08 | 显示全部楼层
下面讲解下配置整体过程:

   第一步: 通过设置寄存器 RCC_APB1ENR 的 PWREN 和 BKPEN 位来打开电源和后备接口的时钟
   调用库函数:
    RCC_APB1PeriphClockCmd (RCC_APB1Periph_PWR | RCC_APB1Periph_BKP,ENABLE );

使用特权

评论回复
 楼主 | 2019-8-17 17:08 | 显示全部楼层
第二步:电源控制寄存器(PWR_CR) 的 DBP 位来使能对后备寄存器和 RTC 的访问

   调用库函数:

    PWR_BackupAccessCmd(ENABLE );

使用特权

评论回复
 楼主 | 2019-8-17 17:08 | 显示全部楼层
第三步:初始化复位 BKP 寄存器

   调用库函数:

   BKP_DeInit ();

使用特权

评论回复
 楼主 | 2019-8-17 17:09 | 显示全部楼层
第四步:设置 RTCCLK,如下图:

   981825d57c43fc2160.png

   我们需要将 RTCCLK 设置为 LSE OSC  这个 32.768KHZ 的晶振。

   调用的库函数:  

   RCC_LSEConfig (RCC_LSE_ON);

   While(!RCC_GetFlagStatus (RCC_FLAG_HSERDY));//设置后需要等待启动

使用特权

评论回复
 楼主 | 2019-8-17 17:09 | 显示全部楼层
第五步:将 RTC 输入时钟 选择为 LSE 时钟输入并使能 RTC,等待 RTC 和 APB 时钟同步

   调用库函数:

   RCC_RTCCLKConfig (RCC_RTCCLKSource_LSE);//选择 LSE 为 RTC 设备的时钟

   RCC_RTCCLKCmd (ENABLE );//使能

   RTC RTC_WaitForSynchro();//等待同步

使用特权

评论回复
 楼主 | 2019-8-17 17:10 | 显示全部楼层
第六步:配置 RTC 时钟参数。

1、查询 RTOFF 位,直到 RTOFF 的值变为’1’
2、置 CNF 值为 1 ,进入配置模式
3、对一个或多个 RTC 寄存器进行写操作
4、清除 CNF 标志位,退出配置模式
5、查询 RTOFF,直至 RTOFF 位变为’1’ 以确认写操作已经完成。仅当 CNF 标志位被清除时,写操作才能进行,这个过程至少需要 3 个 RTCCLK 周期。

使用特权

评论回复
 楼主 | 2019-8-17 17:10 | 显示全部楼层
按照上述步骤用库函数来配置:

  1. /* 1.    查询 RTOFF 位,直到 RTOFF 的值变为’1’ */

  2. RTC_WaitForLastTask();//大家可以打开函数库看看这个函数内部的代码,就是查询 RTOFF的值

  3. /*

  4. 2.置 CNF 值为 1 ,进入配置模式

  5. 3.对一个或多个 RTC 寄存器进行写操作

  6. 4.清除 CNF 标志位,退出配置模式

  7. */

  8. RTC_SetPrescaler(32767); // 这里配置了预分频值,大家可以打开函数库看看这个函数的内部的代码,里面就有包含了 2、3、4 讲述的操作。

  9. /*
  10. 每完成一个操作一般都要查询 RTOFF 来判断是否 RTC 正在更新数据,如果是则等待它完成!!!
  11. */
  12. RTC_WaitForLastTask();//等待更新结束

  13. RTC_ITConfig(RTC_IT_SEC, ENABLE);//配置秒中断

  14. RTC_WaitForLastTask();//等待更新结束
复制代码

使用特权

评论回复
 楼主 | 2019-8-17 17:10 | 显示全部楼层
三、程序演示
  rtc.h

  1. #ifndef __RTC_H
  2. #define __RTC_H
  3. #include "stm32f10x.h"

  4. //时间结构体
  5. typedef struct
  6. {
  7.     vu8 hour;
  8.     vu8 min;
  9.     vu8 sec;            
  10.     //公历年月日周
  11.     vu16 w_year;
  12.     vu8  w_month;
  13.     vu8  w_date;
  14.     vu8  week;     
  15. }_calendar_obj;                     
  16. extern _calendar_obj calendar;
  17. void RCC_Configuration(void);
  18. void RTC_Init(void);
  19. u8 RTC_Set(u16 year,u8 mon,u8 day,u8 hour,u8 min,u8 sec);
  20. u8 RTC_Get(void);
  21. #endif
复制代码

使用特权

评论回复
 楼主 | 2019-8-17 17:11 | 显示全部楼层
rtc.c

  1. #include "rtc.h"
  2. _calendar_obj calendar;    //时钟结构体
  3. //平均的月份日期表
  4. const u8 mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31};
  5. /*rtc中断向量配置*/
  6. void NVIC_Configuration(void)
  7. {
  8.     NVIC_InitTypeDef NVIC_InitStructure;
  9.     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
  10.     NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;
  11.     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  12.     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
  13.     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  14.     NVIC_Init(&NVIC_InitStructure);
  15. }

  16. void RTC_Configuration(void)

  17. {
  18.     /* 使能PWR和BKP时钟 */
  19.     RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR|RCC_APB1Periph_BKP,ENABLE);
  20.     /* 使能对后备寄存器的访问 */
  21.     PWR_BackupAccessCmd(ENABLE);
  22.     /* 复位BKP寄存器 */
  23.     BKP_DeInit();
  24.     /* 使能LSE */
  25.     RCC_LSEConfig(RCC_LSE_ON);
  26.     /*等待启动完成 */
  27.     while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET) {}
  28.     /* 将 RTC时钟设置为LSE这个32.768KHZ的晶振*/
  29.     RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
  30.     /* 使能RTC Clock */
  31.     RCC_RTCCLKCmd(ENABLE);
  32.     /* 等待同步 */
  33.     RTC_WaitForSynchro();
  34.     /* 等待对RTC寄存器最后的写操作完成*/            
  35.     RTC_WaitForLastTask();
  36.     /* 配置了预分频值: 设置RTC时钟周期为1s */
  37.     RTC_SetPrescaler(32767); /* RTC period = RTCCLK/RTC_PR = (32.768 KHz)/(32767+1)*/
  38.     /* 等待对RTC寄存器最后的写操作完成 */
  39.     RTC_WaitForLastTask();
  40.     /* 使能RTC秒中断 */
  41.     RTC_ITConfig(RTC_IT_SEC, ENABLE);
  42.     /* 等待对RTC寄存器最后的写操作完成 */         
  43.     RTC_WaitForLastTask();

  44. void RTC_Init(void)
  45. {
  46.     /*如果是第一次配置时钟,则执行RCC_Configuration()进行配置*/
  47.     if(BKP_ReadBackupRegister(BKP_DR1)!=0x1016)
  48.     {
  49.             RCC_Configuration();
  50.             RTC_Set(2016,5,11,9,7,55);
  51.             GPIO_SetBits(GPIOD, GPIO_Pin_13);//点亮D1
  52.             BKP_WriteBackupRegister(BKP_DR1, 0x1016);//向执行的后备寄存器中写入用户程序数据
  53.     }
  54.     else
  55.     {
  56.         RTC_WaitForSynchro();//等待RTC寄存器同步完成
  57.         RTC_ITConfig(RTC_IT_SEC, ENABLE);//使能RTC秒中断
  58.         RTC_WaitForLastTask();//等待最近一次对RTC寄存器的写操作完成
  59.         GPIO_SetBits(GPIOG, GPIO_Pin_14);//点亮D2
  60.     }
  61.     NVIC_Configuration();
  62.     RTC_Get();//更新时间
  63. }
  64. u8 Is_Leap_Year(u16 pyear)
  65. {
  66.     if(pyear%4==0)//首先需能被4整除
  67.     {
  68.         if(pyear%100==0)
  69.         {
  70.             if(pyear%400==0)    return 1;//如果以00结尾,还要能被400整除
  71.             else    return 0;
  72.         }
  73.         else
  74.             return 1;
  75.     }
  76.     else
  77.         return 0;
  78. }
  79. /*
  80. *设置时钟
  81. *把输入的时钟转换为秒钟
  82. *以1970年1月1日为基准
  83. *1970~2099年为合法年份
  84. 返回值:0,成功;其它:错误
  85. */
  86. u8 RTC_Set(u16 year,u8 mon,u8 day,u8 hour,u8 min,u8 sec)
  87. {
  88.     u16 t;
  89.     u32 secCount=0;
  90.     if(year<1970||year>2099)
  91.         return 1;//³ö´í
  92.     for(t=1970;t<year;t++)    //把所有年份的秒钟相加
  93.     {
  94.         if(Is_Leap_Year(t))//闰年
  95.             secCount+=31622400;//闰年的秒钟数
  96.         else
  97.             secCount+=31536000;   
  98.     }
  99.     mon-=1;//先减掉一个月再算秒数(如现在是5月10日,则只需要算前4个月的天数,再加上10天,然后计算秒数)
  100.     for(t=0;t<mon;t++)
  101.     {
  102.         secCount+=(u32)mon_table[t]*86400;//月份秒钟数相加
  103.         if(Is_Leap_Year(year)&&t==1)
  104.             secCount+=86400;//闰年,2月份增加一天的秒钟数
  105.     }
  106.    
  107.     secCount+=(u32)(day-1)*86400;//把前面日期的秒钟数相加(这一天还没过完,所以-1)
  108.     secCount+=(u32)hour*3600;//小时秒钟数
  109.     secCount+=(u32)min*60;//分钟秒钟数
  110.     secCount+=sec;
  111. //    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR    | RCC_APB1Periph_BKP,ENABLE);
  112. //    PWR_BackupAccessCmd(ENABLE);
  113.     RTC_SetCounter(secCount);//设置RTC计数器的值
  114.     RTC_WaitForLastTask();    //等待最近一次对RTC寄存器的写操作完成
  115.     RTC_Get();//更新时间
  116.     return 0;
  117. }

  118. /*
  119. 得到当前的时间
  120. 成功返回0,错误返回其它
  121. */
  122. u8 RTC_Get(void)
  123. {
  124.         static u16 dayCount=0;
  125.         u32 secCount=0;
  126.         u32 tmp=0;
  127.         u16 tmp1=0;
  128.         secCount=RTC_GetCounter();
  129.         tmp=secCount/86400;//得到天数
  130.         if(dayCount!=tmp)//超过一天
  131.         {
  132.             dayCount=tmp;
  133.             tmp1=1970;//从1970年开始
  134.             while(tmp>=365)
  135.             {
  136.                 if(Is_Leap_Year(tmp1))//是闰年
  137.                 {
  138.                     if(tmp>=366)   
  139.                         tmp-=366;//减掉闰年的天数
  140.                     else
  141.                     {
  142.                     //    tmp1++;
  143.                         break;
  144.                     }
  145.                 }
  146.                 else
  147.                     tmp-=365;//平年
  148.                 tmp1++;
  149.             }
  150.             calendar.w_year=tmp1;//得到年份
  151.             tmp1=0;
  152.             while(tmp>=28)//超过一个月
  153.             {
  154.                 if(Is_Leap_Year(calendar.w_year)&&tmp1==1)/当年是闰年且轮循到2月
  155.                 {
  156.                     if(tmp>=29)   
  157.                         tmp-=29;
  158.                     else
  159.                         break;
  160.                 }
  161.                 else
  162.                 {
  163.                     if(tmp>=mon_table[tmp1])//平年
  164.                         tmp-=mon_table[tmp1];
  165.                     else
  166.                         break;
  167.                 }
  168.                 tmp1++;
  169.             }
  170.             calendar.w_month=tmp1+1;//得到月份,tmp1=0表示1月,所以要加1
  171.             calendar.w_date=tmp+1;    //得到日期,因为这一天还没过完,所以tmp只到其前一天,但是显示的时候要显示正常日期
  172.         }
  173.         tmp=secCount%86400;//得到秒钟数
  174.         calendar.hour=tmp/3600;//小时
  175.         calendar.min=(tmp%3600)/60;//分钟
  176.         calendar.sec=(tmp%3600)%60;//秒
  177.         return 0;
  178. }
  179. /*
  180. RTC时钟中断
  181. 每秒触发一次
  182. */
  183. void RTC_IRQHandler(void)
  184. {         
  185.     if (RTC_GetITStatus(RTC_IT_SEC) != RESET)//秒钟中断
  186.     {                           
  187.         RTC_Get();//更新时间
  188.       
  189.      }
  190.     if(RTC_GetITStatus(RTC_IT_ALR)!= RESET)//闹钟中断
  191.     {
  192.         RTC_ClearITPendingBit(RTC_IT_ALR);//清闹钟中断        
  193.   }                                                   
  194.     RTC_ClearITPendingBit(RTC_IT_SEC|RTC_IT_OW);//清闹钟中断
  195.     RTC_WaitForLastTask();                                                   
  196. }
复制代码

使用特权

评论回复
 楼主 | 2019-8-17 17:11 | 显示全部楼层
main.c

  1. #include "stm32f10x.h"
  2. #include "usart1.h"
  3. #include "LED.h"
  4. #include "delay.h"
  5. #include "flash.h"
  6. #include "rtc.h"
  7. #include "stdio.h"
  8. int main(void)
  9. {
  10.     u8 t=0;
  11.     USART1_Config();
  12.     GPIO_Configuration();
  13.     RTC_Init();
  14.     while(1)
  15.     {
  16.         if(t!=calendar.sec)
  17.         {
  18.             t=calendar.sec;
  19.             printf("\r\n now is %d 年 %d 月 %d 日 %d 时 %d 分 %d 秒 \r\n ",
  20.        calendar.w_year,calendar.w_month,calendar.w_date,calendar.hour,calendar.min,calendar.sec);
  21.         }
  22.         Delay(0x02FFFF);
  23.     }
  24.    
  25.    
  26. }
复制代码

使用特权

评论回复
| 2019-9-13 12:12 | 显示全部楼层
非常不错的资料

使用特权

评论回复
| 2019-9-19 11:04 | 显示全部楼层
非常不错的资料

使用特权

评论回复
| 2019-9-19 11:15 | 显示全部楼层
非常感谢楼主分享

使用特权

评论回复
| 2019-9-19 11:22 | 显示全部楼层
非常感谢楼主分享

使用特权

评论回复
| 2019-9-19 11:29 | 显示全部楼层
非常感谢楼主分享

使用特权

评论回复
| 2019-9-19 11:46 | 显示全部楼层
非常感谢楼主分享

使用特权

评论回复
| 2019-9-19 11:50 | 显示全部楼层
非常好的资料

使用特权

评论回复
扫描二维码,随时随地手机跟帖
您需要登录后才可以回帖 登录 | 注册

本版积分规则

我要发帖 投诉建议 创建版块 申请版主

快速回复

您需要登录后才可以回帖
登录 | 注册
高级模式

论坛热帖

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