RTC,是一个独立的 BCD 定时器/计数器。 RTC 提供一个日历时钟(包含年月日时分秒信息)、两个可编程闹钟(ALARM A 和 ALARM B)中断,以及一个具有中断功能的周期性可编程唤醒标志。 RTC 还包含用于管理低功耗模式的自动唤醒单元。
我们经常用到的功能是,用来计量世界时间。而且可以在没有VCC的时候用纽扣电池供电。这样时间就能正确刷新了。
利用前面RGB屏幕测试的实验,将RTC实时时间显示在屏幕上,每按下一次key0,刷新一次屏幕并显示最新时间。
1.使能RCC的LSE和RTC
2.配置时钟树
这里我之前就没有选上LSE,cubemx默认LSI,所以效果上会比正常时间慢一些。
3.配置RTC
这里需要注意的是,Data Format选择上,binary和BCD的区别,比如12点,binary的形式就是hours=12,而BCD则是hours=0x12.
此外,初始时间虽然设置上了,但不知道是哪里的问题,cubemx生成代码后并未帮我初始化上我设置的时间。明白的同学麻烦评论一下
4.生成代码,cubemx会帮你初始化好RTC,但是时间并未设置,还需要调用两个函数自行配置时间。
HAL_StatusTypeDef HAL_RTC_SetTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime, uint32_t Format);
HAL_StatusTypeDef HAL_RTC_SetDate(RTC_HandleTypeDef *hrtc, RTC_DateTypeDef *sDate, uint32_t Format);
值得注意的是,原子建议先设置时间再设置日期。这样时间上更贴近你的预期。
//RTC时间设置
//hour,min,sec:小时,分钟,秒钟
//ampm:@RTC_AM_PM_Definitions:RTC_HOURFORMAT12_AM/RTC_HOURFORMAT12_PM
//返回值:HAL_OK(0),成功
// HAL_ERROR(1)
HAL_StatusTypeDef RTC_Set_Time(uint8_t hour, uint8_t min, uint8_t sec, uint8_t ampm)
{
RTC_TimeTypeDef RTC_TimeStructure;
RTC_TimeStructure.Hours=hour;
RTC_TimeStructure.Minutes=min;
RTC_TimeStructure.Seconds=sec;
RTC_TimeStructure.TimeFormat=ampm;
RTC_TimeStructure.DayLightSaving=RTC_DAYLIGHTSAVING_NONE;
RTC_TimeStructure.StoreOperation=RTC_STOREOPERATION_RESET;
return HAL_RTC_SetTime(&hrtc,&RTC_TimeStructure,RTC_FORMAT_BIN);
}
//RTC日期设置
//year,month,date:年(0~99),月(1~12),日(0~31)
//week:星期(1~7,0,非法!)
//返回值:HAL_OK(0),成功
// HAL_ERROR(1)
HAL_StatusTypeDef RTC_Set_Date(uint8_t year, uint8_t month, uint8_t date, uint8_t week)
{
RTC_DateTypeDef RTC_DateStructure;
RTC_DateStructure.Date=date;
RTC_DateStructure.Month=month;
RTC_DateStructure.WeekDay=week;
RTC_DateStructure.Year=year;
return HAL_RTC_SetDate(&hrtc,&RTC_DateStructure,RTC_FORMAT_BIN);
}
调用完时间和日期的设置后记得去掉,不然每次重新上电都会执行一遍。对此,cubemx的方法是利用备份域,备份域可在VDD电源关闭时通过VBAT保持上电状态,备份寄存器不会再系统复位或电源复位时复位,也不会再MCU从待机模式唤醒时复位。所以第一次设置好时间后就向备份区固定位置写上一个值,这样每次上电都判断一下,如果已经有这个值了就表明我设置过时间无需再次设置了。
/**Initialize RTC and set the Time and Date */
if(HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR0) != 0x32F2)
{
if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR0,0x32F2);
}
5.RTC的时间和日期是分开的,在实际应用中我们如果合二为一使用起来更加方便
//rtc.h 定义结构体作为系统实时时间
typedef struct
{
uint8_t Year; /*!< Specifies the RTC Date Year.
This parameter must be a number between Min_Data = 0 and Max_Data = 99 */
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 WeekDay; /*!< Specifies the RTC Date WeekDay.
This parameter can be a value of @ref RTC_WeekDay_Definitions */
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 */
}RTC_RealTimeTypeDef;
extern RTC_RealTimeTypeDef gRealTime;
//rtc.c 由main.c不断调用,不断读取RTC更新这个结构体,在其他模块可直接使用
RTC_RealTimeTypeDef gRealTime;
void RTC_GetRealTime(void)
{
RTC_TimeTypeDef RTC_TimeStruct;
RTC_DateTypeDef RTC_DateStruct;
HAL_RTC_GetTime(&hrtc,&RTC_TimeStruct,RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc,&RTC_DateStruct,RTC_FORMAT_BIN);
gRealTime.Year = RTC_DateStruct.Year;
gRealTime.Month = RTC_DateStruct.Month;
gRealTime.Date = RTC_DateStruct.Date;
gRealTime.WeekDay = RTC_DateStruct.WeekDay;
gRealTime.Hours = RTC_TimeStruct.Hours;
gRealTime.Minutes = RTC_TimeStruct.Minutes;
gRealTime.Seconds = RTC_TimeStruct.Seconds;
}
6.最后,借用RGB屏的实验,将时间gRealTime显示在屏幕上
POINT_COLOR=RED;
LTDC_ShowString(10,40,260,32,32, (uint8_t*)"Apollo STM32F4/F7");
LTDC_ShowString(10,80,240,24,24, (uint8_t*)"LTDC TEST");
LTDC_ShowString(10,110,240,16,16, (uint8_t*)"ATOM@ALIENTEK");
sprintf((char*)tbuf,"Date:20%02d-%02d-%02d",gRealTime.Year,gRealTime.Month,gRealTime.Date);
LTDC_ShowString(10,130,240,16,16, (uint8_t*)tbuf);
sprintf((char*)tbuf,"Time:%02d:%02d:%02d",gRealTime.Hours,gRealTime.Minutes,gRealTime.Seconds);
LTDC_ShowString(10,150,240,12,12, (uint8_t*)tbuf);
原子实验的是AlarmA的星期闹钟,我觉得时间的闹钟更常用,每天的某时间进入一次AlarmA中断让LED1翻转电平。
1.使能AlarmA
2.配置AlarmA,分为五种掩码,掩码的意思是掩盖或忽略哪项,比如我要哪一小时、分钟、秒的闹钟,那么Alarm Mask Date Week day设置为使能,hours、minutes、seconds设置为失能,sub seconds设置为使能,效果就是忽略日期、亚秒,关注时分秒。也就是当11时1分0秒时闹钟中断。而如下图所示,当时间为0秒时闹钟中断,也就是每分钟触发一次中断。
week day sel的意思是闹钟选择的是日期还是星期(当然如果掩码设置为使能也就没用了),如果选择为星期,那么Alarm Date就是选择星期几,反之选择日期,那么Alarm Date选择的就是几号(并不能选择年月,我选择的是此项)。
3.开启中断,并配置优先级为1,子优先级为2.
4.然后生成代码,并重新定义闹钟中断的回调函数,执行LED1的翻转
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
{
LED_Twinkle(1);
}
效果就是每分钟的0秒,LED1更换状态。非常简单和实用,比如每分钟发送一次分钟数据。
1.使能唤醒功能
2.配置唤醒为1hz计数器值为0(一秒一次唤醒),若为1HZ计数器为1,那就是两秒一唤醒,count就是计数器的值,每一个周期减一,若为0则唤醒且重填计数器值。
3.使能中断,优先级为1,子优先级为3
4.之后生成代码,cubemx会将唤醒的初始化好,但是回调函数得自己写
我让他翻转一下LED1的状态
//RTC WAKE UP中断处理
void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc)
{
LED_Twinkle(1);
}
效果:LED1一秒翻转一次
最关键的是第一个实验,也就是初始化RTC和设置时间和读取时间。闹钟和唤醒有用但是不是必须。
之前都是用别人的底层,也没好好看RTC怎么来的,这次自己实现了心里就有底了。