于N32G45的RTC电子钟

1.RTC简介

视频:https://live.csdn.net/v/embed/260892

基于N32G45电子钟

  RTC,英文全称:Real-time clock,中文名称:实时时钟,是指可以像时钟一様输出实际时间的电子设备,一般会是集成电路,因此也称为时钟芯片。实时时钟芯片是日常生活中应用最为广泛的消费类电子产品之一。它为人们提供精确的实时时间,或者为电子系统提供精确的时间基准,目前实时时钟芯片大多采用精度较高的晶体振荡器作为时钟源。


  • RTC特性
  1. 实时时钟( RTC)是一个独立的 BCD 定时器/计数器
  2. 软件支持夏时令补偿
  3. 可编程周期性自动唤醒定时器
  4. 两个 32 位寄存器包含时、分、秒、年、月、日(几号)、星期(星期几)
  5. 独立的 32 位寄存器包含亚秒
  6. 两个编程闹钟
  7. 两个 32 位寄存器包含编程闹钟时、分、秒、年、月、日(几号)、星期(星期几)
  8. 两个独立的 32 位寄存器包含编程闹钟亚秒
  9. 数字精密校准功能
  10. 时间戳功能
  11. 在 Backup 域复位后,所有 RTC 寄存器都受到保护, 以防止可能的意外写访问
  12. 多个中断/事件唤醒源,包括闹钟 A、闹钟 B、唤醒定时器、时间戳
  13. RCC 寄存器使能 RTC 模块且电压保持在工作范围内, RTC 在任何模式下都不会停止(包括 RUN - 式、SLEEP 模式、 STOP0 模式、 STOP2 模式和 STANDBY 模式)
  14. RTC 提供多种唤醒源可以使 MCU 从所有的低功耗模式下唤醒( SLEEP 模式, STOP0 模式, STOP2 模式和 STANDBY 模式)


  • RTC框图


2.RTC功能特性

  RTC 包括以下功能模块:
17. Alarm A 和 Alarm B 事件/中断
18. 时间戳事件/中断
19. RTC 输出功能:
20. 256 Hz 或者 1Hz 时钟输出(当 LSE 频率是 32.768 kHz)
21. 闹钟输出(极性可配置),闹钟 A 和闹钟 B 可选
22. 自动唤醒输出(极性可配置)
23. RTC 输入功能:
24. 时间戳事件检测
25. 通过配置输出寄存器控制 PC13:
26. 设置 RTC_OPT.TYPE 位配置 PC13 开漏/推挽输出


3.RTC相关寄存器

  RTC在配置过程中,需要完成配置的有RTC写保护相关寄存器、RTC时钟和预分频寄存器、日历寄存器以及闹钟和校准寄存器。

  • RTC寄存器写保护

  PWR_CTRL.DBKP 位(见电源控制部分) 默认被清除,所以 PWR_CTRL.DBKP 必须置 1 去使能 RTC 寄存器写功能。一旦备份域复位,所有的 RTC 写保护寄存器都会写保护,所有的 RTC 写保护寄存器需要按如下步骤去解锁写保护:

  • 将 0xCA 写入 RTC_WRP 寄存器
  • 将 0x53 写入 RTC_WRP 寄存器

  在解锁这些寄存器后,可以通过清除 PWR_CTRL.DBKP 位激活写保护。解锁机制只检查 RTC_WRP 寄存器的写操作。在解锁过程中、解锁前、解锁后,对其他寄存器的写操作不会影响解锁结果。

  • RTC时钟和预分频

RTC 时钟源:

  • LSE 时钟
  • LSI 时钟
  • HSE/128 时钟
      为了降低功耗,将预分频器分为异步预分频器和同步预分频器。如果同时使用两个预分频器,建议异步预分频器的值尽可能大。
  • 7 位异步预分频器由 RTC_PRE.DIVA[6:0] 位控制
  • 15 位同步预分频器由 RTC_PRE.DIVS[14:0] 位控制


  ck_apre 时钟用于对 RTC_SUBS 亚秒递减计数器提供时钟。当到达 0 时,用 RTC_PRE.DIVS[14:0]的值重新加载 RTC_SUBS。

  • RTC日历

  这里有三个影子寄存器,分别是 RTC_DATE, RTC_TSH 和 RTC_SUBS。 RTC 时间和日期寄存器可以通过影子寄存器访问。也可以直接访问,以避免等待同步时间。这三个影子寄存器如下:

  • RTC_DATE: 设置和读取日期
  • RTC_TSH: 设置和读取时间
  • RTC_SUBS: 读取亚秒

##4.RTC电子钟配置流程

  1.RTC作为实时电子钟外设模块,在默认情况下只需要初始化一次即可。为了达到该目的,我们可以借助后备域寄存器BKP。
  备份存储器位于备份域里,电源 VDD 关闭后由 VBAT 供电维持。 BKP 共有 42 个 16 位的寄存器,可用来存储并保护用户应用数据。这 84 个字节不受系统待机模式唤醒或系统复位的影响。


   根据备份存储器复位不会清除数据特性,我们可以在第一次配置好RTC寄存器后将对应的某一个寄存器写入标志位,这样下次若检测到标志位存在,则直接启动RTC即可。


  2.选择RTC时钟源。因为RTC时钟源选择有3个:HSE/128、LSE、LSI。为了让RTC更精准,应优先选择LSE(外部低速时钟32.768KHZ),而我们当前开发板刚好有LSE。


  在RCC_BDCRCTL的0和1位,选择LSE时钟源;RCC_BDCRCTL的第15位使能RTC时钟。


  3.设置RTC的工作频率。设置RTC的工作频率我们可以通过RTC_PRE预分频寄存器完成。


  我们选择的是LSE=32.768KHZ时钟源,根据时钟频率计算公式,我们可以设置RTC_PRE=0xFF7F来产生1HZ工作频率。根据官方文档,异步预分频尽量设置大些以达到降低功耗。


  4.设置电子日历。电子日历可通过时间寄存器RTC_TSH和日期寄存器RTC_DATE完成配置。


  5.输出1HZ频率。在使用RTC日历功能时,参考官方提供示例日历功能是通过RTC校准输出引脚PC13输出,然后在开启一个外部中断器检测该引脚,从而输出电子日历。

  在使用RTC本身唤醒中断时发现无法触发,也可能是配置有问题,这个等下一次解决后再来叙述说明,本示例则按照官方示例实现。


  • PC13引脚模式配置


4.RTC配置

4.1 RTC配置示例

void RTC_Init(void)
{
  //开启RTC和后备域权限
  RCC->APB1PCLKEN|=1<<27;//开启备份接口时钟
  RCC->APB1PCLKEN|=1<<28;//电源接口时钟
  /* 允许访问RTC*/
  PWR->CTRL|=1<<8;//允许写入RTC和后备区域
	if(BKP->DAT2!=0xAA)//判断是否上一次RTC初始化
	{
    printf("进入初始化\r\n");
    RCC->BDCTRL&=~(1<<15);//关闭RTC时钟
    
		//2.选择RTC时钟源
		RCC->BDCTRL|=1<<0;//开启32.768KHZ时钟
		while(!(RCC->BDCTRL&1<<1)){}//等待32.768KHZ时钟准备就绪	
		RCC->BDCTRL&=~(0x3<<8);//清除原来寄存器中的值
		RCC->BDCTRL|=0x1<<8;//时钟源为32.768KHZ
    RCC->BDCTRL|=1<<15;//开启RTC时钟
    /*解除RTC写保护*/
    RTC->WRP=0xCA;
    RTC->WRP=0x53; 
    while(!(RTC->INITSTS&1<<5)){} //等待日历影子寄存器同步
    
    RTC->INITSTS|=1<<7;//进入初始化模式
    while(!(RTC->INITSTS&1<<6)){}//等待初始化标志置1
    printf("进入配置模式\r\n");  
    /*设置分频系数,产生1HZ*/
    RTC->PRE=0;      
    RTC->PRE|=0X7F<<16;
    RTC->PRE|=0xFF;

    RTC->INITSTS&=~(1<<7);//退出初始化模式
    RTC->WRP=0xff; 
    for(int i=0;i<0x2FF;i++);//等待配置完成
    BKP->DAT2=0xAA;//RTC初始化完成标志	
    RTC_SetDate(&RTC_Time);

	}
  RTC->WRP = 0xCA;
  RTC->WRP = 0x53;  
  RTC->CTRL|=1<<19;//输出1HZ
  RTC->OPT|=1<<0;//推挽输出
  RTC->CTRL|=1<<23;//开启校准输出
  RTC->WRP = 0xFF;
  printf("初始化完成\r\n");
}

4.2 配置PA7引脚,捕获RTC电子日历

   由于RTC产生的1HZ频率是通过PC13引脚输出,所以我们配置一个硬件来实现电子日历。

void EXTI_Init(void)
{
  //1.GPIO口配置

  RCC->APB2PCLKEN|=1<<2;
  GPIOA->PL_CFG&=0x0FFFFFFF;
  GPIOA->PL_CFG|=0x80000000;
  //2.开AFIO时钟,选择触发源
  RCC->APB2PCLKEN|=1<<0;//开AFIO时钟
  /*外部中断7--PA7*/
  AFIO->EXTI_CFG[1]&=~(0xF<<3*4);//PA7
  EXTI->IMASK|=1<<7;//使能中断线7
  EXTI->RT_CFG|=1<<7;//检测上升沿
  N32_NVIC_SetPriority(EXTI9_5_IRQn,1,1);//设置优先级
}
void EXTI9_5_IRQHandler(void)
{
 

  else if(PAin(7))
  {
    RTC_GetDate(&RTC_Time);
  }
  EXTI->PEND|=0xf<<5;//清除标志位
}

4.3 RTC设置时间

   为方便后续时间校准(串口校时或者网络校时或其它方式)实现,这里单独封装一个RTC时间校准函数。

/*设置RTC时间和日期*/
void RTC_SetDate(RTC_TIME *RTC_Time)
{
  RTC->WRP = 0xCA;
  RTC->WRP = 0x53;
  RTC->INITSTS|=1<<7;//进入初始化模式
  while(!(RTC->INITSTS&1<<6)){}//等待初始化标志置1 
   //设置日期
   RTC->DATE=0;
  //年
  RTC->DATE=0;
  RTC->DATE|=(RTC_Time->year / 10)<<20;
  RTC->DATE|=(RTC_Time->year % 10)<<16;
  //星期1
  RTC->DATE|=(RTC_Time->week % 8)<<13;
  //月
  RTC->DATE|=(RTC_Time->mon / 10)<<12;
  RTC->DATE|=(RTC_Time->mon % 10)<<8;
  //日
  RTC->DATE|=(RTC_Time->day / 10)<<4;
  RTC->DATE|=(RTC_Time->day % 10)<<0;  
    
  /*设置时间*/
  RTC->TSH=0;
  RTC->TSH&=~(1<<22);//24小时制
  //时
  RTC->TSH|=(RTC_Time->hour / 10)<<20;
  RTC->TSH|=(RTC_Time->hour % 10)<<16; 
  //分
  RTC->TSH|=(RTC_Time->min /10)<<12; 
  RTC->TSH|=(RTC_Time->min %10)<<8;  
  //秒
  RTC->TSH|=(RTC_Time->sec /10)<<4; 
  RTC->TSH|=(RTC_Time->sec %10)<<0;
  RTC->INITSTS&=~(1<<7);//退出初始化模式
  RTC->WRP=0xff;
}

4.4 RTC时间读取

   为方便后续做时间显示处理,封装时间获取函数。

/*读取RTC时间和日期*/
void RTC_GetDate(RTC_TIME *RTC_Time)
{
  u32 date=RTC->DATE;
  u32 tsh=RTC->TSH;
  RTC_Time->year=((date>>20)&0xf)*10+ ((date>>16)&0xf);
  RTC_Time->mon=((date>>12)&0x1)*10+ ((date>>8)&0xf);
  RTC_Time->day=((date>>4)&0x3)*10+(date&0xf);
  RTC_Time->week=((date>>13)&0x7);
  //时间,注意,+-优先级 高于 &的优先级
  RTC_Time->hour=((tsh>>20)&0x3)*10+((tsh>>16)&0xf);
  RTC_Time->min=((tsh>>12)&0x7)*10+((tsh>>8)&0xf);
  RTC_Time->sec=((tsh>>4)&0x7)*10+(tsh&0xf);
 // printf("%d/%d/%d -- %d:%d:%d \r\n",RTC_Time->year,RTC_Time->mon,RTC_Time->day, \
                                         RTC_Time->hour,RTC_Time->min,RTC_Time->sec);
}

4.5 串口校时

   通过串口方式进行时间校准。串口数据格式如下:

if(usart1_flag)
{
	usart1_rx_buff[usart1_cnt]='\0';//字符串结束标志符
	printf("usart1:%s\r\n",usart1_rx_buff);
	//*20200822102540
	if(usart1_rx_buff[0]=='*' && usart1_cnt==15)
	{
		RTC_Time.year=(usart1_rx_buff[3]-'0')*10+(usart1_rx_buff[4]-'0')*1;
		RTC_Time.mon=(usart1_rx_buff[5]-'0')*10+(usart1_rx_buff[6]-'0')*1;
		RTC_Time.day=(usart1_rx_buff[7]-'0')*10+(usart1_rx_buff[8]-'0')*1;
		RTC_Time.hour=(usart1_rx_buff[9]-'0')*10+(usart1_rx_buff[10]-'0')*1;
		RTC_Time.min=(usart1_rx_buff[11]-'0')*10+(usart1_rx_buff[12]-'0')*1;
		RTC_Time.sec=(usart1_rx_buff[13]-'0')*10+(usart1_rx_buff[14]-'0')*1;
        RTC_ChangeWeek(RTC_Time.year,RTC_Time.mon,RTC_Time.day);//星期
		RTC_SetDate(&RTC_Time);//设置时间和日期
	}
	usart1_flag=0;	
	usart1_cnt=0;			
}

4.6 运行效果

   将获取到的时间通过OLED屏幕显示。OLED屏幕驱动参考https://blog.csdn.net/weixin_44453694/article/details/128200921