完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
本帖最后由 正点原子运营官 于 2020-4-21 14:40 编辑
1)实验平台:alientek NANO STM32F411 V1开发板 2)摘自《正点原子STM32F4 开发指南(HAL 库版》关注官方微信号公众号,获取更多资料:正点原子 第十九章 RTC 实时时钟实验 前面我们介绍了两款液晶模块,这一章我们将介绍 STM32F4 的内部实时时钟(RTC)。在 本章中,我们将使用数码管来显示日期和时间,实现一个简单的时钟,并可以设置闹铃。另外, 本章将顺带向大家介绍 BKP 的使用。本章分为如下几个部分: 19.1 STM32F4 RTC 时钟简介 19.2 硬件设计 19.3 软件设计 19.4 下载验证 19.1 STM32F4 RTC 时钟简介 STM32F4 的实时时钟(RTC)相对于 STM32F1 来说,改进了不少,带了日历功能了,STM32F4 的 RTC,是一个独立的 BCD 定时器/计数器。RTC 提供一个日历时钟(包含年月日时分秒信息)、两个可编程闹钟(ALARM A 和 ALARM B)中断,以及一个具有中断功能的周期性可编程唤醒标志。RTC 还包含用于管理低功耗模 式的自动唤醒单元。 两个 32 位寄存器(TR 和 DR)包含二进码十进数格式 (BCD) 的秒、分钟、小时(12 或 24 小时制)、星期、日期、月份和年份。此外,还可提供二进制格式的亚秒值。 STM32F4 的 RTC 可以自动将月份的天数补偿为 28、29(闰年)、30 和 31 天。并且还可以进行夏令时补偿。 RTC 模块和时钟配置是在后备区域,即在系统复位或从待机模式唤醒后 RTC 的设置和时间维持不变,只要后备区域供电正常,那么 RTC 将可以一直运行。但是在系统复位后,会自动禁止访问后备寄存器和 RTC,以防止对后备区域(BKP)的意外写操作。所以在要设置时间之前, 先要取消备份区域(BKP)写保护。 RTC 的简化框图,如图 19.1.1 所示: 图 19.1.1 RTC 框图 本章我们用到 RTC 时钟和日历,并且用到闹钟功能。接下来简单介绍下 STM32F4 RTC 时钟的使用。 1,时钟和分频 首先,我们看 STM32F4 的 RTC 时钟分频。STM32F4 的 RTC 时钟源(RTCCLK)通过时钟控制器,可以从 LSE 时钟、LSI 时钟以及 HSE 时钟三者中选择(通过 RCC_BDCR 寄存器选择)。 一般我们选择 LSE,即外部 32.768Khz 晶振作为时钟源(RTCCLK),而 RTC 时钟核心,要求提 供 1Hz 的时钟,所以,我们要设置 RTC 的可编程预分配器。STM32F4 的可编程预分配器 (RTC_PRER)分为 2 个部分: 1, 一个通过 RTC_PRER 寄存器的 PREDIV_A 位配置的 7 位异步预分频器。 2, 一个通过 RTC_PRER 寄存器的 PREDIV_S 位配置的 15 位同步预分频器。 图 19.1.1 中,ck_spre 的时钟可由如下计算公式计算: Fck_spre=Frtcclk/[(PREDIV_S+1)*( PREDIV_A+1)] 其中,Fck_spre 即可用于更新日历时间等信息。PREDIV_A 和 PREDIV_S 为 RTC 的异步和同步分频器。且推荐设置 7 位异步预分频器(PREDIV_A)的值较大,以最大程度降低功耗。 要设置为 32768 分频,我们只需要设置:PREDIV_A=0X7F,即 128 分频;PREDIV_S=0XFF, 即 256 分频,即可得到 1Hz 的 Fck_spre。 另外,图 20.1.1 中,ck_apre 可作为 RTC 亚秒递减计数器(RTC_SSR)的时钟,Fck_apre 的计算公式如下: Fck_apre=Frtcclk/( PREDIV_A+1) 当 RTC_SSR 寄存器递减到 0 的时候,会使用 PREDIV_S 的值重新装载 PREDIV_S。而PREDIV_S 一般为 255,这样,我们得到亚秒时间的精度是:1/256 秒,即 3.9ms 左右,有了这个亚秒寄存器 RTC_SSR,就可以得到更加精确的时间数据。 2,日历时间(RTC_TR)和日期(RTC_DR)寄存器 STM32F4 的 RTC 日历时间(RTC_TR)和日期(RTC_DR)寄存器,用于存储时间和日期(也可以用于设置时间和日期),可以通过与 PCLK1(APB1 时钟)同步的影子寄存器来访问,这些时间和日期寄存器也可以直接访问,这样可避免等待同步的持续时间。 每隔 2 个 RTCCLK 周期,当前日历值便会复制到影子寄存器,并置位 RTC_ISR 寄存器的 RSF 位。我们可以读取 RTC_TR 和 RTC_DR 来得到当前时间和日期信息,不过需要注意的是:时间 和日期都是以 BCD 码的格式存储的,读出来要转换一下,才可以得到十进制的数据。 3,可编程闹钟 STM32F4 提供两个可编程闹钟:闹钟 A(ALARM_A)和闹钟 B(ALARM_B)。通过 RTC_CR 寄存器的 ALRAE 和 ALRBE 位置 1 来使能可编程闹钟功能。当日历的亚秒、秒、分、小时、 日期分别与闹钟寄存器 RTC_ALRMASSR/RTC_ALRMAR 和 RTC_ALRMBSSR/RTC_ALRMBR 中的值匹配时,则可以产生闹钟(需要适当配置)。本章我们将利用闹钟 A 产生闹铃,即设置 RTC_ALRMASSR 和 RTC_ALRMAR 即可。 4,周期性自动唤醒 STM32F4 的 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 来使能周期性唤醒中断时,可以使 STM32F4 退出低功耗模式。系统复位以及低功耗模式(睡眠、停机和待机)对唤醒定时器没有任何影响, 它仍然可以正常工作,故唤醒定时器,可以用于周期性唤醒 STM32F4。 接下来,我们看看本章我们要用到的 RTC 部分寄存器,首先是 RTC 时间寄存器:RTC_TR, 该寄存器各位描述如图 19.1.2 所示: 图 19.1.2 RTC_TR 寄存器各位描述 这个寄存器比较简单,注意数据保存时 BCD 格式的,读取之后需要稍加转换,才是十进 制的时分秒等数据,在初始化模式下,对该寄存器进行写操作,可以设置时间。 然后看 RTC 日期寄存器:RTC_DR,该寄存器各位描述如图 19.1.3 所示: 图 19.1.3 RTC_DR 寄存器各位描述 同样,该寄存器的的数据采用 BCD 码格式(如不熟悉 BCD,百度即可),其他的就比较 简单了。同样,在初始化模式下,对该寄存器进行写操作,可以设置日期。 接下来,看 RTC 亚秒寄存器:RTC_SSR,该寄存器各位描述如图:19.1.4 所示: 图 19.1.4 RTC_SSR 寄存器各位描述 该寄存器可用于获取更加精确的 RTC 时间。不过,在本章没有用到,如果需要精确时间的 地方,大家可以使用该寄存器。 接下来看 RTC 控制寄存器:RTC_CR,该寄存器各位描述如图 19.1.5 所示: 图 19.1.5 RTC_CR 寄存器各位描述 该寄存器我们不详细介绍每个位了,重点介绍几个要用到的:WUTIE,ALRAIE 是唤醒定 时器中断和闹钟 A 中断使能位,本章要用到,设置为 1 即可。WUTE 和 ALRAE,则是唤醒定 时器和闹钟 A 定时器使能位,同样设置为 1,开启。FMT 为小时格式选择位,我们设置为 0, 选择 24 小时制。最后 WUCKSEL[2:0],用于唤醒时钟选择,这个前面已经有介绍了,我们这 里就不多说了,RTC_CR 寄存器的详细介绍,请看《STM32F411xC/E 参考手册》第 17.6.3 节。 接下来看 RTC 初始化和状态寄存器:RTC_ISR,该寄存器各位描述如图 19.1.6 所示: 图 19.1.6 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,该寄存器各位描述如图 19.1.7 所示: 图 19.1.7 RTC_PRER 寄存器各位描述 该寄存器用于 RTC 的分频,我们在之前也有讲过,这里就不多说了。该寄存器的配置,必 须在初始化模式(INITF=1)下,才可以进行。 接下来看 RTC 唤醒定时器寄存器:RTC_WUTR,该寄存器各位描述如图 19.1.8 所示: 图 19.1.8 RTC_WUTR 寄存器各位描述 该寄存器用于设置自动唤醒重装载值,可用于设置唤醒周期。该寄存器的配置,必须等待 RTC_ISR 的 WUTWF 为 1 才可以进行。 接下来看 RTC 闹钟 A 器寄存器:RTC_ALRMAR,该寄存器各位描述如图 19.1.9 所示: 图 19.1.9 RTC_ ALRMAR 寄存器各位描述 该寄存器用于设置闹铃 A,当 WDSEL 选择 1 时,使用星期制闹铃,本章我们选择星期制 闹铃。该寄存器的配置,必须等待 RTC_ISR 的 ALRAWF 为 1 才可以进行。另外,还有 RTC_ALRMASSR 寄存器,该寄存器我们这里就不再介绍了,大家参考《STM32F4xx 中文数据 手册》第 23.6.19 节。 接下来看 RTC 写保护寄存器:RTC_WPR,该寄存器比较简单,低八位有效。上电后,所 有 RTC 寄存器都受到写保护(RTC_ISR[13:8]、RTC_TAFCR 和 RTC_BKPxR 除外),必须依 次写入:0XCA、0X53 两关键字到 RTC_WPR 寄存器,才可以解锁。写一个错误的关键字将再 次激活 RTC 的寄存器写保护。 接下来,我们介绍下 RTC 备份寄存器:RTC_BKPxR,该寄存器组总共有 20 个,每个寄 存器是 32 位的,可以存储 80 个字节的用户数据,这些寄存器在备份域中实现,可在 VDD 电 源关闭时通过 VBAT 保持上电状态。备份寄存器不会在系统复位或电源复位时复位,也不会在 MCU 从待机模式唤醒时复位。 复位后,对 RTC 和 RTC 备份寄存器的写访问被禁止,执行以下操作可以使能对 RTC 及RTC 备份寄存器的写访问: 1)通过设置寄存器 RCC_APB1ENR 的 PWREN 位来打开电源接口时钟 2)电源控制寄存器(PWR_CR)的 DBP 位来使能对 RTC 及 RTC 备份寄存器的访问。 我们可以用 BKP 来存储一些重要的数据,相当于一个 EEPROM,不过这个 EEPROM 并不 是真正的 EEPROM,而是需要电池来维持它的数据。 最后,我们还要介绍一下备份区域控制寄存器 RCC_BDCR。该寄存器的个位描述如图 19.1.10 所示: 图 19.1.10 RCC_ BDCR 寄存器各位描述 RTC 的时钟源选择及使能设置都是通过这个寄存器来实现的,所以我们在 RTC 操作之前 先要通过这个寄存器选择 RTC 的时钟源,然后才能开始其他的操作。 RTC 寄存器介绍就给大家介绍到这里了,我们下面来看看要经过哪几个步骤的配置才能使 RTC 正常工作。接下来我们来看看通过 HAL 库配置 RTC 一般配置步骤。RTC 相关的 HAL 库 文件为 stm32f4xx_hal_rtc.c 以及头文件 stm32f4xx_hal_rtc.h 中: 1)使能电源时钟和备份区域时钟。 前面已经介绍了,我们要访问 RTC 和备份区域就必须先使能电源时钟,然后使能 RTC 后 备区域访问。电源时钟使能,通过 RCC_APB1ENR 寄存器来设置;RTC 及 RTC 备份寄存器的 写访问,通过 PWR_CR 寄存器的 DBP 位设置。HAL 库设置方法为: __HAL_RCC_PWR_CLK_ENABLE();//使能电源时钟 PWR HAL_PWR_EnableBkUpAccess(); //取消备份区域写保护 2)开启外部低速振荡器 LSE,选择 RTC 时钟,并使能。 配置开启 LSE 的函数为 HAL_RCC_OscConfig,使用方法为: RCC_OscInitStruct.OscillatorType=RCC_OSCILLATORTYPE_LSE;//LSE 配置 RCC_OscInitStruct.PLL.PLLState=RCC_PLL_NONE; RCC_OscInitStruct.LSEState=RCC_LSE_ON; //RTC 使用 LSE HAL_RCC_OscConfig(&RCC_OscInitStruct); 选择 RTC 时钟源函数为 HAL_RCCEx_PeriphCLKConfig,使用方法: PeriphClkInitStruct.PeriphClockSelection=RCC_PERIPHCLK_RTC;//外设为 RTC PeriphClkInitStruct.RTCClockSelection=RCC_RTCCLKSOURCE_LSE;//RTC时钟源为LSE HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct); 使能 RTC 时钟方法为: __HAL_RCC_RTC_ENABLE();//RTC 时钟使能 3) 初始化 RTC,设置 RTC 的分频,以及配置 RTC 参数。 在 HAL 中,初始化 RTC 是通过函数 HAL_RTC_Init 实现的,该函数声明为: HAL_StatusTypeDef HAL_RTC_Init(RTC_HandleTypeDef *hrtc); 同样按照以前的方式,我们来看看 RTC 初始化参数结构体 RTC_HandleTypeDef 定义: typedef struct { RTC_TypeDef *Instance; RTC_InitTypeDef Init; HAL_LockTypeDef Lock; __IO HAL_RTCStateTypeDef State; }RTC_HandleTypeDef; 这里我们着重讲解成员变量 Init 含义,因为它是真正的 RTC 初始化变量,它是 RTC_Init TypeDef 结构体类型,结构体 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; 该结构体有 6 个成员变量。 成 员 变 量 HourFormat 用 来 设 置小 时 格 式 , 为 12 小 时 制 或者 24 小 时 制 ,取 值 为 RTC_HOURFORMAT_12 或者 RTC_HOURFORMAT_24。 AsynchPrediv 用来设置 RTC 的异步预分频系数,也就是设置 RTC_PRER 寄存器的 PREDIV_A 相关位,因为异步预分频系数是 7 位,所以最大值为 0x7F,不能超过这个值。 SynchPrediv用来设置 RTC 的同步预分频系数,也就是设置 RTC_PRER 寄存器的PREDIV_S 相关位,因为同步预分频系数也是 15 位,所以最大值为 0x7FFF,不能超过这个值。 OutPut 用来选择要连接到 RTC_ALARM 输出的标志,取值为:RTC_OUTPUT_DISABLE (禁止输出),RTC_OUTPUT_ALARMA(使能闹钟 A 输出),RTC_OUTPUT_ALARMB(使 能闹钟 B 输出)和 RTC_OUTPUT_WAKEUP(使能唤醒输出)。 OutPutPolarity 用来设置 RTC_ALARM 的输出极性,与 Output 成员变量配合使用,取值为 RTC_OUTPUT_POLARITY_HIGH(高电平)或 RTC_OUTPUT_POLARITY_LOW(低电平)。 OutPutType 用 来 设 置 RTC_ALARM 的 输 出 类 型 为 开 漏 输 出 (RTC_OUTPUT_TYPE_OPENDRAIN)还是推挽输出(RTC_OUTPUT_TYPE_PUSHPULL), 与成员变量 OutPut 和 OutPutPolarity 配合使用。 接下来我们看看 RTC 初始化的一般格式: RTC_Handler.Instance=RTC; RTC_Handler.Init.HourFormat=RTC_HOURFORMAT_24;//RTC 设置为 24 小时格式 RTC_Handler.Init.AsynchPrediv=0X7F; //RTC 异步分频系数(1~0X7F) RTC_Handler.Init.SynchPrediv=0XFF; //RTC 同步分频系数(0~7FFF) RTC_Handler.Init.OutPut=RTC_OUTPUT_DISABLE; RTC_Handler.Init.OutPutPolarity=RTC_OUTPUT_POLARITY_HIGH; RTC_Handler.Init.OutPutType=RTC_OUTPUT_TYPE_OPENDRAIN; HAL_RTC_Init(&RTC_Handler); 同样,HAL 库也提供了 RTC 初始化 MSP 函数。函数声明为: void HAL_RTC_MspInit(RTC_HandleTypeDef* hrtc); 该函数内部一般存放时钟使能,时钟源选择等操作程序。 4) 设置 RTC 的时间: HAL_StatusTypeDef HAL_RTC_SetTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime, uint32_t Format); 实际上,根据我们前面寄存器的讲解,RTC_SetTime 函数是用来设置时间寄存器 RTC_TR 的相关位的值。 RTC_SetTime 函数的第三个参数 Format,用来设置输入的时间格式为 BIN 格式还是 BCD 格式,可选值为 RTC_FORMAT_BIN 和 RTC_FORMAT_BCD。 我们接下来看看第二个初始化参数结构体 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_SSE 的值,SecondFraction 用来读取保存同步预分频系数的值,也就是 RTC_PRER 的位 0~14,DayLightSaving 用来设置日历时间增加 1 小时,减少 1 小时,还是不变。StoreOperation 用户对此变量设置以记录是否已对夏令时进行更改。HAL_RTC_SetTime 函数参考实例如下: RTC_TimeTypeDef RTC_TimeStructure; RTC_TimeStructure.Hours=1; RTC_TimeStructure.Minutes=1; RTC_TimeStructure.Seconds=1; RTC_TimeStructure.TimeFormat= RTC_HOURFORMAT12_PM; RTC_TimeStructure.DayLightSaving=RTC_DAYLIGHTSAVING_NONE; RTC_TimeStructure.StoreOperation=RTC_STOREOPERATION_RESET; HAL_RTC_SetTime(&RTC_Handler,&RTC_TimeStructure,RTC_FORMAT_BIN); 5 ) 设置 RTC 的日期。 设置 RTC 的日期函数为: HAL_StatusTypeDef HAL_RTC_SetDate(RTC_HandleTypeDef *hrtc, RTC_DateTypeDef *sDate, uint32_t Format); 实际上,根据我们前面寄存器的讲解,HAL_RTC_SetDate 设置日期函数是用来设置日期寄 存器 RTC_DR 的相关位的值。 该 函 数 有 三 个 入 口 参 数 , 我 们 着 重 讲 解 第 二 个 入 口 参 数 sDate , 它 是 结 构 体 RTC_DateTypeDef 结构体类型变量,结构体 RTC_DateTypeDef 定义如下: typedef struct { uint8_t WeekDay; //星期几 uint8_t Month; //月份 uint8_t Date //日期; uint8_t Year; //年份 }RTC_DateTypeDef; 结构体一共四个成员变量,这四个成员变量非常好理解,分别用来设置 RTC_DR 寄存器相 关设置位,这里我们就不做过多讲解。 6)获取 RTC 当前日期和时间: 获取当前 RTC 时间的函数为: HAL_StatusTypeDef HAL_RTC_GetTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime, uint32_t Format); 获取当前 RTC 日期的函数为: HAL_StatusTypeDef HAL_RTC_GetDate(RTC_HandleTypeDef *hrtc, RTC_DateTypeDef *sDate, uint32_t Format); 这两个函数非常简单,实际就是读取RTC_TR寄存器和RTC_DR寄存器的时间和日期的值, 然后将值存放到相应的结构体中。 通过以上 6 个步骤,我们就完成了对 RTC 的配置,RTC 即可正常工作,而且这些操作不 是每次上电都必须执行的,可以视情况而定。当然,我们还需要设置时间、日期、秒中断、闹 钟等,这些将在后面介绍。 19.2 硬件设计 本实验用到的硬件资源有: 1) 指示灯 DS0 2) 串口 3) 数码管 4) RTC 前面 3 个都介绍过了,而 RTC 属于 STM32F4 内部资源,其配置也是通过软件设置好就可 以了。不过 RTC 不能断电,否则数据就丢失了,我们如果想让时间在断电后还可以继续走,那 么必须确保开发板的电池有电(ALIENTEK NANO STM32F4 开发板标配是有电池的)。 19.3 软件设计 同样,打开我们光盘的 RTC 时钟实验,可以看到,我们的工程中加入了 rtc.c 源文件和 rtc.h 头文件,同时,引入了 stm32f4xx_hal_rtc.c 和 stm32f4xx_hal_rtc_ex.c 库文件。 由于篇幅所限,rtc.c 中的代码,我们不全部贴出了,这里针对几个重要的函数,进行简要 说明,首先是 RTC_Init,其代码如下: //RTC 初始化 //返回值:0,初始化成功; // 2,进入初始化模式失败; u8 RTC_Init(void) { RTC_Handler.Instance=RTC; RTC_Handler.Init.HourFormat=RTC_HOURFORMAT_24;//RTC 设置为 24 小时格式 RTC_Handler.Init.AsynchPrediv=0X7F; //RTC 异步分频系数(1~0X7F) RTC_Handler.Init.SynchPrediv=0XFF; //RTC 同步分频系数(0~7FFF) RTC_Handler.Init.OutPut=RTC_OUTPUT_DISABLE; RTC_Handler.Init.OutPutPolarity=RTC_OUTPUT_POLARITY_HIGH; RTC_Handler.Init.OutPutType=RTC_OUTPUT_TYPE_OPENDRAIN; if(HAL_RTC_Init(&RTC_Handler)!=HAL_OK) return 2; if(HAL_RTCEx_BKUPRead(&RTC_Handler,RTC_BKP_DR0)!=0X5050) //是否第一次配置 { RTC_Set_Time(23,59,56,RTC_HOURFORMAT12_PM); //设置时间 ,根据实际时间修改 RTC_Set_Date(15,12,27,7); //设置日期 HAL_RTCEx_BKUPWrite(&RTC_Handler,RTC_BKP_DR0,0X5050); //标记已经初始化过了 } return 0; } 该函数用来初始化 RTC 配置以及日期时钟,但是只在第一次的时候设置时间,以后如果重 新上电/复位都不会再进行时间设置了(前提是备份电池有电),在第一次配置的时候,我们是 按照上面介绍的 RTC 初始化步骤调用函数(HAL_RTC_Init)来实现的,这里这里就不在多说 了。 这里我们设置时间和日期,分别是通过 RTC_Set_Time 和 RTC_Set_Data 函数来实现的,这 两个函数实际就是调用库函数里面的 HAL_RTC_SetTime 函数和 HAL_RTC_SetData 函数来实 现,这里我们之所以要写两个这样的函数,目的是为了我们的 USMART 来调用,方便直接通 过 USMART 来设置时间和日期。 这里默认将时间设置为 15 年 12 月 27 日星期天,23 点 59 分 56 秒。在设置好时间之后, 我们调用函数 HAL_RTCEx_BKUPWrite 向 RTC 的 BKP 寄存器(地址 0)写入标志字 0X5050, 用 于 标 记 时 间 已 经 被 设 置 了 。 这 样 , 再 次 发 生 复 位 的 时 候 , 该 函 数 通 过 调 用 函 数 HAL_RTCEx_BKUPRead 判断 RTC 对应 BKR 地址的值,来决定是不是需要重新设置时间,如 果不需要设置,则调过时间设置,这样不会重复设置时间,使得我们设置的时间不会因复位或 者断电而丢失。 这里我们来看看读备份区域和写备份区域寄存器的两个函数为: uint32_t HAL_RTCEx_BKUPRead(RTC_HandleTypeDef *hrtc, uint32_t BackupRegister); void HAL_RTCEx_BKUPWrite(RTC_HandleTypeDef *hrtc, uint32_t BackupRegister, uint32_t Data) 这两个函数的使用方法就非常简单,分别用来读和写 BKR 寄存器的值。这里我们只是略 微点到为止。 接着,我们介绍一下 RTC_Set_AlarmA 函数,该函数代码如下: //设置闹钟时间(按星期闹铃,24 小时制) //week:星期几(1~7) @ref RTC_WeekDay_Definitions //hour,min,sec:小时,分钟,秒钟 void RTC_Set_AlarmA(u8 week,u8 hour,u8 min,u8 sec) { RTC_AlarmTypeDef RTC_AlarmSturuct; RTC_AlarmSturuct.AlarmTime.Hours=hour; //小时 RTC_AlarmSturuct.AlarmTime.Minutes=min; //分钟 RTC_AlarmSturuct.AlarmTime.Seconds=sec; //秒 RTC_AlarmSturuct.AlarmTime.SubSeconds=0; RTC_AlarmSturuct.AlarmTime.TimeFormat=RTC_HOURFORMAT12_AM; RTC_AlarmSturuct.AlarmMask=RTC_ALARMMASK_NONE;//精确匹配星期,时分秒 RTC_AlarmSturuct.AlarmSubSecondMask=RTC_ALARMSUBSECONDMASK_NONE; RTC_AlarmSturuct.AlarmDateWeekDaySel= RTC_ALARMDATEWEEKDAYSEL_WEEKDAY;//按星期 RTC_AlarmSturuct.AlarmDateWeekDay=week; //星期 RTC_AlarmSturuct.Alarm=RTC_ALARM_A; //闹钟 A HAL_RTC_SetAlarm_IT(&RTC_Handler,&RTC_AlarmSturuct,RTC_FORMAT_BIN); HAL_NVIC_SetPriority(RTC_Alarm_IRQn,0x01,0x02); //抢占优先级 1,子优先级 2 HAL_NVIC_EnableIRQ(RTC_Alarm_IRQn); } 该函数用于设置闹钟 A,也就是设置 ALRMAR 和 ALRMASSR 寄存器的值,来设置闹钟 时间,这里 HAL 库中用来设置闹钟并开启中断的函数为: HAL_StatusTypeDef HAL_RTC_SetAlarm_IT(RTC_HandleTypeDef *hrtc, RTC_AlarmTypeDef *sAlarm, uint32_t Format); 第三个参数 RTC_Format 用来设置格式,这里前面我们讲解过,就不做过多讲解。 接下来我们着重看看第二个参数 sAlarm,该入口参数是 RTC_AlarmTypeDef 结构体指针类 型,结构体定义如下: typedef struct { RTC_TimeTypeDef AlarmTime; uint32_t AlarmMask; uint32_t AlarmSubSecondMask; uint32_t AlarmDateWeekDaySel; uint8_t AlarmDateWeekDay; uint32_t Alarm; }RTC_AlarmTypeDef; 该 结 构 体 有 6 个 成 员 变 量 , 第 一 个 成 员 变 量 AlarmTime 用 来 设 置 闹 钟 时 间 , 是 RTC_TimeTypeDef 结构体类型,该结构体前面我们已经讲解过各个成员变量含义,这里我们就 不做过多讲解。 AlarmMask 用来设置闹钟时间掩码,也就是在我们第一个参数设置的时间中(包括后面参 数 RTC_AlarmDatteWeekDay 设置的星期几/哪一天),那些是无关的。比如我们设置闹钟时间 为每天的 10 点 10 分 10 秒,那么我们可以选择值 RTC_AlarmMask_DateWeekDay,也就是我们 不关系是星期几/每月哪一天。这里我们选择为 RTC_AlarmMask_None,也就是精确匹配时间, 所有的时分秒以及星期几/(或者每月哪一天)都要精确匹配。 AlarmSubSecondMask 和 AlarmMask 作用类似,只不过该变量是用来设置亚秒的。 AlarmDateWeekDaySel 用 来 选 择 是 闹 钟 是 按 日 期 还 是 按 星 期 。 比 如 我 们 选 择 RTC_AlarmDateWeekDaySel_WeekDay 那 么 闹 钟 就 是 按 星 期 。 如 果 我 们 选 择 RTC_AlarmDataWeekDaySel_Date 那么闹钟就是按日期。这与后面第四个参数是有关联的,我 们在后面第四个参数讲解。 AlarmDateWeekDay 用 来 设 置 闹 钟 的 日 期 或 者 星 期 几 , 比 如 我 们 第 三 个 参 数 RTC_AlarmWeekDaySel 设置了值为 RTC_AlarmDateWeekDaySel_WeekDay,也就是按星期,那 么 参 数 RTC_AlarmDateWeekDay 的 数 值 范 围 就 为 星 期 一 ~ 星 期 天 , 也 就 是 RTC_Weekday_Monday~RTC_Weekday_Sunday。如果第三个参数 RTC_AlarmDateWeekDaySel 设置值为 RTC_AlarmDateWeekDaySel_Date,那么它的 取值范围就为日期值,0~31。 Alarm 用来设置闹钟 A 还是闹钟 B,这个很好理解。 调用函数 RTC_SetAlarm 设置闹钟 A 的参数之后,最后,开启闹钟 A 中断(连接在外部中 断线 17),并设置中断分组。当 RTC 的时间和闹钟 A 设置的时间完全匹配时,将产生闹钟中 断。 接着,我们介绍一下 RTC_Set_WakeUp 函数,该函数代码如下: void RTC_Set_WakeUp(u32 wksel,u16 cnt) { __HAL_RTC_WAKEUPTIMER_CLEAR_FLAG(&RTC_Handler, RTC_FLAG_WUTF);//清除 RTC WAKE UP 的标志 HAL_RTCEx_SetWakeUpTimer_IT(&RTC_Handler,cnt,wksel); //设置重装载值和时钟 HAL_NVIC_SetPriority(RTC_WKUP_IRQn,0x02,0x02); //抢占优先级 1,子优先级 2 HAL_NVIC_EnableIRQ(RTC_WKUP_IRQn); } 该函数用于设置 RTC 周期性唤醒定时器,实现周期性唤醒中断,连接外部中断线 22。该 函数调用的是 HAL 库函数 HAL_RTCEx_SetWakeUpTimer_IT 实现的,该函数使用方法比较简 单,这里我们就不做过多讲解。 有了中断设置函数,就必定有中断服务函数,同时因为 HAL 库会开放中断处理回调函数, 接下来看这两个中断的中断服务函数和中断处理回调函数,代码如下: //RTC 闹钟中断服务函数 void RTC_Alarm_IRQHandler(void) { HAL_RTC_AlarmIRQHandler(&RTC_Handler); } //RTC WAKE UP 中断服务函数 void RTC_WKUP_IRQHandler(void) { HAL_RTCEx_WakeUpTimerIRQHandler(&RTC_Handler); } //RTC 闹钟 A 中断处理回调函数 void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc) { printf("ALARM A!rn"); } //RTC WAKE UP 中断处理回调函数 void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc) { LED1=!LED1; } 其中,RTC_Alarm_IRQHandler 函数用于闹钟中断,其中断控制逻辑写在中断回调函数 HAL_RTC_AlarmAEventCallback 中 , 每 当 闹 钟 A 闹 铃 是 , 会 从 串 口 打 印 一 个 字 符 串 “ALARMA!”。RTC_WKUP_IRQHandler 函数用于 RTC 自动唤醒定时器中断,其中断控制 逻辑写在中断回调函数 HAL_RTCEx_WakeUpTimerEventCallback 中,可以通过观察 LED1 的状 态来查看 RTC 自动唤醒中断的情况。 rtc.c 的其他程序,这里就不再介绍了,请大家直接看光盘的源码。rtc.h 头文件中主要是一 些函数声明,我们就不多说了,有些函数在这里没有介绍,请大家参考本例程源码。 最后我们看看 main 函数源码如下: // 共阴数字数组 // 0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F, .,全灭 u8 smg_num[]={0xfc,0x60,0xda,0xf2,0x66,0xb6,0xbe,0xe0,0xfe,0xf6, 0xee,0x3e,0x9c,0x7a,0x9e,0x8e,0x01,0x00}; RTC_TimeTypeDef RTC_TimeStruct; RTC_DateTypeDef RTC_DateStruct; int main(void) { HAL_Init(); //初始化 HAL 库 Stm32_Clock_Init(96,4,2,4); //设置时钟,96Mhz delay_init(96); //初始化延时函数 uart_init(115200); //初始化串口 115200 LED_Init(); //初始化 LED LED_SMG_Init(); //数码管初始化 TIM3_Init(20-1,9600-1); //数码管 2ms 定时显示 usmart_dev.init(96); //初始化 USMART while(RTC_Init()) //RTC 初始化,一定要初始化成功 { printf("RTC ERROR!rn"); delay_ms(800); printf("RTC Trying...rn"); } RTC_Set_WakeUp(RTC_WAKEUPCLOCK_CK_SPRE_16BITS,0); //配置 WAKE UP 中断,1 秒钟中断一次 while(1) { } } u8 smg_wei=0;//数码管位选 u8 num=0;//数码管数值 u8 time=0;//时间值 //回调函数,定时器中断服务函数调用 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if(htim==(&TIM3_Handler)) { HAL_RTC_GetTime(&RTC_Handler,&RTC_TimeStruct,RTC_FORMAT_BIN); HAL_RTC_GetDate(&RTC_Handler,&RTC_DateStruct,RTC_FORMAT_BIN); switch(smg_wei) { case 0: num = smg_num[RTC_TimeStruct.Hours/10]; break;//时 case 1: num = smg_num[RTC_TimeStruct.Hours%10]; break; case 2: case 5: num = 0x02; break; case 3: num = smg_num[RTC_TimeStruct.Minutes/10]; break; //分 case 4: num = smg_num[RTC_TimeStruct.Minutes%10]; break; case 6: num = smg_num[RTC_TimeStruct.Seconds/10]; break; //秒 case 7: num = smg_num[RTC_TimeStruct.Seconds%10]; break; } if(time!=RTC_TimeStruct.Seconds)//LED0 每秒闪烁 { time=RTC_TimeStruct.Seconds; LED0=!LED0; } LED_Write_Data(num,smg_wei);//写数据到数码管 LED_Refresh();//更新显示 smg_wei++; if(smg_wei==8) smg_wei=0; } } 这 main.c 文件中,在包含了 rtc.h 之后,先对用到的外设初始化,由于数码管是使用动态扫 描显示的,我们开启了定时器 3 以 2ms 中断周期动态刷新显示,在定时器 3 中断更新时间的显 示,同时我们设置了 LED0 每 1 秒钟闪烁一次,用来提示程序已经开始跑了。由于数码管的显 示位数限制,这里我们只显示时分秒。 为了方便设置时间,我们在 usmart_config.c 里面,修改 usmart_nametab 如下: struct _m_usmart_nametab usmart_nametab[]= { #if USMART_USE_WRFUNS==1 //如果使能了读写操作 (void*)read_addr,"u32 read_addr(u32 addr)", (void*)write_addr,"void write_addr(u32 addr,u32 val)", #endif (void*)delay_ms,"void delay_ms(u16 nms)", (void*)delay_us,"void delay_us(u32 nus)", (void*)RTC_Set_Time,"u8 RTC_Set_Time(u8 hour,u8 min,u8 sec,u8 ampm)", (void*)RTC_Set_Date,"u8 RTC_Set_Date(u8 year,u8 month,u8 date,u8 week)", (void*)RTC_Set_AlarmA,"void RTC_Set_AlarmA(u8 week,u8 hour,u8 min,u8 sec)", (void*)RTC_Set_WakeUp,"void RTC_Set_WakeUp(u8 wksel,u16 cnt)", }; 将 RTC_Set 加入了 usmart,这样通过串口就可以直接设置 RTC 时间了。 至此,RTC 实时时钟的软件设计就完成了,接下来就让我们来检验一下,我们的程序是否 正确了。 19.4 下载验证 将程序下载到 NANO STM32F4 后,可以看到 DS0 不停的闪烁,提示程序已经在运行了。 同时可以看到数码管开始显示时间,实际显示效果如图 19.4.1 所示: 图 19.4.1 RTC 实验效果图 如果时间和日期不正确,可以利用上一章介绍的 usmart 工具,通过串口来设置,并且可以 设置闹钟时间等,如图 19.4.2 所示: 图 19.4.2 通过 USMART 设置时间和日期并测试闹钟 A 可以看到,设置闹钟 A 后,串口返回了 ALARM A!字符串,说明我们的闹钟 A 代码正常 运行了! |
|
相关推荐
|
|
1085 浏览 0 评论
AD7686芯片不传输数据给STM32,但是手按住就会有数据。
1041 浏览 2 评论
2145 浏览 0 评论
如何解决MPU-9250与STM32通讯时,出现HAL_ERROR = 0x01U
1240 浏览 1 评论
hal库中i2c卡死在HAL_I2C_Master_Transmit
1660 浏览 1 评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-25 01:14 , Processed in 0.525424 second(s), Total 64, Slave 46 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号