|  **银杏科技有限公司旗下技术文档发布平台**  ||||
|技术支持电话|**0379-69926675-801**|||
|技术支持邮件|Gingko@vip.163.com|||
^  版本  ^  日期  ^  作者  ^  修改内容  ^
|  V1.0  |  2020-07-31  |  gingko  |  初次建立  | 
===== 实验十三:RTC实时时钟实验——显示日期和时间 =====
==== 一、 实验目的与意义 ====
  - 了解STM32 RTC结构。
  - 了解STM32 RTC特征。
  - 掌握RTC的使用方法。
  - 掌握STM32 HAL库中RTC属性的配置方法。
  - 掌握KEIL MDK 集成开发环境使用方法。
==== 二、 实验设备及平台 ====
  - iCore3 双核心板。[[https://item.taobao.com/item.htm?spm=a1z10.1-c.w4024-251734887.3.5923532fXD2RIN&id=524229438677&scene=taobao_shop|点击购买]]
  - JLINK(或相同功能)仿真器[[https://item.taobao.com/item.htm?id=554869837940|点击购买]]。
  - Micro USB线缆。
  - Keil MDK 开发平台。
  - STM32CubeMX开发平台。
  - 装有WIN XP(及更高版本)系统的计算机。
==== 三、 实验原理 ====
=== 1、STM32F407 RTC时钟简介 ===
  * STM32F407的实时时钟(RTC)相对于STM32F1来说,改进了不少,带了日历功能了,STM32F767的RTC,是一个独立的BCD定时器/计数器。RTC提供一个日历时钟(包含年月日时分秒信息)、两个可编程闹钟(ALARMA和ALARMB)中断,以及一个具有中断功能的周期性可编程唤醒标志。RTC还包含用于管理低功耗模式的自动唤醒单元。
  * 两个32位寄存器(TR和DR)包含二进码十进数格式(BCD)的秒、分钟、小时(12或24小时制)、星期、日期、月份和年份。此外,还可提供二进制格式的亚秒值。
  * STM32F407的RTC可以自动将月份的天数补偿为28、29(闰年)、30和31天。并且还可以进行夏令时补偿。
  * RTC模块和时钟配置是在后备区域,即在系统复位或从待机模式唤醒后RTC的设置和时间维持不变,只要后备区域供电正常,那么RTC将可以一直运行。但是在系统复位后,会自动禁止访问后备寄存器和RTC,以防止对后备区域(BKP)的意外写操作。所以在要设置时间之前,先要取消备份区域(BKP)写保护。
=== 2、RTC主要特性 ===
  * RTC单元的主要特性如下:
    *  包含亚秒、秒、分钟、小时(12/24小时制)、星期几、日期、月份和年份的日历。
    *  软件可编程的夏令时补偿。
    *  具有中断功能的可编程闹钟。可通过任意日历字段的组合触发闹钟。
    *  自动唤醒单元,可周期性地生成标志以触发自动唤醒中断。
    *  参考时钟检测:可使用更加精确的第二时钟源(50Hz或60Hz)来提高日历的精确度。
    *  利用亚秒级移位特性与外部时钟实现精确同步。
    *  数字校准电路(周期性计数器调整):精度为0.95ppm,在数秒钟的校准窗口中获得。
    *  用于事件保存的时间戳功能。
    *  带可配置过滤器和内部上拉的入侵检测事件。
    *  可屏蔽中断/事件:
    *  –闹钟A
    *  –闹钟B
    *  –唤醒中断
    *  –时间戳
    *  –入侵检测
    *  32备份寄存器。
=== 3、RTC框图 ===
{{ :icore3:icore3_arm_hal_13_1.png?direct |}} 
=== 4、时钟和分频 ===
  * 首先,我们看STM32F407的RTC时钟分频。STM32F407的RTC时钟(RTCCLK)通过时钟控制器,可以从LSE时钟、LSI时钟以及HSE时钟三者中选择(通过RCC_BDCR寄存器选择)。一般我们选择LSE,即外部32.768Khz晶振作为时钟源(RTCCLK),而RTC时钟核心,要求提供1Hz的时钟,所以,我们要设置RTC的可编程预分配器。STM32F407的可编程预分配器(RTC_PRER)分为2个部分:
    * (1)一个通过RTC_PRER寄存器的PREDIV_A位配置的7位异步预分频器。
    * (2)一个通过RTC_PRER寄存器的PREDIV_S位配置的15位同步预分频器。
  * RTC框图中,ck_spre的时钟可由如下计算公式计算:
  * Fck_spre=Frtcclk/[(PREDIV_S+1)*(PREDIV_A+1)]
  * 其中,Fck_spre即可用于更新日历时间等信息。PREDIV_A和PREDIV_S为RTC的异步和同步分频器。且推荐设置7位异步预分频器(PREDIV_A)的值较大,以最大程度降低功耗。
=== 5、日历时间(RTC_TR)和日期(RTC_DR)寄存器 ===
  * STM32F407的RTC日历时间(RTC_TR)和日期(RTC_DR)寄存器,用于存储时间和日期(也可以用于设置时间和日期),可以通过与PCLK1(APB1时钟)同步的影子寄存器来访问,这些时间和日期寄存器也可以直接访问,这样可避免等待同步的持续时间。
  * 每隔2个RTCCLK周期,当前日历值便会复制到影子寄存器,并置位RTC_ISR寄存器的RSF位。我们可以读取RTC_TR和RTC_DR来得到当前时间和日期信息,不过需要注意的是:时间和日期都是以BCD码的格式存储的,读出来要转换一下,才可以得到十进制的数据。
=== 6、可编程闹钟 ===
  * 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即可。
=== 7、周期性自动唤醒 ===
  * STM32F407的RTC不带秒钟中断,但是多了一个周期性自动唤醒功能。周期性唤醒功能,由一个16位可编程自动重载递减计数器(RTC_WUTR)生成,可用于周期性中断/唤醒。
  * 我们可以通过RTC_CR寄存器中的WUTE位设置使能此唤醒功能。唤醒定时器的时钟输入可以是:2、4、8或16分频的RTC时钟(RTCCLK),也可以是ck_spre时钟(一般为1Hz)。
  * 本实验中,通过软件对RTC计数器进行相关的配置,可以提供时钟功能,通过修改计时器的值可以调整时钟。最终通过串口在终端显示时间。
==== 四、 实验程序 ====
=== 1、主函数 ===
int main(void)  
{    
RTC_TimeTypeDef sTime;
RTC_DateTypeDef sDate;
int second_bak = 0; 
    HAL_Init();  
SystemClock_Config();   //配置系统时钟
MX_GPIO_Init();          //初始化所有已配置的外围设备
MX_RTC_Init();
MX_UART4_Init();  
uart4.printf("\x0c");           //清屏                
uart4.printf("\033[1;32;40m");//字体终端设置为绿色            
uart4.printf("\r\n\r\nhello! I am iCore3!\r\n\r\n\r\n");
						          //在串口终端打印“Hello! I am iCore3”
LED_GREEN_ON;					//绿灯亮
while (1)
  {	
		HAL_Delay (100);
HAL_RTC_GetTime (&hrtc,&sTime,RTC_FORMAT_BIN); //读取RTC日期和时间
		HAL_RTC_GetDate (&hrtc,&sDate,RTC_FORMAT_BIN);
		if(second_bak != sTime.Seconds){	
//当秒数据与备份不一致时,像终端打印时间/日期
uart4.printf(" %02d:%02d:%02d",sTime.Hours,sTime.Minutes,sTime.Seconds);
	uart4.printf("20%02d-%02d-%02d\r",sDate.Year,sDate.Month,sDate.Date);
		second_bak = sTime.Seconds; //秒数据备份
		}		
}
}
=== 2、RTC初始化 ===
void MX_RTC_Init(void)
{
  RTC_TimeTypeDef sTime = {0};
  RTC_DateTypeDef sDate = {0};
  hrtc.Instance = RTC;
  hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
  hrtc.Init.AsynchPrediv = 127;//RTC异步分频系数
  hrtc.Init.SynchPrediv = 255; //RTC同步分频系数
  hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
  hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH; //输出信号的极性
  hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN; //RTC输出引脚模式
  if (HAL_RTC_Init(&hrtc) != HAL_OK) //初始化RTC外设
  {
    Error_Handler();
  } //判断读到的备份寄存器的值与写入的值是否一致
	if(HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR0) != 0x32F2){ //设置RTC初始日期和时间
		sTime.Hours = 0x17; //小时
		sTime.Minutes = 0x30;//分钟
		sTime.Seconds = 0x30;//秒
		sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
		sTime.StoreOperation = RTC_STOREOPERATION_RESET;
		if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD) != HAL_OK)
		{
			Error_Handler();
		}
		sDate.WeekDay = RTC_WEEKDAY_THURSDAY;
		sDate.Month = RTC_MONTH_JANUARY;
		sDate.Date = 0x15;
		sDate.Year = 0x20;
		if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BCD) != HAL_OK)
		{
			Error_Handler();
		}//向备份寄存器中写入数值
	 HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR0,0x32F2);
	}
  	if (HAL_RTCEx_SetWakeUpTimer(&hrtc,0,RTC_WAKEUPCLOCK_RTCCLK_DIV16)!= HAL_OK)
     {
       Error_Handler();
     }
}
  * 该函数用来初始化RTC配置以及日期和时钟,这里设置时间和日期,分别是通过 HAL_RTC_SetTime函数和 HAL_RTC_SetDate函数来实现。
=== 3、获取时间 ===
HAL_StatusTypeDef HAL_RTC_GetTime(RTC_HandleTypeDef *hrtc,RTC_TimeTypeDef *sTime, uint32_t Format)
{
  uint32_t tmpreg = 0U;
  /* 检查参数 */
  assert_param(IS_RTC_FORMAT(Format));
  /* 从对应的寄存器中获取亚秒值 */
  sTime->SubSeconds = (uint32_t)(hrtc->Instance->SSR);
  /*从相应的寄存器字段中获取SecondFraction 结构字段*/
  sTime->SecondFraction = (uint32_t)(hrtc->Instance->PRER & RTC_PRER_PREDIV_S);
  /* 获取TR寄存器 */
  tmpreg = (uint32_t)(hrtc->Instance->TR & RTC_TR_RESERVED_MASK);
  /* 用读取的参数填充结构字段 */
  sTime->Hours = (uint8_t)((tmpreg & (RTC_TR_HT | RTC_TR_HU)) >> 16U);
  sTime->Minutes = (uint8_t)((tmpreg & (RTC_TR_MNT | RTC_TR_MNU)) >> 8U);
  sTime->Seconds = (uint8_t)(tmpreg & (RTC_TR_ST | RTC_TR_SU));
  sTime->TimeFormat = (uint8_t)((tmpreg & (RTC_TR_PM)) >> 16U);
  /* 检查输入参数格式 */
  if(Format == RTC_FORMAT_BIN)
  {
    /* 将时间结构参数转换为二进制格式 */
    sTime->Hours = (uint8_t)RTC_Bcd2ToByte(sTime->Hours);
    sTime->Minutes = (uint8_t)RTC_Bcd2ToByte(sTime->Minutes);
    sTime->Seconds = (uint8_t)RTC_Bcd2ToByte(sTime->Seconds);
  }
  return HAL_OK;
}
==== 五、 实验步骤 ====
  * 1.把仿真器与iCore3的SWD调试口相连(直接相连或者通过转接器相连);
  * 2.把iCore3通过Micro USB线与计算机相连,为iCore3供电;
  * 3.打开putty软件,从设备管理器内查看端口号,设置波特率为115200;
 {{ :icore3:icore3_arm_hal_13_2.png?direct |}} 
  * 4.点击Open;
  * 5.打开Keil MDK 开发环境,并打开本实验工程;
  * 6.烧写程序到iCore3上;
  * 7.也可以进入Debug 模式,单步运行或设置断点验证程序逻辑。
==== 六、 实验现象 ====
  * 在终端屏幕上可以看到显示的时间和日期。如图所示。
{{ :icore3:icore3_arm_hal_13_3.png?direct |}}