本项目主要完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
RTC,英文全称:Real-time clock,中文名称:实时时钟,是指可以像时钟一様输出实际时间的电子设备,一般会是集成电路,因此也称为时钟芯片。实时时钟芯片是日常生活中应用最为广泛的消费类电子产品之一。它为人们提供精确的实时时间,或者为电子系统提供精确的时间基准,目前实时时钟芯片大多采用精度较高的晶体振荡器作为时钟源。
RTC特性
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输出,然后在开启一个外部中断器检测该引脚,从而输出电子日历。
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");
}
由于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;//清除标志位
}
通过串口方式进行时间校准。串口数据格式如下:
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;
}
为了展示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();//更新数据到屏幕
}
}
通过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;
}
}
更多回帖