STM32 (十四) RTC时钟

简介:

一、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时钟
在这里插入图片描述

 

 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);

}

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值