STM32_HAL_RTC时钟

news2024/11/18 5:29:30

1. RTC 时钟简介

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

        RTC 模块和时钟配置系统(RCC_BDCR 寄存器)是在后备区域,即在系统复位或从待机模式唤醒后 RTC 的设置和时间维持不变,只要后备区域供电正常,那么 RTC 将可以一直运行。但是在系统复位后,会自动禁止访问后备寄存器和 RTC,以防止对后备区域(BKP)的意外写操作。所以在要设置时间之前,先要取消备份区域(BKP)写保护。

1.1 RTC 框图

① 时钟源

        STM32F407 的 RTC 时钟源(RTCCLK)通过时钟控制器,可以从 LSE 时钟、 LSI 时钟以及 HSE 时钟三者中选择其一(通过设置 RCC_BDCR 寄存器选择)。一般我们选择 LSE,即外部 32.768KHz 晶振作为时钟源(RTCCLK)。外部晶振具有精度高的优点。 LSI 是 STM32 芯片内部的低速 RC 振荡器, 频率约 32 KHz,缺点是精度较低,所以一般不建议使用。比如当没有外部低速晶振(32.768KHz)的时候,分频后的 HSE 可以作为备选使用的时钟源。

② 预分频器

        预分配器(RTC_PRER)分为 2 个部分:一个通过 RTC_PRER 寄存器的 PREDIV_A 位配置的 7 位异步预分频器。另一个通过 RTC_PRER 寄存器的 PREDIV_S 位配置的 15 位同步预分频器。

        经过 7 位异步预分频器出来的时钟 ck_apre 可作为 RTC_SSR 亚秒递减计数器(RTC_SSR)的时钟, ck_apre 时钟频率的计算公式如下:Fck_apre = Frtcclk/( PREDIV_A + 1)

        当 RTC_SSR 寄存器递减到 0 的时候,会使用 PREDIV_S 的值重新装载 PREDIV_S。而PREDIV_S 一般为 255,这样,我们得到亚秒时间的精度是: 1/256 秒,即 3.9ms 左右,有了这个亚秒寄存器 RTC_SSR,就可以得到更加精确的时间数据。

        经过 15 位同步预分频器出来的时钟 ck_spre 可以用于更新日历,也可以用作 16 位唤醒自动重载定时器的时基, ck_apre 时钟频率的计算公式如下:Fck_spre = Frtcclk/((PREDIV_S + 1) ∗ ( PREDIV_A + 1))

        PREDIV_A 和 PREDIV_S 分别为 RTC 的异步和同步分频器,使用两个预分频器时,我们推荐设置 7 位异步预分频器(PREDIV_A)的值较大,以最大程度降低功耗。例如:本实验我们的外部低速晶振的频率 32.768KHz 经过 7 位异步预分频器后,再经过 15 位同步预分频器,要得到 1Hz 频率的时钟用于更新日历。通过计算知道, 32.768KHz 的时钟要经过 32768 分频,才能得到 1Hz 的 ck_spre。于是我们只需要设置: PREDIV_A=0X7F,即 128 分频; PREDIV_S=0XFF,即 256 分频,即可得到 1Hz 的 Fck_spre, PREDIV_A 的值我们也是往尽量大的原则,以最大程度降低功耗。

③ 时间和日期相关寄存器

        该部分包括三个影子寄存器, RTC_SSR(亚秒)、 RTC_TR(时间)、 RTC_DR(日期)。 实时时钟一般表示为:时/分/秒/亚秒。 RTC_TR 寄存器用于存储时/分/秒时间数据,可读可写(即可设置或者获取时间)。 RTC_DR 寄存器用于存储日期数据,包括年/月/日/星期,可读可写(即可设置或者获取日期)。 RTC_SSR 寄存器用于存储亚秒级的时间,这样我们可以获取更加精确的时间数据。

        这三个影子寄存器可以通过与 PCLK1(APB1 时钟)同步的影子寄存器来访问, 这些时间和日期寄存器也可以直接访问,这样可避免等待同步的持续时间。

        每隔 2 个 RTCCLK 周期,当前日历值便会复制到影子寄存器,并置位 RTC_ISR 寄存器的RSF 位。我们可以读取 RTC_TR 和 RTC_DR 来得到当前时间和日期信息,不过需要注意的是:时间和日期都是以 BCD 码的格式存储的,读出来要转换一下,才可以得到十进制的数据。

④ 可编程闹钟

        STM32F407 提供两个可编程闹钟:闹钟 A(ALARM_A)和闹钟 B(ALARM_B)。通过RTC_CR 寄存器的 ALRAE 和 ALRBE 位置 1 来使能闹钟。当亚秒、秒、分、小时、日期分别与闹钟寄存器 RTC_ALRMASSR/RTC_ALRMAR 和 RTC_ALRMBSSR/RTC_ALRMBR 中的值匹配时,则可以产生闹钟(需要适当配置)。本章我们将利用闹钟 A 产生闹铃,即设置RTC_ALRMASSR 和 RTC_ALRMAR 即可。

⑤ 周期性自动唤醒

        STM32F407 的 RTC 不带秒钟中断了,但是多了一个周期性自动唤醒功能。周期性唤醒功能,由一个 16 位可编程自动重载递减计数器(RTC_WUTR)生成,可用于周期性中断/唤醒。

        我们可以通过 RTC_CR 寄存器中的 WUTE 位设置使能此唤醒功能。

        唤醒定时器的时钟输入可以是: 2、 4、 8 或 16 分频的 RTC 时钟(RTCCLK),也可以是 ck_spre时钟(一般为 1Hz)。

        当选择 RTCCLK(假定 LSE 是: 32.768 kHz)作为输入时钟时,可配置的唤醒中断周期介于122us(因为 RTCCLK/2 时, RTC_WUTR 不能设置为 0)和 32 s 之间,分辨率最低为: 61us。当选择 ck_spre(1Hz)作为输入时钟时,可得到的唤醒时间为 1s 到 36h 左右,分辨率为 1秒。并且这个 1s~36h 的可编程时间范围分为两部分:当 WUCKSEL[2:1]=10 时为: 1s 到 18h。

        当 WUCKSEL[2:1]=11 时约为: 18h 到 36h。

        在后一种情况下,会将 2^16 添加到 16 位计数器当前值(即扩展到 17 位,相当于最高位用WUCKSEL [1]代替)。

        初始化完成后,定时器开始递减计数。在低功耗模式下使能唤醒功能时,递减计数保持有效。此外,当计数器计数到 0 时, RTC_ISR 寄存器的 WUTF 标志会置 1,并且唤醒寄存器会使用其重载值(RTC_WUTR 寄存器值)自动重载,之后必须用软件清零 WUTF 标志。

        通过将 RTC_CR 寄存器中的 WUTIE 位置 1 来使能周期性唤醒中断时,可以使 STM32 退出低功耗模式。系统复位以及低功耗模式(睡眠、停机和待机)对唤醒定时器没有任何影响,它仍然可以正常工作,故唤醒定时器,可以用于周期性唤醒 STM32。

 1.2 RTC 寄存器

 RTC时间寄存器(RTC_TR)

        该寄存器是 RTC 的时间寄存器,可读可写,对该寄存器写,可以设置时间,对该寄存器读,可以获取当前的时间,此外该寄存器受到寄存器写保护,通过 RTC 写保护寄存器(RTC_WPR)设置,后面会讲解到 RTC_WPR 寄存器。需要注意的是:本寄存器存储的数据都是 BCD 格式的,读取之后需要进行转换,方可得到十进制的时分秒等数据。

RTC日期寄存器(RTC_DR)

        该寄存器是 RTC 的日期寄存器,可读可写,对该寄存器写,可以设置日期,对该寄存器读,可以获取当前的日期,同样该寄存器也受到寄存器写保护,存储的数据也都是 BCD 格式的。

RTC控制寄存器(RTC_CR)

        该寄存器重点介绍几个要用到的位: WUTIE 是唤醒定时器中断使能位, ALRAIE 是闹钟 A中断使能位,本章用到这两个使能位,都设置为 1 即可。 WUTE 和 ALRAE 分别是唤醒定时器和闹钟 A 使能位,同样都设置为 1,开启。 FMT 为小时格式选择位,我们设置为 0,选择 24 小时制。 WUCKSEL[2:0],用于唤醒时钟选择。

RTC亚秒寄存器(RTC_SSR)

        该寄存器可用于获取更加精确的 RTC 时间。

RTC初始化和状态寄存器(RTC_ISR)

        该寄存器中, WUTF、 ALRBF 和 ALRAF,分别是唤醒定时器、闹钟 B 和闹钟 A 的中断标志位,当对应事件产生时,这些标志位被置 1,如果设置了中断,则会进入中断服务函数,这些位通过软件写 0 清除。

        INIT 为初始化模式控制位,要初始化 RTC 时,必须先设置 INIT=1。

        INITF 为初始化标志位,当设置 INIT 为 1 以后,要等待 INITF 为 1,才可以更新时间、日期和预分频寄存器等。

        RSF 位为寄存器同步标志,仅在该位为 1 时,表示日历影子寄存器已同步,可以正确读取RTC_TR/RTC_TR 寄存器的值了。

        WUTWF、 ALRBWF 和 ALRAWF 分别是唤醒定时器、闹钟 B 和闹钟 A 的写标志,只有在这些位为 1 的时候,才可以更新对应的内容。比如:要设置闹钟 A 的 ALRMAR 和 ALRMASSR,则必须先等待 ALRAWF 为 1,才可以设置。

 RTC预分频寄存器(RTC_PRER)

        该寄存器用于 RTC 的分频,该寄存器的配置,必须在初始化模式(INITF=1)下,才可以进行。

RTC唤醒寄存器(RTC_WUTR)

        该寄存器用于设置自动唤醒重装载值,可用于设置唤醒周期。该寄存器的配置,必须等待RTC_ISR 的 WUTWF 为 1 才可以进行。

RTC闹钟A寄存器(RTC_ALRMAR)

        该寄存器用于设置闹铃 A,当 WDSEL 选择 1 时,使用星期制闹铃,本章我们选择星期制闹铃。该寄存器的配置,必须等待 RTC_ISR 的 ALRAWF 为 1 才可以进行。

RTC 写保护寄存器(RTC_WPR)
        RTC 写保护寄存器: RTC_WPR,该寄存器比较简单,低八位有效。上电后,所有 RTC 寄存器都受到写保护(RTC_ISR[13:8]、RTC_TAFCR 和 RTC_BKPxR 除外),必须依次写入: 0xCA、0x53 两关键字到 RTC_WPR 寄存器,才可以解锁。写一个错误的关键字将再次激活 RTC 的寄存器写保护。

RTC备份寄存器(RTC_BKPxR)

        该寄存器组总共有 32 个,每个寄存器是 32 位的,可以存储 128 个字节的用户数据, 这些寄存器在备份域中实现,可在 VDD 电源关闭时通过 VBAT 保持上电状态。备份寄存器不会在系统复位或电源复位时复位,也不会在 MCU 从待机模式唤醒时复位。

        复位后,对 RTC 和 RTC 备份寄存器的写访问被禁止, 执行以下操作可以使能 RTC 及 RTC备份寄存器的写访问:

        1)电源控制寄存器(PWR_CR)的 DBP 位来使能 RTC 及 RTC 备份寄存器的访问。

        2) 往 RTC_WPR 写入 0xCA、 0x53 解锁序列(先写 0xCA,再写 0x53)。

        我们可以用 BKP 来存储一些重要的数据,相当于一个 EEPROM,不过这个 EEPROM 并不是真正的 EEPROM,而是需要电池来维持它的数据。

 备份区域控制(RTC_BDCR)

        RTC 的时钟源选择及使能设置都是通过这个寄存器来实现的,所以我们在 RTC 操作之前先要通过这个寄存器选择 RTC 的时钟源,然后才能开始其他的操作。

2. 硬件设计

1) LED 灯

LED0 – PF9

LED1 – PF10

2)串口 1(PA9/PA10 连接在板载 USB 转串口芯片 CH340 上面)

3) RTC(实时时钟)

4) 正点原子 2.8/3.5/4.3/7 寸 TFTLCD 模块(仅限 MCU 屏, 16 位 8080 并口驱动)

 3. 程序设计

1. HAL_RTC_Init 函数
RTC 的初始化函数,其声明如下:

HAL_StatusTypeDef HAL_RTC_Init(RTC_HandleTypeDef *hrtc);

形参1是RTC_HandleTypeDef结构体变量

typedef struct
{
RTC_TypeDef *Instance; /* 寄存器基地址 */
RTC_InitTypeDef Init; /* RTC 配置结构体 */
HAL_LockTypeDef Lock; /* RTC 锁定对象 */
__IO HAL_RTCStateTypeDef State; /* RTC 设备访问状态 */
}RTC_HandleTypeDef;

Init: 是真正的 RTC 初始化结构体,其结构体类型 RTC_InitTypeDef 定义如下:
 

typedef struct
{
uint32_t HourFormat; /* 小时格式 */
uint32_t AsynchPrediv; /* 异步预分频系数 */
uint32_t SynchPrediv; /* 同步预分频系数 */
uint32_t OutPut; /* 选择连接到 RTC_ALARM 输出的标志 */
uint32_t OutPutPolarity; /* 设置 RTC_ALARM 的输出极性 */
uint32_t OutPutType; /* 设置 RTC_ALARM 的输出类型为开漏输出还是推挽输出 */
}RTC_InitTypeDef;

2. HAL_RTC_SetTime 函数
HAL_RTC_SetTime 是设置 RTC 的时间函数

HAL_StatusTypeDef HAL_RTC_SetTime(RTC_HandleTypeDef *hrtc,
RTC_TimeTypeDef *sTime, uint32_t Format);

形参 1 是 RTC_HandleTypeDef 结构体类型指针变量,即 RTC 的句柄。

形参 2 是 RTC_TimeTypeDef 结构体类型指针变量, 定义如下:

typedef struct
{
uint8_t Hours;
uint8_t Minutes;
uint8_t Seconds;
uint8_t TimeFormat;
uint32_t SubSeconds;
uint32_t SecondFraction;
uint32_t DayLightSaving;
uint32_t StoreOperation;
}RTC_TimeTypeDef;

前面四个成员变量就比较好理解了,分别用来设置 RTC 时间参数的小时,分钟,秒钟,以及 AM/PM 符号,大家参考前面讲解的 RTC_TR 的位描述即可。 SubSeconds 用来读取保存亚秒寄存器 RTC_SSR 的值, SecondFraction 用来读取保存同步预分频系数的值,也就是 RTC_PRER的位 0~14, DayLightSaving 用来设置日历时间增加 1 小时,减少 1 小时,还是不变。StoreOperation用户可对此变量设置以记录是否已对夏令时进行更改。

形参 3 是 uint32_t 类型变量, 用来设置输入的时间格式为 BIN 格式还是 BCD 格式,可选值为 RTC_FORMAT_BIN 或者 RTC_FORMAT_BCD。

3. HAL_RTC_SetDate 函数
HAL_RTC_SetDate 是设置 RTC 的日期函数。其声明如下:

HAL_StatusTypeDef HAL_RTC_SetDate(RTC_HandleTypeDef *hrtc, RTC_DateTypeDef *sDate, uint32_t Format);

形参 1 是 RTC_HandleTypeDef 结构体类型指针变量,即 RTC 的句柄。

形参 2 是 RTC_DateTypeDef 结构体类型指针变量, 定义如下:

typedef struct
{
uint8_t WeekDay; /* 星期 */
uint8_t Month; /* 月份 */
uint8_t Date; /* 日期 */
uint8_t Year; /* 年份 */
}RTC_DateTypeDef;

形参 3 是 uint32_t 类型变量, 用来设置输入的时间格式为 BIN 格式还是 BCD 格式,可选值为 RTC_FORMAT_BIN 或者 RTC_FORMAT_BCD。

4. HAL_RTC_GetTime 函数

HAL_RTC_GetTime 是获取当前 RTC 时间函数。其声明如下:

HAL_StatusTypeDef HAL_RTC_GetTime(RTC_HandleTypeDef *hrtc,RTC_TimeTypeDef *sTime, uint32_t Format);

形参 1 是 RTC_HandleTypeDef 结构体类型指针变量,即 RTC 的句柄。

形参 2 是 RTC_TimeTypeDef 结构体类型指针变量,对应的是 RTC_TR 寄存器。

形参 3 是 uint32_t 类型变量, 用来设置获取的时间格式为 BIN 格式还是 BCD 格式,可选值为 RTC_FORMAT_BIN 或者 RTC_FORMAT_BCD。

 RTC 配置步骤

1)使能电源时钟,并使能 RTC 及 RTC 后备寄存器写访问。

        我们要访问 RTC 和 RTC 备份区域就必须先使能电源时钟,然后使能 RTC 即后备区域访问。电源时钟使能,通过 RCC_APB1ENR 寄存器来设置; RTC 及 RTC 备份寄存器的写访问,通过 PWR_CR 寄存器的 DBP 位设置。 HAL 库设置方法为:

__HAL_RCC_PWR_CLK_ENABLE(); /* 使能电源时钟 PWR */
__HAL_RCC_BKP_CLK_ENABLE(); /* 使能备份时钟 */
HAL_PWR_EnableBkUpAccess(); /* 取消备份区域写保护 */

2)开启外部低速振荡器 LSE,选择 RTC 时钟,并使能

        调用 HAL_RCC_OscConfig 函数配置开启 LSE。

        调用 HAL_RCCEx_PeriphCLKConfig 函数选择 RTC 时钟源。使能 RTC 函数为: __HAL_RCC_RTC_ENABLE。

3) 初始化 RTC, 设置 RTC 的分频,以及配置 RTC 参数

        在 HAL 中,通过 HAL_RTC_Init 函数配置 RTC 分频系数,以及 RTC 的工作参数。

        注意:该函数会调用: HAL_RTC_MspInit 函数来完成对 RTC 的底层初始化,包括: RTC 时钟使能、时钟源选择等。

4) 设置 RTC 的时间

        调用 HAL_RTC_SetTime 函数设置 RTC 时间,该函数实际设置时间寄存器 RTC_TR 的相关位的值。

5) 设置 RTC 的日期

        调用 HAL_RTC_SetDate 函数设置 RTC 的日期,该函数实际设置日期寄存器 RTC_DR 的相关位的值。

6) 获取 RTC 当前日期和时间

        调用 HAL_RTC_GetTime 函数获取当前 RTC 时间,该函数实际读取 RTC_TR 寄存器,然后将值存放到相应的结构体中。

        调用 HAL_RTC_GetDate 函数获取当前 RTC 日期,该函数实际读取 RTC_DR 寄存器,然后将值存放到相应的结构体中。

rtc.h

#ifndef __RTC_H
#define __RTC_H

#include "./SYSTEM/sys/sys.h"


extern RTC_HandleTypeDef g_rtc_handle;

uint8_t rtc_init(void);                             /* 初始化RTC */
uint32_t rtc_read_bkr(uint32_t bkrx);               /* 读后备寄存器 */
void rtc_write_bkr(uint32_t bkrx, uint32_t data);   /* 写后备寄存器 */
void rtc_get_time(uint8_t *hour, uint8_t *min, uint8_t *sec, uint8_t *ampm);            /* 获取时间 */
HAL_StatusTypeDef rtc_set_time(uint8_t hour, uint8_t min, uint8_t sec, uint8_t ampm);   /* 设置时间 */
void rtc_get_date(uint8_t *year, uint8_t *month, uint8_t *date, uint8_t *week);         /* 获取日期 */
HAL_StatusTypeDef rtc_set_date(uint8_t year, uint8_t month, uint8_t date, uint8_t week);/* 设置日期 */

void rtc_set_wakeup(uint8_t wksel, uint16_t cnt);                                       /* 设置周期性唤醒 */
uint8_t rtc_get_week(uint16_t year, uint8_t month, uint8_t day);                        /* 获取星期 */
void rtc_set_alarma(uint8_t week, uint8_t hour, uint8_t min, uint8_t sec);              /* 设置闹钟 */

#endif

rtc.c

#include "./BSP/RTC/rtc.h"
#include "./BSP/LED/led.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"


RTC_HandleTypeDef g_rtc_handle;  /* RTC句柄 */

/**
 * @brief       RTC写入后备区域SRAM
 * @param       bkrx : 后备区寄存器编号,范围:0~31
 *                     对应 RTC_BKP_DR0~RTC_BKP_DR31
 * @param       data : 要写入的数据,32位长度
 * @retval      无
 */
void rtc_write_bkr(uint32_t bkrx, uint32_t data)
{
    HAL_PWR_EnableBkUpAccess();     /* 取消备份区写保护 */
    HAL_RTCEx_BKUPWrite(&g_rtc_handle, bkrx, data);
}

/**
 * @brief       RTC读取后备区域SRAM
 * @param       bkrx : 后备区寄存器编号,范围:0~31
 * @retval      读取到的值
 */
uint32_t rtc_read_bkr(uint32_t bkrx)
{
    uint32_t temp = 0;
    temp = HAL_RTCEx_BKUPRead(&g_rtc_handle, bkrx);
    return temp; /* 返回读取到的值 */
}

/**
 * @brief       RTC时间设置
 * @param       hour,min,sec: 小时,分钟,秒钟
 * @param       ampm        : AM/PM, 0=AM/24H; 1=PM/12H
 * @retval      0,成功
 *              其他,异常状态
 */
HAL_StatusTypeDef rtc_set_time(uint8_t hour, uint8_t min, uint8_t sec, uint8_t ampm)
{
    RTC_TimeTypeDef rtc_time_handle;

    rtc_time_handle.Hours = hour;
    rtc_time_handle.Minutes = min;
    rtc_time_handle.Seconds = sec;
    rtc_time_handle.TimeFormat = ampm;
    rtc_time_handle.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
    rtc_time_handle.StoreOperation = RTC_STOREOPERATION_RESET;
    return HAL_RTC_SetTime(&g_rtc_handle, &rtc_time_handle, RTC_FORMAT_BIN);
}

/**
 * @brief       RTC日期设置
 * @param       year,month,date : 年(0~99),月(1~12),日(0~31)
 * @param       week            : 星期(1~7,0,非法!)
 * @retval      0,成功
 *              其他,异常状态
 */
HAL_StatusTypeDef rtc_set_date(uint8_t year, uint8_t month, uint8_t date, uint8_t week)
{
    RTC_DateTypeDef rtc_date_handle;

    rtc_date_handle.Date = date;
    rtc_date_handle.Month = month;
    rtc_date_handle.WeekDay = week;
    rtc_date_handle.Year = year;
    return HAL_RTC_SetDate(&g_rtc_handle, &rtc_date_handle, RTC_FORMAT_BIN);
}

/**
 * @brief       获取RTC时间
 * @param       *hour,*min,*sec : 小时,分钟,秒钟
 * @param       *ampm           : AM/PM,0=AM/24H,1=PM/12H.
 * @retval      无
 */
void rtc_get_time(uint8_t *hour, uint8_t *min, uint8_t *sec, uint8_t *ampm)
{
    
    RTC_TimeTypeDef rtc_time_handle;

    HAL_RTC_GetTime(&g_rtc_handle, &rtc_time_handle, RTC_FORMAT_BIN);

    *hour = rtc_time_handle.Hours;
    *min = rtc_time_handle.Minutes;
    *sec = rtc_time_handle.Seconds;
    *ampm = rtc_time_handle.TimeFormat;
}

/**
 * @brief       获取RTC日期
 * @param       *year,*mon,*date: 年,月,日
 * @param       *week           : 星期
 * @retval      无
 */
void rtc_get_date(uint8_t *year, uint8_t *month, uint8_t *date, uint8_t *week)
{
    RTC_DateTypeDef rtc_date_handle;

    HAL_RTC_GetDate(&g_rtc_handle, &rtc_date_handle, RTC_FORMAT_BIN);

    *year = rtc_date_handle.Year;
    *month = rtc_date_handle.Month;
    *date = rtc_date_handle.Date;
    *week = rtc_date_handle.WeekDay;
}

/* 月修正数据表 */
uint8_t const table_week[12] = {0, 3, 3, 6, 1, 4, 6, 2, 5, 0, 3, 5};

/**
 * @breif       获得现在是星期几, 输入公历日期得到星期(只允许1901-2099年)
 * @param       year,month,day : 公历年月日
 * @retval      星期号(1~7,代表周1~周日)
 */
uint8_t rtc_get_week(uint16_t year, uint8_t month, uint8_t day)
{
    uint16_t temp;
    uint8_t yearH, yearL;

    yearH = year / 100;
    yearL = year % 100;

    /*  如果为21世纪,年份数加100 */
    if (yearH > 19)
    {
        yearL += 100;
    }

    /*  所过闰年数只算1900年之后的 */
    temp = yearL + yearL / 4;
    temp = temp % 7;
    temp = temp + day + table_week[month - 1];

    if (yearL % 4 == 0 && month < 3)
    {
        temp--;
    }

    temp %= 7;

    if (temp == 0)
    {
        temp = 7;
    }

    return temp;
}

/**
 * @brief       RTC初始化
 *   @note
 *              默认尝试使用LSE,当LSE启动失败后,切换为LSI.
 *              通过BKP寄存器0的值,可以判断RTC使用的是LSE/LSI:
 *              当BKP0==0x5050时,使用的是LSE
 *              当BKP0==0x5051时,
 *              注意:切换LSI/LSE将导致时间/日期丢失,切换后需重新设置.
 *
 * @param       无
 * @retval      0,成功
 *              1,进入初始化模式失败
 */
uint8_t rtc_init(void)
{
    uint16_t bkpflag = 0;
    
    __HAL_RCC_RTC_ENABLE();     /* RTC使能 */
    HAL_PWR_EnableBkUpAccess(); /* 取消备份区域写保护 */
    __HAL_RCC_PWR_CLK_ENABLE(); /* 使能电源时钟PWR */
    
    g_rtc_handle.Instance = RTC;
    g_rtc_handle.Init.HourFormat = RTC_HOURFORMAT_24;   /* RTC设置为24小时格式 */
    g_rtc_handle.Init.AsynchPrediv = 0x7F;              /* RTC异步分频系数(1~0x7F) */
    g_rtc_handle.Init.SynchPrediv = 0xFF;               /* RTC同步分频系数(0~0x7FFF) */
    g_rtc_handle.Init.OutPut = RTC_OUTPUT_DISABLE;
    g_rtc_handle.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
    g_rtc_handle.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;

    /* 检查是不是第一次配置时钟 */
    bkpflag = rtc_read_bkr(0);                /* 读取BKP0的值 */

    if (HAL_RTC_Init(&g_rtc_handle) != HAL_OK)
    {
        return 1;
    }

    if ((bkpflag != 0x5050) && (bkpflag != 0x5051))     /* 之前未初始化过, 重新配置 */
    {
        rtc_set_time(6, 59, 56, RTC_HOURFORMAT12_AM);   /* 设置时间, 根据实际时间修改 */
        rtc_set_date(20, 4, 22, 3);                     /* 设置日期 */
    }
    return 0;
}

/**
 * @brief       RTC底层驱动, 时钟配置
 * @param       hrtc : RTC句柄
 * @note        此函数会被HAL_RTC_Init()调用
 * @retval      无
 */
void HAL_RTC_MspInit(RTC_HandleTypeDef* hrtc)
{
    uint16_t retry = 200;

    RCC_OscInitTypeDef rcc_osc_init_handle;
    RCC_PeriphCLKInitTypeDef rcc_periphclk_init_handle;

    __HAL_RCC_RTC_ENABLE();     /* RTC使能 */
    HAL_PWR_EnableBkUpAccess(); /* 取消备份区域写保护 */
    __HAL_RCC_PWR_CLK_ENABLE(); /* 使能电源时钟PWR */
    __HAL_RCC_RTC_ENABLE();     /* RTC使能 */

    /* 使用寄存器的方式去检测LSE是否可以正常工作 */
    RCC->BDCR |= 1 << 0;        /* 尝试开启LSE */

    while (retry && ((RCC->BDCR & 0x02) == 0))  /* 等待LSE准备好 */
    {
        retry--;
        delay_ms(5);
    }
    
    if (retry == 0)     /* LSE起振失败 使用LSI */
    {
        rcc_osc_init_handle.OscillatorType = RCC_OSCILLATORTYPE_LSI;  /* 选择要配置的振荡器 */
        rcc_osc_init_handle.LSIState = RCC_LSI_ON;                    /* LSI状态:开启 */
        rcc_osc_init_handle.PLL.PLLState = RCC_PLL_NONE;              /* PLL无配置 */
        HAL_RCC_OscConfig(&rcc_osc_init_handle);                      /* 配置设置的rcc_oscinitstruct */

        rcc_periphclk_init_handle.PeriphClockSelection = RCC_PERIPHCLK_RTC; /* 选择要配置外设 RTC */
        rcc_periphclk_init_handle.RTCClockSelection = RCC_RTCCLKSOURCE_LSI; /* RTC时钟源选择LSI */
        HAL_RCCEx_PeriphCLKConfig(&rcc_periphclk_init_handle);              /* 配置设置的rcc_periphclkinitstruct */
        rtc_write_bkr(0, 0x5051);
    }
    else
    {
        rcc_osc_init_handle.OscillatorType = RCC_OSCILLATORTYPE_LSE;        /* 选择要配置的振荡器 */
        rcc_osc_init_handle.PLL.PLLState = RCC_PLL_NONE;                    /* PLL不配置 */
        rcc_osc_init_handle.LSEState = RCC_LSE_ON;                          /* LSE状态:开启 */
        HAL_RCC_OscConfig(&rcc_osc_init_handle);                            /* 配置设置的rcc_oscinitstruct */

        rcc_periphclk_init_handle.PeriphClockSelection = RCC_PERIPHCLK_RTC; /* 选择要配置外设RTC */
        rcc_periphclk_init_handle.RTCClockSelection = RCC_RTCCLKSOURCE_LSE; /* RTC时钟源选择LSE */
        HAL_RCCEx_PeriphCLKConfig(&rcc_periphclk_init_handle);              /* 配置设置的rcc_periphclkinitstruct */
        rtc_write_bkr(0, 0x5050);
    }
}

/**
 * @breif       设置闹钟时间(按星期闹铃,24小时制)
 * @param       week        : 星期几(1~7) 
 * @param       hour,min,sec: 小时,分钟,秒钟
 * @retval      无
 */
void rtc_set_alarma(uint8_t week, uint8_t hour, uint8_t min, uint8_t sec)
{
    RTC_AlarmTypeDef rtc_alarm_handle;
    
    rtc_alarm_handle.AlarmTime.Hours = hour;                                /* 小时 */
    rtc_alarm_handle.AlarmTime.Minutes = min;                               /* 分钟 */
    rtc_alarm_handle.AlarmTime.Seconds = sec;                               /* 秒 */
    rtc_alarm_handle.AlarmTime.SubSeconds = 0;
    rtc_alarm_handle.AlarmTime.TimeFormat = RTC_HOURFORMAT12_AM;
    
    rtc_alarm_handle.AlarmMask = RTC_ALARMMASK_NONE;                        /* 精确匹配星期, 时分秒 */
    rtc_alarm_handle.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_NONE;
    rtc_alarm_handle.AlarmDateWeekDaySel = RTC_ALARMDATEWEEKDAYSEL_WEEKDAY; /* 按星期 */
    rtc_alarm_handle.AlarmDateWeekDay = week;                               /* 星期 */
    rtc_alarm_handle.Alarm = RTC_ALARM_A;                                   /* 闹钟A */
    HAL_RTC_SetAlarm_IT(&g_rtc_handle, &rtc_alarm_handle, RTC_FORMAT_BIN);
    
    HAL_NVIC_SetPriority(RTC_Alarm_IRQn, 1, 2);   /* 抢占优先级1,子优先级2 */
    HAL_NVIC_EnableIRQ(RTC_Alarm_IRQn);
}

/**
 * @breif       周期性唤醒定时器设置
 * @param       wksel
 *   @arg       RTC_WAKEUPCLOCK_RTCCLK_DIV16        ((uint32_t)0x00000000)
 *   @arg       RTC_WAKEUPCLOCK_RTCCLK_DIV8         ((uint32_t)0x00000001)
 *   @arg       RTC_WAKEUPCLOCK_RTCCLK_DIV4         ((uint32_t)0x00000002)
 *   @arg       RTC_WAKEUPCLOCK_RTCCLK_DIV2         ((uint32_t)0x00000003)
 *   @arg       RTC_WAKEUPCLOCK_CK_SPRE_16BITS      ((uint32_t)0x00000004)
 *   @arg       RTC_WAKEUPCLOCK_CK_SPRE_17BITS      ((uint32_t)0x00000006)
 * @note        000,RTC/16;001,RTC/8;010,RTC/4;011,RTC/2;
 * @note        注意:RTC就是RTC的时钟频率,即RTCCLK!
 * @param       cnt: 自动重装载值.减到0,产生中断.
 * @retval      无
 */
void rtc_set_wakeup(uint8_t wksel, uint16_t cnt)
{ 
    __HAL_RTC_WAKEUPTIMER_CLEAR_FLAG(&g_rtc_handle, RTC_FLAG_WUTF);  /* 清除RTC WAKE UP的标志 */

    HAL_RTCEx_SetWakeUpTimer_IT(&g_rtc_handle, cnt, wksel);          /* 设置重装载值和时钟 */

    HAL_NVIC_SetPriority(RTC_WKUP_IRQn, 2, 2);                       /* 抢占优先级2, 子优先级2 */
    HAL_NVIC_EnableIRQ(RTC_WKUP_IRQn);
}

/**
 * @breif       RTC闹钟中断服务函数
 * @param       无
 * @retval      无
 */
void RTC_Alarm_IRQHandler(void)
{
    HAL_RTC_AlarmIRQHandler(&g_rtc_handle);
}

/**
 * @breif       RTC闹钟A中断处理回调函数
 * @param       hrtc : RTC句柄
 * @retval      无
 */
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
{
    printf("ALARM A!\r\n");
}

/**
 * @breif       RTC WAKE UP中断服务函数
 * @param       无
 * @retval      无
 */
void RTC_WKUP_IRQHandler(void)
{
    HAL_RTCEx_WakeUpTimerIRQHandler(&g_rtc_handle); 
}

/**
 * @breif       RTC WAKE UP中断处理处理回调函数
 * @param       hrtc : RTC句柄
 * @retval      无
 */
void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc)
{
    LED1_TOGGLE();
}

这样,就可以在主函数编写显示时间了,可显示在OLED屏幕上或者LCD屏幕上也可通过串口打印

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1687734.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

antd-vue a-tree 当两个不同一级下二级key相同的时候就会导致两个同时选择, 拿到node.parent的数据也会出问题, 解决办法

一、问题如下图&#xff1a; 当两个不同一级下二级key相同的时候就会导致两个同时选择&#xff0c; 同时拿到node.parent的数据也会出问题, 出现一下问题的原因是因为数据treeData 的key出现相同的了 然后如下图、因为我的查询条件 第二层是给 cloud , 第二层是给 relatedPool…

1、pikachu靶场之xss钓鱼复现

一、复现过程 1、payload <script src"http://127.0.0.1/pkxss/xfish/fish.php"></script> 将这段代码插入到含有储存xss的网页上&#xff0c;如下留言板 2、此时恶意代码已经存入数据库&#xff0c;并存在网页中&#xff0c;当另一个用户打开这个网页…

WPF中快速使用iconfont中的icon图标资源

在WPF开发中经常需要用到Icon图标&#xff0c;我们这用用的是Iconfont网站查找icon的资源&#xff0c;本文讲如何把iconfont图标资源当成字体文件导入到WPF程序中使用。 查找打包资源 1.在Iconfont官网查找资源 根据自己需要查找&#xff0c;资源然后添加到购物车 https://…

windows Oracle 11g服务器端和客户端安装 SQLark连接ORACLE

1 从ORACLE官网下载数据库安装包 https://edelivery.oracle.com/osdc/faces/SoftwareDelivery 2:安装数据库 注意&#xff1a;在加载组件的这一步&#xff0c;如果你的电脑里面有杀毒软件&#xff0c;首先把安装目录加入白名单&#xff0c;要不然可能会一直加载组件失败。…

netdiscover一键收集子网内的所有信息(KALI工具系列六)

目录 1、KALI LINUX简介 2、netdiscover工具简介 3、在KALI中使用netdiscover 3.1 目标主机IP&#xff08;win&#xff09; 3.2 KALI的IP 4、命令示例 4.1 扫描子网整个网段 4.2 指定网卡进行扫描 4.3 扫描网卡的公共网络 4.4 快速扫描网卡的公共lan地址 4.5 设置…

echart指定坐标markline

效果如图&#xff1a; 测试代码&#xff0c;可以直接黏贴到echart测试页面中 https://echarts.apache.org/examples/zh/editor.html?cline-simple option {xAxis: {type: value,data: [1, 2, 3, 4, 6, 8, 10]},yAxis: {type: value},series: [{data: [5, 5, 5, null, 6, 6…

UNI-APP设置屏幕保持常亮-不熄灭屏幕

前言 最近在实际开发过程中&#xff0c;我们会发现在自己使用的app当中会根据系统无操作熄灭屏幕对于一下需要长时间保持屏幕的业务就很不友好&#xff0c;uni-app也是提供了相应方法加上代码之后-注意app端没报错-不生效就是权限问题-需要设置相对应权限-打自定义包 代码实现…

Plesk中如何移除之前添加的域名

我这边想要移除我之前绑定到主机的域名&#xff0c;但是不知道如何在主机上面进行移除&#xff0c;由于我使用的Hostease的Windows虚拟主机产品默认带普通用户权限的Plesk面板&#xff0c;但是不知道如何在Plesk上操作移除域名&#xff0c;因为也是对于Hostease主机产品不是很了…

储油罐智控:ThingsBoard网关实现液位温度精准监测

储油罐是采油、炼油企业储存油品的重要设备&#xff0c;对储油罐液位、温度的实时数据监测对企业的库存和安全管理有着重大意义。 场景 对于企业&#xff0c;尤其是加油站来说&#xff0c;高效的罐体液位、温度监测对于优化燃油库存、防止短缺或过剩至关重要。不准确的燃油液位…

GS5812G 21V、2A同步降压DC/DC转换器芯片IC

一般描述 该GS5812G是一个同步降压DC/DC转换器与快速恒定的时间(FCOT)模式控制。该器件提供4. 5V至21V的输入电压范围和2A连续负载电流能力。它是恒定时间脉宽调制(PWM)控制器&#xff0c;支持 FCOT模式控制。工作频率取决于输入和输出电压条件。 该GS5812G故障…

拨云见日,ATFX七场研讨会揭秘投资先机

财经先机&#xff0c;一手掌握。近期&#xff0c;随着国际金价持续走高&#xff0c;避险情绪高涨&#xff0c;由此激发新一轮投资热潮。作为业界领先的金融创新品牌&#xff0c;ATFX深受投资者认可和信赖&#xff0c;为助力广大投资者了解市场运行规律&#xff0c;捕捉财经脉络…

基于STC12C5A60S2系列1T 8051单片机的TM1637键盘数码管模块的数码管显示与单片机连接的按键的按键值的功能

基于STC12C5A60S2系列1T 8051单片机的TM1638键盘数码管模块的数码管显示与单片机连接的按键的按键值应用 STC12C5A60S2系列1T 8051单片机管脚图STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式及配置STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式介绍TM1637键盘数码…

MyCat2之安装与配置文件介绍

安装 1.新建文件夹tools mkdir tools&#xff0c;并进入tools 2.下载MaCat wget http://dl.mycat.org.cn/2.0/install-template/mycat2-install-template-1.21.zip wget http://dl.mycat.org.cn/2.0/1.21-release/mycat2-1.21-release-jar-with-dependencies.jar 3.解压zip u…

【编译原理复习笔记】属性文法

属性文法 也称为属性翻译文法&#xff0c;由 Knuth 提出&#xff0c;以上下文无关文法为基础 &#xff08;1&#xff09;为每个文法符号&#xff08;终结符与非终结符&#xff09;配备相关的属性&#xff0c;代表与该文法符号相关的信息 &#xff08;2&#xff09;属性文法对于…

Sass是什么?有哪些优缺点?

目录 一、Sass是什么&#xff1f; 二、Sass的优缺点 三、Sass与SaaS 一、Sass是什么&#xff1f; Sass是世界上最成熟、最稳定、最强大的专业级CSS扩展语言。 Sass makes CSS fun again. Sass is an extension of CSS, adding nested rules, variables, mixins, selector in…

操作系统3_作业与处理机调度

操作系统3_作业与处理机调度 文章目录 操作系统3_作业与处理机调度1. 作业的概念与组成2. 作业的建立及状态3. 处理机调度相关概念3.1 调度级别3.2 调度队列模型3.3 选择准则4. 作业调度与进程调度5. 典型处理机调度算法5.1 先来先服务算法FCFS5.2 短作业优先算法SJF5.3 优先级…

理解 Hologres 和 MaxCompute 的关系

理解了 Hologres 和 MaxCompute 的关系&#xff0c;就理解了数据仓库&#xff08;DW&#xff09;和数据服务&#xff08;Serving&#xff09;之间的关系&#xff0c;也有助于理解实时数仓和离线数仓之间的关系。 图片来源&#xff1a;阿里云官方帮助文档 可以看到&#xff0c…

TypeScript-Ts的内置类型 Omit Pick

TS的内置类型 Omit Pick Omit 是从对象中排出一些属性&#xff0c;得到对象类型 // 排除单个 type OmitPerson Omit<Person, age> // 排除多个 type OmitPerson Omit<Person, age | gender> Pick 是从对象中摘取一些属性&#xff0c;得到对象类型 // 摘取单个 …

kubernetes(k8s) v1.30.1 helm 集群安装 Dashboard v7.4.0 可视化管理工具 图形化管理工具

本文 紧接上一篇&#xff1a;详细教程 Centos8.5 基于 k8s v1.30.1 部署高可用集群 kubeadm 安装 kubernetes v1.30.1 docker集群搭建 延长证书有效期-CSDN博客 1 Dashboard 从版本 7.0.0 开始&#xff0c;不再支持基于清单的安装。仅支持基于 Helm 的安装. #Helm 下载安装 …

人类交互4 感觉输入和运动输出

人类感觉系统概述 人类感觉系统是由多个感觉器官和神经系统组成&#xff0c;负责感知外部世界的各种刺激和信息。人类感觉系统包括以下几个主要部分&#xff1a; 视觉系统&#xff1a;视觉系统由眼睛、视神经和大脑视觉皮层组成&#xff0c;负责感知光线、颜色和形状&#xff…