基于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
- 项目运行效果
)
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)
{
RCC->APB1PCLKEN|=1<<27;
RCC->APB1PCLKEN|=1<<28;
PWR->CTRL|=1<<8;
if(BKP->DAT2!=0xAA)
{
printf("进入初始化\r\n");
RCC->BDCTRL&=~(1<<15);
RCC->BDCTRL|=1<<0;
while(!(RCC->BDCTRL&1<<1)){}
RCC->BDCTRL&=~(0x3<<8);
RCC->BDCTRL|=0x1<<8;
RCC->BDCTRL|=1<<15;
RTC->WRP=0xCA;
RTC->WRP=0x53;
while(!(RTC->INITSTS&1<<5)){}
RTC->INITSTS|=1<<7;
while(!(RTC->INITSTS&1<<6)){}
printf("进入配置模式\r\n");
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_SetDate(&RTC_Time);
}
RTC->WRP = 0xCA;
RTC->WRP = 0x53;
RTC->CTRL|=1<<19;
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)
{
RCC->APB2PCLKEN|=1<<2;
GPIOA->PL_CFG&=0x0FFFFFFF;
GPIOA->PL_CFG|=0x80000000;
RCC->APB2PCLKEN|=1<<0;
AFIO->EXTI_CFG[1]&=~(0xF<<3*4);
EXTI->IMASK|=1<<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