简介:
一、RTC
什么是RTC
RTC (Real Time Clock):实时时钟
RTC是个独立的定时器。RTC模块拥有一个连续计数的计数器,在相应的软件配置下,可以提供时钟日历的功能。修改计数器的值可以重新设置当前时间和日期 RTC还包含用于管理低功耗模式的自动唤醒单元。
在断电情况下 RTC仍可以独立运行 只要芯片的备用电源一直供电,RTC上的时间会一直走。
RTC实质是一个掉电后还继续运行的定时器,从定时器的角度来看,相对于通用定时器TIM外设,它的功能十分简单,只有计时功能(也可以触发中断)。但其高级之处也就在于掉电之后还可以正常运行。
两个 32 位寄存器包含二进码十进数格式 (BCD) 的秒、分钟、小时( 12 或 24 小时制)、星期几、日期、月份和年份。此外,还可提供二进制格式的亚秒值。系统可以自动将月份的天数补偿为 28、29(闰年)、30 和 31 天。
上电复位后,所有RTC寄存器都会受到保护,以防止可能的非正常写访问。
无论器件状态如何(运行模式、低功耗模式或处于复位状态),只要电源电压保持在工作范围内,RTC使不会停止工作。
RCT特征:
可编程的预分频系数:分频系数高为220。
32位的可编程计数器,可用于较长时间段的测量。
2个分离的时钟:用于APB1接口的PCLK1和RTC时钟(RTC时钟的频率必须小于PCLK1时钟 频率的四分之一以上)。
2个独立的复位类型:
● APB1接口由系统复位;
● RTC核心(预分频器、闹钟、计数器和分频器)只能由后备域复位
3个专门的可屏蔽中断:
● 1.闹钟中断,用来产生一个软件可编程的闹钟中断。
● 2.秒中断,用来产生一个可编程的周期性中断信号(长可达1秒)。
● 3.溢出中断,指示内部可编程计数器溢出并回转为0的状态。
RTC原理框图
RTC时钟的框图还是比较简单的,这里我们把他分成 两个部分:
APB1 接口:用来和 APB1 总线相连。 此单元还包含一组 16 位寄存器,可通过 APB1 总线对其进行读写操作。APB1 接口由 APB1 总 线时钟驱动,用来与 APB1 总线连接。
通过APB1接口可以访问RTC的相关寄存器(预分频值,计数器值,闹钟值)。
RTC 核心接口:由一组可编程计数器组成,分成 两个主要模块 。
第一个模块是 RTC 的 预分频模块,它可编程产生 1 秒的 RTC 时间基准 TR_CLK。RTC 的预分频模块包含了一个 20 位的可编程分频器(RTC 预分频器)。如果在 RTC_CR 寄存器中设置了相应的允许位,则在每个 TR_CLK 周期中 RTC 产生一个中断(秒中断)。
第二个模块是一个 32 位的可编程计数器 (RTC_CNT),可被初始化为当前的系统时间,一个 32 位的时钟计数器,按秒钟计算,可以记 录 4294967296 秒,约合 136 年左右,作为一般应用,这已经是足够了的。
RTC具体流程:
RTCCLK经过RTC_DIV预分频,RTC_PRL设置预分频系数,然后得到TR_CLK时钟信号,我们一般设置其周期为1s,RTC_CNT计数器计数,假如1970设置为时间起点为0s,通过当前时间的秒数计算得到当前的时间。RTC_ALR是设置闹钟时间,RTC_CNT计数到RTC_ALR就会产生计数中断,
RTC_Second为秒中断,用于刷新时间,
RTC_Overflow是溢出中断。
RTC Alarm 控制开关机
RTC时钟选择
使用HSE分频时钟或者LSI的时候,在主电源VDD掉电的情况下,这两个时钟来源都会受到影响,因此没法保证RTC正常工作.所以RTC一般都时钟低速外部时钟LSE,频率为实时时钟模块中常用的32.768KHz,因为32768 = 2^15,分频容易实现,所以被广泛应用到RTC模块.(在主电源VDD有效的情况下(待机),RTC还可以配置闹钟事件使STM32退出待机模式).
RTC复位过程
除了RTC_PRL、RTC_ALR、RTC_CNT和RTC_DIV寄存器外,所有的系统寄存器都由系统复位或电源复位进行异步复位。
RTC_PRL、RTC_ALR、RTC_CNT和RTC_DIV寄存器仅能通过备份域复位信号复位。
系统复位后,禁止访问后备寄存器和RCT,防止对后卫区域(BKP)的意外写操作
RTC中断
秒中断:
这里时钟自带一个秒中断,每当计数加一的时候就会触发一次秒中断,。注意,这里所说的秒中断并非一定是一秒的时间,它是由RTC时钟源和分频值决定的“秒”的时间,当然也是可以做到1秒钟中断一次。我们通过往秒中断里写更新时间的函数来达到时间同步的效果
闹钟中断:
闹钟中断就是设置一个预设定的值,计数每自加多少次触发一次闹钟中断
工程创建:
1设置RCC时钟
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200723095456402.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2FzNDgwMTMzOTM3,size_16,color_FFFFFF,t_70)
2.设置SYS
3.串口设置
4.RTC设置:
- Activate Clock Source :激活时钟源
- Activate calendar:激活日历
RTC设置参数
- Data Format: 日期格式
- Binary data format 十六进制
- BCD data format BCD码进制
- 使用自动配置,初始化时间必须使用BCD data format,原因是库函数存在bug,如果使用Binary data format,月份配置会出错,比如说11月,配置时会赋值为RTC_MONTH_NOVEMBER,而此宏定义值为0x11,也就是说其十进制值为17
- Hours: 小时
- Minutes: 分钟
- Seconds: 秒
- Week Day: 星期
- Month 月份
- Date: 日期
- Year: 年份
使能中断
然后就可以生成代码了
相关API:
库函数:
打开stm32f1xx_hal_rtc.h文件可以看到以下关于时间和日期的函数
hal库函数:
/*设置系统时间*/
HAL_StatusTypeDef HAL_RTC_SetTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime, uint32_t Format)
/*读取系统时间*/
HAL_StatusTypeDef HAL_RTC_GetTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime, uint32_t Format)
/*设置系统日期*/
HAL_StatusTypeDef HAL_RTC_SetDate(RTC_HandleTypeDef *hrtc, RTC_DateTypeDef *sDate, uint32_t Format)
/*读取系统日期*/
HAL_StatusTypeDef HAL_RTC_GetDate(RTC_HandleTypeDef *hrtc, RTC_DateTypeDef *sDate, uint32_t Format)
/*启动报警功能*/
HAL_StatusTypeDef HAL_RTC_SetAlarm(RTC_HandleTypeDef *hrtc, RTC_AlarmTypeDef *sAlarm, uint32_t Format)
/*设置报警中断*/
HAL_StatusTypeDef HAL_RTC_SetAlarm_IT(RTC_HandleTypeDef *hrtc, RTC_AlarmTypeDef *sAlarm, uint32_t Format)
/*报警时间回调函数*/
__weak void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
/*写入后备储存器*/
void HAL_RTCEx_BKUPWrite(RTC_HandleTypeDef *hrtc, uint32_t BackupRegister, uint32_t Data)
/*读取后备储存器*/
uint32_t HAL_RTCEx_BKUPRead(RTC_HandleTypeDef *hrtc, uint32_t BackupRegister
/*秒中断开启函数*/
__HAL_RTC_SECOND_ENABLE_IT(&hrtc,RTC_IT_SEC);
/*秒中断关闭函数*/
__weak __HAL_RTC_SECOND_DISABLE_IT(&hrtc,RTC_IT_SEC);
/*秒中断回调函数*/
__weak void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
时间和日期结构体的定义
RTC_TimeTypeDef RtcTime;
RTC_DateTypeDef RtcDate;
我们需要定义两个结构体来存放我们时间和日期的数据,这里我们32的库自己定义好了,我们和创建就好。
实验示例:
我们需要对RTC初始化程序进行修改,因为每次从新上电,都会从新开始进行执行,所以我们要开启,上电之后内部进行时间的保存,并且继续进行走时。
在rtc.c里面
void MX_RTC_Init(void)
{
/* USER CODE BEGIN RTC_Init 0 */
/* USER CODE END RTC_Init 0 */
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef DateToUpdate = {0};
/* USER CODE BEGIN RTC_Init 1 */
__HAL_RCC_PWR_CLK_ENABLE(); //开启BKP时钟
HAL_PWR_EnableBkUpAccess(); //取消BKP区域写保护
/* USER CODE END RTC_Init 1 */
/** Initialize RTC Only
*/
hrtc.Instance = RTC;
hrtc.Init.AsynchPrediv = RTC_AUTO_1_SECOND;
hrtc.Init.OutPut = RTC_OUTPUTSOURCE_ALARM;
if (HAL_RTC_Init(&hrtc) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN Check_RTC_BKUP */
if(HAL_RTCEx_BKUPRead(&hrtc,RTC_BKP_DR1)!=0x5051)
{
HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR1,0x5051);
/* USER CODE END Check_RTC_BKUP */
/** Initialize RTC and set the Time and Date
*/
sTime.Hours = 0x10;
sTime.Minutes = 0x0;
sTime.Seconds = 0x0;
if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD) != HAL_OK)
{
Error_Handler();
}
DateToUpdate.WeekDay = RTC_WEEKDAY_SATURDAY;
DateToUpdate.Month = RTC_MONTH_JANUARY;
DateToUpdate.Date = 0x14;
DateToUpdate.Year = 0x0;
if (HAL_RTC_SetDate(&hrtc, &DateToUpdate, RTC_FORMAT_BCD) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN RTC_Init 2 */
}
/* USER CODE END RTC_Init 2 */
}
接下来写串口控制函数,也就是串口中断回调函数:
在main.c里面:
先定义一些需要的变量:
RTC_TimeTypeDef RtcTime;
RTC_DateTypeDef RtcDate;
const char LedMode1[8] = "kaishi";
const char LedMode2[11] = "chushihua";
const char LedMode3[14] = "shezhi12311111";
const char LedMode4[9] = "xianshi";
uint16_t LED_value = 0;
#define RXBUFFERSIZE 256 //最大接收字节数
char RxBuffer[RXBUFFERSIZE]; //接收数据
uint8_t aRxBuffer; //接收中断缓冲
uint8_t Uart1_Rx_Cnt = 0; //接收缓冲计数
uint8_t LedMode = 3;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
UNUSED(huart);
//if(Uart1_Rx_Cnt >= 255) //溢出判断
//{
//Uart1_Rx_Cnt = 0;
//memset(RxBuffer,0x00,sizeof(RxBuffer));
//HAL_UART_Transmit(&huart1, (uint8_t *)"数据溢出", 10,0xFFFF);
//printf("数据溢出");
//}
//else
//{
RxBuffer[Uart1_Rx_Cnt++] = aRxBuffer; //接收数据转存
if((RxBuffer[Uart1_Rx_Cnt-1] == 0x0A)&&(RxBuffer[Uart1_Rx_Cnt-2] == 0x0D)) //判断结束位
{
//HAL_UART_Transmit(&huart1, (uint8_t *)&RxBuffer, Uart1_Rx_Cnt,0xFFFF); //将收到的信息发送出去
Uart1_Rx_Cnt = 0;
if(strstr((const char *)RxBuffer,LedMode1) != NULL)
{
HAL_RTC_GetTime(&hrtc, &RtcTime, RTC_FORMAT_BIN);//读出时间值
HAL_RTC_GetDate(&hrtc, &RtcDate, RTC_FORMAT_BIN);//一定要先读时间后读日期
printf(" 开发板RTC实时时钟测试 \r\n");
printf(" 实时时间:%04d-%02d-%02d %02d:%02d:%02d \r\n",2000+RtcDate.Year,
RtcDate.Month, RtcDate.Date,RtcTime.Hours, RtcTime.Minutes, RtcTime.Seconds);//显示日期时间
printf(" 单按回车键更新时间,输入字母C初始化时钟 \r\n");
printf(" 请输入设置时间,格式20170806120000,按回车键确定! \r\n");
}
if(strstr((const char *)RxBuffer,LedMode2) != NULL)
{
MX_RTC_Init(); //键盘输入c或C,初始化时钟
printf("初始化成功! \r\n");//显示初始化成功
}
if(strlen((const char *)RxBuffer) == 16)
{
RtcDate.Year = (RxBuffer[2]-0x30)*10+RxBuffer[3]-0x30;//减0x30后才能得到十进制0~9的数据
RtcDate.Month = (RxBuffer[4]-0x30)*10+RxBuffer[5]-0x30;
RtcDate.Date = (RxBuffer[6]-0x30)*10+RxBuffer[7]-0x30;
RtcTime.Hours = (RxBuffer[8]-0x30)*10+RxBuffer[9]-0x30;
RtcTime.Minutes = (RxBuffer[10]-0x30)*10+RxBuffer[11]-0x30;
RtcTime.Seconds = (RxBuffer[12]-0x30)*10+RxBuffer[13]-0x30;
if (HAL_RTC_SetTime(&hrtc, &RtcTime, RTC_FORMAT_BIN) != HAL_OK)//将数据写入RTC程序
{
printf("写入时间失败! \r\n"); //显示写入失败
}
else if (HAL_RTC_SetDate(&hrtc, &RtcDate, RTC_FORMAT_BIN) != HAL_OK)//将数据写入RTC程序
{
printf("写入日期失败! \r\n"); //显示写入失败
}
else
{
printf("写入成功! \r\n");//显示写入成功
__HAL_RTC_SECOND_ENABLE_IT(&hrtc,RTC_IT_SEC);
}
}
if(strstr((const char *)RxBuffer,LedMode4) != NULL)
{
__HAL_RTC_SECOND_ENABLE_IT(&hrtc,RTC_IT_SEC);
}
memset(RxBuffer,0x00,sizeof(RxBuffer)); //清空数组
}
//}
HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer, 1); //再开启接收中断
}
我们在上面的中断回调函数中开启了秒中断函数,也就是每秒回触发一次的中断,接下来重新编写编写一下秒中断回调函数:
void HAL_RTCEx_RTCEventCallback(RTC_HandleTypeDef *hrtc)
{
HAL_RTC_GetTime(&*hrtc,&RtcTime,RTC_FORMAT_BIN);//获取时间
HAL_RTC_GetDate(&*hrtc,&RtcDate,RTC_FORMAT_BIN);//获取日期
printf("time:%2d:%2d:%2d\r\n",RtcTime.Hours,RtcTime.Minutes,RtcTime.Seconds);
printf("data:%4d.%2d.%2d\r\n",2000+RtcDate.Year,RtcDate.Month,RtcDate.Date);
}