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;
}

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");
  }
}

设置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);
}

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;

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;
}

只需调用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);
}

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);
}

注意:

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

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

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

缺点:

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

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

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

实验效果

在这里插入图片描述

  • 4
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值