国民技术
直播中

wang123a

7年用户 108经验值
擅长:嵌入式技术 控制/MCU RF/无线
私信 关注

【国民技术N32项目移植】基于N32G45的电子钟项目移植

电子钟

基于N32G45的电子钟项目移植

1.项目简介

  本项目主要完RCC时钟、GPIO引脚、USART、DMA协助串口收发、TIM输出PWM控制LED闪烁以实现程序运行指示、硬件SPI驱动0.96寸OLED屏幕用于显示实时时间、RTC驱动获取电子日历以及EXTI外部中断检测RTC输出引脚PC13的边沿极性。

SPI驱动OLED屏幕请参考示例:https://www.elecfans.com/d/1950506.htmltrack_id=myCenter&mod=article&share

  • 项目运行效果
    IMG_20221207_204238.jpg
    image.png)

2.电子钟RTC

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

  • RTC特性

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

3.RTC电子钟配置流程

  1.RTC作为实时电子钟外设模块,在默认情况下只需要初始化一次即可。为了达到该目的,我们可以借助后备域寄存器BKP。

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

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

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

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

3.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 串口时间校准

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

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);//设置时间和日期
        OLED_ClearGram(0);//清空缓冲区
        sDynamicClockInitial();//初始化数码管
   }
	usart1_flag=0;	
	usart1_cnt=0;			
}

5.OLED屏幕时间显示

  为了展示N32G45的硬件SPI效率,这里移植了一套数码管RTC时间显示代码。本次数码管显示RTC时间纯依靠OLED底层画点函数实现。接下来仅展示其中两个需要用户自行调用的函数。详细代码请参考示例工程。

  1.初始化数码管各个数据段

/*************************************************************************
 ** Function Name:	sDynamicClockInitial		                               
 ** Purpose:		初始化时钟的各个数码段部分   		        
 ** Params:															                              
 **	@ 	                                        			 
 ** Return:							  	 
 ** Notice:	  None.												 
 ** Author:		公众号:最后一个bug											 
 *************************************************************************/
void sDynamicClockInitial(void)
{
	 #define NUM_OFFSET (19)
	
	uint16_t x_Location = 5;
	uint16_t y_Location = 20;
	
	stSuperNum1.pDrawPoint = OLED_DrawPoint;
	InitialSuperNum(&stSuperNum1,x_Location,y_Location,10,10,2);
  InitialSegShowAction(&stSuperNum1,(uint8_t*)SegAction);

	x_Location += NUM_OFFSET;

  stSuperNum2.pDrawPoint = OLED_DrawPoint;
	InitialSuperNum(&stSuperNum2,x_Location,y_Location,10,10,2);
	InitialSegShowAction(&stSuperNum2,(uint8_t*)SegAction);

	x_Location += NUM_OFFSET;
  stSuperNum3.pDrawPoint = OLED_DrawPoint;
	InitialSuperNum(&stSuperNum3,x_Location,y_Location,2,10,2);
	InitialSegShowAction(&stSuperNum3,(uint8_t*)SegAction);

	x_Location += NUM_OFFSET/2 + 2;
	stSuperNum4.pDrawPoint = OLED_DrawPoint;
	InitialSuperNum(&stSuperNum4,x_Location,y_Location,10,10,2);
	InitialSegShowAction(&stSuperNum4,(uint8_t*)SegAction);

	x_Location += NUM_OFFSET;
  stSuperNum5.pDrawPoint = OLED_DrawPoint;
	InitialSuperNum(&stSuperNum5,x_Location,y_Location,10,10,2);
	InitialSegShowAction(&stSuperNum6,(uint8_t*)SegAction);

	x_Location += NUM_OFFSET;
	stSuperNum6.pDrawPoint = OLED_DrawPoint;
	InitialSuperNum(&stSuperNum6,x_Location,y_Location,2,10,2);
	InitialSegShowAction(&stSuperNum6,(uint8_t*)SegAction);

	x_Location += NUM_OFFSET/2+2;
	stSuperNum7.pDrawPoint = OLED_DrawPoint;
	InitialSuperNum(&stSuperNum7,x_Location,y_Location+10,5,5,2);
	InitialSegShowAction(&stSuperNum7,(uint8_t*)SegAction);

	x_Location += NUM_OFFSET/2+4;
	stSuperNum8.pDrawPoint = OLED_DrawPoint;
	InitialSuperNum(&stSuperNum8,x_Location,y_Location+10,5,5,2);
	InitialSegShowAction(&stSuperNum8,(uint8_t*)SegAction);
	
}

  2.显示实时时间。实时时间显示以数码管的方式实现。显示的内容包含:时、分、秒。

   通过传入时分秒时间,当秒时间变化一次则修改一次显示内容。

/*************************************************************************
 ** Function Name:	sDynamicClockProcess		                               
 ** Purpose:		动态时钟处理		        
** Params:		Hour、	Min	、Sec --传入要显示的时分秒								                              
 **	@ 	                                        			 
 ** Return:							  	 
 ** Notice:	  None.												 
 ** Author:		公众号:最后一个bug											 
 *************************************************************************/
#include <stdio.h>
void sDynamicClockProcess(unsigned char Hour,unsigned char Min,unsigned char Sec)
{
	static uint16_t DPoint = 11;
	static uint16_t CurrSecOld = 0xFFFF;//保存的s
	static uint16_t SecondPoint = 0;
  static uint16_t CurrHour,CurrMin,CurrSec;
  static u8 sec2;
  if(sec2!=Sec)
  {
    sec2=sec2;
    CurrHour=Hour;
    CurrMin=Min;
    CurrSec=Sec;
  }
  //下面是更新显示处理
    if(CurrSecOld != CurrSec)
    {
      if(CurrSecOld == 0xFFFF) //表示开机第1s不处理
      {
        CurrSecOld = 0xFFFE;
      }
      else 
      {
        CurrSecOld = CurrSec;//更新
        DPoint = ((DPoint == 11)?(DPoint = 10):(DPoint = 11)); //点闪烁
      }
    }
    if(CurrSecOld < 60)
    {
      SuperNumActionPlay(&stSuperNum1,(uint8_t*)SegAction,CurrHour/10);
      SuperNumActionPlay(&stSuperNum2,(uint8_t*)SegAction,CurrHour%10);
      SuperNumActionPlay(&stSuperNum3,(uint8_t*)SegAction,DPoint); 
      SuperNumActionPlay(&stSuperNum4,(uint8_t*)SegAction,CurrMin/10);
      SuperNumActionPlay(&stSuperNum5,(uint8_t*)SegAction,CurrMin%10);
      SuperNumActionPlay(&stSuperNum6,(uint8_t*)SegAction,DPoint);
      SuperNumActionPlay(&stSuperNum7,(uint8_t*)SegAction,CurrSecOld/10);
      SuperNumActionPlay(&stSuperNum8,(uint8_t*)SegAction,CurrSecOld%10);	
      OLED_Refresh();//更新数据到屏幕
      
    }
	
}

6.OLED屏幕动态刷新

  通过OLED屏幕画点函数实现汉字和字符串口显示,编写动态刷屏函数。

  汉字显示从左往右显示和从上往下显示示例:

void OLED_Font_reflash(int format)
{
  int i=0;
  int j=0;
  u8 k=0;
  u8 temp;
  u8 y0;
  u8 cnt=0;
  switch(format)
  {
    case 1://从上往下
      for(i=0;i<8;i++)
      {
        for(j=0;j<128;j++)
        {
          temp=oled_gram[i][j];
          y0=i*8;
          for(k=0;k<8;k++)
          {
            if(temp&0x01)
              OLED_DrawPoint2(j,y0,1);
            else 
              OLED_DrawPoint2(j,y0,0);
             temp>>=1;
             y0++;
          }
          cnt++;
          if(cnt>=48)
          {
            cnt=0;
            OLED_Refresh3();
            Delay_Ms(200);
          }
        }
        
      }
      break;
    case 2://从左往右
      for(i=0;i<128;i++)
      {
        for(j=0;j<8;j++)
        {
           temp=oled_gram[j][i];
          y0=j*8;
          for(k=0;k<8;k++)
          {
            if(temp&0x01)
              OLED_DrawPoint2(i,y0,1);
            else 
              OLED_DrawPoint2(i,y0,0);
             temp>>=1;
             y0++;
          }
        }
        OLED_Refresh3();
        Delay_Ms(60);
      }
      break;
    default:
      OLED_Refresh();
      break;
      
      
  }  
}

*附件:RTC电子钟.rar

更多回帖

发帖
×
20
完善资料,
赚取积分