• STM32物联网项目-RTC时钟


    RTC时钟

    RTC简介

    实时时钟是一个独立的定时器。RTC模块拥有一组连续计数的计数器,在相应软件配置下,可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。

    RTC模块和时钟配置系统(RCC_BDCR寄存器)处于后备区域,即在系统复位或从待机模式唤醒后,RTC的设置和时间维持不变。

    系统复位后,对后备寄存器和RTC的访问被禁止,这是为了防止对后备区域(BKP)的意外写操作。

    执行以下操作将使能对后备寄存器和RTC的访问:

    1、设置寄存器RCC_APB1ENR的PWREN和BKPEN位,使能电源和后备接口时钟

    2、设置寄存器PWR_CR的DBP位,使能对后备寄存器和RTC的访问。

    实验目标

    通过串口可以设置RTC时钟的日期和时间,设置好后,可以在数码管上显示设定的时间,同时日期和时间也会被实时发送到串口上进行显示;并且系统复位或者断电后,RTC的时钟依然运行

    RTC供电

    由STM32电源框图可以看到,当主电源VDD掉电后,通过VBAT脚为实时时钟(RTC)和备份寄存器提供电源

    在这里插入图片描述

    因为RTC时钟在系统断电后需要继续工作,就需要用电池连接到VBAT引脚上,给RTC提供电源,当电池被拔掉后,RTC也不能工作了

    在这里插入图片描述

    CubeMX配置

    RTC配置

    激活RTC

    在这里插入图片描述

    参数默认即可

    在这里插入图片描述

    时钟树配置

    选择外部32.768KHz的低速时钟源,直接给到RTC时钟

    从时钟树可以看出,RTC时钟源还可以选择HSE的128分频和内部低速时钟LSI RC,那为什么要使用LSE外部低速时钟源呢?

    因为HSE在系统断电后是不起振了,无法提供时钟源,而内部LSI随着芯片停止工作也不起振,还有原因是LSE比较精确
    在这里插入图片描述

    串口1配置

    因为需要串口来设置时间和打印时间,所以初始化串口1
    在这里插入图片描述

    程序

    Public.c

    因为使用到串口输入字符串来设置RTC的时钟,所以与printf函数重定向一样,将 getchar 的底层函数 fgetc 映射到物理串口,后面只需使用getchar()函数即可接收串口发来的信息

    /*
    * @name   fgetc
    * @brief  fgetc映射到物理串口
    * @param  None
    * @retval ch:已接收的字符
    */
    int fgetc(FILE* f)
    {
      uint8_t ch = 0;
      //通过查询方法等待接收
      HAL_UART_Receive(&huart_debug,&ch,1,0xFFFF);
      return ch;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    MyRTC.c

    设置RTC的日期和时间,日期就是年,月,日,星期,时间是时,分,秒,进入函数会首先判断RTC备份寄存器1的值,因为该寄存器不会被系统复位,断电复位或者待机模式唤醒复位,在设置好一次RTC的日期和时间后,往该寄存器里写入一个随机值,下次就会通过判断该寄存器的值,如果没被修改,则说明已经设置过日期和时间,不用再设置,如果值被修改,则进行设置日期和时间的操作

    有两种方法可修改RTC的值,方法一:修改备份寄存器的值,通过触摸按键来实现;方法二:系统断电的时候拔掉VBAT的电池,RTC则停止工作,上电后会重新设置时间

    /*
    * @name   Calendar_Set
    * @brief  设置日历
    * @param  None
    * @retval None   
    */
    static void Calendar_Set()
    {
      /*上电复位时,读取RTC备份寄存器1的值,如果为0xAAAA,表示已经设置了时间,不需要重新设置
      
      注:0xAAAA是自己随便设定的值,该寄存器不会被系统复位,电源复位或从待机模式唤醒而复位,
      所以该寄存器的值在断电后依然保持不变,判断该寄存器是否有预设定值,有就表示已经设置了时间
      */
      if(HAL_RTCEx_BKUPRead(&hrtc,RTC_BKP_DR1) != 0xAAAA)
      {
        printf("开始设置RTC的日期和时间\r\n\r\n");
        RTC_Date_Set();   //设置日期
        RTC_Time_Set();   //设置时间
    
        //设置完日期和时间后写RTC备份寄存器1的值,表示日期和时间已经设定
        HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR1,0xAAAA);
      }
      else
      {
        printf("RTC的日期和时间已经设置\r\n\r\n");
        printf("重新设置的方法如下:\r\n");
        printf("方法一:长按触摸按键1两秒以上\r\n");
        printf("方法二:系统断电,同时拔掉RTC电池\r\n");
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    设置RTC日期

    定义一个日期结构体变量,从串口接收年,月,日的数据后,调用函数HAL_RTC_SetDate将日期写入到结构体变量中;

    设置RTC时间的函数类似,只是最大值不同,时钟是23,分钟是59,秒钟也是59

    /*
    * @name   RTC_Date_Set
    * @brief  设置RTC日期
    * @param  None
    * @retval None   
    */
    static void RTC_Date_Set()
    {
      RTC_DateTypeDef RTC_DateStruct;   //定义一个日期结构体变量
      uint8_t SetValue;
    
      printf("=============日期设置===========\r\n");
      printf("请输入年份(00-99):20\r\n");
      //等待串口设置
      SetValue = 0xFF;
      while(SetValue == 0xFF)
      {
        //对串口输入值进行校验后,再赋给SetValue,参数99表示年份的最大值
        SetValue = Input_RTC_SetValue(99);
      }
      printf("年份被设置为:20%02u\r\n",SetValue);
      RTC_DateStruct.Year = SetValue;
    
      printf("请输入月份(1-12):\r\n");
      SetValue = 0xFF;
      while(SetValue == 0xFF)
      {
        SetValue = Input_RTC_SetValue(12);
        if(SetValue == 0x00)
        {
          printf("月份不能设置为0,请重新输入月份:\r\n");
          SetValue = 0xFF;
        }
      }
      printf("月份被设置为:%02u\r\n",SetValue);
      RTC_DateStruct.Month = SetValue;
    
      printf("请输入日期(01-31):\r\n");
      SetValue = 0xFF;
      while(SetValue == 0xFF)
      {
        SetValue = Input_RTC_SetValue(31);
        if(SetValue == 0x00)
        {
          printf("日期不能设置为0,请重新输入日期:\r\n");
          SetValue = 0xFF;
        }
      }
      printf("日期被设置为:%02u\r\n",SetValue);
      RTC_DateStruct.Date = SetValue;
    
      //设置日期——二进制数据格式
      HAL_RTC_SetDate(&hrtc,&RTC_DateStruct,RTC_FORMAT_BIN);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    stm32f1xx_hal_rtc.h

    通过查看RTC的HAL库头文件可以看到,RTC日期结构体的年份的取值范围是0到99,所以不能直接设置如2022这样的数值,但串口打印年份时可加上2000,数码管显示同理

    /**
      * @brief  RTC Date structure definition
      */
    typedef struct
    {
      uint8_t WeekDay;  /*!< Specifies the RTC Date WeekDay (not necessary for HAL_RTC_SetDate).
                             This parameter can be a value of @ref RTC_WeekDay_Definitions */
    
      uint8_t Month;    /*!< Specifies the RTC Date Month (in BCD format).
                             This parameter can be a value of @ref RTC_Month_Date_Definitions */
    
      uint8_t Date;     /*!< Specifies the RTC Date.
                             This parameter must be a number between Min_Data = 1 and Max_Data = 31 */
    
      uint8_t Year;     /*!< Specifies the RTC Date Year.
                             This parameter must be a number between Min_Data = 0 and Max_Data = 99 */
    
    } RTC_DateTypeDef;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    MyRTC.c

    Input_RTC_SetValue函数是接收串口的设置值,并判断其有效性

    /*
    * @name   Input_RTC_SetValue
    * @brief  输入RTC设置值
    * @param  None
    * @retval None   
    */
    static uint8_t Input_RTC_SetValue(uint8_t MaxValue)
    {
      uint8_t SetValue = 0;                 //返回值
      uint8_t Value_Arr[2] = {0};     //串口接收缓存
      uint8_t Index = 0;
    
      //以等待方式从串口2接收两个字符
      while (Index < 2)
      {
        //等待串口接收数据
        Value_Arr[Index++] = getchar();
        //校验数据有效性
        if((Value_Arr[Index-1] < '0')||(Value_Arr[Index-1] > '9'))
        {
          printf("请输入0 到 9之间的数字-->\r\n");
          Index--;    //下标减1,重新接收
        }
      }
      //接收到的两个字符转化为数值
      SetValue = (Value_Arr[0]-'0')*10 +(Value_Arr[1]-'0');
      //判断返回值有效性
      if(SetValue > MaxValue)
      {
        printf("请输入 0 到 %d 之间的数字\r\n",MaxValue);
        SetValue = 0xFF;
      }
      return SetValue;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    只需调用HAL库的RTC获取日期和时间就能得到刚刚通过串口设置的值,通过指针,MyRTC.pRTC_DataStruct保存到文件开头定义的结构体变量中

    /*
    * @name   Calendar_Get
    * @brief  获取日历
    * @param  None
    * @retval None   
    */
    static void Calendar_Get()
    {
      //获取当前日期
      HAL_RTC_GetDate(&hrtc,MyRTC.pRTC_DataStruct,RTC_FORMAT_BIN);
      //获取当前时间
      HAL_RTC_GetTime(&hrtc,MyRTC.pRTC_TimeStruct,RTC_FORMAT_BIN);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    System.c

    主函数判断RTC的设置标志位RTC_Set_Flag,该标志位初始化为TRUE,所以一上电就会先进行RTC的日期和时间设置

    /*
    * @name   Run
    * @brief  系统运行
    * @param  None
    * @retval None   
    */
    static void Run()
    {
      if(MyRTC.RTC_Set_Flag == TRUE)
      {
        MyRTC.RTC_Set_Flag = FALSE;
        //设置RTC日期和时间
        MyRTC.Calendar_Set();
      }
      //获取RTC日期和时间
      MyRTC.Calendar_Get();
      //显示RTC日期和时间
      MyRTC.Calendar_Show();
    
      //延时
      HAL_Delay(1000);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    注意:

    分析框图,RTC的后备区域没有日期寄存器,断电恢复后,没法直接读取日期;如果需要读取断电恢复后日期更新值,有两种方法:

    方法一:使用高级些的MCU,比如STM32F4,RTC自带日期寄存器。缺点:MCU成本贵

    方法二:利用32位可编程计数器进行计算。具体方法是:假定计数器为0时,为一个起始日期,比如2000年1月1日;设定日期后,减去起始日期,换算成秒钟初始化给计数器;断电后,计数器值不重新装载,与设定日期对比,换算出当前日期。

    缺点:

    1、不能使用HAL库编程,因为这种方法是自己设计的,没有库函数

    2、需要CPU大量计算,效率低;

    3、RTC溢出与闹钟功能不可用。

    实验效果

    在这里插入图片描述

  • 相关阅读:
    数据结构 1、基本概念 动态数组实现
    智慧气象解决方案-最新全套文件
    【408复习】在b站开播通知
    selenium 自动化测试:如何搭建自动化测试环境,搭建环境过程应该注意的问题
    MyBatis的缓存(包括MyBatis的一级缓存、二级缓存、二级缓存的相关配置、缓存查询的顺序、整合第三方缓存EHCache)
    C语言CRC-8 MAXIM格式校验函数
    安全风险 - 检测设备是否为模拟器
    Tomcat长轮询原理与源码解析
    禅道系统迁移笔记
    用于神经网络的FLOP和Params计算工具
  • 原文地址:https://blog.csdn.net/weixin_46251230/article/details/126747194