[002] [蓝桥杯物联网] RTC时钟硬件框架与配置流程

蓝桥杯
Contents
RTC硬件框架
时钟源
RTC框图
RTC初始化和配置流程
读取日历时间
HAL库编程
CubeMX配置
RTC_HAL函数
时间读取测试

MCU型号:STM32L071KBU
SDK:HAL库
工具:CubeMX + MDK

1 RTC硬件框架

1.1 时钟源

image-20220331194043777

  • LSI 37 kHz
  • LSE 32.768 kHz
  • HSE预分频后的时钟

f_ck_apre时钟用于对二进制RTC_SSR亚秒下行计数器进行计时。当其值为0时,RTC_SSR重新加载PREDIV_S的内容,f_ck_apre时钟计算:

image-20220331201807181

f_ck_spre时钟既可以用于更新日历,也可以作为16位唤醒自动重新加载计时器的时间基础。为了获得较短的超时时间,16位唤醒自动重新加载定时器也可以使用RTCCLK除以可编程的4位异步预分频器运行。f_ck_spre时钟计算:

image-20220331202001342

RTC时钟显示秒更新周期即有f_ck_spre决定(重点):

  • 当选择LSE(32.768 kHz)作为时钟源时,设置RTC_PRER寄存器异步分频因子PREDIV_A[6:0]128 - 1,同步分频因子PREDIV_S[14:0]256 - 1时,f_ck_spre = 32768/128/256 = 1 HZ。
  • 当选择**LSI**(37kHz)作为时钟源时,异步分频因子PREDIV_A[6:0]设为124,同步分频因子PREDIV_S[14:0]设为295,时,f_ck_spre = 37000/(124+1)/(295+1)= 1 HZ。

LORA物联网开发板没有HSE和LSE,因此只能选择LS1!

1.2 RTC框图

image-20220331200306725

可屏蔽 interrupts/events:

  • Alarm A 闹钟A
  • Alarm B 闹钟B
  • Wakeup interrupt 唤醒中断
  • Time-stamp 时间戳
  • Tamper detection 入侵检测

灰框部分为RTC核心寄存器:

  • RTC_SSR for the subseconds
  • RTC_TR for the time
  • RTC_DR for the date

每两个RTCCLK周期,当前的日历值被复制到影子寄存器shadow register,并且RTC_ISR寄存器的RSF位被设置。

注意:复制不会在“停止”和“待机”模式下进行。当退出这些模式时,影子寄存器最多在2个rtclk周期后更新。

当读取日历寄存器时,它将访问影子寄存器的内容。可以通过在RTC_CR寄存器中设置BYPSHAD位直接访问日历寄存器(默认除能)。

注意:在影子模式下(BYPSHAD位为0)读取RTC_SSR、RTC_TR或RTC_DR寄存器时,APB时钟(f_APB)的频率必须至少是RTC时钟(f_RTCCLK)频率的7倍

日历格式

  • time format:12 or 24 hours,通过RTC_CR寄存器的FMT位设置。
  • Data Format:
    • Binary data format 二进制格式
    • BCD data format BCD码格式

1.3 RTC初始化和配置流程

在这里插入图片描述

1.3.1 RTC 寄存器访问

系统复位后,RTC寄存器通过清除PWR_CR寄存器中的DBP位(参考电源控制部分)来保护RTC寄存器免受寄生写访问。为了启用RTC寄存器的写访问,必须设置DBP位:

image-20220331212819859

1.3.2 RTC 寄存器写保护

RTC域复位后,所有的RTC寄存器都是写保护的。写入RTC寄存器是通过将一个密钥写入写入保护寄存器RTC_WPR来 实现的。通过以下方法解除RTC寄存器的写保护(除了RTC_TAMPCR、RTC_BKPxR、RTC_OR和RTC_ISR[13:8]):

  • Write ‘0xCA’ into the RTC_WPR register.

  • Write ‘0x53’ into the RTC_WPR register.

写错键将重新激活写保护。

1.3.3 日历初始化和配置

要编程初始时间和日期日历值,包括时间格式和预分频器配置,需要以下顺序:

  • RTC_ISR寄存器的INIT位设置为1,进入初始化模式。在这种模式下,日历计数器将停止,并且可以更新它的值
  • 轮询RTC_ISR寄存器中的INITF位。当“INITF”设置为1时,进入初始化阶段模式。它大约需要2个rtclk时钟周期(由于时钟同步)。
  • 选择时钟源,设置RTC_PRER寄存器异步分频因子PREDIV_A[6:0]和同步分频因子PREDIV_S[14:0],将f_ck_spre频率配置为1HZ,为日历计数器生成1 Hz的时钟
  • 在影子寄存器(RTC_TRRTC_DR)中加载初始时间和日期值,并通过RTC_CR寄存器中的FMT位配置时间格式(12或24小时)。
  • 通过清除INIT位退出初始化模式。然后,将自动加载实际的日历计数器值,并在4个rtclk时钟周期后重新开始计数。当初始化序列完成时,日历开始计数。

Note:系统重置后,应用程序可以读取RTC_ISR寄存器中的INITS标志,以检查日历是否已初始化。如果这个标志等于0,日历还没有被初始化,因为year字段被设置在RTC域重置默认值(0x00)。

为了在初始化后读取日历,软件必须首先检查RSF标志是否在RTC_ISR寄存器中设置。

1.3.4 夏令时

夏令时管理通过RTC_CR寄存器的SUB1H、ADD1H和BKP位进行。使用SUB1H或ADD1H,软件可以一次操作在日历上减去或增加1小时,而不需要经过初始化过程。此外,软件还可以使用BKP位来存储此操作(掉电保存)。

1.3.5 闹钟编程

以闹钟A为例:

  1. 清除RTC_CR中的ALRAE,使闹钟A失效。
  2. 编程闹钟A寄存器(RTC_ALRMASSR/RTC_ALRMAR)。
  3. 在RTC_CR寄存器中设置ALRAE使能闹钟A

Note:由于时钟同步,在大约2个RTCCLK时钟周期后,RTC_CR寄存器的每次更改都会被更新。

1.3.6 唤醒定时器编程

配置或更改唤醒计时器自动重新加载值(RTC_WUTR中的WUT[15:0])需要如下序列:

  1. 清除RTC_CR中的WUTE,关闭唤醒定时器。
  2. 轮询WUTWF位直到它在RTC_ISR中被设置,以确保允许访问唤醒自动重载计数器和WUCKSEL[2:0]位。它大约需要2个rtclk时钟周期(由于时钟同步)。
  3. 编程唤醒自动重载值WUT[15:0],以及唤醒时钟选择(RTC_CR中的WUCKSEL[2:0]位)。在RTC_CR中再次设置WUTE使能定时器。唤醒定时器重新开始倒数计时。清除WUTE后,由于时钟同步,WUTWF位最多会被清除2个rtclk时钟周期。

夏令时、闹钟中断、唤醒定时器中断可选。

1.4 读取日历时间

1.4.1 RTC_CRBYPSHAD位被清除时(使用影子寄存器)

为了正确读取RTC日历寄存器(RTC_SSRRTC_TRRTC_DR), APB1时钟频率(fpcLk)必须等于或大于RTC时钟频率(fRTccLK)的7倍。这确保了同步机制的安全行为。

如果APB1时钟频率小于RTC时钟频率的7倍,软件必须读取两次日历时间和日期寄存器。如果RTC_TR的第二次读取与第一次读取的结果相同,这确保了数据是正确的。否则必须进行第三次读访问。在任何情况下,APB1时钟频率都不能低于RTC时钟频率。

每次将日历寄存器复制到RTC_SSR、RTC_TR和RTC_DR影子寄存器时,RSF位都会在RTC_ISR寄存器中设置。每两个rtclk周期执行一次复制。为了确保这3个值之间的一致性,读取RTC_SSR或RTC_TR将锁定高阶日历阴影寄存器中的值,直到读取RTC_DR。如果软件在小于2个RTCCLK周期的时间间隔内读取日历:第一次读取日历后,软件必须清除RSF,然后软件必须等待RSF被设置,才能再次读取RTC_SSR、RTC_TR和RTC_DR寄存器。

从低功耗模式(Stop或Standby)唤醒后,必须通过软件清除RSF。在读取RTC_SSR、RTC_TR和RTC_DR寄存器之前,软件必须等待它再次被设置。

RSF位必须在唤醒后清除,而不能在进入低功耗模式前清除。

系统复位后,软件必须等待RSF设置后才能读取RTC_SSR、RTC_TR和RTC_DR寄存器。实际上,系统重置会将影子寄存器重置为它们的默认值。

在初始化之后,软件必须等待RSF被设置后才能读取RTC_SSR、RTC_TR和RTC_DR寄存器。

同步后,软件必须等待RSF设置后才能读取RTC_SSR、RTC_TR和RTC_DR寄存器。

示例代码

if((RTC->ISR & RTC_ISR_RSF) == RTC_ISR_RSF)
{
    TimeToCompute = RTC->TR; /* get time */
    DateToCompute = RTC->DR; /* need to read date also */
}

1.4.2 RTC_CRBYPSHAD位被置位时(不使用影子寄存器)

读取日历寄存器可以直接从日历计数器中获得值,从而不需要等待RSF位被设置。这在退出低功耗模式(停止或待机)后特别有用,因为在这些模式期间影子寄存器不会更新。

BYPSHAD位设置为1时,如果在两次读取寄存器之间出现RTCCLK边沿,则不同寄存器的结果可能不一致。此外,如果在读取操作期间出现RTCCLK边沿,其中一个寄存器的值可能不正确。软件必须读取所有寄存器两次,然后比较结果,以确认数据是连贯和正确的。或者,该软件可以只比较最不重要的日历寄存器的两个结果。

Note:当BYPSHAD=1时,读取日历寄存器的指令需要一个额外的APB周期才能完成。

2 HAL库编程

2.1 CubeMX配置

image-20220331223109010

  • 24h时间制
  • 选择了LSI(37K)时钟,配置异步和同步分频因子,将f_ck_spre配置为1HZ
  • 日期格式选择BCD码
  • 年、月、日、星期、时分秒均为当前时间(自定义,年范围为0~99,基准为2000)
  • 不使用夏令时校准
  • 存储复位(RTC_TimeTypeDef中StoreOperation成员已弃用,改用HAL_RTC_DST_xxx函数设置夏令时)

2.2 RTC_HAL函数

2.2.1 读取和设置系统时间

  • 读取系统时间
/**
  * @brief  Get RTC current time.
  * @param  hrtc RTC handle
  * @param  sTime  指向时间结构的指针,带有小时、分钟和秒字段,并以输入格式(BIN或BCD)返回
  * @param  Format 获取时间的格式
  *            @arg RTC_FORMAT_BIN: 使用16进制
  *            @arg RTC_FORMAT_BCD: BCD码格式
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_RTC_GetTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime, uint32_t Format)

时间结构体RTC_TimeTypeDef

typedef struct
{
  uint8_t Hours;            /*!< Specifies the RTC Time Hour.
                                 This parameter must be a number between Min_Data = 0 and Max_Data = 12 if the RTC_HourFormat_12 is selected.
                                 This parameter must be a number between Min_Data = 0 and Max_Data = 23 if the RTC_HourFormat_24 is selected */

  uint8_t Minutes;          /*!< Specifies the RTC Time Minutes.
                                 This parameter must be a number between Min_Data = 0 and Max_Data = 59 */

  uint8_t Seconds;          /*!< Specifies the RTC Time Seconds.
                                 This parameter must be a number between Min_Data = 0 and Max_Data = 59 */

  uint8_t TimeFormat;       /*!< Specifies the RTC AM/PM Time.
                                 This parameter can be a value of @ref RTC_AM_PM_Definitions */

  uint32_t SubSeconds;     /*!< Specifies the RTC_SSR RTC Sub Second register content.
                                 This parameter corresponds to a time unit range between [0-1] Second
                                 with [1 Sec / SecondFraction +1] granularity */
    
  uint32_t SecondFraction;  /*!< Specifies the range or granularity of Sub Second register content
                                 corresponding to Synchronous pre-scaler factor value (PREDIV_S)
                                 This parameter corresponds to a time unit range between [0-1] Second
                                 with [1 Sec / SecondFraction +1] granularity.
                                 This field will be used only by HAL_RTC_GetTime function */

  uint32_t DayLightSaving;  /*!< This interface is deprecated. To manage Daylight
                                 Saving Time, please use HAL_RTC_DST_xxx functions */

  uint32_t StoreOperation;  /*!< This interface is deprecated. To manage Daylight
                                 Saving Time, please use HAL_RTC_DST_xxx functions */
} RTC_TimeTypeDef;

注意:必须在HAL_RTC_GetTime()之后调用HAL_RTC_GetDate()来解锁这些值在日历影子寄存器中,以确保时间和日期值之间的一致性。读取RTC当前时间会锁定日历影子寄存器中的值,直到读取当前日期以确保时间和日期值之间的一致性。

  • 设置系统时间
/**
  * @brief  Set RTC current time.
  * @param  hrtc RTC handle
  * @param  sTime  指向时间结构的指针
  * @param  Format 获取时间的格式
  *            @arg RTC_FORMAT_BIN: 使用16进制
  *            @arg RTC_FORMAT_BCD: BCD码格式
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_RTC_SetTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime, uint32_t Format)
  • 读取系统日期
/**
  * @brief  Get RTC current date.
  * @param  hrtc RTC handle
  * @param  sDate  指向日期结构的指针
  * @param  Format 获取日期的格式
  *            @arg RTC_FORMAT_BIN: 使用16进制
  *            @arg RTC_FORMAT_BCD: BCD码格式
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_RTC_GetDate(RTC_HandleTypeDef *hrtc, RTC_DateTypeDef *sDate, uint32_t Format)

日期结构体RTC_TimeTypeDef

typedef struct
{
  uint8_t WeekDay;  /*!< Specifies the RTC Date WeekDay.
                         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;
  • 设置系统日期
/**
  * @brief  Set RTC current date.
  * @param  hrtc RTC handle
  * @param  sDate  指向日期结构的指针
  * @param  Format 获取日期的格式
  *            @arg RTC_FORMAT_BIN: 使用16进制
  *            @arg RTC_FORMAT_BCD: BCD码格式
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_RTC_SetDate(RTC_HandleTypeDef *hrtc, RTC_DateTypeDef *sDate, uint32_t Format)

夏令时、闹钟相关函数暂时不用。

2.3 时间读取测试

RTC_TimeTypeDef rtc_time;    // 获取时间结构体
RTC_DateTypeDef rtc_data;    // 获取日期结构体
void get_rtc_time(void)
{
    // 获取当前时间
    HAL_RTC_GetTime(&hrtc, &rtc_time, RTC_FORMAT_BIN);
    // 获取当前日期
    HAL_RTC_GetDate(&hrtc, &rtc_data, RTC_FORMAT_BIN);
}
void main_thread(void)
{
    static uint32_t sec_temp = 0xff;
    get_rtc_time();
    if (sec_temp != rtc_time.Seconds)
    {
        sec_temp = rtc_time.Seconds;
        printf("%02d/%02d/%02d\r\n",2000 + rtc_data.Year, rtc_data.Month, rtc_data.Date);
        printf("%02d:%02d:%02d\r\n",rtc_time.Hours, rtc_time.Minutes, rtc_time.Seconds);
        printf("\r\n");
    }
}

RTC初始为BCD码格式,如果用BIN格式读取的话,结果是十进制:

image-20220401000956038

如果改成BCD码格式读取RTC_FORMAT_BCD,结果则是十六进制:

image-20220401000830458

分析源码

HAL_StatusTypeDef HAL_RTC_GetTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime, uint32_t Format)
{
  uint32_t tmpreg;
  /* Get subseconds structure field from the corresponding register*/
  sTime->SubSeconds = (uint32_t)(hrtc->Instance->SSR);

  /* Get SecondFraction structure field from the corresponding register field*/
  sTime->SecondFraction = (uint32_t)(hrtc->Instance->PRER & RTC_PRER_PREDIV_S);

  /* Get the TR register */
  tmpreg = (uint32_t)(hrtc->Instance->TR & RTC_TR_RESERVED_MASK);

  /* Fill the structure fields with the read parameters */
  sTime->Hours = (uint8_t)((tmpreg & (RTC_TR_HT | RTC_TR_HU)) >> 16U);
  sTime->Minutes = (uint8_t)((tmpreg & (RTC_TR_MNT | RTC_TR_MNU)) >> 8U);
  sTime->Seconds = (uint8_t)(tmpreg & (RTC_TR_ST | RTC_TR_SU));
  sTime->TimeFormat = (uint8_t)((tmpreg & (RTC_TR_PM)) >> 16U);

  /* Check the input parameters format */
  if (Format == RTC_FORMAT_BIN)
  {
    /* Convert the time structure parameters to Binary format */
    sTime->Hours = (uint8_t)RTC_Bcd2ToByte(sTime->Hours);
    sTime->Minutes = (uint8_t)RTC_Bcd2ToByte(sTime->Minutes);
    sTime->Seconds = (uint8_t)RTC_Bcd2ToByte(sTime->Seconds);
  }

  return HAL_OK;
}
/**
  * @brief  Convert from 2 digit BCD to Binary.
  * @param  Value BCD value to be converted
  * @retval Converted word
  */
uint8_t RTC_Bcd2ToByte(uint8_t Value)
{
  uint32_t tmp;
  tmp = (((uint32_t)Value & 0xF0U) >> 4U) * 10U;
  return (uint8_t)(tmp + ((uint32_t)Value & 0x0FU));
}

Hours/Minutes/Seconds从寄存器读取的数值为16进制(如RTC_TR_HT为小时十位,RTC_TR_HU为小时个位,均为16进制)

如果格式为RTC_FORMAT_BIN会调用RTC_Bcd2ToByte()函数,将16进制转换为10进制。

注意:这里是十进制不是数值上的转换,而是格式上的转换,如22点,BCD码值是0x22,转换后的BIN码值为22

网上有人说RTC_FORMAT_BIN格式初始化有问题,但测试后并没有问题。

END

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柯西的彷徨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值