• stm32入门学习13-时钟RTC


    (一)时钟RTC

    stm32内部集成了一个秒计数器RTC,用于显示我们日常的时间,如日期年月日,时分秒等,RTC的主要原理就是进行每秒自增,如果我们知道开始记秒的开始时间,就可以计算现在的日期,但是这不需要我们计算,我们只要调用C语言库函数即可自动完成秒数到日常时间或日常时间到秒数的转换,如果只是实现秒自增的功能,那么内部任何一个计数器都可以实现,但是时钟RTC与其他计数器不同的是其可以使用备用电源保持工作,在最小板供电停止时如果有备用电源其会继续工作;这里的备用电源供电引脚为stm32最小板的1好引脚,标注为VBT;

    (二)备份寄存器BKP

    和时钟RTC一样,备份寄存器BKP可以在备用电源供电时保持数据不丢失,但是其却不能在备用电源和主电源同时断电时候还保持掉电不丢失,这里使用BKP主要是为了给时钟判断是否需要初始化,如果备用电源没有断开,RTC依旧在工作,那么在主电源重新供电的时候时钟就不需要初始化,直接读取其时钟值即可;

    (三)RTC时钟和BKP备份寄存器

    这里BKP主要是为了检测备用电源是否掉电的,我们的思路是在上电的时候在备份寄存器BKP上写入一个数据(不要写默认值0),如果备用电源没有断电,这个数据会一直保持,如果断电则会恢复为0,我们只要会初始化BKP和读写BKP即可,主要用到这几个函数

    1. void BKP_WriteBackupRegister(uint16_t BKP_DR, uint16_t Data);
    2. // 写入备份寄存器
    3. uint16_t BKP_ReadBackupRegister(uint16_t BKP_DR);
    4. // 读出备份寄存器
    5. void RTC_WaitForLastTask(void);
    6. // 等待上次任务完成,内部自带while循环和进入RTC的配置模式
    7. void RTC_WaitForSynchro(void);
    8. // 等待系统同步,自带while循环和进入RTC的配置模式

    (1)BKP初始化

    BKP初始化要初始BKP的时钟,还有电源管理模块的时钟RCC_APB1Periph_PWR,以便在使用备用电源时stm32可以进行电源管理进入待机模式,这些时钟都是APB1上的外设,因此我们要调动APB1的初始化函数

    1. void rtc_bkp_init()
    2. {
    3. RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
    4. RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);
    5. }

    (2)RTC初始化

    RTC不想其他外设一样用初始化结构体来进行初始化,我们要对每一步手动初始化,初始化流程图如下

    首先要开启RTC的时钟并选择对应通道,然后要写入重装寄存器和预分频值以使得计数器实现每秒自增(即频率为1Hz),当然我们还要打开对应的电源控制

    如果在主电源断开但备用电源没有断开时,RTC仍然会工作,但是主电源开始供电时,程序会重启,这时会再次调用RTC的初始化函数,这时不希望的,因此我们在BKP中写入一个标志位,如果该标志位没有清零,表示备用电源和主电源没有同时断电,这时我们就不需要重新对RTC进行初始化,我们在RTC初始化中加入if判断即可

    1. void rtc_init()
    2. {
    3. PWR_BackupAccessCmd(ENABLE);
    4. if (BKP_ReadBackupRegister(BKP_DR1) != 1)
    5. {
    6. RCC_LSEConfig(RCC_LSE_ON);
    7. while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) != SET);
    8. RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
    9. RCC_RTCCLKCmd(ENABLE);
    10. RTC_WaitForSynchro(); // wait synchronization
    11. RTC_WaitForLastTask(); // wait write
    12. RTC_SetPrescaler(32768-1);
    13. RTC_WaitForLastTask();
    14. rtc_set_time(2024, 8, 9, 16, 7, 0);
    15. BKP_WriteBackupRegister(BKP_DR1, 1);
    16. }
    17. }

    由于RTC的时钟和系统时钟不是一个频率,在开启时钟后要等待时钟同步(synchronization),每次进行写操作时要等待上次任务完成;

    这里选择的是外部低速时钟,其晶振为32.768KHz,因此要进行32768分频使其频率为1Hz

    (3)设置和读取时间

    我们可以从该时钟读取到的只有秒值,且要修改时间也只有输入对应时间的秒数才能修改,但是我们可以借助time库来计算我们常见的时间格式和对应秒数的转换,主要有两个函数和一个结构体

    1. struct tm {
    2. int tm_sec; /* seconds after the minute, 0 to 60
    3. (0 - 60 allows for the occasional leap second) */
    4. int tm_min; /* minutes after the hour, 0 to 59 */
    5. int tm_hour; /* hours since midnight, 0 to 23 */
    6. int tm_mday; /* day of the month, 1 to 31 */
    7. int tm_mon; /* months since January, 0 to 11 */
    8. int tm_year; /* years since 1900 */
    9. int tm_wday; /* days since Sunday, 0 to 6 */
    10. int tm_yday; /* days since January 1, 0 to 365 */
    11. int tm_isdst; /* Daylight Savings Time flag */
    12. union { /* ABI-required extra fields, in a variety of types */
    13. struct {
    14. int __extra_1, __extra_2;
    15. };
    16. struct {
    17. long __extra_1_long, __extra_2_long;
    18. };
    19. struct {
    20. char *__extra_1_cptr, *__extra_2_cptr;
    21. };
    22. struct {
    23. void *__extra_1_vptr, *__extra_2_vptr;
    24. };
    25. };
    26. };
    27. // 时间结构体,对应秒、分、时、日、月、年、星期、天
    28. extern _ARMABI time_t mktime(struct tm * /*timeptr*/) __attribute__((__nonnull__(1)));
    29. // 将时间结构体转换为秒数,输入一个时间结构体指针输出其到1990年的秒数,结构体只要年月日即可
    30. extern _ARMABI struct tm *localtime(const time_t * /*timer*/) __attribute__((__nonnull__(1)));
    31. // 输入到1990年的秒数输出一个时间结构体指针

    这里有一些规定与日常不合,这里的月是从0开始的,年是如今到1900年的年数,星期从星期天(0)开始

    这里转换的是伦敦时间,要计算北京时间还要加上8小时(东八区),对应秒数加8*60*60

    1. void rtc_set_time(unsigned int year, unsigned int month, unsigned int day, unsigned int hour, unsigned int min, unsigned int sec)
    2. {
    3. struct tm timer;
    4. unsigned int counter;
    5. timer.tm_year = year-1900;
    6. timer.tm_mon = month-1;
    7. timer.tm_mday = day;
    8. timer.tm_hour = hour;
    9. timer.tm_min = min;
    10. timer.tm_sec = sec;
    11. counter = mktime(&timer);
    12. counter -= 8*60*60;
    13. RTC_SetCounter(counter);
    14. RTC_WaitForLastTask();
    15. }
    1. struct tm* rtc_read_time()
    2. {
    3. struct tm* timer;
    4. unsigned int counter = RTC_GetCounter() + 8*60*60;
    5. timer = localtime(&counter);
    6. timer->tm_year += 1900;
    7. timer->tm_mon += 1;
    8. return timer;
    9. }

    (4)封装与声明

    最终.c 和 .h文件如下

    1. #include "stm32f10x.h" // Device header
    2. #include
    3. void rtc_set_time(unsigned int year, unsigned int month, unsigned int day, unsigned int hour, unsigned int min, unsigned int sec);
    4. void rtc_bkp_init()
    5. {
    6. RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
    7. RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);
    8. }
    9. void rtc_init()
    10. {
    11. PWR_BackupAccessCmd(ENABLE);
    12. if (BKP_ReadBackupRegister(BKP_DR1) != 1)
    13. {
    14. RCC_LSEConfig(RCC_LSE_ON);
    15. while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) != SET);
    16. RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
    17. RCC_RTCCLKCmd(ENABLE);
    18. RTC_WaitForSynchro(); // wait synchronization
    19. RTC_WaitForLastTask(); // wait write
    20. RTC_SetPrescaler(32768-1);
    21. RTC_WaitForLastTask();
    22. rtc_set_time(2024, 8, 9, 16, 7, 0);
    23. BKP_WriteBackupRegister(BKP_DR1, 1);
    24. }
    25. }
    26. void rtc_set_time(unsigned int year, unsigned int month, unsigned int day, unsigned int hour, unsigned int min, unsigned int sec)
    27. {
    28. struct tm timer;
    29. unsigned int counter;
    30. timer.tm_year = year-1900;
    31. timer.tm_mon = month-1;
    32. timer.tm_mday = day;
    33. timer.tm_hour = hour;
    34. timer.tm_min = min;
    35. timer.tm_sec = sec;
    36. counter = mktime(&timer);
    37. counter -= 8*60*60;
    38. RTC_SetCounter(counter);
    39. RTC_WaitForLastTask();
    40. }
    41. struct tm* rtc_read_time()
    42. {
    43. struct tm* timer;
    44. unsigned int counter = RTC_GetCounter() + 8*60*60;
    45. timer = localtime(&counter);
    46. timer->tm_year += 1900;
    47. timer->tm_mon += 1;
    48. return timer;
    49. }
    1. #ifndef __RTC_H__
    2. #define __RTC_H__
    3. void rtc_bkp_init(void);
    4. void rtc_init(void);
    5. void rtc_set_time(unsigned int year, unsigned int mon, unsigned int day, unsigned int hour, unsigned int min, unsigned int sec);
    6. struct tm* rtc_read_time(void);
    7. #endif

    (四)主函数

    只要在主函数进行对应初始化和调用读取函数即可,初始化已经包含了开始时间的设定,如果要重新设定开始时间,可以在初始化中更改,也可以在主函数中使用rtc_set_time函数来修改,但是要注意上电后stm32复位问题,应该和初始化一样加入备用电源是否断电的判断代码

    1. #include "stm32f10x.h" // Device header
    2. #include "rtc.h"
    3. #include "OLED.h"
    4. #include
    5. int main()
    6. {
    7. struct tm* time_inf;
    8. OLED_Init();
    9. rtc_bkp_init();
    10. rtc_init();
    11. OLED_ShowString(1, 1, "time:");
    12. OLED_ShowString(2, 1, "xxxx-xx-xx");
    13. OLED_ShowString(3, 1, "xx:xx:xx");
    14. while(1)
    15. {
    16. time_inf = rtc_read_time();
    17. OLED_ShowNum(2, 1, time_inf->tm_year, 4);
    18. OLED_ShowNum(2, 6, time_inf->tm_mon, 2);
    19. OLED_ShowNum(2, 9, time_inf->tm_mday, 2);
    20. OLED_ShowNum(3, 1, time_inf->tm_hour, 2);
    21. OLED_ShowNum(3, 4, time_inf->tm_min, 2);
    22. OLED_ShowNum(3, 7, time_inf->tm_sec, 2);
    23. }
    24. return 0;
    25. }

    (五)总结

    通过实现一个时钟,我们学习了RTC和BKP两个可以使用备用电源供电工作的外设,了解了计数器RTC的原理和备份寄存器BKP的原理,通过备份寄存器实现了RTC的掉电继续计时和数据不丢失

  • 相关阅读:
    2022年3月13日安装和启动ActiveMQ遇到问题
    Linux内核版本号
    Spring 当中的Bean 作用域
    github公钥设置
    【Linux】在Xilinx平台上实现UVC Gadget(2)- 解决dwc3驱动bug
    01-Linux部署MinIo
    Flink 开发环境搭建
    普冉PY32系列(八) GPIO模拟和硬件SPI方式驱动无线收发芯片XN297LBW
    udev 挂载SD卡 USB设备
    海康威视Java实习面试
  • 原文地址:https://blog.csdn.net/Ly2020Wj/article/details/141071794