STM32+OLED显示屏制作指针式电子钟

举报
DS小龙哥 发表于 2022/07/06 20:46:42 2022/07/06
【摘要】 该电子钟使用的是STM32内部RTC实时时钟,电子钟的界面仪表盘画面更新是在RTC秒中断里调用,实现时间指针的更新,编写OLED驱动代码,编写常用的OLED接口函数,比如:字符串显示,画点,划线等。

一、硬件环境介绍

单片机: STM32F103C8T6

RTC时钟来源: 使用STM32内部RTC定时器得到时间。

显示屏: 中景园0.96寸 SPI接口的OLED显示屏

编程软件: keil5

image.png

二、实现效果图与代码技术部分介绍

2.1 实现的效果图

image.png
image.png

2.2 代码技术部分介绍

核心代码内容里分为以下几个部分:

(1) RTC时钟代码部分:该电子钟使用的是STM32内部RTC实时时钟,需要编写RTC初始化代码。

(2) 电子钟界面逻辑代码部分:电子钟的界面仪表盘画面更新是在RTC秒中断里调用,实现时间指针的更新。

(3) OLED驱动代码部分:编写OLED驱动代码,编写常用的OLED接口函数,比如:字符串显示,画点,划线等。

OLED在程序的驱动方式采用显存的方式驱动,定义一个显存数组,程序里的所有逻辑代码先绘制在显存数组里,然后再刷新到OLED显示屏上。

三、核心代码

3.1 OLED显示屏驱动代码

(1). oled.h

#ifndef _OLED_H
#define _OLED_H
#include "stm32f10x.h"
#include "delay.h"
#include "sys.h"
#include <string.h>

extern const u8 ChineseFont_16_16[][32];
extern const u8 ChineseFont_24_24[][24*24/8];
extern const u8 ASCII_8_16[][16];
extern const u8 bmp_foot_18_29_1[];
extern const u8 bmp_foot_17_32_2[];
extern const u8 ASCII_12_24[][12*3];

/*
初始化OLED显示屏硬件
硬件连接:
D0--PB14--时钟线
D1--PB13--数据线
RES-PB12-复位脚
DC--PB1--命令数据选择脚
CS--PA7--片选
*/
#define OLED_SCK PBout(14)
#define OLED_MOSI PBout(13)
#define OLED_RES PBout(12)
#define OLED_DC PBout(1)
#define OLED_CS PAout(7)

//定义命令
#define OLED_WRITE_CMD 0
#define OLED_WRITE_DAT 1

//函数声明
void OLED_SPI_WriteOneByte(u8 data,u8 flag);
void OLED_Init(void);
void OLED_Clear(u8 data);
void OLED_SetPos(u8 x,u8 y);
void OLED_DisplayPoint(u8 x,u8 y,u8 c);
void OLED_DisplayData(u8 x,u8 y,u8 w,u8 h,u8 *p);
void OLED_WriteGRAM(void);

void OLED_DrawLine(u16 x1, u16 y1, u16 x2, u16 y2); //两点画线
void OLED_DrawAngleLine(u32 x,u32 y,float du,u32 len,u32 w,u8 c);//角度画线
void OLED_DrawRectangle(u16 x1, u16 y1, u16 x2, u16 y2); //矩形
void OLED_Circle(u16 x0,u16 y0,u8 r); //圆
void OLED_DrawAngleLine2(u32 x,u32 y,int du,u32 len,u8 c);
void OLED_ShowString(u8 x,u8 y,u8 h,char *str);
void OLED_ShowChineseFont(u8 x,u8 y,u8 size,u8 number);

extern u8 OLED_GRAM[8][128]; //8行128列---->8页128列
#endif

3.2 rtc驱动代码

(1) rtc.c

#include "rtc.h"

//定义RTC标准结构体
struct RTC_CLOCK rtc_clock; 

/*
函数功能: RTC初始化函数
*/
void RTC_Init(void)
{
	 if(BKP->DR1!=0xAB) //表示RTC第一次初始化
	 {
			//1. 备份寄存器时钟
			RCC->APB1ENR|=1<<27; //备份时钟接口
			RCC->APB1ENR|=1<<28; //电源时钟接口
			PWR->CR|=1<<8; 			 //允许写入RTC和后备寄存器
		  
		  //2. 配置RTC时钟源
			RCC->BDCR|=1<<0;     //开启外部32.768K时钟
		  while(!(RCC->BDCR&1<<1)){} //等待时钟就绪
			RCC->BDCR&=~(0x3<<8);  //清空时钟配置
			RCC->BDCR|=0x1<<8;     //选择外部32.768K时钟
			
			//3. 配置RTC核心寄存器
			RCC->BDCR|=1<<15;     //开启RTC时钟
      while(!(RTC->CRL&1<<5)){} //判断上一次寄存器是否写完成
			RTC->CRL|=1<<4;  //进入配置模式
			RTC->PRLH=0;      //预分频高位
			RTC->PRLL=0x7FFF; //32767	预分频低位
			RTC->CNTH=0;      //计数器高位
			RTC->CNTL=0;      //计数器低位
			RTC->ALRH=0;      //闹钟寄存器高位
			RTC->ALRL=60;      //闹钟寄存器低位
			RTC->CRL&=~(1<<4);//退出配置模式	
			while(!(RTC->CRL&1<<5)){} //判断上一次寄存器是否写完成
			BKP->DR1=0xAB; 		//表示配置成功了	
		}

		RTC->CRH|=1<<0; //秒中断
		RTC->CRH|=1<<1; //闹钟中断
		STM32_SetPriority(RTC_IRQn,2,2); //优先级
}

extern void Update_FrameShow(void);
/*
函数功能: RTC闹钟中断服务函数
*/
void RTC_IRQHandler(void)
{
	  u32 SecCnt;
		if(RTC->CRL&1<<0)
		{
				SecCnt=RTC->CNTH<<16;//获取高位
				SecCnt|=RTC->CNTL;   //获取低位
				RTC_GetTime(SecCnt); //转换标准时间
				RTC_GetWeek(SecCnt);
			 // printf("%d-%d-%d %d:%d:%d week:%d\n",rtc_clock.year,rtc_clock.mon,rtc_clock.day,rtc_clock.hour,rtc_clock.min,rtc_clock.sec,rtc_clock.week);
				Update_FrameShow(); //更新显示
				RTC->CRL&=~(1<<0); //清除秒中断标志位
		}
		
		if(RTC->CRL&1<<1)
		{
//				printf("闹钟时间到达!....\n");
//			  BEEP=1;
//			  DelayMs(500);
//				BEEP=0;
				RTC->CRL&=~(1<<1); //清除闹钟中断标志位
		}
}



//闰年的月份
static int mon_r[12]={31,29,31,30,31,30,31,31,30,31,30,31};
//平年的月份
static int mon_p[12]={31,28,31,30,31,30,31,31,30,31,30,31};


/*
函数功能: 设置RTC时间
函数形参:
    u32 year;   2018
	  u32 mon;     8
	  u32 day;
		u32 hour;
		u32 min;
	  u32 sec;
*/
void RTC_SetTime(u32 year,u32 mon,u32 day,u32 hour,u32 min,u32 sec)
{
		u32 i;
	  u32 SecCnt=0; //总秒数
		/*1. 累加已经过去的年份*/
		for(i=2017;i<year;i++)  //基准年份:20170101000000
	  {
				if(RTC_GetYearState(i))
				{
						SecCnt+=366*24*60*60; //闰年一年的秒数
				}
				else
				{
						SecCnt+=365*24*60*60; //平年一年的秒数
				}
		}
		/*2. 累加过去的月份*/
		for(i=0;i<mon-1;i++)
		{
			  if(RTC_GetYearState(year))
				{
						SecCnt+=mon_r[i]*24*60*60; //闰年一月的秒数
				}
				else
				{
						SecCnt+=mon_p[i]*24*60*60; //平年一月的秒数
				}	
		}
		
		/*3. 累加过去的天数*/
		SecCnt+=(day-1)*24*60*60;
		
		/*4. 累加过去小时*/
		SecCnt+=hour*60*60;
		
		/*5. 累加过去的分钟*/
		SecCnt+=min*60;
		
		/*6. 累加过去的秒*/
		SecCnt+=sec;
		
		/*7. 设置RTC时间*/
		RCC->APB1ENR|=1<<27; //备份时钟接口
		RCC->APB1ENR|=1<<28; //电源时钟接口
		PWR->CR|=1<<8; 			 //允许写入RTC和后备寄存器
		while(!(RTC->CRL&1<<5)){} //判断上一次寄存器是否写完成
		RTC->CRL|=1<<4;           //进入配置模式
		RTC->CNTH=SecCnt>>16;     //计数器高位
		RTC->CNTL=SecCnt&0xFFFF;  //计数器低位
		RTC->CRL&=~(1<<4);//退出配置模式	
		while(!(RTC->CRL&1<<5)){} //判断上一次寄存器是否写完成
}


/*
函数功能: 获取RTC时间
函数参数: u32 sec 秒单位时间
*/
void RTC_GetTime(u32 sec)
{
		u32 i;
		rtc_clock.year=2017; //基准年份
		
	  /*1. 计算当前的年份*/
	  while(1)
		{
			  if(RTC_GetYearState(rtc_clock.year))
				{
						if(sec>=366*24*60*60) //够一年
						{
								sec-=366*24*60*60;
								rtc_clock.year++;
						}
						else break;
				}
				else
				{
						if(sec>=365*24*60*60) //够一年
						{
								sec-=365*24*60*60;
								rtc_clock.year++;
						}
						else break;
				}
		}
		
		/*2. 计算当前的月份*/
		rtc_clock.mon=1;
		for(i=0;i<12;i++)
		{
				if(RTC_GetYearState(rtc_clock.year))
				{
					 	if(sec>=mon_r[i]*24*60*60)
						{
							 sec-=mon_r[i]*24*60*60;
							 rtc_clock.mon++;
						}
						else break;		
				}
				else
				{
						if(sec>=mon_p[i]*24*60*60)
						{
							 sec-=mon_p[i]*24*60*60;
							 rtc_clock.mon++;
						}
						else break;	
				}
		}
		
		/*3. 计算当前的天数*/
		rtc_clock.day=1;
		while(1)
		{
				if(sec>=24*60*60)
				{
						sec-=24*60*60;
						rtc_clock.day++;
				}
				else break;
		}
		
		/*4. 计算当前的小时*/
		rtc_clock.hour=0;
		while(1)
		{
				if(sec>=60*60)
				{
						sec-=60*60;
						rtc_clock.hour++;
				}
				else break;
		}
		
		/*5. 计算当前的分钟*/
		rtc_clock.min=0;
		while(1)
		{
				if(sec>=60)
				{
						sec-=60;
						rtc_clock.min++;
				}
				else break;
		}
		
		/*6. 计算当前的秒*/
		rtc_clock.sec=sec;
}


/*
函数功能: 判断年份是否是平年、闰年
返回值  : 0表示平年 1表示闰年
*/
u8 RTC_GetYearState(u32 year)
{
	 if((year%4==0&&year%100!=0)||year%400==0)
	 {
		 return 1;
	 }
	 return 0;
}


/*
函数功能: 获取星期
*/
void RTC_GetWeek(u32 sec)
{
	u32 day1=sec/(60*60*24); //将秒单位时间转为天数
	switch(day1%7)
	{
		case 0:
			rtc_clock.week=0;
			break;
	 	case 1:
			rtc_clock.week=1;
			break;
		case 2:
			rtc_clock.week=2;
			break;
		case 3:
			rtc_clock.week=3;
			break;
		case 4:
			rtc_clock.week=4;
			break;
		case 5:
			rtc_clock.week=5;
			break;
		case 6:
			rtc_clock.week=6;
			break;
	}
}

(2) rtc.h

#ifndef RTC_H
#define RTC_H
#include "stm32f10x.h"
#include "sys.h"
#include "usart.h"
#include "delay.h"

//定时RTC时钟结构体
struct RTC_CLOCK
{
		u32 year;
	  u32 mon;
	  u32 day;
		u32 hour;
		u32 min;
	  u32 sec;
		u32 week;
};
extern struct RTC_CLOCK rtc_clock; 

//函数声明
void RTC_Init(void);
u8 RTC_GetYearState(u32 year);
void RTC_GetTime(u32 sec);
void RTC_GetWeek(u32 sec);
void RTC_SetTime(u32 year,u32 mon,u32 day,u32 hour,u32 min,u32 sec);
#endif

3.3 界面绘制部分代码-主函数

#include "stm32f10x.h"
#include "led.h"
#include "delay.h"
#include "key.h"
#include "usart.h"
#include <string.h>
#include "timer.h"
#include "oled.h"
#include "rtc.h"

/*
函数功能: 绘制时钟表盘框架
*/
void DrawTimeFrame(void)
{
	u8 i;
	OLED_Circle(32,32,31);//画外圆
	OLED_Circle(32,32,1); //画中心圆
	//画刻度
	for(i=0;i<60;i++)
	{
		if(i%5==0)OLED_DrawAngleLine(32,32,6*i,31,3,1);
	}
	OLED_WriteGRAM();  //刷新数据到OLED屏幕
}


/*
函数功能: 更新时间框架显示,在RTC中断里调用
*/
char TimeBuff[20];
void Update_FrameShow(void)
{
	/*1. 绘制秒针、分针、时针*/
	OLED_DrawAngleLine2(32,32,rtc_clock.sec*6-6-90,27,0);//清除之前的秒针
	OLED_DrawAngleLine2(32,32,rtc_clock.sec*6-90,27,1); //画秒针
	
	OLED_DrawAngleLine2(32,32,rtc_clock.min*6-6-90,24,0);
	OLED_DrawAngleLine2(32,32,rtc_clock.min*6-90,24,1);
	
	OLED_DrawAngleLine2(32,32,rtc_clock.hour*30-6-90,21,0);
	OLED_DrawAngleLine2(32,32,rtc_clock.hour*30-90,21,1);
	
	//绘制电子钟时间
	sprintf(TimeBuff,"%d",rtc_clock.year);
	OLED_ShowString(65,16*0,16,TimeBuff);  //年份字符串
	OLED_ShowChineseFont(66+32,16*0,16,4); //显示年
	
	sprintf(TimeBuff,"%d/%d",rtc_clock.mon,rtc_clock.day);
	OLED_ShowString(75,16*1,16,TimeBuff); //月
	
	if(rtc_clock.sec==0)OLED_ShowString(65,16*2,16,"        ");	//清除多余的数据
	sprintf(TimeBuff,"%d:%d:%d",rtc_clock.hour,rtc_clock.min,rtc_clock.sec);
	OLED_ShowString(65,16*2,16,TimeBuff); //秒
	
	//显示星期
	OLED_ShowChineseFont(70,16*3,16,5); //星
	OLED_ShowChineseFont(70+16,16*3,16,6); //期
	OLED_ShowChineseFont(70+32,16*3,16,rtc_clock.week+7); //具体的值
}


int main()
{
    LED_Init();
    BEEP_Init();
    KEY_Init();
    USART1_Init(115200);
    TIMER1_Init(72,20000); //超时时间20ms
    USART2_Init(9600);//串口-蓝牙
    TIMER2_Init(72,20000); //超时时间20ms
    USART3_Init(115200);//串口-WIFI
    TIMER3_Init(72,20000); //超时时间20ms
    USART1_Printf("正在初始化WIFI请稍等.\n");
    RTC_Init(); //RTC初始化
    OLED_Init();
    OLED_Clear(0x00); //清屏
    
    RTC_SetTime(2020,7,15,10,52,10);
    DrawTimeFrame();
    while(1)
    { 
        
    }
}
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。