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

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

[复制链接]
STMCU小助手 发布时间:2022-1-13 20:47
1、RTC时钟简介
  STM32 的实时时钟(RTC)是一个独立的定时器,在相应软件配置下,可提供时钟日历的功能。 详细资料请参考ALIENTEK的官方文档——《STM32F1开发指南(精英版-库函数版)》,以下为博主摘录要点:
  • RTC 模块和时钟配置系统(RCC_BDCR 寄存器)在后备区域 ,系统复位后,会自动禁止访问后备寄存器和 RTC ,所以在要设置时间之前, 先要取消备份区域(BKP)的写保护
    . i$ ]; c7 u* m5 s$ D! T
  • RTC 内核完全独立于 RTC APB1 接口,而软件是通过 APB1 接口访问 RTC 的预分频值、计数器值和闹钟值,因此需要等待时钟同步,寄存器同步标志位(RSF)会硬件置1
    7 f2 X/ ^* C6 h  L5 F- j6 Z- W3 c6 O
  • RTC相关寄存器包括:控制寄存器(CRH、CRL)、预分频装载寄存器(PRLH、PRLL)、预分频器余数寄存器(DIVH、DIVL)、计数寄存器(CNTH、CNTL)、闹钟寄存器(ALRH、ALRL)
  • STM32备份寄存器,存RTC校验值和一些重要参数,最大字节84,可由VBAT供电
  • 计数器时钟频率:RTCCLK频率/(预分频装载寄存器值+1)* o5 s; U# y, @
    - M4 w9 V! I) z. L$ p! K# q& {6 X
" C1 M% K0 |, R; c* B
2、软硬件设计
  由于RTC是STM32芯片自带的时钟资源,所以自主开发的时候只需要在设计时加上晶振电路和纽扣电池即可。编程时在HARDWARE文件夹新建 rtc.c、rtc.h 文件。

4 z4 t* u+ \6 U5 @/ ^  o5 n; Q, z: K2 O# T8 m
3、时钟配置与函数编写
  为了使用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)
闰年判断

& ]/ L: h1 c1 C% U! s
 事实上,以上函数并不都要,闹钟没有用到的话就不要,秒中断也可以不作处理,看项目需求。
  1.   1 #include "sys.h"
    / f6 H3 R4 V0 W8 s7 q2 a
  2.   2 #include "delay.h"
    ! N' j1 U* u/ s7 _% f
  3.   3 #include "rtc.h"             . X$ J; c! ]) @6 p1 H$ L# V) _
  4.   4        
    , ]& A7 m9 q1 F& t( n% d
  5.   5 _calendar_obj calendar;//时钟结构体 6 N2 e9 ], W* p" u* J; C& o+ ~- k
  6.   6  ! S- z5 c( N/ n! r) `
  7.   7 static void RTC_NVIC_Config(void)# o- L! a0 C9 P: C
  8.   8 {   
    + `+ ~4 `$ ~: b: W# @- m
  9.   9     NVIC_InitTypeDef NVIC_InitStructure;
    2 K+ N( T2 p4 i) T9 _/ y
  10. 10     NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;        //RTC全局中断  E+ Q* C: _/ V/ z# X( ~( _5 e+ N
  11. 11     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;    //先占优先级1位,从优先级3位
    3 }( f8 q8 O3 Z4 U
  12. 12     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;    //先占优先级0位,从优先级4位7 f# Y* ]/ G  z6 f5 G- J8 T. \/ S
  13. 13     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;        //使能该通道中断
    0 r: n* t6 l" s% m
  14. 14     NVIC_Init(&NVIC_InitStructure);        //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器% P! y5 `; L9 {
  15. 15 }
    ( h. E. _; |$ I2 R: \+ c" b; ~
  16. 16
    ! N, _  I% P2 Z/ I. r& ~, ]
  17. 17 //实时时钟配置
    8 m: m9 \5 E! \* U
  18. 18 //初始化RTC时钟,同时检测时钟是否工作正常
    5 V3 C2 f4 Z9 [4 f2 Z  s$ |" Y
  19. 19 //BKP->DR1用于保存是否第一次配置的设置
    $ H3 m0 B$ ?# D$ @1 s" T
  20. 20 //返回0:正常
    / U: v( v$ E6 {% e: a' `+ `) e
  21. 21 //其他:错误代码
    2 Y) f5 ]2 v' t. A) g* P8 j- A
  22. 22
    ' T# k" ?& Z8 o! m( }
  23. 23 u8 RTC_Init(void)4 e9 M( @/ R: j4 G
  24. 24 {
    5 X- E+ C& g) n# A- a
  25. 25     //检查是不是第一次配置时钟+ h7 S4 C8 B" [4 |, B+ U
  26. 26     u8 temp=0;
    ) X6 G0 s& e  e) ?' O; m
  27. 27     RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);    //使能PWR和BKP外设时钟   
    1 s* T6 D+ _1 i
  28. 28     PWR_BackupAccessCmd(ENABLE);    //使能后备寄存器访问  
    / q/ q* i$ E7 j7 v; N0 x
  29. 29     if (BKP_ReadBackupRegister(BKP_DR1) != 0x5051)        //从指定的后备寄存器中读出数据:读出了与写入的指定数据不相乎
    6 `* n( [+ T  U7 l( j! t
  30. 30     {                  
    * ?* N  o% Y& b! ]7 D/ J. C
  31. 31         BKP_DeInit();    //复位备份区域     
    / J4 g; Z% o7 l- t7 Y9 K' c7 x
  32. 32         RCC_LSEConfig(RCC_LSE_ON);    //设置外部低速晶振(LSE),使用外设低速晶振
    ) Y( e" C+ \; s! Q6 m! I) R
  33. 33         while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET&&temp<250)    //检查指定的RCC标志位设置与否,等待低速晶振就绪  V. `2 @# I; l! W4 X% X
  34. 34         {
    6 t. E0 P. B: @& j
  35. 35             temp++;
    7 N; j+ r; s, ^9 L2 s$ ^
  36. 36             delay_ms(10);: f0 v5 c$ [( w% j$ _
  37. 37         }; _6 L. _$ n! T1 Y! K6 a$ [6 [
  38. 38         if(temp>=250)return 1;//初始化时钟失败,晶振有问题          y* B+ y4 S6 q- a
  39. 39         RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);        //设置RTC时钟(RTCCLK),选择LSE作为RTC时钟    6 g" R* v. X1 C' f) g
  40. 40         RCC_RTCCLKCmd(ENABLE);    //使能RTC时钟  " y" P4 E" y% m2 g9 _  y
  41. 41         RTC_WaitForLastTask();    //等待最近一次对RTC寄存器的写操作完成' ?+ n" m5 l9 ?9 o; J
  42. 42         RTC_WaitForSynchro();        //等待RTC寄存器同步  5 \1 M9 N/ W  ?# e! b0 c3 d
  43. 43         RTC_ITConfig(RTC_IT_SEC, ENABLE);        //使能RTC秒中断
    5 M9 c$ S3 n4 g! Y* p
  44. 44         RTC_WaitForLastTask();    //等待最近一次对RTC寄存器的写操作完成; b$ s* B" o: l! z
  45. 45         RTC_EnterConfigMode();/// 允许配置   
      N) [7 j" {2 ?4 z5 c* A3 Q
  46. 46         RTC_SetPrescaler(32767); //设置RTC预分频的值2 y9 v& N5 v; a& Z
  47. 47         RTC_WaitForLastTask();    //等待最近一次对RTC寄存器的写操作完成
    6 W, Q. ~0 v! F8 ~
  48. 48         RTC_Set(2018,4,2,17,37,00);  //设置时间    9 q  M2 v* R% {6 V  O" V
  49. 49         RTC_ExitConfigMode(); //退出配置模式  
    + J( ^4 u- w5 T
  50. 50         BKP_WriteBackupRegister(BKP_DR1, 0X5051);    //向指定的后备寄存器中写入用户程序数据# @9 I# Q. J3 @9 B  j8 J9 G
  51. 51     }
    ' y( W- q, b+ B3 J
  52. 52     else//系统继续计时
    ( V* [% j: M  d3 P& |; O
  53. 53     {' q, C. y+ k% r3 X2 Q: m! i
  54. 54
    / o' K6 N) Z3 t6 T
  55. 55         RTC_WaitForSynchro();    //等待最近一次对RTC寄存器的写操作完成
    ( e: i* i5 |0 [. y& l
  56. 56         RTC_ITConfig(RTC_IT_SEC, ENABLE);    //使能RTC秒中断& h( d2 B: ?+ c6 _1 t5 h( M
  57. 57         RTC_WaitForLastTask();    //等待最近一次对RTC寄存器的写操作完成
    6 `$ `& W# @0 J- u3 J6 t
  58. 58     }
    % i& x7 s  F3 Q4 Y" j; u) f1 j( b/ N
  59. 59     RTC_NVIC_Config();//RCT中断分组设置                                 
      N0 p, y4 b- T, g& v
  60. 60     RTC_Get();//更新时间    ' A* I4 d. a) G& s4 C( e
  61. 61     return 0; //ok
    1 k4 F9 J& Y1 p* J
  62. 62 6 U% i' v5 C; ?1 q" q% s0 n/ p% m
  63. 63 }                             * l8 v9 S& T, o/ @
  64. 64 //RTC时钟中断1 ^7 E" g" l0 t
  65. 65 //每秒触发一次  9 J0 F  |$ m- E& ~
  66. 66 //extern u16 tcnt;
    : D0 b4 a$ E& u+ r. P) o* {! U
  67. 67 void RTC_IRQHandler(void)
    9 R# d+ A! P/ y+ C
  68. 68 {         
    5 x" I: W7 o: E  s9 C
  69. 69 //    if (RTC_GetITStatus(RTC_IT_SEC) != RESET)//秒钟中断- w6 I4 f5 Z: \
  70. 70 //    {                            3 L8 C" ~, w$ a
  71. 71 //        RTC_Get();//更新时间   
    $ \* Y" W8 V4 X, O) ^. F* N9 ?
  72. 72 //     }7 z# `: p1 u/ B
  73. 73 //    if(RTC_GetITStatus(RTC_IT_ALR)!= RESET)//闹钟中断
    3 C, a: x" S2 G0 l) X, @8 h
  74. 74 //    {
    0 J& O) Y* i5 ?7 P4 T1 F0 a- R
  75. 75 //        RTC_ClearITPendingBit(RTC_IT_ALR);        //清闹钟中断          & i0 i8 U& K+ s
  76. 76 //      RTC_Get();                //更新时间   - j( G; B2 A$ p  N
  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);//输出闹铃时间   
    9 k+ u- Z% E5 M' q5 x! r
  78. 78 //        , h5 t' N: Q+ j+ w$ @/ i, x/ d; q6 ]
  79. 79 //      }                                                    + Z0 y, r! s1 }" m( Y& g5 c/ [
  80. 80     RTC_ClearITPendingBit(RTC_IT_SEC|RTC_IT_OW);        //清闹钟中断9 D8 r$ m% e6 _0 i
  81. 81     RTC_WaitForLastTask();                                                   - c, z7 M8 h4 y& H$ W/ u+ F$ V
  82. 82 }
    / s% a& M+ H0 M; Q* z! \
  83. 83 ' _& P, L6 F; i6 o* u* m
  84. 84
    4 n$ v) e" L6 G9 ]  @: J9 n. H
  85. 85 //判断是否是闰年函数
    ; {, x5 j, a" a+ {' q! i: `
  86. 86 //月份   1  2  3  4  5  6  7  8  9  10 11 12
    4 h2 k7 p* x7 B  H5 r
  87. 87 //闰年   31 29 31 30 31 30 31 31 30 31 30 31
    0 Q9 s; F% Y% V' s3 z% N
  88. 88 //非闰年 31 28 31 30 31 30 31 31 30 31 30 31
    : r7 Y7 l0 ?- R7 b
  89. 89 //输入:年份
    ' v/ d/ o$ L8 K% o9 e
  90. 90 //输出:该年份是不是闰年.1,是.0,不是
    2 `) P, @# v) m+ Q3 z- t
  91. 91 u8 Is_Leap_Year(u16 year)
    # _+ [9 b1 R, q$ Q2 g- R
  92. 92 {              ) P" W0 r/ i' Z& G7 E; x4 v
  93. 93     if(year%4==0) //必须能被4整除, k2 f4 J! ?) }" C
  94. 94     {
    & G2 U9 \. q0 B7 G# _; n
  95. 95         if(year%100==0)
    ' N( _6 D& T* g* x$ l
  96. 96         {
    3 O4 c+ r; D' q( T0 z
  97. 97             if(year%400==0)return 1;//如果以00结尾,还要能被400整除        
      p, ^6 |9 G0 z6 _6 ?/ M# H: Y7 m
  98. 98             else return 0;   6 e6 y7 x+ E2 _
  99. 99         }else return 1;   
    6 e+ \0 C2 M! t) `6 e
  100. 100     }else return 0;   
      d% L; g; q% A; l* T
  101. 101 }4 a% P% d6 A( a, c8 C2 {6 Z; E
  102. 102
    5 M, }0 i' e- Y9 u) k7 p
  103. 103 3 r0 K* D# B% Z* r5 y9 M
  104. 104 //设置时钟/ L+ M  w- e0 _; ?; L
  105. 105 //把输入的时钟转换为秒钟
    ) ^5 ]) A2 G& G) O! ^- X5 ]0 S
  106. 106 //以1970年1月1日为基准
    $ E1 ^5 }$ p7 {/ o' ~4 l
  107. 107 //1970~2099年为合法年份' U% Z: Q3 B  g  f7 x
  108. 108 //返回值:0,成功;其他:错误代码.+ q! W+ L; l8 c$ V
  109. 109 //月份数据表                                             
    / L0 ]. n5 i( O/ ]
  110. 110 const u8  table_week[12]={0,3,3,6,1,4,6,2,5,0,3,5}; //月修正数据表      
    $ a, P, I4 u- T7 ^& ~
  111. 111 const u8 mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31};//平年的月份日期表
    % F4 Y1 I: G( d. \0 D! q- I
  112. 112
    / H# a* U: T% w3 m: A
  113. 113 u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec)
    7 c9 p7 s5 L, S3 D1 }6 A
  114. 114 {
    3 b. {6 u; z6 X, J8 J$ X
  115. 115     u16 t;
    3 ?7 _9 j0 A7 U5 [
  116. 116     u32 seccount=0;
    ' V( f& C" g( c/ ^
  117. 117     if(syear<1970||syear>2099)return 1;       # L: J* Y4 b' E2 D; K" @% K5 N
  118. 118     for(t=1970;t<syear;t++)    //把所有年份的秒钟相加
    2 c7 ]! o) `/ C  s
  119. 119     {8 ^1 n  C" E& L  E, a7 J7 v
  120. 120         if(Is_Leap_Year(t))seccount+=31622400;//闰年的秒钟数
    % P' c. _1 c1 ~" L3 O
  121. 121         else seccount+=31536000;              //平年的秒钟数
    ; D2 a6 l; h/ |+ [
  122. 122     }( W$ [" y4 ]: g1 N% Y! y, c2 X
  123. 123     smon-=1;6 V; s0 c1 f3 c. L% G+ x- n
  124. 124     for(t=0;t<smon;t++)       //把前面月份的秒钟数相加5 V; N' Y9 ?  {. k: {2 \
  125. 125     {% Y  F3 Z5 d2 \& p  p
  126. 126         seccount+=(u32)mon_table[t]*86400;//月份秒钟数相加
    7 L% g' P, G9 b# y0 m: Z' T" @
  127. 127         if(Is_Leap_Year(syear)&&t==1)seccount+=86400;//闰年2月份增加一天的秒钟数       - M) v& i2 z' r& r! t6 q2 ]7 N/ F
  128. 128     }
    * U0 k* z6 U" D8 [& A$ A" S
  129. 129     seccount+=(u32)(sday-1)*86400;//把前面日期的秒钟数相加 3 ~9 O' K# P- s( O' R+ y+ s; }% Y- c9 [% l
  130. 130     seccount+=(u32)hour*3600;//小时秒钟数6 i: P6 N3 X% z$ Z3 _
  131. 131     seccount+=(u32)min*60;     //分钟秒钟数
    4 |5 r0 d$ l' n1 m2 o# |
  132. 132     seccount+=sec;//最后的秒钟加上去$ X$ N  k* R. l3 O' E6 d
  133. 133
    0 R8 R# n- ^3 O
  134. 134     RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);    //使能PWR和BKP外设时钟  # m/ Q( I9 Z9 G3 v& u# O$ C
  135. 135     PWR_BackupAccessCmd(ENABLE);    //使能RTC和后备寄存器访问
    ; D- x2 C: a& z$ v4 G/ `
  136. 136     RTC_SetCounter(seccount);    //设置RTC计数器的值
    5 H4 G; x9 d6 j4 [6 x& g8 N* T: D
  137. 137
    % D6 W# S6 V8 g2 `$ l9 Z9 e
  138. 138     RTC_WaitForLastTask();    //等待最近一次对RTC寄存器的写操作完成      
    1 Y; Z. `( ^! f9 q# P+ A/ S
  139. 139     return 0;        ) g+ K* I: `- w7 }! ]# ?' Z& v
  140. 140 }* ]% Z$ Z/ @5 p. `
  141. 141
    ( R, q& j  ~% D1 M
  142. 142 //初始化闹钟          5 G; g" P$ _* ?+ l/ O
  143. 143 //以1970年1月1日为基准
    1 ^# ]" @. P- g/ L  P" b( ?+ i
  144. 144 //1970~2099年为合法年份; n/ a' `/ ^; }8 V2 Z
  145. 145 //syear,smon,sday,hour,min,sec:闹钟的年月日时分秒   
    - v* v2 a5 `- }/ Q4 V- N8 P
  146. 146 //返回值:0,成功;其他:错误代码.: ]& X/ W' L) B  l/ i) u
  147. 147 u8 RTC_Alarm_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec)* M% D+ h* l$ K) w
  148. 148 {7 U& r6 C( N0 l0 `- q1 a  U4 ~
  149. 149     u16 t;# \5 T6 O& o# c5 |3 ^2 }
  150. 150     u32 seccount=0;: o/ p1 g) \- a7 d( R: i- l
  151. 151     if(syear<1970||syear>2099)return 1;       ) D+ ~* R# ]* l  \* b- L8 Q
  152. 152     for(t=1970;t<syear;t++)    //把所有年份的秒钟相加
    . ?& h2 S* Y& v5 H
  153. 153     {
    $ P: m* R/ }/ j1 B
  154. 154         if(Is_Leap_Year(t))seccount+=31622400;//闰年的秒钟数
    $ ]; k! U) Z+ O" Q
  155. 155         else seccount+=31536000;              //平年的秒钟数
    5 w% q) j* V9 J" h- W
  156. 156     }% T# }4 e. g6 J! X9 [7 e
  157. 157     smon-=1;! G& G( b# v4 i' }4 [0 y
  158. 158     for(t=0;t<smon;t++)       //把前面月份的秒钟数相加
    . g5 U3 |( \8 B! ^" E
  159. 159     {3 I3 \" u$ Y4 N
  160. 160         seccount+=(u32)mon_table[t]*86400;//月份秒钟数相加+ m9 r( z5 Y( o) o! G
  161. 161         if(Is_Leap_Year(syear)&&t==1)seccount+=86400;//闰年2月份增加一天的秒钟数       / `7 Q- r; x- ]. G3 f0 o% J/ ?0 g% O9 U
  162. 162     }3 H0 A: q$ O, d( X
  163. 163     seccount+=(u32)(sday-1)*86400;//把前面日期的秒钟数相加
    % s% Q8 q% T$ ~# D9 L% t4 U" Q
  164. 164     seccount+=(u32)hour*3600;//小时秒钟数
    ) Z; N" B$ N* I/ B. c
  165. 165     seccount+=(u32)min*60;     //分钟秒钟数/ m1 C- l2 X2 _4 I
  166. 166     seccount+=sec;//最后的秒钟加上去                 9 J  f! P! {  }7 H$ k# `
  167. 167     //设置时钟$ T" Q! b" Y" ]0 B4 u9 s! \
  168. 168     RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);    //使能PWR和BKP外设时钟   6 {! e! ~/ Z* Q- f  |8 n" K
  169. 169     PWR_BackupAccessCmd(ENABLE);    //使能后备寄存器访问  
    ) a8 J3 X! K" d& p( u3 p
  170. 170     //上面三步是必须的!
    ; h" A4 C* z$ b
  171. 171     ! o* f: ?' G# W
  172. 172     RTC_SetAlarm(seccount);
    $ S+ h4 j* M$ q( ^9 k
  173. 173  
    : R; Q" O: R; t/ i: Q" n4 W8 {
  174. 174     RTC_WaitForLastTask();    //等待最近一次对RTC寄存器的写操作完成      
    6 h/ A, |3 s2 x0 K! J- R$ @  i
  175. 175     # e% G2 v. v  y$ X
  176. 176     return 0;        
    1 c7 @( Z. g+ L! ^0 J
  177. 177 }
    " P. }, {4 g6 Q0 o8 v" S4 ]! b
  178. 178
    ! B: l3 P. B. a' ?% A& J* t7 b$ B0 a
  179. 179 & y. o2 y) j  _6 g$ t/ K/ s
  180. 180 //得到当前的时间
    ! ]0 Q  {  i+ V4 J; a: ~0 s; d
  181. 181 //返回值:0,成功;其他:错误代码.
    3 {4 }& r0 e* j0 Y5 A
  182. 182 u8 RTC_Get(void)
    6 E4 e9 X9 U* i/ m) u  i
  183. 183 {9 p. G% f" C9 |7 ?2 v
  184. 184     static u16 daycnt=0;( c- u) ~  b! d2 d
  185. 185     u32 timecount=0;
    : H$ V, _" }  F& o7 V# B' r5 d0 a
  186. 186     u32 temp=0;
    1 J! X6 I/ e( O1 e3 M% g
  187. 187     u16 temp1=0;      ) M8 k4 n) R! v. d
  188. 188     timecount=RTC_GetCounter();     
    % R3 O& P: s, x. O3 c- F
  189. 189      temp=timecount/86400;   //得到天数(秒钟数对应的)
    0 ]  N$ X- o  ~  f) O
  190. 190     if(daycnt!=temp)//超过一天了
    1 m$ D  p- i( l, t5 O7 G3 E
  191. 191     {      1 _7 b3 ]- z  Z! E6 b
  192. 192         daycnt=temp;! M6 N, r! _' k8 V
  193. 193         temp1=1970;    //从1970年开始
    9 H+ l0 a4 Y# u
  194. 194         while(temp>=365)" g% S* f6 v! [/ C& i8 f
  195. 195         {                 * b0 g* D% P7 s& ~
  196. 196             if(Is_Leap_Year(temp1))//是闰年
    + R" T2 X# s2 [8 ]) ^
  197. 197             {
    5 ~1 B) R& p; _- V
  198. 198                 if(temp>=366)temp-=366;//闰年的秒钟数
      x3 V4 H9 s8 `# r) O
  199. 199                 else {temp1++;break;}  
    7 h/ A% |6 i# s* _+ B
  200. 200             }9 V% a7 N: E% z0 u* ^
  201. 201             else temp-=365;      //平年
      k# P. E# Q1 g3 M
  202. 202             temp1++;  
    ! `& w/ H, Z) L: r& V4 t7 S
  203. 203         }   
    3 H; i& u( B; O- {) E  L
  204. 204         calendar.w_year=temp1;//得到年份' z6 h+ s* p& O2 b2 i- C4 B
  205. 205         temp1=0;
    * m2 V% Q0 c! l$ j$ W
  206. 206         while(temp>=28)//超过了一个月
    5 @% ]8 o5 Y$ h, Q( I) i, i
  207. 207         {
    % W, U! N+ h2 P% R( W$ Z
  208. 208             if(Is_Leap_Year(calendar.w_year)&&temp1==1)//当年是不是闰年/2月份
    : `# G1 Z9 p5 y, Y
  209. 209             {7 y$ ]% a2 b! k0 o% t
  210. 210                 if(temp>=29)temp-=29;//闰年的秒钟数; `( V7 c  H; s  _
  211. 211                 else break;   w7 A8 a. m# ]
  212. 212             }, f4 w; x0 z. f2 I$ p
  213. 213             else 3 u+ h- l6 e7 l0 O3 m: x, @
  214. 214             {; v7 L4 C' ]9 y, y- R
  215. 215                 if(temp>=mon_table[temp1])temp-=mon_table[temp1];//平年
    3 ~- h9 |, W+ h
  216. 216                 else break;
    5 x. Y& ~$ a) g: r0 V+ m
  217. 217             }
    - w+ W" }% X+ ]. Z8 a5 T4 D
  218. 218             temp1++;  
    5 ^, I# P  t1 H1 @
  219. 219         }
    : j( ^0 u1 J4 n; L# {% i% G# b$ k
  220. 220         calendar.w_month=temp1+1;    //得到月份
    3 N8 q( r# [0 @0 \+ L* L% e; \
  221. 221         calendar.w_date=temp+1;      //得到日期 6 ?' b+ U- I1 q3 q, B9 U) M
  222. 222     }
    1 N9 O+ _9 {1 }1 D& ?* L5 V
  223. 223     temp=timecount%86400;             //得到秒钟数         
    ( j" s; V6 ]5 j0 ~
  224. 224     calendar.hour=temp/3600;         //小时
    7 l5 C8 O, B+ h0 b7 U
  225. 225     calendar.min=(temp%3600)/60;     //分钟   
    ' M0 q) b" h% e! D" ~
  226. 226     calendar.sec=(temp%3600)%60;     //秒钟1 `, T& B7 S& }. C
  227. 227     calendar.week=RTC_Get_Week(calendar.w_year,calendar.w_month,calendar.w_date);//获取星期1 P: q5 Y' e2 B1 g; K7 o* q0 ]
  228. 228     calendar.msec=(32767-RTC_GetDivider())*    1000/32767;
    ! I1 u8 q# V* {9 Z9 w# C
  229. 229     return 0;
    5 V. b2 B) }1 V/ s' u$ k$ b, o1 w
  230. 230 }    ) z5 C( m4 [6 W; _0 G* l, B
  231. 231 . ]' j: v+ q! E9 g
  232. 232
    , P1 ]) l4 e1 f# `0 r. m
  233. 233 //获得现在是星期几
    ( T$ }/ j! T" [( U, g/ s6 c
  234. 234 //功能描述:输入公历日期得到星期(只允许1901-2099年)
    / k2 A% \$ M- q' U* F" y5 X+ ?
  235. 235 //输入参数:公历年月日 + E+ _4 k, g6 P7 N: t5 a5 R9 h
  236. 236 //返回值:星期号                                                                                         
    ! Z* F( ]; d3 ?! K8 W0 e  p* N6 S
  237. 237 u8 RTC_Get_Week(u16 year,u8 month,u8 day)5 ]* E6 }0 h; W+ l& Q- r6 g+ ?, {/ ]
  238. 238 {   
    ; a* l- Y0 k8 t- J; P
  239. 239     u16 temp2;4 m2 N+ k* k# z, O* g
  240. 240     u8 yearH,yearL;+ J" E2 ?% Z$ D& Q' a5 M
  241. 241     4 w" x9 O; r! h
  242. 242     yearH=year/100;    yearL=year%100;
    $ |# ]) m/ N/ V6 F. n$ o
  243. 243     // 如果为21世纪,年份数加100  
    % M/ c& q9 D" v/ Y
  244. 244     if (yearH>19)yearL+=100;
    + k) H, x( v8 S3 p
  245. 245     // 所过闰年数只算1900年之后的  
    # d7 ?- B1 X8 v* ^8 V8 l
  246. 246     temp2=yearL+yearL/4;! N9 a0 |. z- \  K3 n0 f& t
  247. 247     temp2=temp2%7; 9 B- u# o6 a2 i  G5 v2 c
  248. 248     temp2=temp2+day+table_week[month-1];4 _+ ^6 W  }0 H0 Q# {. S
  249. 249     if (yearL%4==0&&month<3)temp2--;
    - M; F( T* f0 s1 X- U
  250. 250     return(temp2%7);
    3 j4 h& @& r% E  q* _( ~* _9 X! O
  251. 251 }   
复制代码
  1. 1 #include "sys.h"
    7 \! P0 i) D5 ]4 Q3 E; s
  2. 2
    / H4 ~: g, Q, z! \
  3. 3 //时间结构体" Q6 E6 [* ]$ ~6 p! V( w, o" X: u
  4. 4 typedef struct
    4 h- M) x* ]* y7 v/ H/ Z2 `8 h
  5. 5 {- T, l! b" s1 v3 x
  6. 6     vu8 hour;//vu8& y- I- u. @0 U/ X
  7. 7     vu8 min;7 p) \3 v" o8 W6 t! K2 j; o# h
  8. 8     vu8 sec;" m% u/ I% d# W- C% |7 {
  9. 9     vu16 msec;% b7 f$ d; H+ E! X3 Y
  10. 10     ' b2 _) H9 @7 c8 K& \5 V
  11. 11     //公历日月年周2 L* P4 `! C% i. d2 I7 Z+ B7 b
  12. 12     vu16 w_year;
    ' u* z: m& p* Y/ C- I' O, {% q5 R' S3 ^
  13. 13     vu8  w_month;
    8 M1 j( ?" n- s" m$ n; u' ]
  14. 14     vu8  w_date;
    ( x3 h9 Z3 ?2 M( j( A$ Q
  15. 15     vu8  week;         1 w8 {  _" n' u1 s1 p2 ?9 a
  16. 16 }_calendar_obj;                     ) ?6 I) d9 Y9 d/ S
  17. 17 2 n4 p+ g& m6 j" l, k9 P5 a
  18. 18 extern _calendar_obj calendar;    //日历结构体
    1 F& v6 l1 [0 A
  19. 19 extern u8 const mon_table[12];    //月份日期数据表
    $ T. I. y& b% ~: i( h
  20. 20 . _' p  l; f! L# n, \
  21. 21 u8 RTC_Init(void);        //初始化RTC,返回0,失败;1,成功;; M2 {( N& Q0 e0 Z7 w- r6 l, F4 I$ P
  22. 22 u8 Is_Leap_Year(u16 year);//平年,闰年判断
    # `7 k5 y$ j" ^% x1 m
  23. 23 # d% c3 c8 F4 d
  24. 24 //u8 RTC_Alarm_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec);
    * C, s! t3 ^6 z+ R* C$ N
  25. 25 u8 RTC_Get(void);         //更新时间     I" r1 Z0 w. q0 |" l7 R* g- \
  26. 26 u8 RTC_Get_Week(u16 year,u8 month,u8 day);# s- [# J5 v7 q8 n- m
  27. 27 u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec);//设置时间   
复制代码

& o  h6 D, [. P: w# b+ H4 \4、秒钟计时原理
使用外部32.768KHz的晶振作为时钟的输入频率,设置预分频装载寄存器的值为32767,根据计算公式,刚好可以得到1秒的计数频率。时间基准设置为1970年1月1日0时0分0秒,后续的时间都以这个为基准进行计算。RTC计数器是32位的,理论上可以记录136年左右的时间。(注意不必在秒中断里更新时间)

9 m# K7 P% w& ?9 h' ^1 l+ H1 |6 ^* a. G: W! I+ Y3 \2 f
5、毫秒计时原理
如果要获取到毫秒级的时钟怎么办?在我的项目中就有这样的要求。事实上,获取毫秒时钟也非常简单。
查阅开发指南,RTC预分频器余数寄存器(RTC_DIVH、RTC_DIVL),这两个寄存器的作用就是用来获得比秒钟更为准确的时钟。 该寄存器的值自减的,用于保存还需要多少时钟周期获得一个秒信号。在一次秒钟更新后,由硬件重新装载。这两个寄存器和 RTC 预分频装载寄存器位数是一样的。也就是说,如果预分频装载寄存器的值为32767,那么余数寄存器就会在每一次秒更新时由硬件重新装载为32767,然后向下计数,计数到0表示一秒,也即1000ms。
  因此,我们在时钟结构体中添加msec成员
  1. 1 //时间结构体* F# u: W, q# l# ?
  2. 2 typedef struct
    & j6 S# o1 C) j' @
  3. 3 {& h2 V! R1 l4 j$ s1 s% g) N' v" o
  4. 4     vu8 hour;//vu8
    6 v) e1 q1 {  f- c5 q. [% L! f5 w
  5. 5     vu8 min;7 W* m6 C0 Q8 x$ c' G: C$ ]
  6. 6     vu8 sec;
    9 J4 t3 I: V% u0 b! o3 a& e" M
  7. 7     vu16 msec;& `9 H' ?. O1 i0 }, V
  8. 8     
    + m0 j' A; W' \, [9 R; a8 X$ Z1 _
  9. 9     //公历日月年周
    " y- v9 g3 w: }7 @
  10. 10     vu16 w_year;
    ' J5 F( c% G1 W. d. A
  11. 11     vu8  w_month;
    . ^  D- F/ Z) @4 ~4 O, c
  12. 12     vu8  w_date;" x1 t2 g1 o0 Z7 L5 n+ q7 l. }
  13. 13     vu8  week;         
    ; {# A# U' b( p: Y
  14. 14 }_calendar_obj;  
复制代码
" ?" E0 v( ^" C% k3 U/ \, u
 获取毫秒时间
  1. 1 calendar.msec=(32767-RTC_GetDivider())*1000/32767;
复制代码

8 F8 ^0 `- ]  W% d8 z6、修改时间  
   如果RTC时钟在使用的过程中不准了(我遇到的情况大概是掉电跑了2个月,重新测试的时候差了2分钟左右),可以重新校准时钟。我们在备份区域 BKP_DR1 中写入 0X5051 ,下次开机(或复位)的时候,先读取 BKP_DR1 的值,然后判断是否是 0X5051来决定是不是要配置。 如果要修改时间,请将0x5051改为其它数据,修改RTC_Set函数实参,再重新烧写一下程序即可。

; N+ J# D& z6 X" S$ ^. X
  1. if (BKP_ReadBackupRegister(BKP_DR1) != 0x5051)
    3 f( ]/ Q% H6 z3 V+ I: [
  2. {
    " C: j  T0 e0 F; O( U! J
  3.     ...
    3 T$ q+ V5 l6 t$ C0 C
  4.     RTC_EnterConfigMode();/// 允许配置    - F8 @8 x2 ^) t4 n4 B
  5.     RTC_SetPrescaler(32767); //设置RTC预分频的值9 b$ }# s0 ~4 E+ I% D
  6.     RTC_WaitForLastTask();    //等待最近一次对RTC寄存器的写操作完成# @( N' ~- ?& i  |' F! m
  7.     RTC_Set(2018,4,2,17,37,00);  //设置时间    : q7 X: L; X1 l( G! Z" Y0 t6 L9 @
  8.     RTC_ExitConfigMode(); //退出配置模式  2 W) |9 W4 _  l. s. d
  9.     BKP_WriteBackupRegister(BKP_DR1, 0X5051);    //向指定的后备寄存器中写入用户程序数据   
    $ k% e! [* x- D, e4 }) }
  10. }
复制代码
5 X6 d; ]$ O0 W0 v" `1 S4 N
收藏 1 评论0 发布时间:2022-1-13 20:47

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版