你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】STM32-RTC实时时钟-毫秒计时实现

[复制链接]
STMCU小助手 发布时间:2022-1-13 20:47
1、RTC时钟简介
  STM32 的实时时钟(RTC)是一个独立的定时器,在相应软件配置下,可提供时钟日历的功能。 详细资料请参考ALIENTEK的官方文档——《STM32F1开发指南(精英版-库函数版)》,以下为博主摘录要点:
  • RTC 模块和时钟配置系统(RCC_BDCR 寄存器)在后备区域 ,系统复位后,会自动禁止访问后备寄存器和 RTC ,所以在要设置时间之前, 先要取消备份区域(BKP)的写保护
    ; D( S. w9 @$ l+ c6 n0 W4 h) O
  • RTC 内核完全独立于 RTC APB1 接口,而软件是通过 APB1 接口访问 RTC 的预分频值、计数器值和闹钟值,因此需要等待时钟同步,寄存器同步标志位(RSF)会硬件置1
    8 T: l. s% M8 b+ g8 f
  • RTC相关寄存器包括:控制寄存器(CRH、CRL)、预分频装载寄存器(PRLH、PRLL)、预分频器余数寄存器(DIVH、DIVL)、计数寄存器(CNTH、CNTL)、闹钟寄存器(ALRH、ALRL)
  • STM32备份寄存器,存RTC校验值和一些重要参数,最大字节84,可由VBAT供电
  • 计数器时钟频率:RTCCLK频率/(预分频装载寄存器值+1)
    4 f, j5 ]' @- h$ a

    $ m6 l' C# q* B$ E! I( C8 y

+ A4 ~& N$ P( p* q/ P2、软硬件设计
  由于RTC是STM32芯片自带的时钟资源,所以自主开发的时候只需要在设计时加上晶振电路和纽扣电池即可。编程时在HARDWARE文件夹新建 rtc.c、rtc.h 文件。
9 I0 J# ]; k' [$ N; T+ @: Y* B

% j# J+ L2 M3 r3、时钟配置与函数编写
  为了使用RTC时钟,需要进行配置和时间获取,基本上按照例程来写就可以了。为避免零散,我将附上完整代码。函数说明如下:
rtc.c中需要编写的函数列表
RTC_Init(void)
配置时钟
RTC_NVIC_Config(void)
中断分组
RTC_IRQHandler(void)
秒中断处理
RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec)
设置时间
RTC_Alarm_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec)
闹钟设置
RTC_Get(void)
获取时钟
RTC_Get_Week(u16 year,u8 month,u8 day)
星期计算
Is_Leap_Year(u16 year)
闰年判断

% R3 m* l. Z: S8 Y. p1 o
 事实上,以上函数并不都要,闹钟没有用到的话就不要,秒中断也可以不作处理,看项目需求。
  1.   1 #include "sys.h"% C# \$ C& ]# @" v
  2.   2 #include "delay.h"
    - M& `6 ?) h8 r* o
  3.   3 #include "rtc.h"            
    / W! d) {  ]8 u- O) w, R2 n
  4.   4        
    6 {6 ~6 g1 q% [0 |
  5.   5 _calendar_obj calendar;//时钟结构体
    3 r+ N4 L& `: C/ n
  6.   6  
    4 e$ H+ U$ U0 A6 E% H! H0 X
  7.   7 static void RTC_NVIC_Config(void)
    , e' Z% P: z! @: l' T* H* q/ y
  8.   8 {    / m$ P$ Z& ]( ?: ^6 _$ W
  9.   9     NVIC_InitTypeDef NVIC_InitStructure;
    7 m! I% N5 B3 F! E, a/ Z7 \: Y" H
  10. 10     NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;        //RTC全局中断
    0 C0 Q: X7 Z; o' G; c
  11. 11     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;    //先占优先级1位,从优先级3位
    . q' J0 S* N4 l
  12. 12     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;    //先占优先级0位,从优先级4位( x6 t2 K& F! G1 [" Y  O
  13. 13     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;        //使能该通道中断
    & f: q  a$ W4 Q7 |: M& [3 R( W5 y
  14. 14     NVIC_Init(&NVIC_InitStructure);        //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器2 ]5 \: B0 d3 h$ C5 ^
  15. 15 }3 I) R6 j4 L5 y$ U
  16. 16
    5 Q* t% K% ]6 Z% M% z) s
  17. 17 //实时时钟配置
    ' @2 b3 `" I1 z; a0 m) o9 a
  18. 18 //初始化RTC时钟,同时检测时钟是否工作正常3 }2 B6 D# p3 ^/ G2 \. S, `, m$ E
  19. 19 //BKP->DR1用于保存是否第一次配置的设置& G" o- y$ d1 Z% c+ y% Y% Z
  20. 20 //返回0:正常
    / G* `) y) U2 Z, @/ o
  21. 21 //其他:错误代码- S( \9 x$ N- q7 \
  22. 22
    . n! ]1 P  W8 V- K1 z' U
  23. 23 u8 RTC_Init(void)" S! w. I9 q# d5 p
  24. 24 {7 t, j8 D* _. h  s
  25. 25     //检查是不是第一次配置时钟% I/ m$ ^, T$ H4 S
  26. 26     u8 temp=0;/ E2 p6 U9 B2 v" G. Q
  27. 27     RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);    //使能PWR和BKP外设时钟   / q8 b, `4 M, X  }
  28. 28     PWR_BackupAccessCmd(ENABLE);    //使能后备寄存器访问  
    + h" q6 @. _6 p$ R9 P, Y: ~
  29. 29     if (BKP_ReadBackupRegister(BKP_DR1) != 0x5051)        //从指定的后备寄存器中读出数据:读出了与写入的指定数据不相乎# K) t: D4 y& }, z
  30. 30     {                  , r& k$ J2 g7 H% [6 _( ?
  31. 31         BKP_DeInit();    //复位备份区域     / x: V& H7 j0 R) s  y: `
  32. 32         RCC_LSEConfig(RCC_LSE_ON);    //设置外部低速晶振(LSE),使用外设低速晶振
    " x" P, h. u) D6 S4 F4 B0 v2 M
  33. 33         while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET&&temp<250)    //检查指定的RCC标志位设置与否,等待低速晶振就绪
    # ^3 q) Q3 N2 ?8 M; k- @( A
  34. 34         {
      T/ [: o  b+ F9 y
  35. 35             temp++;6 i0 i# Q7 Z$ V/ @* \; ]; d/ P! U
  36. 36             delay_ms(10);
    " V, e+ @# i3 I1 M. y
  37. 37         }
      ^% q1 R4 U- V. R7 ^& E* y3 m
  38. 38         if(temp>=250)return 1;//初始化时钟失败,晶振有问题        ! {  \3 c* c) h! G8 t
  39. 39         RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);        //设置RTC时钟(RTCCLK),选择LSE作为RTC时钟   
    $ i+ E: i2 L0 u: ~2 x/ O' F3 m
  40. 40         RCC_RTCCLKCmd(ENABLE);    //使能RTC时钟  
    3 ]+ q, F3 i6 Y1 u4 H- b
  41. 41         RTC_WaitForLastTask();    //等待最近一次对RTC寄存器的写操作完成
    $ n4 m  j6 u; _) [+ v! o% F. g
  42. 42         RTC_WaitForSynchro();        //等待RTC寄存器同步  
    ' g& y- r. C3 N) V* o/ r+ o
  43. 43         RTC_ITConfig(RTC_IT_SEC, ENABLE);        //使能RTC秒中断$ l. U6 s7 f: \: ^( z. j5 h6 t2 j
  44. 44         RTC_WaitForLastTask();    //等待最近一次对RTC寄存器的写操作完成: n2 }5 J, a9 {, I9 k( I
  45. 45         RTC_EnterConfigMode();/// 允许配置   
    ( G7 ~' p! m, E+ [  L$ `
  46. 46         RTC_SetPrescaler(32767); //设置RTC预分频的值  J1 X# o/ H; U3 J) d+ P
  47. 47         RTC_WaitForLastTask();    //等待最近一次对RTC寄存器的写操作完成5 h/ y4 N7 A7 ]2 K9 m9 D
  48. 48         RTC_Set(2018,4,2,17,37,00);  //设置时间    % ~* ~8 _2 p! s# L7 g7 x0 |
  49. 49         RTC_ExitConfigMode(); //退出配置模式  * s' ^1 c) f0 M7 l5 P3 C$ q
  50. 50         BKP_WriteBackupRegister(BKP_DR1, 0X5051);    //向指定的后备寄存器中写入用户程序数据
    # @$ G% Q* t$ i
  51. 51     }" K! [+ ~7 h# ~; w/ g0 d: `
  52. 52     else//系统继续计时
    ! i" h0 D7 j/ ?/ a- Q4 d* S% p
  53. 53     {2 E) s  u2 b0 c7 k
  54. 54   Q9 |+ i3 U' x, C4 s  P7 r
  55. 55         RTC_WaitForSynchro();    //等待最近一次对RTC寄存器的写操作完成! [: \1 \& Z/ D+ ]
  56. 56         RTC_ITConfig(RTC_IT_SEC, ENABLE);    //使能RTC秒中断
    % \1 ^) Y+ o/ }; u8 |' g# N8 r
  57. 57         RTC_WaitForLastTask();    //等待最近一次对RTC寄存器的写操作完成
    % k( a4 Y' g+ {  s7 }
  58. 58     }3 F0 z! j% p; X* D5 \0 P
  59. 59     RTC_NVIC_Config();//RCT中断分组设置                                 
    * b# @8 E# F2 P6 f
  60. 60     RTC_Get();//更新时间    - W8 f% q/ g6 f% m" t$ L+ T
  61. 61     return 0; //ok
    : u. A9 f' V8 Z# O  K
  62. 62
    - _6 F/ @3 y5 Z2 c
  63. 63 }                             
    - b% ^5 \3 ?. A: w8 ~: A6 A1 w
  64. 64 //RTC时钟中断
    * _* G0 @6 E6 r  L" [
  65. 65 //每秒触发一次  " G, q1 `  H, |* L; B; v3 y
  66. 66 //extern u16 tcnt;
    2 @5 O& N" e: e; L6 k
  67. 67 void RTC_IRQHandler(void)
    % O# y) j( J4 l' ^
  68. 68 {         
    ) B+ p3 j- ]- I9 N( w" e9 a
  69. 69 //    if (RTC_GetITStatus(RTC_IT_SEC) != RESET)//秒钟中断
    - u2 b8 P- x6 K0 Q
  70. 70 //    {                           
    9 l' @6 ~" ]5 S# g$ Z; p6 M
  71. 71 //        RTC_Get();//更新时间   2 K2 E! i  O! x
  72. 72 //     }  c; u3 j. E- U5 W% a
  73. 73 //    if(RTC_GetITStatus(RTC_IT_ALR)!= RESET)//闹钟中断
    , m7 F8 Y6 e# r/ d" v5 B
  74. 74 //    {$ G3 s& y6 F, U, C8 z
  75. 75 //        RTC_ClearITPendingBit(RTC_IT_ALR);        //清闹钟中断         
    " \9 \/ Q- B* X4 l
  76. 76 //      RTC_Get();                //更新时间   
    ' g& R/ |+ X4 @. b. X
  77. 77 //      //printf("Alarm Time:%d-%d-%d %d:%d:%d\n",calendar.w_year,calendar.w_month,calendar.w_date,calendar.hour,calendar.min,calendar.sec);//输出闹铃时间    2 d2 p' `, a7 U, O; |
  78. 78 //        
    $ |4 u  F+ m& l! g0 I7 X" `
  79. 79 //      }                                                    ; o' R# N( [7 g9 r
  80. 80     RTC_ClearITPendingBit(RTC_IT_SEC|RTC_IT_OW);        //清闹钟中断
    $ f5 H( y3 T+ E  m% M& m8 f4 i+ q
  81. 81     RTC_WaitForLastTask();                                                   / S5 R5 u/ ^7 \8 ?& t
  82. 82 }8 b5 x( c3 q" k0 n) i
  83. 83
    ( v9 g* F1 q) v, }$ w
  84. 84
    ) Y* ?* {' [6 {) b6 v- W  {% z
  85. 85 //判断是否是闰年函数" [* Y  g: d5 I, n/ g
  86. 86 //月份   1  2  3  4  5  6  7  8  9  10 11 12
    9 Y7 O2 |4 H4 q0 L0 Q
  87. 87 //闰年   31 29 31 30 31 30 31 31 30 31 30 31
    7 b2 ~7 |/ `, f. q" z0 e6 U( c
  88. 88 //非闰年 31 28 31 30 31 30 31 31 30 31 30 31
    # `2 b" g" C( d- y1 c
  89. 89 //输入:年份, f& ]' f& P, J5 N9 j
  90. 90 //输出:该年份是不是闰年.1,是.0,不是5 D5 a+ f  k( x6 r& c7 b: Y
  91. 91 u8 Is_Leap_Year(u16 year)
    6 w3 H8 q$ c! S
  92. 92 {              
    , v8 }, H2 G  u3 m( X( b" J
  93. 93     if(year%4==0) //必须能被4整除
    ( p0 A7 W2 O9 v7 c
  94. 94     {
    + V% P$ r3 R7 D% {7 H
  95. 95         if(year%100==0) 5 P+ s8 g$ d& `4 O: A$ t( T
  96. 96         { & }1 w2 C! B* l( O/ p
  97. 97             if(year%400==0)return 1;//如果以00结尾,还要能被400整除        
    2 |5 j+ J# H  O" z! @
  98. 98             else return 0;   
    3 \! q5 ?: T; m, b
  99. 99         }else return 1;   2 k& K! q. M; F5 r' T
  100. 100     }else return 0;   
    . e/ \# p$ ]4 ^& o. {
  101. 101 }
    & ~9 _4 k5 Z& v) K" B+ e; ?
  102. 102 5 r  q: T8 P5 ]9 j) f; ]7 X* @
  103. 103 1 Y. N$ e0 ?# _0 c8 N1 F) K5 x
  104. 104 //设置时钟: `) j& B. w- h) `, l
  105. 105 //把输入的时钟转换为秒钟
    $ q4 B! m1 F( ~  x" Y* N
  106. 106 //以1970年1月1日为基准
    # }2 x  u0 v+ I3 i1 u: L: K
  107. 107 //1970~2099年为合法年份) k, s  z' C  K1 |$ j
  108. 108 //返回值:0,成功;其他:错误代码.
    $ u( a1 |2 G( n( u
  109. 109 //月份数据表                                             % l2 r$ z; S( x" S; `6 V) b
  110. 110 const u8  table_week[12]={0,3,3,6,1,4,6,2,5,0,3,5}; //月修正数据表      
    , b' p4 n1 H0 Q# ~) w
  111. 111 const u8 mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31};//平年的月份日期表
    2 _0 {- R0 D& f, }2 \
  112. 112
    - m9 a0 p; S/ \1 H5 u' O3 F
  113. 113 u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec)
    , Q; e. k2 [" @! N+ i. W
  114. 114 {
    ' t3 b8 U) E. q2 j, m8 {
  115. 115     u16 t;, z' f8 P! Z8 A3 x2 _
  116. 116     u32 seccount=0;* Y- F3 d2 s$ g+ }) I, x/ n1 ~
  117. 117     if(syear<1970||syear>2099)return 1;       , ~* }& ~: z) x
  118. 118     for(t=1970;t<syear;t++)    //把所有年份的秒钟相加. [; Z) a# T8 c, V
  119. 119     {6 t7 r3 `; Z9 s! A3 q# c/ m4 Z& j
  120. 120         if(Is_Leap_Year(t))seccount+=31622400;//闰年的秒钟数
    9 c+ {$ s# N7 O- A
  121. 121         else seccount+=31536000;              //平年的秒钟数
    $ W9 w0 I5 r$ {+ c
  122. 122     }
    ; y) h* l8 h3 q
  123. 123     smon-=1;
    : T4 F* D, \: a
  124. 124     for(t=0;t<smon;t++)       //把前面月份的秒钟数相加
    * Z& ^& y+ {6 g* j* Z3 o
  125. 125     {2 C# r& y* {3 x+ y
  126. 126         seccount+=(u32)mon_table[t]*86400;//月份秒钟数相加+ N8 G! r% T2 O/ C
  127. 127         if(Is_Leap_Year(syear)&&t==1)seccount+=86400;//闰年2月份增加一天的秒钟数       % R2 S* y* c$ x1 w2 R) t  n
  128. 128     }
    - N' I4 P: C$ t3 `
  129. 129     seccount+=(u32)(sday-1)*86400;//把前面日期的秒钟数相加
    : B5 w* x5 v( z8 N" r' i1 U
  130. 130     seccount+=(u32)hour*3600;//小时秒钟数) b7 z' J3 L" r+ ?, s
  131. 131     seccount+=(u32)min*60;     //分钟秒钟数
    ' A: ^* w2 ^& i  h. l* R
  132. 132     seccount+=sec;//最后的秒钟加上去
    : t" o6 }5 S0 D) m
  133. 133 0 ^# o; @  g" U' O8 M
  134. 134     RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);    //使能PWR和BKP外设时钟  : E+ P' L; x: l5 _( r( G
  135. 135     PWR_BackupAccessCmd(ENABLE);    //使能RTC和后备寄存器访问 " b/ m6 s( c8 d. z
  136. 136     RTC_SetCounter(seccount);    //设置RTC计数器的值, M# u2 K+ p/ O; K" |
  137. 137
    / ~5 m& O3 S7 m' [- u; [
  138. 138     RTC_WaitForLastTask();    //等待最近一次对RTC寄存器的写操作完成      
    5 z! v) n3 R) n
  139. 139     return 0;        ; k3 v: Z# }$ n; Z* y0 _. B  X) r
  140. 140 }
    - Y3 {2 B# y9 m$ W& |/ Q) G. A& E. c
  141. 141
    3 I9 \4 L$ L+ ]- R( A( R$ `, |
  142. 142 //初始化闹钟          6 h5 O9 p% E1 a% X* W) F
  143. 143 //以1970年1月1日为基准
    9 a# ~, x2 S  X0 C" N# E5 [6 w
  144. 144 //1970~2099年为合法年份' n* J: |0 `- G( q; E2 o" L. F
  145. 145 //syear,smon,sday,hour,min,sec:闹钟的年月日时分秒   
    ) r+ o; b- X: m" z3 S
  146. 146 //返回值:0,成功;其他:错误代码.- L3 I8 T' r1 f/ t
  147. 147 u8 RTC_Alarm_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec)( N$ d. @' ]4 w; s$ W2 {! D3 a" @
  148. 148 {3 F1 P, N; ~" G/ O+ R
  149. 149     u16 t;8 d: o5 h" @0 ]
  150. 150     u32 seccount=0;
    3 Z% x$ `( x5 C+ A8 `
  151. 151     if(syear<1970||syear>2099)return 1;      
    0 S8 q- n/ y# M) O  R1 T8 ?3 w/ e
  152. 152     for(t=1970;t<syear;t++)    //把所有年份的秒钟相加
    + o. t! W% t' B# z! u; g: s
  153. 153     {, j$ P( U8 @3 u2 f1 M' f
  154. 154         if(Is_Leap_Year(t))seccount+=31622400;//闰年的秒钟数
    2 H/ k6 _# z8 P! W
  155. 155         else seccount+=31536000;              //平年的秒钟数
    & j4 e4 I0 Y$ p% Z
  156. 156     }
    " F# G) k( |$ Q* }8 s
  157. 157     smon-=1;: A0 {% s) g) e
  158. 158     for(t=0;t<smon;t++)       //把前面月份的秒钟数相加8 H& x* y; `3 G  R+ w2 T. K
  159. 159     {1 [: D) W" M0 a1 _0 T
  160. 160         seccount+=(u32)mon_table[t]*86400;//月份秒钟数相加1 B8 f8 D# d$ k' j$ E
  161. 161         if(Is_Leap_Year(syear)&&t==1)seccount+=86400;//闰年2月份增加一天的秒钟数       - _  G" ]+ e# f8 \) c" e
  162. 162     }
    9 p" g( u, J6 L' h/ ~
  163. 163     seccount+=(u32)(sday-1)*86400;//把前面日期的秒钟数相加 : l/ d# S- ?! j% x9 u
  164. 164     seccount+=(u32)hour*3600;//小时秒钟数
    / ~3 _8 n( R" s5 y1 f
  165. 165     seccount+=(u32)min*60;     //分钟秒钟数' d+ w$ l$ A$ S1 ^# u! b7 K9 d
  166. 166     seccount+=sec;//最后的秒钟加上去                 
    / _8 L7 u4 a" y; l% B
  167. 167     //设置时钟
    2 [' F- T, ]& p4 Z
  168. 168     RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);    //使能PWR和BKP外设时钟   
    6 f% x: A6 n+ z6 J3 G
  169. 169     PWR_BackupAccessCmd(ENABLE);    //使能后备寄存器访问  # \9 _2 \  x" y  \+ S% ~* J( n
  170. 170     //上面三步是必须的!8 L/ t& j8 o1 J0 ~
  171. 171       T+ b* i5 R  ~  O8 a
  172. 172     RTC_SetAlarm(seccount);
    ( r: E+ k* {* s( h  A, ~
  173. 173  
    7 ?$ `7 n* E8 [9 l! @
  174. 174     RTC_WaitForLastTask();    //等待最近一次对RTC寄存器的写操作完成      
    % V8 s+ {3 x& q7 `8 w, `3 _# [
  175. 175     / l4 n! w* C# S* h9 c. i! k
  176. 176     return 0;        
    5 U1 e& ]2 H  I. s$ k* Q
  177. 177 }
    5 E0 h  D! V: j7 H! K
  178. 178 0 z+ j5 O0 E9 ]1 u: L8 P8 z
  179. 179 7 f0 {; e6 j7 S, D, n% E
  180. 180 //得到当前的时间
    * }0 ~' O! d: T1 t% F
  181. 181 //返回值:0,成功;其他:错误代码.! K$ E$ A0 M" ^4 h9 j' K/ w4 e
  182. 182 u8 RTC_Get(void), ^2 S- @2 Q. V8 \6 Z, ^
  183. 183 {
    : w: L3 _  ^# c7 l: X4 Y
  184. 184     static u16 daycnt=0;
    : @2 f# }5 I7 r! T" D/ f: P
  185. 185     u32 timecount=0; , D4 t! B( x5 {7 n# `3 O
  186. 186     u32 temp=0;
    * c7 B5 N1 G/ {% p- E6 N
  187. 187     u16 temp1=0;      0 n; L& E$ C) v7 {9 a
  188. 188     timecount=RTC_GetCounter();     + s- i# j, m' G% Q; ?
  189. 189      temp=timecount/86400;   //得到天数(秒钟数对应的)9 q% J! |4 f9 V9 V4 f1 t
  190. 190     if(daycnt!=temp)//超过一天了
    % ]! a+ n8 |1 D
  191. 191     {      
    1 G# ~* \: m" C0 O& G
  192. 192         daycnt=temp;
    " Z. C$ V" F5 D0 W. @" T' \
  193. 193         temp1=1970;    //从1970年开始
    % {, Y1 _/ t( D' H
  194. 194         while(temp>=365)
    % `% u! q7 _& E0 F% i+ B" \/ V
  195. 195         {                 * C3 Y0 _/ P  S1 {( \6 n" }, I
  196. 196             if(Is_Leap_Year(temp1))//是闰年5 {) j( ?% `: \
  197. 197             {
    1 B) n. X: @1 y
  198. 198                 if(temp>=366)temp-=366;//闰年的秒钟数: G! R1 \2 a' `; W$ K/ f8 g
  199. 199                 else {temp1++;break;}  
    5 J7 l/ S- ~/ s
  200. 200             }
    & O' ~  P) \  E! T. b) a. j
  201. 201             else temp-=365;      //平年
    4 `: R4 }6 T* Y% {2 I* u' G/ q
  202. 202             temp1++;  
    ) r7 I. q2 m. _2 s9 x
  203. 203         }   
    & J7 d! i4 M* k% g8 r
  204. 204         calendar.w_year=temp1;//得到年份  j( T8 |7 W. m- f/ d5 x* _, A  b
  205. 205         temp1=0;$ n% w0 |( D. L; M
  206. 206         while(temp>=28)//超过了一个月8 [" T/ Q# w4 V  B
  207. 207         {* D9 C) H' `* ?6 J1 v4 |! x9 y1 f  x
  208. 208             if(Is_Leap_Year(calendar.w_year)&&temp1==1)//当年是不是闰年/2月份
    . C- j; w* k& _7 V- x( c( D3 [0 `0 `) h
  209. 209             {
    # }) a, ]8 v; F& q  K; {5 y; D# S
  210. 210                 if(temp>=29)temp-=29;//闰年的秒钟数- M3 a3 w, o% q# l1 @" W
  211. 211                 else break; ' b% s4 t) L- ?5 Q' Q% y2 v. O! O
  212. 212             }
    " o* S  N5 A/ R, G' F# U) h, f
  213. 213             else ; h) A& v7 f( y: _( ^. h
  214. 214             {
    # [5 M/ g1 I, c& p
  215. 215                 if(temp>=mon_table[temp1])temp-=mon_table[temp1];//平年- F+ y- |: R- I9 X# i5 _
  216. 216                 else break;
    / [" j  t+ |3 p5 z- v4 _3 V1 H: N
  217. 217             }3 G! t5 w7 j, R+ E  t( \, B" o, D/ D3 ]
  218. 218             temp1++;  1 Q) |, x' y/ I0 F
  219. 219         }# p1 [: Q! u( O% z# H1 C
  220. 220         calendar.w_month=temp1+1;    //得到月份1 J+ K2 k; r2 W# {7 T
  221. 221         calendar.w_date=temp+1;      //得到日期
    $ h( w% Y' I  c1 |
  222. 222     }! }% Q! s" C# m
  223. 223     temp=timecount%86400;             //得到秒钟数          / K2 P! p$ b3 K+ T# C- s. }" K: V
  224. 224     calendar.hour=temp/3600;         //小时
    / I  A& K: p! J, W
  225. 225     calendar.min=(temp%3600)/60;     //分钟   
    + K9 J( {. t+ r, [- U
  226. 226     calendar.sec=(temp%3600)%60;     //秒钟
    $ z, K1 T1 z% W
  227. 227     calendar.week=RTC_Get_Week(calendar.w_year,calendar.w_month,calendar.w_date);//获取星期0 J1 x- P- `' y9 T3 T
  228. 228     calendar.msec=(32767-RTC_GetDivider())*    1000/32767;% f% I/ ^9 z8 U; a% W* @
  229. 229     return 0;, H% m1 ~/ ^& I4 K& b' {8 N
  230. 230 }    & S, p* \6 x% x* i
  231. 231 ; C4 k  y8 j; o, a5 w
  232. 232
    , B- v5 y9 R" w$ o
  233. 233 //获得现在是星期几/ V& [& Y& M. ^6 X1 R
  234. 234 //功能描述:输入公历日期得到星期(只允许1901-2099年)
    , _& i9 I/ @( z8 [7 ~
  235. 235 //输入参数:公历年月日
    ( z' _( A" E, v) j% N" s: q6 s
  236. 236 //返回值:星期号                                                                                         
    2 c4 x- o% F$ G3 x
  237. 237 u8 RTC_Get_Week(u16 year,u8 month,u8 day)- h9 }+ j3 K* h9 }1 C$ e
  238. 238 {    5 m$ }# m' w  a+ n, N6 T7 m& k
  239. 239     u16 temp2;
    7 I! r' c/ [' C% c, M( t# m" w
  240. 240     u8 yearH,yearL;
    8 r. X' p% w' e6 a' k8 ~
  241. 241     
    : y5 I. x2 E- J
  242. 242     yearH=year/100;    yearL=year%100;
    % t+ F  X; k. ]/ A+ i: ?6 L
  243. 243     // 如果为21世纪,年份数加100  
    8 E8 u8 a& u5 X# e0 z$ u
  244. 244     if (yearH>19)yearL+=100;' s! w3 E& M# u! `+ P
  245. 245     // 所过闰年数只算1900年之后的  
    " z0 L* |( }8 F' ?4 S
  246. 246     temp2=yearL+yearL/4;/ U- k! S3 U3 ]. y, k
  247. 247     temp2=temp2%7;
    + @& E, J8 O* ~, w% d
  248. 248     temp2=temp2+day+table_week[month-1];
    0 x3 J  P' O7 r8 F4 T9 d3 d
  249. 249     if (yearL%4==0&&month<3)temp2--;  M, c$ K7 c6 V2 K& m: f1 A  r
  250. 250     return(temp2%7);
    " Z$ F* T* e0 J% ^# `
  251. 251 }   
复制代码
  1. 1 #include "sys.h"; y7 }2 r: g# v0 i0 G, `" z) z
  2. 2 , b8 m- c. H1 M" _$ Y3 [/ u
  3. 3 //时间结构体
    & d% r& x. L1 A' S7 o: }8 h
  4. 4 typedef struct 6 z; a6 P8 V" z9 g& q, |- ~2 _' s" t
  5. 5 {4 H4 K4 c9 r3 B9 I6 M4 @8 L/ V
  6. 6     vu8 hour;//vu8
    3 A* M: w; u5 f
  7. 7     vu8 min;
    : S" E; t' Q0 A* a% ]3 }' q
  8. 8     vu8 sec;
    , y5 @6 R. N! e2 B( H
  9. 9     vu16 msec;
    5 j- S; k) }( T1 n* e
  10. 10     1 j' o, S  @. t" W9 Y2 E# I, Z
  11. 11     //公历日月年周
    ( j& |" B/ j  V- e" o+ n
  12. 12     vu16 w_year;' F1 T: c) g. a9 f- L
  13. 13     vu8  w_month;1 b6 `$ P2 i6 w% Y8 \
  14. 14     vu8  w_date;  X( ~1 ~2 M/ t1 d0 y3 U+ P1 V, N* Y
  15. 15     vu8  week;         3 a) E+ j# V  y4 |3 U! C5 E/ m$ q% I
  16. 16 }_calendar_obj;                     , I9 e2 a9 W. m+ N1 K7 e
  17. 17 ! W2 d; @8 O6 g) G! a! n2 k
  18. 18 extern _calendar_obj calendar;    //日历结构体
    9 m6 p2 ]& o2 x8 d+ w, q- d
  19. 19 extern u8 const mon_table[12];    //月份日期数据表
    : T9 D& S( p5 ^' `
  20. 20
    " C" S, C/ E0 }6 I! x. u6 m0 n
  21. 21 u8 RTC_Init(void);        //初始化RTC,返回0,失败;1,成功;7 z2 |: g3 P$ _/ }6 i$ w
  22. 22 u8 Is_Leap_Year(u16 year);//平年,闰年判断9 w$ j8 ?6 I$ F
  23. 23
    % o6 z# U3 h2 e% s
  24. 24 //u8 RTC_Alarm_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec);
    7 @% v8 U5 Q" D9 |, S
  25. 25 u8 RTC_Get(void);         //更新时间   
      P0 z1 @0 n9 s5 ?/ P  u" y
  26. 26 u8 RTC_Get_Week(u16 year,u8 month,u8 day);
    1 v) V- |) b9 C; [
  27. 27 u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec);//设置时间   
复制代码
+ ~" v  p+ y: _' I! U  I0 k! G- k6 D
4、秒钟计时原理
使用外部32.768KHz的晶振作为时钟的输入频率,设置预分频装载寄存器的值为32767,根据计算公式,刚好可以得到1秒的计数频率。时间基准设置为1970年1月1日0时0分0秒,后续的时间都以这个为基准进行计算。RTC计数器是32位的,理论上可以记录136年左右的时间。(注意不必在秒中断里更新时间)
* C8 H; G# A8 t& s

' @% q4 Z' o. @: s$ V- r+ w5、毫秒计时原理
如果要获取到毫秒级的时钟怎么办?在我的项目中就有这样的要求。事实上,获取毫秒时钟也非常简单。
查阅开发指南,RTC预分频器余数寄存器(RTC_DIVH、RTC_DIVL),这两个寄存器的作用就是用来获得比秒钟更为准确的时钟。 该寄存器的值自减的,用于保存还需要多少时钟周期获得一个秒信号。在一次秒钟更新后,由硬件重新装载。这两个寄存器和 RTC 预分频装载寄存器位数是一样的。也就是说,如果预分频装载寄存器的值为32767,那么余数寄存器就会在每一次秒更新时由硬件重新装载为32767,然后向下计数,计数到0表示一秒,也即1000ms。
  因此,我们在时钟结构体中添加msec成员
  1. 1 //时间结构体
    4 s! `( V: Q+ F
  2. 2 typedef struct
    2 }0 b; M: N1 x4 _3 h. ]
  3. 3 {
    7 S% j3 l- w! ~6 J( a
  4. 4     vu8 hour;//vu81 ~: B6 U, Y- S! }% @% D& \1 f. d8 ]9 Z
  5. 5     vu8 min;
      @! n6 R9 o6 B; Q$ w
  6. 6     vu8 sec;
    : N5 g5 T/ D, `1 t
  7. 7     vu16 msec;
    # k: ]4 I- ?2 I0 z0 {0 p
  8. 8     
    7 b" B3 {7 b+ K% V8 Z
  9. 9     //公历日月年周
    3 B7 l+ s4 E  f% Z: g8 M% ]1 ~4 v" p
  10. 10     vu16 w_year;" g- d1 i. Q/ [" J$ L$ j/ x
  11. 11     vu8  w_month;9 c+ S* Z& K' l/ l" y2 i
  12. 12     vu8  w_date;
    6 |. {2 M8 ?4 g3 w" t8 V
  13. 13     vu8  week;         * i1 g$ x. m3 v
  14. 14 }_calendar_obj;  
复制代码

0 F$ {$ L5 l8 z, _1 g
 获取毫秒时间
  1. 1 calendar.msec=(32767-RTC_GetDivider())*1000/32767;
复制代码
4 _. Q) [7 U4 R- P
6、修改时间  
   如果RTC时钟在使用的过程中不准了(我遇到的情况大概是掉电跑了2个月,重新测试的时候差了2分钟左右),可以重新校准时钟。我们在备份区域 BKP_DR1 中写入 0X5051 ,下次开机(或复位)的时候,先读取 BKP_DR1 的值,然后判断是否是 0X5051来决定是不是要配置。 如果要修改时间,请将0x5051改为其它数据,修改RTC_Set函数实参,再重新烧写一下程序即可。

5 k+ A1 z1 c. C4 y: W/ y
  1. if (BKP_ReadBackupRegister(BKP_DR1) != 0x5051)& x7 l  z; N2 x; R
  2. {
    $ z0 I+ a4 i5 q0 t( O6 q+ g+ C
  3.     ...% D- ]; u" `: U* X0 X
  4.     RTC_EnterConfigMode();/// 允许配置   
    6 r" U5 j9 B! f$ e' z
  5.     RTC_SetPrescaler(32767); //设置RTC预分频的值) |+ Q0 l+ e# o) S
  6.     RTC_WaitForLastTask();    //等待最近一次对RTC寄存器的写操作完成9 T8 Y/ n$ j% }/ z( i, V3 W, c
  7.     RTC_Set(2018,4,2,17,37,00);  //设置时间    ; I" {2 W8 {2 X( q
  8.     RTC_ExitConfigMode(); //退出配置模式  
    ( G, y9 [7 m2 E9 r& w! k
  9.     BKP_WriteBackupRegister(BKP_DR1, 0X5051);    //向指定的后备寄存器中写入用户程序数据   
    ) T: ?' w& D; \7 E. s- w9 B% l; ^& G
  10. }
复制代码

5 F- i) i, m( Q" S$ G
收藏 1 评论0 发布时间:2022-1-13 20:47

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版