| 银杏科技有限公司旗下技术文档发布平台 | 
	
		| 技术支持电话 | 0379-69926675-801 | 
	
		| 技术支持邮件 | Gingko@vip.163.com | 
	
		| 版本 | 日期 | 作者 | 修改内容 | 
	
		| V1.0 | 2020-07-04 | gingko | 初次建立 | 
实验十:RTC实时时钟实验——显示日期和时间
一、 实验目的与意义
-  了解STM32 RTC结构。 
-  了解STM32 RTC特征。 
-  掌握RTC的使用方法。 
-  掌握STM32 HAL库中RTC属性的配置方法。 
-  掌握KEIL MDK 集成开发环境使用方法。 
 
二、 实验设备及平台
- 
- 
-  Micro USB线缆。 
-  Keil MDK 开发平台。 
-  STM32CubeMX开发平台。 
-  装有WIN XP(及更高版本)系统的计算机。 
 
三、 实验原理
1、STM32F767 RTC时钟简介
-  STM32F767的实时时钟(RTC)相对于STM32F1来说,改进了不少,带了日历功能了,STM32F767的RTC,是一个独立的BCD定时器/计数器。RTC提供一个日历时钟(包含年月日时分秒信息)、两个可编程闹钟(ALARMA和ALARMB)中断,以及一个具有中断功能的周期性可编程唤醒标志。RTC还包含用于管理低功耗模式的自动唤醒单元。 
-  两个32位寄存器(TR和DR)包含二进码十进数格式(BCD)的秒、分钟、小时(12或24小时制)、星期、日期、月份和年份。此外,还可提供二进制格式的亚秒值。 
-  STM32F767的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框图
4、时钟和分频
-  首先,我们看STM32F767的RTC时钟分频。STM32F767的RTC时钟(RTCCLK)通过时钟控制器,可以从LSE时钟、LSI时钟以及HSE时钟三者中选择(通过RCC_BDCR寄存器选择)。一般我们选择LSE,即外部32.768Khz晶振作为时钟源(RTCCLK),而RTC时钟核心,要求提供1Hz的时钟,所以,我们要设置RTC的可编程预分配器。STM32F767的可编程预分配器(RTC_PRER)分为2个部分: 
-  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)寄存器
-  STM32F767的RTC日历时间(RTC_TR)和日期(RTC_DR)寄存器,用于存储时间和日期(也可以用于设置时间和日期),可以通过与PCLK1(APB1时钟)同步的影子寄存器来访问,这些时间和日期寄存器也可以直接访问,这样可避免等待同步的持续时间。 
-  每隔2个RTCCLK周期,当前日历值便会复制到影子寄存器,并置位RTC_ISR寄存器的RSF位。我们可以读取RTC_TR和RTC_DR来得到当前时间和日期信息,不过需要注意的是:时间和日期都是以BCD码的格式存储的,读出来要转换一下,才可以得到十进制的数据。 
 
6、可编程闹钟
7、周期性自动唤醒
-  STM32F767的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;
  /* MCU 配置 */
  /* 重置所有外围设备,初始化Flash接口和Systick. */
  HAL_Init();
  /* 配置系统时钟 */
  SystemClock_Config();
  /* 初始化所有已配置的外围设备*/
  MX_GPIO_Init();
  MX_RTC_Init();
  MX_USART6_UART_Init();
  usart6.initialize(115200);           //串口波特设置
  usart6.printf("\x0c");               //清屏
  usart6.printf("\033[1;32;40m");       //设置终端字体为绿色
  usart6.printf(" Hello, I am iCore4!\r\n"); //串口信息输出
  LED_GREEN_ON; 
   //设置RTC日期和时间
  my_rtc.set_date(17,8,11,5);
  my_rtc.set_time(17,43,20);
  /* 无限循环 */
  while (1)
  {
        HAL_Delay(100);
        //读取RTC日期和时间
        HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
        HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
        if(second_bak != sTime.Seconds){
            usart6.printf(" %02d:%02d:%02d  ",sTime.Hours,sTime.Minutes,sTime.Seconds);
            usart6.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;
  RTC_DateTypeDef sDate;
  /**仅初始化RTC */
  hrtc.Instance = RTC;
  hrtc.Init.HourFormat = RTC_HOURFORMAT_24;// RTC设置为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(__FILE__, __LINE__);
  }
    /**初始化RTC并设置时间和日期 */
  if(HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR0) != 0x32F2){
  sTime.Hours = 0x16;//小时
  sTime.Minutes = 0x19;//分钟
  sTime.Seconds = 0x0;//秒
  sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
  sTime.StoreOperation = RTC_STOREOPERATION_RESET;
  if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }
  sDate.WeekDay = RTC_WEEKDAY_MONDAY;
  sDate.Month = RTC_MONTH_AUGUST;
  sDate.Date = 0x7;
  sDate.Year = 0x0;
  if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BCD) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }
    HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR0,0x32F2);
  }
    /**使能唤醒 */
  if (HAL_RTCEx_SetWakeUpTimer(&hrtc, 0, RTC_WAKEUPCLOCK_RTCCLK_DIV16) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }
}
 
3、设置时间和日期
static int rtc_set_time(unsigned char hour,unsigned char min,unsigned char sec)
{
    RTC_TimeTypeDef sTime;
    sTime.Hours = hour;//小时
    sTime.Minutes = min; //分钟
    sTime.Seconds = sec; //秒
    sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
    sTime.StoreOperation = RTC_STOREOPERATION_RESET;
    if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK)
    {
        while(1);
    }
    return 0;
}
static int rtc_set_date(unsigned char year,unsigned char month,unsigned char date,unsigned char week)
{
    RTC_DateTypeDef sDate;
    sDate.WeekDay = week; //星期
    sDate.Month = month; //月
    sDate.Date = date; //日
    sDate.Year = year; //年
  if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN) != HAL_OK)
  {
    while(1);
  }   
    return 0;
}
 
4、获取时间和日期
 /*获取RTC当前时间*/
HAL_StatusTypeDef HAL_RTC_GetTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime, uint32_t Format)
{
  uint32_t tmpreg = 0;
  /* 检查参数*/
  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)) >> 16);
  sTime->Minutes = (uint8_t)((tmpreg & (RTC_TR_MNT | RTC_TR_MNU)) >>8);
  sTime->Seconds = (uint8_t)(tmpreg & (RTC_TR_ST | RTC_TR_SU));
  sTime->TimeFormat = (uint8_t)((tmpreg & (RTC_TR_PM)) >> 16); 
  /* 检查输入参数格式 */
  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;
}
 /*获取RTC当前日期*/
HAL_StatusTypeDef HAL_RTC_GetDate(RTC_HandleTypeDef *hrtc, RTC_DateTypeDef *sDate, uint32_t Format)
{
  uint32_t datetmpreg = 0;
  /* 检查参数 */
  assert_param(IS_RTC_FORMAT(Format));
  /* 获取DR寄存器 */
  datetmpreg = (uint32_t)(hrtc->Instance->DR & RTC_DR_RESERVED_MASK); 
  /* 用读取的参数填充结构字段 */
  sDate->Year = (uint8_t)((datetmpreg & (RTC_DR_YT | RTC_DR_YU)) >> 16);
  sDate->Month = (uint8_t)((datetmpreg & (RTC_DR_MT | RTC_DR_MU)) >> 8);
  sDate->Date = (uint8_t)(datetmpreg & (RTC_DR_DT | RTC_DR_DU));
  sDate->WeekDay = (uint8_t)((datetmpreg & (RTC_DR_WDU)) >> 13); 
  /* 检查输入参数格式 */
  if(Format == RTC_FORMAT_BIN)
  {    
    /* 将日期结构参数转换为二进制格式 */
    sDate->Year = (uint8_t)RTC_Bcd2ToByte(sDate->Year);
    sDate->Month = (uint8_t)RTC_Bcd2ToByte(sDate->Month);
    sDate->Date = (uint8_t)RTC_Bcd2ToByte(sDate->Date);  
  }
  return HAL_OK;
}
 
 
五、 实验步骤
-  把仿真器与iCore4的SWD调试口相连(直接相连或者通过转接器相连); 
-  把iCore4通过Micro USB线与计算机相连,为iCore4供电; 
-  打开Keil MDK 开发环境,并打开本实验工程; 
-  烧写程序到iCore4上; 
-  也可以进入Debug 模式,单步运行或设置断点验证程序逻辑。 
 
六、 实验现象