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

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

[复制链接]
STMCU小助手 发布时间:2022-1-13 20:47
1、RTC时钟简介
  STM32 的实时时钟(RTC)是一个独立的定时器,在相应软件配置下,可提供时钟日历的功能。 详细资料请参考ALIENTEK的官方文档——《STM32F1开发指南(精英版-库函数版)》,以下为博主摘录要点:
  • RTC 模块和时钟配置系统(RCC_BDCR 寄存器)在后备区域 ,系统复位后,会自动禁止访问后备寄存器和 RTC ,所以在要设置时间之前, 先要取消备份区域(BKP)的写保护
    4 _2 `: g) d( r
  • RTC 内核完全独立于 RTC APB1 接口,而软件是通过 APB1 接口访问 RTC 的预分频值、计数器值和闹钟值,因此需要等待时钟同步,寄存器同步标志位(RSF)会硬件置18 D/ ]+ K, {. F# Q4 X! O% R1 n" U
  • RTC相关寄存器包括:控制寄存器(CRH、CRL)、预分频装载寄存器(PRLH、PRLL)、预分频器余数寄存器(DIVH、DIVL)、计数寄存器(CNTH、CNTL)、闹钟寄存器(ALRH、ALRL)
  • STM32备份寄存器,存RTC校验值和一些重要参数,最大字节84,可由VBAT供电
  • 计数器时钟频率:RTCCLK频率/(预分频装载寄存器值+1)& z5 ]( d- A7 s! y( o
    4 t- ?; z- W7 Q, q2 u
+ ?; K% Y2 e2 Z. g2 z  b# e
2、软硬件设计
  由于RTC是STM32芯片自带的时钟资源,所以自主开发的时候只需要在设计时加上晶振电路和纽扣电池即可。编程时在HARDWARE文件夹新建 rtc.c、rtc.h 文件。
2 C6 {# Y% M6 y% N) v
( m6 B. v2 ]6 t( K9 B
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)
闰年判断
2 R' |& u' F) F4 e. [
 事实上,以上函数并不都要,闹钟没有用到的话就不要,秒中断也可以不作处理,看项目需求。
  1.   1 #include "sys.h"
    ; y4 e+ _% X  s1 T* Q) G
  2.   2 #include "delay.h"+ ?2 u$ k# a/ x
  3.   3 #include "rtc.h"             : Z4 D& w( W3 w8 M. U
  4.   4        
    3 U4 j- i& k6 R) D2 [/ d( K2 S
  5.   5 _calendar_obj calendar;//时钟结构体 3 @" w8 I! o2 j' P
  6.   6  
    ! [# x( H/ C# B( r! K
  7.   7 static void RTC_NVIC_Config(void)
    % G7 X" O8 a- l% p0 K
  8.   8 {    + a& O' B8 T7 [) D3 u0 Z% H3 I
  9.   9     NVIC_InitTypeDef NVIC_InitStructure;
    % e# N% k# u8 l5 @+ i
  10. 10     NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;        //RTC全局中断
    8 ~) A3 P; u* O) ?
  11. 11     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;    //先占优先级1位,从优先级3位* p, l/ m, a7 k
  12. 12     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;    //先占优先级0位,从优先级4位
    6 K, n9 |$ x, ?- i5 ^! j4 l
  13. 13     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;        //使能该通道中断+ z1 x* n) h- c" v8 f# B
  14. 14     NVIC_Init(&NVIC_InitStructure);        //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
    5 v$ {2 v' O) c& _( h6 D7 W8 B
  15. 15 }) J+ E3 P9 e$ |8 t. Y! V: \; d
  16. 16 % F7 G! I; l+ ~4 Z
  17. 17 //实时时钟配置
    : q  h9 B" \+ s1 I
  18. 18 //初始化RTC时钟,同时检测时钟是否工作正常
    # S, I, u( K# Z' Y5 f) ~
  19. 19 //BKP->DR1用于保存是否第一次配置的设置
    ! e0 B6 w. p, ~) }, E
  20. 20 //返回0:正常
    , R! [: }: f7 \/ Q, x  D
  21. 21 //其他:错误代码
    5 n+ N6 u4 p0 p9 x
  22. 22   w9 [$ P) Q2 V" \
  23. 23 u8 RTC_Init(void)
    : u( v8 V% s9 r8 x$ k! |  R' |
  24. 24 {* e2 B6 @, p: N9 @& A; d2 O6 D; ?  j
  25. 25     //检查是不是第一次配置时钟2 O1 _( J& ]6 q2 S+ [( c" t# S
  26. 26     u8 temp=0;
    $ t+ W# |0 _* W) U; X9 h+ }% }
  27. 27     RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);    //使能PWR和BKP外设时钟   $ Y. J9 `0 Y' ^: j$ H% u- e
  28. 28     PWR_BackupAccessCmd(ENABLE);    //使能后备寄存器访问  4 g, Z/ x( f  ?) @8 i; [
  29. 29     if (BKP_ReadBackupRegister(BKP_DR1) != 0x5051)        //从指定的后备寄存器中读出数据:读出了与写入的指定数据不相乎
    # `! s, o/ I& n
  30. 30     {                  
    ' c! P- g3 T" e/ {' T( @! e0 ]
  31. 31         BKP_DeInit();    //复位备份区域     
    " ]" }7 m! [: s" ?$ C0 m
  32. 32         RCC_LSEConfig(RCC_LSE_ON);    //设置外部低速晶振(LSE),使用外设低速晶振
    * }1 V+ A5 W3 t
  33. 33         while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET&&temp<250)    //检查指定的RCC标志位设置与否,等待低速晶振就绪
    # j. _( y+ x% j1 E. k) c* k
  34. 34         {
    $ k$ C; n* E5 S& o  X( I9 j  v- a# X/ l
  35. 35             temp++;' d/ [+ d% Y+ Z' K% W. \- D/ b
  36. 36             delay_ms(10);& c' F2 G, O) p$ q- l0 C" k
  37. 37         }3 r# {4 u" n2 q# z7 R% g
  38. 38         if(temp>=250)return 1;//初始化时钟失败,晶振有问题        0 @' M0 j( ^) a( \; c) ^, Q2 M
  39. 39         RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);        //设置RTC时钟(RTCCLK),选择LSE作为RTC时钟    $ F- S/ T- y  {  n
  40. 40         RCC_RTCCLKCmd(ENABLE);    //使能RTC时钟    J7 B8 m& s! o. E2 `
  41. 41         RTC_WaitForLastTask();    //等待最近一次对RTC寄存器的写操作完成
    9 Q, l# U$ o& G* E/ q
  42. 42         RTC_WaitForSynchro();        //等待RTC寄存器同步  : M3 J) k- ~6 s% v- q* m. b0 l9 i" o  F
  43. 43         RTC_ITConfig(RTC_IT_SEC, ENABLE);        //使能RTC秒中断- _1 g3 E+ d0 \# @. Y
  44. 44         RTC_WaitForLastTask();    //等待最近一次对RTC寄存器的写操作完成( n& p3 D6 P- A- L3 }
  45. 45         RTC_EnterConfigMode();/// 允许配置   
    " @( r5 p& B2 _9 a
  46. 46         RTC_SetPrescaler(32767); //设置RTC预分频的值
    6 R" A+ {' s2 z( o
  47. 47         RTC_WaitForLastTask();    //等待最近一次对RTC寄存器的写操作完成/ n. M3 S$ i$ ~5 b6 @& |
  48. 48         RTC_Set(2018,4,2,17,37,00);  //设置时间    ! e, |* j: [' O6 O; X  a
  49. 49         RTC_ExitConfigMode(); //退出配置模式  ( Y2 X7 d5 {; u& M
  50. 50         BKP_WriteBackupRegister(BKP_DR1, 0X5051);    //向指定的后备寄存器中写入用户程序数据! D5 L$ A+ h3 c& A' G( T
  51. 51     }2 ~* \$ `# X9 _( h- y
  52. 52     else//系统继续计时) F* c2 v; u1 P& S, Q. a3 q1 f
  53. 53     {: d( e$ K+ \. h5 X+ o7 m# ?
  54. 54
    # l) j9 j- \$ f6 z
  55. 55         RTC_WaitForSynchro();    //等待最近一次对RTC寄存器的写操作完成5 t) e. M' J( M' V4 n7 V# R
  56. 56         RTC_ITConfig(RTC_IT_SEC, ENABLE);    //使能RTC秒中断
    0 ]6 x0 h3 }0 {% ]+ Y+ W% ?
  57. 57         RTC_WaitForLastTask();    //等待最近一次对RTC寄存器的写操作完成( u0 h2 B/ S/ Y3 }$ g3 x, @
  58. 58     }  x" D* t5 }9 S) m" [/ x1 k% p
  59. 59     RTC_NVIC_Config();//RCT中断分组设置                                 
    6 X; ~' Q( R- q/ n& i2 |8 M+ m6 w, H
  60. 60     RTC_Get();//更新时间    $ j3 L* }! i4 ]  a4 W" a; ?2 M
  61. 61     return 0; //ok
    % ^0 p# D! m6 L3 S% C
  62. 62 / N" g8 T, r( O4 |/ |" i7 [
  63. 63 }                             ' ]9 R& O$ [! h& h1 |* E
  64. 64 //RTC时钟中断
    4 p5 i' r) B, x; _
  65. 65 //每秒触发一次  - Z$ L2 {8 E. c' x" @
  66. 66 //extern u16 tcnt; 4 [4 Q7 P& p: W, O: M
  67. 67 void RTC_IRQHandler(void)( K5 D  F. [! e% q4 K. w4 G
  68. 68 {         
    # |( k9 O) V! }; M! Q9 `
  69. 69 //    if (RTC_GetITStatus(RTC_IT_SEC) != RESET)//秒钟中断
    ( K/ X# q  n+ D; T6 C  r
  70. 70 //    {                           
    $ ]7 r- L; |  p  D: H7 k
  71. 71 //        RTC_Get();//更新时间   
    ' V& g  j2 ^1 K9 a$ L- }, F8 V* @
  72. 72 //     }
    0 H8 s+ K5 P; O* E
  73. 73 //    if(RTC_GetITStatus(RTC_IT_ALR)!= RESET)//闹钟中断
    2 p  V# \; t( ]3 Z1 t6 ^+ P
  74. 74 //    {
    5 z0 k/ m4 l5 z; L( s) s
  75. 75 //        RTC_ClearITPendingBit(RTC_IT_ALR);        //清闹钟中断         
    / |. q0 v5 ]: C7 @! j2 {6 [
  76. 76 //      RTC_Get();                //更新时间   
    : A7 z6 s# Q8 t; w. K
  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);//输出闹铃时间    ! Z$ e. O; @" U$ c8 L. Z
  78. 78 //        
    9 ^$ a7 A* W5 p9 g- }* t* {
  79. 79 //      }                                                    # x! }! v( g0 X/ ]3 C) h, H
  80. 80     RTC_ClearITPendingBit(RTC_IT_SEC|RTC_IT_OW);        //清闹钟中断' x, V: s& F& A( I% P2 {
  81. 81     RTC_WaitForLastTask();                                                   1 q' w1 A5 j. T9 @+ _$ x6 {
  82. 82 }
    ' S! |, I$ N2 r/ C% C
  83. 83
    6 W6 B- f* {' f7 X: s+ J
  84. 84 ( S# U" l% f' e
  85. 85 //判断是否是闰年函数8 R- N* h9 s( N) w$ |4 h: Y
  86. 86 //月份   1  2  3  4  5  6  7  8  9  10 11 12
    ( A- }: Z. j1 T0 \8 d. K6 F4 S0 ]3 _
  87. 87 //闰年   31 29 31 30 31 30 31 31 30 31 30 31
    + X) [! s1 J3 h- }( O
  88. 88 //非闰年 31 28 31 30 31 30 31 31 30 31 30 31
    : t* ~/ W1 ~8 k, d
  89. 89 //输入:年份
    , Z4 g* n9 a( X  ?
  90. 90 //输出:该年份是不是闰年.1,是.0,不是
    9 t) L1 F8 |0 B; K0 D
  91. 91 u8 Is_Leap_Year(u16 year). h% q8 d* _/ Y8 {) A7 o2 }
  92. 92 {              ( a% Z  y8 @# D, [
  93. 93     if(year%4==0) //必须能被4整除  h& x9 S1 H5 F/ |( t
  94. 94     {
    ( O  L5 i" I0 P1 T
  95. 95         if(year%100==0)
    ; ^2 r% v; N3 |6 S7 B* v  P$ E) o7 m
  96. 96         { 4 T6 u( g2 L+ W5 r$ d# x- T
  97. 97             if(year%400==0)return 1;//如果以00结尾,还要能被400整除        2 m& [5 ^- @( R1 ~
  98. 98             else return 0;   
    9 q2 z/ u7 g0 c
  99. 99         }else return 1;   / P' I0 N/ b4 p; x# r2 G
  100. 100     }else return 0;   
    ( X9 Y; V, z1 \, }; E; Y2 S
  101. 101 }" j' A. n- O5 X* |6 B1 L$ G7 r
  102. 102 + G9 w! h. l1 @0 Y' E/ F
  103. 103 4 y4 K2 i2 U: V( X# ^5 C( o
  104. 104 //设置时钟/ _2 [: T% Z, f
  105. 105 //把输入的时钟转换为秒钟
    4 R& b/ }7 E. }6 j( e
  106. 106 //以1970年1月1日为基准
    " r! i4 J- h2 U- R1 B$ J
  107. 107 //1970~2099年为合法年份
    , Q2 n5 h; y7 j& J
  108. 108 //返回值:0,成功;其他:错误代码.
    8 h1 J: b: z- J$ l* w
  109. 109 //月份数据表                                             
    % C' b+ I' z% K; x  `  {7 h, u1 u, q8 p
  110. 110 const u8  table_week[12]={0,3,3,6,1,4,6,2,5,0,3,5}; //月修正数据表      
    & `, [! r; p7 ^: c: N
  111. 111 const u8 mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31};//平年的月份日期表0 I( K" |$ X8 M' m, l2 E. M
  112. 112 - i+ G+ p) X0 T0 ]; t
  113. 113 u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec)
    : O9 f" u' E1 {1 r, C9 d. _2 ]
  114. 114 {' R2 ~2 F7 Z! g$ Y& w' u
  115. 115     u16 t;% u/ i/ q0 C& [" q
  116. 116     u32 seccount=0;
    - F+ B, h5 `: r" g8 [
  117. 117     if(syear<1970||syear>2099)return 1;       4 R# I! n9 V$ h/ n5 N, c$ G! J5 S) P/ [
  118. 118     for(t=1970;t<syear;t++)    //把所有年份的秒钟相加2 ]6 f# b# U+ U  m2 R, u9 O
  119. 119     {
    / S  u) t' r/ h- a. V
  120. 120         if(Is_Leap_Year(t))seccount+=31622400;//闰年的秒钟数! j3 p+ W' s$ j7 h+ z' H/ q
  121. 121         else seccount+=31536000;              //平年的秒钟数
    1 F0 v1 K0 T+ Y7 p% k. C* S
  122. 122     }
    0 s* }8 f% f4 B! j
  123. 123     smon-=1;
    & m* n5 q2 A' h& r
  124. 124     for(t=0;t<smon;t++)       //把前面月份的秒钟数相加7 v1 d  p: z* W1 k" I
  125. 125     {
    1 l6 i7 Y7 T7 o# T
  126. 126         seccount+=(u32)mon_table[t]*86400;//月份秒钟数相加
    / x+ ~5 Q1 c, ^& D- D! l( e/ C  N- X4 s
  127. 127         if(Is_Leap_Year(syear)&&t==1)seccount+=86400;//闰年2月份增加一天的秒钟数       # x6 K1 x6 N6 S! I% c7 D
  128. 128     }' J$ d! [) Y1 X5 P$ F9 ~
  129. 129     seccount+=(u32)(sday-1)*86400;//把前面日期的秒钟数相加
    ( b5 o' r8 q8 D& K+ v
  130. 130     seccount+=(u32)hour*3600;//小时秒钟数
    + c; `3 ]6 c3 [- g" s! C$ f4 n; }
  131. 131     seccount+=(u32)min*60;     //分钟秒钟数
    2 U+ }; i7 D. z# j
  132. 132     seccount+=sec;//最后的秒钟加上去
    % p& P0 J6 N+ m& W0 J  h
  133. 133
    ) `' ~' |) i, D, X6 f. R  q
  134. 134     RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);    //使能PWR和BKP外设时钟  & r0 @- X0 ?* c$ k
  135. 135     PWR_BackupAccessCmd(ENABLE);    //使能RTC和后备寄存器访问
    , J. A8 E1 [% x8 R* ?3 T+ b
  136. 136     RTC_SetCounter(seccount);    //设置RTC计数器的值+ Q% l' }! E8 A$ x: D# i* r
  137. 137 $ l- \( Y  j) `
  138. 138     RTC_WaitForLastTask();    //等待最近一次对RTC寄存器的写操作完成      / T1 w2 ~/ _  r; B0 D/ U
  139. 139     return 0;        9 l+ \7 K8 O: w6 J6 a8 G7 S6 |8 G% Y7 v
  140. 140 }
    + U) Z/ P" T% _
  141. 141
    / c2 V" Q8 _( f: o
  142. 142 //初始化闹钟         
    2 }/ v7 f) \3 C- f4 Q% `* n0 e' n; X
  143. 143 //以1970年1月1日为基准
    0 r0 Q4 t$ [8 K8 Z( r9 m. F( J, ?
  144. 144 //1970~2099年为合法年份7 U* v# u' m+ y& Q8 Y; o: D
  145. 145 //syear,smon,sday,hour,min,sec:闹钟的年月日时分秒   8 M2 U5 C" a  ?/ Q
  146. 146 //返回值:0,成功;其他:错误代码.' C7 A- O' m1 l4 n2 X
  147. 147 u8 RTC_Alarm_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec): u" ]' i0 O% t; C+ V6 @! |9 S. X
  148. 148 {
    ; n7 f/ W% _- K
  149. 149     u16 t;4 b4 b; W' o0 s) _
  150. 150     u32 seccount=0;
    # K/ U: v# |7 z1 O' a. a
  151. 151     if(syear<1970||syear>2099)return 1;      
    ( {- |" h' ?# r6 Q, u8 J
  152. 152     for(t=1970;t<syear;t++)    //把所有年份的秒钟相加
    7 x; ?2 S$ d( @
  153. 153     {2 p$ f0 S, u1 d* C. t) |, c
  154. 154         if(Is_Leap_Year(t))seccount+=31622400;//闰年的秒钟数
      f' O5 ^0 y  k2 b2 s
  155. 155         else seccount+=31536000;              //平年的秒钟数
    / i+ l) L) n7 g- d
  156. 156     }* \. r" r$ N: |0 Y  o
  157. 157     smon-=1;8 ]: x* {6 T6 e7 p* E
  158. 158     for(t=0;t<smon;t++)       //把前面月份的秒钟数相加# U9 e. D, i, R$ x
  159. 159     {2 W$ ^2 J: S  C+ J) c  v- }( S0 H
  160. 160         seccount+=(u32)mon_table[t]*86400;//月份秒钟数相加7 e  B3 K# l  q* u; ~
  161. 161         if(Is_Leap_Year(syear)&&t==1)seccount+=86400;//闰年2月份增加一天的秒钟数       0 S9 |% _) i0 ?2 j. Z9 ^- H
  162. 162     }
    ' y! V* F" D9 v4 z) _! v8 g
  163. 163     seccount+=(u32)(sday-1)*86400;//把前面日期的秒钟数相加
    ' f2 w7 Q9 \8 y  `
  164. 164     seccount+=(u32)hour*3600;//小时秒钟数
    : H  C# }8 m/ c" g( R4 C# U
  165. 165     seccount+=(u32)min*60;     //分钟秒钟数
    7 v" v) c  F% w/ t9 X7 }4 t
  166. 166     seccount+=sec;//最后的秒钟加上去                 
    ) H+ c4 j4 t2 H3 G
  167. 167     //设置时钟. S& |5 b( {8 x2 L, X9 C* `/ q0 w
  168. 168     RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);    //使能PWR和BKP外设时钟   : T5 J3 j, O: I6 B( `: ]
  169. 169     PWR_BackupAccessCmd(ENABLE);    //使能后备寄存器访问  
    9 ?; L- x3 f6 U5 Z6 y
  170. 170     //上面三步是必须的!$ a* i2 Z" N; E$ H- w# m
  171. 171     
    7 U9 ~- ^) g. p  [8 h. `
  172. 172     RTC_SetAlarm(seccount);, @  }, p. @3 c8 K1 b
  173. 173  . o0 N: U* Z- ^* y; \, h. ]
  174. 174     RTC_WaitForLastTask();    //等待最近一次对RTC寄存器的写操作完成      
    + X$ D0 [! I* z
  175. 175     
    % I; j/ c  P* J  k' e2 O2 Z. o- E
  176. 176     return 0;        
    / ]6 W1 [- q. H9 ?
  177. 177 }
    ; w" Z2 D5 b$ a$ C
  178. 178
    " U- l" }  `1 P7 ~
  179. 179 ' i5 T. \4 v2 U' I5 {
  180. 180 //得到当前的时间
    - d1 L7 G% Y" R6 c( c: l3 w, b
  181. 181 //返回值:0,成功;其他:错误代码.
    , z! K6 e/ J: q! @1 \5 q
  182. 182 u8 RTC_Get(void)7 z6 u0 Z+ i: V0 g( z* b4 o# ?; Y
  183. 183 {
    1 J. }1 X& k# \3 o$ m4 Q6 o7 {
  184. 184     static u16 daycnt=0;: I1 [7 }( _5 N. |: T
  185. 185     u32 timecount=0; : o+ h4 i7 ^  k# z8 J
  186. 186     u32 temp=0;4 y% F- k3 N1 {9 A
  187. 187     u16 temp1=0;      
    9 Q4 j0 w9 V; m" b" y
  188. 188     timecount=RTC_GetCounter();     
    1 c* `: B7 h0 e  I9 c8 A0 y$ w( P
  189. 189      temp=timecount/86400;   //得到天数(秒钟数对应的)
    ( j9 N8 B: o; b6 w
  190. 190     if(daycnt!=temp)//超过一天了9 g0 G# U0 O0 r. n
  191. 191     {      2 `* j& N. y4 b5 ~# \5 A! J" E- `
  192. 192         daycnt=temp;
    9 e4 [; F% g/ ]2 N/ U' K- w7 ?
  193. 193         temp1=1970;    //从1970年开始
    0 }/ n5 d$ b$ D2 C& I6 u7 s
  194. 194         while(temp>=365)+ U  K% B. Q; A2 e* t
  195. 195         {                 
    , Q7 P2 X2 `( `- G7 ]2 ?
  196. 196             if(Is_Leap_Year(temp1))//是闰年
    9 \% h9 t$ Y: V  E9 i% w) g
  197. 197             {
    . [( a+ a1 m. v& @. r
  198. 198                 if(temp>=366)temp-=366;//闰年的秒钟数
    1 }4 |% n9 \2 G
  199. 199                 else {temp1++;break;}  
    6 E" o) V" q( T; w/ Q( d
  200. 200             }6 O' f0 s/ Y& E* \: x
  201. 201             else temp-=365;      //平年
    8 _; [, G! ^! \, Y) E
  202. 202             temp1++;  9 O4 p% Z/ _0 {9 k
  203. 203         }   # w: A+ G+ V6 L
  204. 204         calendar.w_year=temp1;//得到年份
    & k" H' ^. z' `% H
  205. 205         temp1=0;
    , T/ _) l% G+ x$ ^7 V
  206. 206         while(temp>=28)//超过了一个月+ m& n! d8 O& ~% k$ N& x; l
  207. 207         {0 @$ Y9 O3 X- j/ {3 e
  208. 208             if(Is_Leap_Year(calendar.w_year)&&temp1==1)//当年是不是闰年/2月份
      A  [( ~* ~; Y9 _9 t/ L
  209. 209             {
    % i( K4 x- y5 N( l
  210. 210                 if(temp>=29)temp-=29;//闰年的秒钟数
    : r5 t3 G, ^: o) L. S/ h
  211. 211                 else break; ' w- C, D$ c* q! {$ x6 m$ k2 X
  212. 212             }( l( h" I( E7 ^: D# V
  213. 213             else
    + b' D9 D) Y! @* j
  214. 214             {7 L7 X: b3 \, S' m; I
  215. 215                 if(temp>=mon_table[temp1])temp-=mon_table[temp1];//平年* z. {9 A  A; R6 e9 u. z# h. Y
  216. 216                 else break;5 c9 K/ V  f6 n' x; B. U9 l. x
  217. 217             }
    0 `* p; u0 e+ s! Y8 z
  218. 218             temp1++;  
    ) H2 P+ P, Z; [1 W' u
  219. 219         }$ B6 h! a! G2 y
  220. 220         calendar.w_month=temp1+1;    //得到月份# B) V; w, o* |1 y" D& o
  221. 221         calendar.w_date=temp+1;      //得到日期
    7 W# `4 `% v, ]- C
  222. 222     }
    , i8 a  a9 U( ?8 Y( s+ O$ R* Q- r
  223. 223     temp=timecount%86400;             //得到秒钟数          6 B2 x( ?! I4 D& q
  224. 224     calendar.hour=temp/3600;         //小时7 ]' n" E- o# Y* j+ r! b1 J4 P
  225. 225     calendar.min=(temp%3600)/60;     //分钟    " A: P0 N: W. c5 ~* t9 T$ r% }
  226. 226     calendar.sec=(temp%3600)%60;     //秒钟
    / z5 R$ D( S( I. ~
  227. 227     calendar.week=RTC_Get_Week(calendar.w_year,calendar.w_month,calendar.w_date);//获取星期! t* }: [5 `; V- V: {
  228. 228     calendar.msec=(32767-RTC_GetDivider())*    1000/32767;
    : `# T0 t9 s7 S; ^& t9 N
  229. 229     return 0;5 L8 \# U2 c& }  C
  230. 230 }    6 O" F/ U9 P1 H
  231. 231
    6 \* S' q" V: n1 K
  232. 232 " |7 q5 Z% D! R1 `# N7 v1 e3 K5 d
  233. 233 //获得现在是星期几$ p6 S& C/ L( {; N. ?2 t/ M  Y
  234. 234 //功能描述:输入公历日期得到星期(只允许1901-2099年)
    . H; k, Z+ ?2 W% `1 b! m7 @3 m
  235. 235 //输入参数:公历年月日 ' Q, j- ]1 ]0 E9 v
  236. 236 //返回值:星期号                                                                                         
    : S' w. q! U% F/ C$ y8 e
  237. 237 u8 RTC_Get_Week(u16 year,u8 month,u8 day)
    ! t. f- M  B' y% q* N8 e; Q
  238. 238 {      f/ p( @' i. C6 b/ {
  239. 239     u16 temp2;
    : D" X0 f! y9 Q( g
  240. 240     u8 yearH,yearL;
    & k$ b- V! ]' |1 g
  241. 241     1 V0 t, b; k$ M0 ~- X4 `& n1 z
  242. 242     yearH=year/100;    yearL=year%100;
    9 t2 q+ v2 N" k* R2 @$ h6 c( ^
  243. 243     // 如果为21世纪,年份数加100  % m8 Y/ W/ w- E9 V2 H
  244. 244     if (yearH>19)yearL+=100;
    3 U9 W  d7 w) i2 b4 X$ l: z
  245. 245     // 所过闰年数只算1900年之后的  " ]) t" M8 e9 a. z- ~3 F- y) |/ a
  246. 246     temp2=yearL+yearL/4;
    6 E$ i# g, @5 R, i0 H* j
  247. 247     temp2=temp2%7; - S: V. S2 F1 t# C6 H
  248. 248     temp2=temp2+day+table_week[month-1];
    - e  Z- U  j% P) U9 V/ x
  249. 249     if (yearL%4==0&&month<3)temp2--;. ?# G3 ]& {4 e
  250. 250     return(temp2%7);1 }8 k' @) U4 r% W% d
  251. 251 }   
复制代码
  1. 1 #include "sys.h"1 ?/ S0 X/ ~& @5 }
  2. 2 , A* \; Q" s4 ~( }$ v/ B
  3. 3 //时间结构体# M# ?1 I! b. ]& r5 s0 W, b
  4. 4 typedef struct 7 F7 N3 O% b1 y9 e1 J, e
  5. 5 {  \, Y! _% |/ W3 w, G
  6. 6     vu8 hour;//vu8
    ! v$ m& S9 ^, \0 R
  7. 7     vu8 min;
      g# L7 D* v  x9 q6 J1 ?+ n+ n% ^5 N
  8. 8     vu8 sec;
    8 y5 U+ L  z, @7 g6 x
  9. 9     vu16 msec;
    / g" e  A: j7 C( _6 i. I3 F' _
  10. 10     
    0 ^0 }: E$ \. d6 M8 M+ \) I
  11. 11     //公历日月年周
    $ X( C' D: {# @2 p6 f
  12. 12     vu16 w_year;
    , ^1 h7 o' [5 o! o
  13. 13     vu8  w_month;
    5 j$ k- B; k6 k" E- b, n" h
  14. 14     vu8  w_date;
    4 Q; `; u( H) }$ C, `' X* ~& s
  15. 15     vu8  week;         
    : p% Y; z6 @  J( G8 ^& a+ v
  16. 16 }_calendar_obj;                     
    ! A" n- Q+ b$ Z5 j9 p; _( @
  17. 17
    * L: {+ C: j& O% H" z
  18. 18 extern _calendar_obj calendar;    //日历结构体
    . w4 C. ]7 h5 i( l
  19. 19 extern u8 const mon_table[12];    //月份日期数据表3 T( k. D* d) M6 ~
  20. 20 ' B8 A4 y' @! t: q6 s; {/ K
  21. 21 u8 RTC_Init(void);        //初始化RTC,返回0,失败;1,成功;
    ; b  D7 l4 G" t( i( `* x: o
  22. 22 u8 Is_Leap_Year(u16 year);//平年,闰年判断& ^) k+ b. Y2 c- Y  J7 n
  23. 23 $ }. m. Q% F4 ^3 k3 b" n
  24. 24 //u8 RTC_Alarm_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec);: z) |! ^+ |4 l6 ]
  25. 25 u8 RTC_Get(void);         //更新时间   + @# M) }8 E* z, L1 e( o. d
  26. 26 u8 RTC_Get_Week(u16 year,u8 month,u8 day);
    , u2 N. }7 b9 J8 g; s2 A
  27. 27 u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec);//设置时间   
复制代码

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

2 p* f2 D1 t/ Z- s% v8 K2 i! @! j# r; a8 N
5、毫秒计时原理
如果要获取到毫秒级的时钟怎么办?在我的项目中就有这样的要求。事实上,获取毫秒时钟也非常简单。
查阅开发指南,RTC预分频器余数寄存器(RTC_DIVH、RTC_DIVL),这两个寄存器的作用就是用来获得比秒钟更为准确的时钟。 该寄存器的值自减的,用于保存还需要多少时钟周期获得一个秒信号。在一次秒钟更新后,由硬件重新装载。这两个寄存器和 RTC 预分频装载寄存器位数是一样的。也就是说,如果预分频装载寄存器的值为32767,那么余数寄存器就会在每一次秒更新时由硬件重新装载为32767,然后向下计数,计数到0表示一秒,也即1000ms。
  因此,我们在时钟结构体中添加msec成员
  1. 1 //时间结构体
    " w8 V* u" s7 ]  Q7 z2 {- L
  2. 2 typedef struct
    " w, |+ {# _! @
  3. 3 {
    7 D( ?0 r, v5 Q
  4. 4     vu8 hour;//vu80 [4 S2 `& b  @7 T  V
  5. 5     vu8 min;
    / q4 W3 ]9 q: v% E2 ~6 j
  6. 6     vu8 sec;
    ) R. g7 O3 @' e
  7. 7     vu16 msec;
      z+ L4 }$ D- a  J/ s. ]) k8 g! U
  8. 8     ) u1 w. e, v7 L: }) d4 n
  9. 9     //公历日月年周2 `- i- k  @9 S. q% X  @' q3 p
  10. 10     vu16 w_year;& J0 S+ S" d9 C7 \& Y
  11. 11     vu8  w_month;
    " M5 d" |4 a: r1 I$ ]
  12. 12     vu8  w_date;  A# }, a3 F) R
  13. 13     vu8  week;         0 x7 X1 Q$ r% A1 p/ \  T
  14. 14 }_calendar_obj;  
复制代码

! Z5 L( V9 ~( a  M
 获取毫秒时间
  1. 1 calendar.msec=(32767-RTC_GetDivider())*1000/32767;
复制代码

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

2 |9 R* @! t! L7 }7 M# n  V
  1. if (BKP_ReadBackupRegister(BKP_DR1) != 0x5051)7 j  W$ ?- r. _8 ?
  2. {! M3 j$ W' ]! Z7 n8 w
  3.     ...
    * D! i0 o0 Z& D1 j1 a0 z; x  o* k
  4.     RTC_EnterConfigMode();/// 允许配置    8 k4 n: `) z4 G+ J8 D( V) W: D& b9 y
  5.     RTC_SetPrescaler(32767); //设置RTC预分频的值6 I2 ^9 |  p* h, T- U1 q' `
  6.     RTC_WaitForLastTask();    //等待最近一次对RTC寄存器的写操作完成
    ; G% d. c% p, |( H
  7.     RTC_Set(2018,4,2,17,37,00);  //设置时间   
    * U0 x% v1 G5 E* W* u7 @# C" Q
  8.     RTC_ExitConfigMode(); //退出配置模式  
    ' l: W* B; C8 v: Y5 j- R5 N5 a/ a* _
  9.     BKP_WriteBackupRegister(BKP_DR1, 0X5051);    //向指定的后备寄存器中写入用户程序数据   
    / |' ~, v7 C2 ~$ G4 _/ I5 ~
  10. }
复制代码
) c' r! I! A; q0 F1 y: k5 ^' U
收藏 1 评论0 发布时间:2022-1-13 20:47

举报

0个回答

所属标签

相似分享

官网相关资源

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