江涛带你玩0.96-OLED之实战stm32的RTC时钟(上)

目录

1.前言

2.准备工作

3.关于OLED驱动,模拟SPI和硬件SPI

4.关于SPI和IIC的驱动的异同点

5.开始驱动OLED,使用STM32-CubeMX生成代码

6.显示函数

7.改造显示函数

 


 

1.前言

因为疫情原因,宅在家无聊,翻到了之前的一些STM32的开发板,还有两块0.96的OLED屏,于是想着再来折腾下电子,虽然我已经有5年时间没碰过电子了(毕业后本人就转行Android和Java开发了),但是还是觉得搞电子比较有趣,回忆从前玩单片机都是拿来主义,很少从代码层去深究实现原理,很多屏幕的驱动都是抄现成的代码,有时候不好用了又不知道怎么办,所以奉劝学电子的后来人,如果打算在这条路上走下去,还是得搞清楚原理,才能以不变应万变,成为真正的大神。而这篇博文呢,只是为了给自己一个警醒,凡是切勿浅尝辄止,要深入研究,同时也给驱动OLED同样有困惑的人一些经验借鉴。好了,废话不多说了。

 

2.准备工作

工欲善其事必先利其器,所以先把需要用到的工具啥的准备好。

需要安装的软件 : Keil5(V-5.25.2.0)     STM32-CubeMX(V-5.6.0)

需要准备的硬件 : STM32系列开发板(本人使用的是STM32F103C8T6),OLED屏幕(4线SPI),ST-Link烧录器

软件下载放上分享链接:

链接:https://pan.baidu.com/s/1MgRisNjq3rcqYgmM6Jmpiw 
提取码:vkmn

关于环境搭建,大家可以自行百度,我上面分享的也不一定是最新的软件版本,stm32cubeMX的安装需要java支持。

例如安装教程:

STM32CubeMx入门系列教程----安装教程(适用萌新)

关于KEIL的安装强调一点:安装keil的时候,破解软件运行的时候要使用右键以管理员身份运行,同样keil也要使用右键管理员方式运行,然后复制CID破解。如果不是管理员身份运行,破解的时候会出现如下的报错

江涛带你玩0.96-OLED之实战stm32的RTC时钟(上)_第1张图片

You are not logged in as an "Administrator" !!!!

同样也给出了解决方法 Solution:用管理员账号登陆或者Run as Administrator(以管理员方式运行)
 

3.关于OLED驱动,模拟SPI和硬件SPI

本文驱动OLED使用的是4线SPI方式驱动,当然如果你的外设IO口有限,也可以选择IIC方式驱动。我这里先就SPI驱动来谈论,下面放上两段代码对比。

//向SSD1306写入一个字节。
//dat:要写入的数据/命令
//cmd:数据/命令标志 0,表示命令;1,表示数据;
void OLED_WR_Byte(u8 dat ,u8 cmd)
{	
	if(cmd)
	  OLED_DC_Set(); // 控制脚拉高,写数据
	else 
	  OLED_DC_Clr(); // 控制脚拉低,写命令

	// 硬件SPI写法
	HAL_SPI_Transmit(&hspi2,&dat,sizeof(dat),1000); // SPI写数据或者命令
	OLED_DC_Set(); 
} 
//向SSD1306写入一个字节。
//dat:要写入的数据/命令
//cmd:数据/命令标志 0,表示命令;1,表示数据;
void OLED_WR_Byte(u8 dat ,u8 cmd)
{	
	u8 i;	
	// 根据传入的CMD,判断是写数据还是写命令
	if(cmd)
	  OLED_DC_Set(); // 控制脚拉高,写数据
	else 
	  OLED_DC_Clr(); // 控制脚拉低,写命令

    // 模拟SPI数据传输写法
	OLED_CS_Clr(); 									// 片选拉低,开始写数据或命令
	for(i=0;i<8;i++)								// 一字节有8bit,所以需要循环8次
	{			  
		OLED_SCLK_Clr(); 							// 时钟置低
		dat&0x80 ? OLED_SDIN_Set():OLED_SDIN_Clr(); // 与位判断,最终数据脚写0或1就是根据这个
		OLED_SCLK_Set(); 							// 时钟拉高
		dat<<=1;       								// 数据或命令左移一位  
	}				 		  
	OLED_CS_Set(); 									// 片选拉高,结束写数据或命令

	OLED_DC_Set();  
} 

上面第一段代码是硬件SPI的写法,下面一段是模拟SPI的写法。可以看到硬件SPI驱动的写法相较模拟SPI少了很多。我放个图上来对比下二者的写法

江涛带你玩0.96-OLED之实战stm32的RTC时钟(上)_第2张图片

 

模拟SPI和硬件SPI这两部分实现的功能其实是一模一样的,都是有序的将数据传输到OLED驱动芯片SSD1306上。而使用STM32的硬件SPI,我们可以少关注CS拉低开始写入,开关时钟SCLK,8bit依次判断写0或1,然后CS拉高禁止写入的整个过程。其实懂了这个之后对于之后移植这个驱动就很简单了。下面我们对照着代码来分析下SSD1306数据手册中的时序图

江涛带你玩0.96-OLED之实战stm32的RTC时钟(上)_第3张图片

对照着手册再回过头来看代码,发现是能够一一对应的。 

 

4.IIC的驱动OLED的例子

用IIC来驱动OLED相比复杂度更高,因为只用到了时钟和数据两根线,所以代码上也会复杂一点,我找一段IIC的驱动代码贴上来给大家品鉴下。

void IIC_Start()
{
	OLED_SCLK_Set() ;
	OLED_SDIN_Set();
	OLED_SDIN_Clr();
	OLED_SCLK_Clr();
}

void IIC_Stop()
{
	OLED_SCLK_Set() ;
	OLED_SDIN_Clr();
	OLED_SDIN_Set();
}

void IIC_Wait_Ack()
{
	OLED_SCLK_Set() ;
	OLED_SCLK_Clr();
}
/**********************************************
// IIC Write byte
**********************************************/
void Write_IIC_Byte(unsigned char IIC_Byte)
{
	unsigned char i;
	unsigned char m,da;
	da=IIC_Byte;
	OLED_SCLK_Clr();
	for(i=0;i<8;i++)		
	{
		m=da;
		m=m&0x80;
		if(m==0x80)
			OLED_SDIN_Set();
		else 
			OLED_SDIN_Clr();
		da=da<<1;
		OLED_SCLK_Set();
		OLED_SCLK_Clr();
	}
}
/**********************************************
// IIC Write Command
**********************************************/
void Write_IIC_Command(unsigned char IIC_Command)
{
   IIC_Start();
   Write_IIC_Byte(0x78);            //Slave address,SA0=0
	IIC_Wait_Ack();	
   Write_IIC_Byte(0x00);			//write command
	IIC_Wait_Ack();	
   Write_IIC_Byte(IIC_Command); 
	IIC_Wait_Ack();	
   IIC_Stop();
}
/**********************************************
// IIC Write Data
**********************************************/
void Write_IIC_Data(unsigned char IIC_Data)
{
   IIC_Start();
   Write_IIC_Byte(0x78);			//D/C#=0; R/W#=0
	IIC_Wait_Ack();	
   Write_IIC_Byte(0x40);			//write data
	IIC_Wait_Ack();	
   Write_IIC_Byte(IIC_Data);
	IIC_Wait_Ack();	
   IIC_Stop();
}

/**********************************************
// OLED写数据或命令 cmd=1:数据,cmd=0:命令
**********************************************/
void OLED_WR_Byte(unsigned dat,unsigned cmd)
{
	if(cmd)
		Write_IIC_Data(dat);
	else 
		Write_IIC_Command(dat);
}

因为IIC的通讯方式和SPI的通讯协议的不同,导致二者在驱动OLED的时候有差异,我们主要看下核心函数

void Write_IIC_Byte(unsigned char IIC_Byte)其实这个函数的实现也是数据和时钟脚在工作,高低交替,8bit数据在一位一位的传输。跟SPI驱动所不同的是,IIC写命令和写数据不再有DC脚和CS脚来控制,而是全由时钟脚和数据脚来控制,另外增加了数据传输的等待操作和增加了从地址质量操作,所以代码写法上相对更复杂些。传输速率上,SPI和比IIC的快,对IIC感兴趣的同学可以对照着手册自行研究,我这里就不深入了。

5.开始驱动OLED,使用STM32-CubeMX生成代码

关于stm32-cubeMX的操作步骤,我这里截了长图,大家可以看下,主要是配置GPIO口和SW下载调试口,时钟保持默认是哦给你内部时钟就可以了。

具体需要注意的事项我已经在图上做了标注,其他有疑问的地方大家可以自行查阅资料。

记得生成的代码,KEIL打开后,需要先全部rebuild一遍!!!

记得生成的代码,KEIL打开后,需要先全部rebuild一遍!!!

记得生成的代码,KEIL打开后,需要先全部rebuild一遍!!!

看下KEIL的注意事项

江涛带你玩0.96-OLED之实战stm32的RTC时钟(上)_第4张图片

按要求添加好自己的.h文件和.c文件,我这里添加两个文件。然后贴上代码

江涛带你玩0.96-OLED之实战stm32的RTC时钟(上)_第5张图片

 下面是 oled.h 

/***************************************
** OLED 头文件
** 作者:菜鸟江涛
** 时间:2020-03-17 
** CSDN: https://blog.csdn.net/u010898329
**************************************/
#ifndef __OLED_H
#define __OLED_H

// 由于会使用到GPIO口,所以引入相关的头文件
#include "main.h"

// 常量定义
#define u8         unsigned char  // 将unsigned char 定义简写
#define OLED_CMD   1  // 写命令
#define OLED_DATA  0  // 写数据

// 端口高低操纵定义
#define OLED_RST_H HAL_GPIO_WritePin(OLED_RST_GPIO_Port,OLED_RST_Pin,GPIO_PIN_SET)    // 复位脚高
#define OLED_RST_L HAL_GPIO_WritePin(OLED_RST_GPIO_Port,OLED_RST_Pin,GPIO_PIN_RESET)  // 复位脚低

// 对应有些OLED模块的丝印是 D0
#define OLED_SCK_H HAL_GPIO_WritePin(OLED_SCK_GPIO_Port,OLED_SCK_Pin,GPIO_PIN_SET)    // 时钟脚高
#define OLED_SCK_L HAL_GPIO_WritePin(OLED_SCK_GPIO_Port,OLED_SCK_Pin,GPIO_PIN_RESET)  // 时钟脚低

// 对应有些OLED模块的丝印是 D1
#define OLED_SDA_H HAL_GPIO_WritePin(OLED_SDA_GPIO_Port,OLED_SDA_Pin,GPIO_PIN_SET)    // 数据脚高
#define OLED_SDA_L HAL_GPIO_WritePin(OLED_SDA_GPIO_Port,OLED_SDA_Pin,GPIO_PIN_RESET)  // 数据脚低

#define OLED_CS_H HAL_GPIO_WritePin(OLED_CS_GPIO_Port,OLED_CS_Pin,GPIO_PIN_SET)  	    // 片选脚高
#define OLED_CS_L HAL_GPIO_WritePin(OLED_CS_GPIO_Port,OLED_CS_Pin,GPIO_PIN_RESET)     // 片选脚低

#define OLED_DC_H HAL_GPIO_WritePin(OLED_DC_GPIO_Port,OLED_DC_Pin,GPIO_PIN_SET)  			// 控制脚高
#define OLED_DC_L HAL_GPIO_WritePin(OLED_DC_GPIO_Port,OLED_DC_Pin,GPIO_PIN_RESET)  		// 控制脚低

// 函数定义
// OLED的基础操作函数
void OLED_Write_Byte(u8 data , u8 cmd);
void OLED_Init(void);
void OLED_On(void);
void OLED_Off(void);
void OLCD_Set_Pos(u8 x, u8 y);

// OLED的显示操作函数
void OLED_Clear(void);
void OLED_Show_Char(u8 x, u8 y , u8 chr);

#endif

接着是 oled.c

#include "oled.h"
#include "ascii.h"

/****************
** 向OLED写一个字节的数据或是命令
** cmd: 1=命令,0=数据
** data: 待写入的字节(命令/数据)
*******************/
void OLED_Write_Byte( u8 data , u8 cmd )
{
    u8 i=0;

    // 这里直接使用三目运算符,不使用if/else写法,看起来简洁些
    cmd ? OLED_DC_L : OLED_DC_H ; 				// 写命令DC输出低,写数据DC输出高

    // 模拟SPI数据传输写法
    OLED_CS_L; 														// 片选拉低,开始写数据或命令
    for(i=0; i<8; i++)											// 一字节有8bit,所以需要循环8次
    {
        OLED_SCK_L; 												// 时钟置低
        data&0x80 ? OLED_SDA_H:OLED_SDA_L; 	// 与位判断,最终数据脚写0或1就是根据这个
        OLED_SCK_H; 												// 时钟拉高
        data<<=1;       										// 数据或命令左移一位
    }
    OLED_CS_H; 														// 片选拉高,结束写数据或命令

    OLED_DC_H;														// 控制脚拉高,置成写数据状态
}


/*******************
** OLED初始化
********************/
void OLED_Init(void)
{
    // 制造一个先低后高的电平变换,达到复位的效果
    OLED_RST_L;
    HAL_Delay(500);
    OLED_RST_H;

    OLED_Write_Byte(0xAE,OLED_CMD);//--turn off oled panel
    OLED_Write_Byte(0x02,OLED_CMD);//---set low column address
    OLED_Write_Byte(0x10,OLED_CMD);//---set high column address
    OLED_Write_Byte(0x40,OLED_CMD);//--set start line address  Set Mapping RAM Display Start Line (0x00~0x3F)
    OLED_Write_Byte(0x81,OLED_CMD);//--set contrast control register
    OLED_Write_Byte(0xff,OLED_CMD);// Set SEG Output Current Brightness
    OLED_Write_Byte(0xA1,OLED_CMD);//--Set SEG/Column Mapping     0xa0左右反置 0xa1正常
    OLED_Write_Byte(0xC8,OLED_CMD);//Set COM/Row Scan Direction   0xc0上下反置 0xc8正常
    OLED_Write_Byte(0xA8,OLED_CMD);//--set multiplex ratio(1 to 64)
    OLED_Write_Byte(0x3f,OLED_CMD);//--1/64 duty
    OLED_Write_Byte(0xD3,OLED_CMD);//-set display offset	Shift Mapping RAM Counter (0x00~0x3F)
    OLED_Write_Byte(0x00,OLED_CMD);//-not offset
    OLED_Write_Byte(0xd5,OLED_CMD);//--set display clock divide ratio/oscillator frequency
    OLED_Write_Byte(0x80,OLED_CMD);//--set divide ratio, Set Clock as 100 Frames/Sec
    OLED_Write_Byte(0xD9,OLED_CMD);//--set pre-charge period
    OLED_Write_Byte(0xF1,OLED_CMD);//Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
    OLED_Write_Byte(0xDA,OLED_CMD);//--set com pins hardware configuration
    OLED_Write_Byte(0x12,OLED_CMD);
    OLED_Write_Byte(0xDB,OLED_CMD);//--set vcomh
    OLED_Write_Byte(0x40,OLED_CMD);//Set VCOM Deselect Level
    OLED_Write_Byte(0x20,OLED_CMD);//-Set Page Addressing Mode (0x00/0x01/0x02)
    OLED_Write_Byte(0x02,OLED_CMD);//
    OLED_Write_Byte(0x8D,OLED_CMD);//--set Charge Pump enable/disable
    OLED_Write_Byte(0x14,OLED_CMD);//--set(0x10) disable
    OLED_Write_Byte(0xA4,OLED_CMD);// Disable Entire Display On (0xa4/0xa5)
    OLED_Write_Byte(0xA6,OLED_CMD);// 开启正常显示 (0xA6 = 正常 / 0xA7 = 反显)
    OLED_Write_Byte(0xAF,OLED_CMD);//--turn on oled panel

}

/******************
**  开启OLED显示
*******************/
void OLED_On(void)
{
    OLED_Write_Byte(0X8D,OLED_CMD);  //SET DCDC命令
    OLED_Write_Byte(0X14,OLED_CMD);  //DCDC ON
    OLED_Write_Byte(0XAF,OLED_CMD);  //DISPLAY ON
}

/******************
**  关闭OLED显示
*******************/
void OLED_Off(void)
{
    OLED_Write_Byte(0X8D,OLED_CMD);  //SET DCDC命令
    OLED_Write_Byte(0X10,OLED_CMD);  //DCDC OFF
    OLED_Write_Byte(0XAE,OLED_CMD);  //DISPLAY OFF
}


/****************************
** 设置坐标点
** x: 行坐标 0~127
** y: 页坐标 0~7
*****************************/
void OLCD_Set_Pos(u8 x, u8 y)
{
    OLED_Write_Byte(0xb0+y,OLED_CMD);
    OLED_Write_Byte(((x&0xf0)>>4)|0x10,OLED_CMD);
    OLED_Write_Byte((x&0x0f)|0x01,OLED_CMD);
}

/********************
** OLED清屏操作
*********************/
void OLED_Clear(void)
{
    u8 i,n;
    for(i=0; i<8; i++)
    {
        OLED_Write_Byte (0xb0+i,OLED_CMD);    //设置页地址(0~7)
        OLED_Write_Byte (0x00,OLED_CMD);      //设置显示位置—列低地址
        OLED_Write_Byte (0x10,OLED_CMD);      //设置显示位置—列高地址
        for(n=0; n<128; n++)OLED_Write_Byte(0,OLED_DATA); // 每一列都置零
    }
}

/**************************************
** 显示一个字符
** x: 列坐标位置
** y: 页坐标位置
** chr: 待显示的字符
***************************************/
void OLED_Show_Char(u8 x, u8 y , u8 chr)
{
    u8 i=0 ,c=0;
    c = chr - ' '; // 从空字符串算起,得到偏移后的值

    OLCD_Set_Pos(x,y); // 设置显示位置
    for(i=0; i<6; i++)
    {
        OLED_Write_Byte(F6x8[c][i],OLED_DATA);
    }
}


还有字体文件 ascii.h

#ifndef __ASCII_H
#define __ASCII_H

//常用ASCII表
//偏移量32
//ASCII字符集
const unsigned char  F6x8[][6] =		
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,// sp
0x00, 0x00, 0x00, 0x2f, 0x00, 0x00,// !
0x00, 0x00, 0x07, 0x00, 0x07, 0x00,// "
0x00, 0x14, 0x7f, 0x14, 0x7f, 0x14,// #
0x00, 0x24, 0x2a, 0x7f, 0x2a, 0x12,// $
0x00, 0x62, 0x64, 0x08, 0x13, 0x23,// %
0x00, 0x36, 0x49, 0x55, 0x22, 0x50,// &
0x00, 0x00, 0x05, 0x03, 0x00, 0x00,// '
0x00, 0x00, 0x1c, 0x22, 0x41, 0x00,// (
0x00, 0x00, 0x41, 0x22, 0x1c, 0x00,// )
0x00, 0x14, 0x08, 0x3E, 0x08, 0x14,// *
0x00, 0x08, 0x08, 0x3E, 0x08, 0x08,// +
0x00, 0x00, 0x00, 0xA0, 0x60, 0x00,// ,
0x00, 0x08, 0x08, 0x08, 0x08, 0x08,// -
0x00, 0x00, 0x60, 0x60, 0x00, 0x00,// .
0x00, 0x20, 0x10, 0x08, 0x04, 0x02,// /
0x00, 0x3E, 0x51, 0x49, 0x45, 0x3E,// 0
0x00, 0x00, 0x42, 0x7F, 0x40, 0x00,// 1
0x00, 0x42, 0x61, 0x51, 0x49, 0x46,// 2
0x00, 0x21, 0x41, 0x45, 0x4B, 0x31,// 3
0x00, 0x18, 0x14, 0x12, 0x7F, 0x10,// 4
0x00, 0x27, 0x45, 0x45, 0x45, 0x39,// 5
0x00, 0x3C, 0x4A, 0x49, 0x49, 0x30,// 6
0x00, 0x01, 0x71, 0x09, 0x05, 0x03,// 7
0x00, 0x36, 0x49, 0x49, 0x49, 0x36,// 8
0x00, 0x06, 0x49, 0x49, 0x29, 0x1E,// 9
0x00, 0x00, 0x36, 0x36, 0x00, 0x00,// :
0x00, 0x00, 0x56, 0x36, 0x00, 0x00,// ;
0x00, 0x08, 0x14, 0x22, 0x41, 0x00,// <
0x00, 0x14, 0x14, 0x14, 0x14, 0x14,// =
0x00, 0x00, 0x41, 0x22, 0x14, 0x08,// >
0x00, 0x02, 0x01, 0x51, 0x09, 0x06,// ?
0x00, 0x32, 0x49, 0x59, 0x51, 0x3E,// @
0x00, 0x7C, 0x12, 0x11, 0x12, 0x7C,// A
0x00, 0x7F, 0x49, 0x49, 0x49, 0x36,// B
0x00, 0x3E, 0x41, 0x41, 0x41, 0x22,// C
0x00, 0x7F, 0x41, 0x41, 0x22, 0x1C,// D
0x00, 0x7F, 0x49, 0x49, 0x49, 0x41,// E
0x00, 0x7F, 0x09, 0x09, 0x09, 0x01,// F
0x00, 0x3E, 0x41, 0x49, 0x49, 0x7A,// G
0x00, 0x7F, 0x08, 0x08, 0x08, 0x7F,// H
0x00, 0x00, 0x41, 0x7F, 0x41, 0x00,// I
0x00, 0x20, 0x40, 0x41, 0x3F, 0x01,// J
0x00, 0x7F, 0x08, 0x14, 0x22, 0x41,// K
0x00, 0x7F, 0x40, 0x40, 0x40, 0x40,// L
0x00, 0x7F, 0x02, 0x0C, 0x02, 0x7F,// M
0x00, 0x7F, 0x04, 0x08, 0x10, 0x7F,// N
0x00, 0x3E, 0x41, 0x41, 0x41, 0x3E,// O
0x00, 0x7F, 0x09, 0x09, 0x09, 0x06,// P
0x00, 0x3E, 0x41, 0x51, 0x21, 0x5E,// Q
0x00, 0x7F, 0x09, 0x19, 0x29, 0x46,// R
0x00, 0x46, 0x49, 0x49, 0x49, 0x31,// S
0x00, 0x01, 0x01, 0x7F, 0x01, 0x01,// T
0x00, 0x3F, 0x40, 0x40, 0x40, 0x3F,// U
0x00, 0x1F, 0x20, 0x40, 0x20, 0x1F,// V
0x00, 0x3F, 0x40, 0x38, 0x40, 0x3F,// W
0x00, 0x63, 0x14, 0x08, 0x14, 0x63,// X
0x00, 0x07, 0x08, 0x70, 0x08, 0x07,// Y
0x00, 0x61, 0x51, 0x49, 0x45, 0x43,// Z
0x00, 0x00, 0x7F, 0x41, 0x41, 0x00,// [
0x00, 0x55, 0x2A, 0x55, 0x2A, 0x55,// 55
0x00, 0x00, 0x41, 0x41, 0x7F, 0x00,// ]
0x00, 0x04, 0x02, 0x01, 0x02, 0x04,// ^
0x00, 0x40, 0x40, 0x40, 0x40, 0x40,// _
0x00, 0x00, 0x01, 0x02, 0x04, 0x00,// '
0x00, 0x20, 0x54, 0x54, 0x54, 0x78,// a
0x00, 0x7F, 0x48, 0x44, 0x44, 0x38,// b
0x00, 0x38, 0x44, 0x44, 0x44, 0x20,// c
0x00, 0x38, 0x44, 0x44, 0x48, 0x7F,// d
0x00, 0x38, 0x54, 0x54, 0x54, 0x18,// e
0x00, 0x08, 0x7E, 0x09, 0x01, 0x02,// f
0x00, 0x18, 0xA4, 0xA4, 0xA4, 0x7C,// g
0x00, 0x7F, 0x08, 0x04, 0x04, 0x78,// h
0x00, 0x00, 0x44, 0x7D, 0x40, 0x00,// i
0x00, 0x40, 0x80, 0x84, 0x7D, 0x00,// j
0x00, 0x7F, 0x10, 0x28, 0x44, 0x00,// k
0x00, 0x00, 0x41, 0x7F, 0x40, 0x00,// l
0x00, 0x7C, 0x04, 0x18, 0x04, 0x78,// m
0x00, 0x7C, 0x08, 0x04, 0x04, 0x78,// n
0x00, 0x38, 0x44, 0x44, 0x44, 0x38,// o
0x00, 0xFC, 0x24, 0x24, 0x24, 0x18,// p
0x00, 0x18, 0x24, 0x24, 0x18, 0xFC,// q
0x00, 0x7C, 0x08, 0x04, 0x04, 0x08,// r
0x00, 0x48, 0x54, 0x54, 0x54, 0x20,// s
0x00, 0x04, 0x3F, 0x44, 0x40, 0x20,// t
0x00, 0x3C, 0x40, 0x40, 0x20, 0x7C,// u
0x00, 0x1C, 0x20, 0x40, 0x20, 0x1C,// v
0x00, 0x3C, 0x40, 0x30, 0x40, 0x3C,// w
0x00, 0x44, 0x28, 0x10, 0x28, 0x44,// x
0x00, 0x1C, 0xA0, 0xA0, 0xA0, 0x7C,// y
0x00, 0x44, 0x64, 0x54, 0x4C, 0x44,// z
0x14, 0x14, 0x14, 0x14, 0x14, 0x14,// horiz lines
};

#endif

字体有三种,后面会完善三种字体的显示函数,目前的代码我先仅仅展示如何写一个 6*8 的字体到 OLED上,主要用到了OLED_Show_Char(u8 x , u8 y , u8 chr);  看下main.c

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * 

© Copyright (c) 2020 STMicroelectronics. * All rights reserved.

* * This software component is licensed by ST under BSD 3-Clause license, * the "License"; You may not use this file except in compliance with the * License. You may obtain a copy of the License at: * opensource.org/licenses/BSD-3-Clause * ****************************************************************************** */ /* USER CODE END Header */ /* Includes ------------------------------------------------------------------*/ #include "main.h" #include "gpio.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include "oled.h" /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ /* USER CODE BEGIN PTD */ /* USER CODE END PTD */ /* Private define ------------------------------------------------------------*/ /* USER CODE BEGIN PD */ /* USER CODE END PD */ /* Private macro -------------------------------------------------------------*/ /* USER CODE BEGIN PM */ /* USER CODE END PM */ /* Private variables ---------------------------------------------------------*/ /* USER CODE BEGIN PV */ /* USER CODE END PV */ /* Private function prototypes -----------------------------------------------*/ void SystemClock_Config(void); /* USER CODE BEGIN PFP */ /* USER CODE END PFP */ /* Private user code ---------------------------------------------------------*/ /* USER CODE BEGIN 0 */ /* USER CODE END 0 */ /** * @brief The application entry point. * @retval int */ int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); /* USER CODE BEGIN 2 */ OLED_Init(); OLED_Clear(); OLED_Show_Char(0,0,'E'); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ } /** * @brief System Clock Configuration * @retval None */ void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; /** Initializes the CPU, AHB and APB busses clocks */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI; RCC_OscInitStruct.HSIState = RCC_HSI_ON; RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } /** Initializes the CPU, AHB and APB busses clocks */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK) { Error_Handler(); } } /* USER CODE BEGIN 4 */ /* USER CODE END 4 */ /** * @brief This function is executed in case of error occurrence. * @retval None */ void Error_Handler(void) { /* USER CODE BEGIN Error_Handler_Debug */ /* User can add his own implementation to report the HAL error return state */ /* USER CODE END Error_Handler_Debug */ } #ifdef USE_FULL_ASSERT /** * @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */ void assert_failed(uint8_t *file, uint32_t line) { /* USER CODE BEGIN 6 */ /* User can add his own implementation to report the file name and line number, tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* USER CODE END 6 */ } #endif /* USE_FULL_ASSERT */ /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

注意我自己代码放的位置

江涛带你玩0.96-OLED之实战stm32的RTC时钟(上)_第6张图片

看下显示效果,我手头的是黄蓝双色的屏幕。

江涛带你玩0.96-OLED之实战stm32的RTC时钟(上)_第7张图片

至此,屏幕的驱动就基本大功告成了, 后面我们接着学习更为复杂的显示函数,其实都是基于这个函数来改造的。

6.常规显示函数

6.0 字符显示拓展,显示三种不同大小的字符 

/**************************************
** 显示一个字符
** x: 列坐标位置
** y: 页坐标位置
** chr: 待显示的字符
***************************************/
void OLED_Show_Char(u8 x, u8 y , u8 chr , enum Font_Size size)
{
    u8 i=0  ,c=0;
    c = chr - ' '; // 从空字符串算起,得到偏移后的值

    switch(size)
    {
    case SMALL:
        OLCD_Set_Pos(x,y); // 设置显示位置
        for(i=0; i< size ; i++)
            OLED_Write_Byte(F6x8[c][i],OLED_DATA);
        break;
    case MEDIA:
        OLCD_Set_Pos(x,y); // 设置显示位置
        for(i=0; i< 6 ; i++)
            OLED_Write_Byte(F12X6[c][i],OLED_DATA);
        OLCD_Set_Pos(x,y+1); // 设置显示位置
        for(i=6; i< 12 ; i++)
            OLED_Write_Byte(F12X6[c][i],OLED_DATA);
        break;
    case BIG:
        OLCD_Set_Pos(x,y); // 设置显示位置
        for(i=0; i< 8 ; i++)
            OLED_Write_Byte(F16x8[c][i],OLED_DATA);
        OLCD_Set_Pos(x,y+1); // 设置显示位置
        for(i=8; i< 16 ; i++)
            OLED_Write_Byte(F16x8[c][i],OLED_DATA);
        break;
    }
}

这里我准备了三种字体大小,定义了一个字体的枚举类,使用这个函数的时候需要传入枚举值,例如

	OLED_Show_Char(0,0,'A',SMALL);
	OLED_Show_Char(12,0,'A',MEDIA);
	OLED_Show_Char(24,0,'A',BIG);
	OLED_Show_String(0,2,"123ABC",SMALL);
	OLED_Show_String(0,3,"123ABC",MEDIA);
	OLED_Show_String(0,5,"123ABC",BIG);

6.1 字符串显示函数

/****************************************
** 显示字符串
** x: 起始列坐标
** y: 起始页
** chr: 待显示的字符串
*****************************************/
void OLED_Show_String(u8 x, u8 y , u8 *chr)
{
    u8 i = 0;
    while(chr[i] != '\0') {
        OLED_Show_Char(x,y,chr[i]);
        x+=6; // 字体宽高 6*8
        if(x>120)    // 如果大于了极限值 128
        {
            x = 0 ;  //  列从零开始
            y += 1;  //  另起一页
        }
        i ++ ;    // 循环继续,直到字符串都写完了为止
    }
}

注意,上面显示的字符串的宽高是6*8 ,而屏幕的像素点是128*64 。 上面有个大于120的判断,是因为如果启示横坐标是120,那么整个字体显示完整就到了到 126  , 如果继续执行就会出现 126+6 = 132 , 超过了最大限制,会出现重叠,所以会以120作为判断标准。后面会将此处的设置为动态判断,因为肯定会出现宽度不是6个像素的字体,这个留着到后面分析

6.2 改造后的字符串显示函数,支持传入字体枚举值

/****************************************
** 显示字符串
** x: 起始列坐标
** y: 起始页
** chr: 待显示的字符串
*****************************************/
void OLED_Show_String(u8 x, u8 y , u8 *chr , enum Font_Size size)
{
    u8 i = 0;
    u8 page =  (size>8 ? size >> 3 : 1 );  // 字体大小决定翻几页
    u8 len = (size>8 ? size >>1 : size ) ;   // 字体宽度,最后结果对应了字体表中的每行长度索引
    u8 limit = MAX_LEN/len - 1 ;
    while(chr[i] != '\0') {
        OLED_Show_Char(x,y,chr[i],size);
        x+=len; // 字体宽高 6*8
        if(x>(len*limit-1))    // 如果大于了极限值 128
        {
            x = 0 ;  //  列从零开始
            y += page;  //  另起一页
        }
        i ++ ;    // 循环继续,直到字符串都写完了为止
    }
}

这里有个常量定义,MAX_LEN = 128,定义的是屏幕宽度的最大像素点。

如果字体高度大于8像素,肯定是要翻页的,我这里做了判断,page = size/8 或者 page=1(至少一页) ,limit为一行能最多能显示的字符个数,len*limit -1 就是最后一个字符开始的位置,跟上面的字符显示函数做了对应了,这里改成了动态了。

看下三个的显示效果,我都显示在了一个屏幕上了

江涛带你玩0.96-OLED之实战stm32的RTC时钟(上)_第8张图片

 如果大家有其他的字体需要,可以自行去使用取模软件生成ASCII码,放上ASCII如下

 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~

6.2 自定义数字显示

自定义数字显示我们需要用到取模软件,取模软件一并在分享文件中有。软件取模时候的配置如下

江涛带你玩0.96-OLED之实战stm32的RTC时钟(上)_第9张图片

这里的取模方式使用 逐列 或者 列行 都可以,只要是右侧那个取模演示中是从上往下填充的方式都可以实现。具体是什么原因呢,还得看SSD1306的数据手册。

江涛带你玩0.96-OLED之实战stm32的RTC时钟(上)_第10张图片

自定义的数字,我这里使用三种字体大小,分别是  宽x高:12*24,16*32,20*40三种字体,下面贴上我的字体头文件 number.h

#ifndef __NUMBER_H
#define __NUMBER_H

//宽x高:12*24
const unsigned char N24x12[][12]={	  
	

{0x00,0x00,0xC0,0xE0,0x30,0x10,0x10,0x30,0xE0,0xC0,0x00,0x00},
{0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00},
{0x00,0x00,0x03,0x07,0x0C,0x08,0x08,0x0C,0x07,0x03,0x00,0x00},/*"0",0*/

{0x00,0x00,0x20,0x20,0x20,0xE0,0xF0,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x08,0x08,0x08,0x0F,0x0F,0x08,0x08,0x08,0x00,0x00},/*"1",1*/

{0x00,0xC0,0xA0,0x10,0x10,0x10,0x10,0x30,0xE0,0xC0,0x00,0x00},
{0x00,0x01,0x01,0x00,0x80,0x60,0x30,0x18,0x0F,0x03,0x00,0x00},
{0x00,0x0C,0x0E,0x09,0x08,0x08,0x08,0x08,0x08,0x0F,0x00,0x00},/*"2",2*/

{0x00,0xE0,0xE0,0x10,0x10,0x10,0x30,0xE0,0xC0,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x08,0x08,0x0C,0x17,0xF3,0xC0,0x00,0x00},
{0x00,0x07,0x07,0x08,0x08,0x08,0x08,0x0C,0x07,0x03,0x00,0x00},/*"3",3*/

{0x00,0x00,0x00,0x00,0x00,0x80,0x40,0xF0,0xF0,0x00,0x00,0x00},
{0x00,0x60,0x50,0x4C,0x42,0x41,0x40,0xFF,0xFF,0x40,0x40,0x40},
{0x00,0x00,0x00,0x00,0x00,0x08,0x08,0x0F,0x0F,0x08,0x08,0x00},/*"4",4*/

{0x00,0x00,0xF0,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x00,0x00},
{0x00,0x80,0x9F,0x08,0x04,0x04,0x04,0x0C,0xF8,0xF0,0x00,0x00},
{0x00,0x03,0x05,0x08,0x08,0x08,0x08,0x0E,0x07,0x01,0x00,0x00},/*"5",5*/

{0x00,0x00,0xC0,0xE0,0x20,0x10,0x10,0x10,0x70,0x60,0x00,0x00},
{0x00,0xFE,0xFF,0x10,0x08,0x04,0x04,0x04,0x0C,0xF8,0xF0,0x00},
{0x00,0x00,0x03,0x06,0x0C,0x08,0x08,0x08,0x04,0x07,0x01,0x00},/*"6",6*/

{0x00,0x00,0xE0,0x30,0x10,0x10,0x10,0x10,0x90,0x70,0x30,0x00},
{0x00,0x00,0x00,0x00,0x00,0x80,0xF0,0x0C,0x03,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x0F,0x0F,0x00,0x00,0x00,0x00,0x00},/*"7",7*/

{0x00,0xC0,0xE0,0x30,0x10,0x10,0x10,0x10,0x30,0xE0,0xC0,0x00},
{0x00,0xC1,0xE7,0x36,0x0C,0x08,0x18,0x18,0x34,0xE7,0xC1,0x00},
{0x00,0x03,0x07,0x04,0x08,0x08,0x08,0x08,0x0C,0x07,0x03,0x00},/*"8",8*/

{0x00,0x80,0xE0,0x60,0x10,0x10,0x10,0x10,0x60,0xC0,0x00,0x00},
{0x00,0x0F,0x1F,0x30,0x20,0x20,0x20,0x10,0x88,0xFF,0x7F,0x00},
{0x00,0x00,0x06,0x0E,0x08,0x08,0x08,0x04,0x07,0x01,0x00,0x00},/*"9",9*/

{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x0E,0x0E,0x0E,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x0E,0x0E,0x0E,0x00,0x00,0x00,0x00},/*":",10*/

{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*" ",11*/

};

//宽x高:16*32
const unsigned char N32x16[][16]={	  
{0x00,0x00,0x00,0x80,0xC0,0xE0,0x70,0x30,0x10,0x10,0x70,0xE0,0xC0,0x80,0x00,0x00},
{0x00,0x00,0xFC,0xFF,0xFF,0x03,0x00,0x00,0x00,0x00,0x00,0x01,0xFF,0xFF,0xFC,0x00},
{0x00,0x00,0x0F,0x7F,0xFF,0xF0,0x80,0x00,0x00,0x00,0x80,0xE0,0xFF,0x7F,0x0F,0x00},
{0x00,0x00,0x00,0x00,0x00,0x01,0x03,0x03,0x02,0x02,0x03,0x01,0x00,0x00,0x00,0x00},/*"0",0*/

{0x00,0x00,0x00,0x40,0x40,0x40,0x40,0xE0,0xF0,0xF0,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x02,0x02,0x02,0x03,0x03,0x03,0x03,0x03,0x02,0x02,0x02,0x00,0x00},/*"1",1*/

{0x00,0x00,0x80,0xC0,0x60,0x30,0x10,0x10,0x10,0x10,0x30,0x70,0xE0,0xE0,0x80,0x00},
{0x00,0x00,0x07,0x07,0x06,0x00,0x00,0x00,0x00,0x80,0xC0,0xF0,0x7F,0x3F,0x0F,0x00},
{0x00,0x00,0x80,0xC0,0x60,0x30,0x1C,0x0E,0x07,0x03,0x01,0x00,0x80,0xF0,0xF0,0x00},
{0x00,0x00,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x00,0x00},/*"2",2*/

{0x00,0x00,0xC0,0xE0,0xE0,0x30,0x10,0x10,0x10,0x30,0x70,0xE0,0xC0,0x80,0x00,0x00},
{0x00,0x00,0x03,0x03,0x03,0x00,0x40,0x40,0x40,0xE0,0xF0,0xBF,0x9F,0x0F,0x00,0x00},
{0x00,0x00,0xF0,0xF0,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x81,0xFF,0xFF,0x3C,0x00},
{0x00,0x00,0x00,0x01,0x01,0x03,0x02,0x02,0x02,0x02,0x03,0x01,0x01,0x00,0x00,0x00},/*"3",3*/

{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0xF0,0xF0,0xF0,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x80,0xE0,0x78,0x1C,0x07,0x03,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00},
{0x00,0x0C,0x0F,0x0B,0x09,0x08,0x08,0x08,0x08,0xFF,0xFF,0xFF,0x08,0x08,0x08,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x02,0x02,0x03,0x03,0x03,0x02,0x02,0x02,0x00},/*"4",4*/

{0x00,0x00,0x00,0xC0,0xF0,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x00},
{0x00,0x00,0x00,0xFF,0xFF,0x60,0x30,0x10,0x10,0x10,0x30,0x70,0xE0,0xC0,0x00,0x00},
{0x00,0x00,0xF0,0xF0,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0xFF,0xFF,0x3F,0x00},
{0x00,0x00,0x00,0x01,0x01,0x03,0x02,0x02,0x02,0x02,0x03,0x01,0x01,0x00,0x00,0x00},/*"5",5*/

{0x00,0x00,0x00,0x00,0xC0,0xE0,0x60,0x30,0x10,0x10,0x10,0xF0,0xE0,0xE0,0x00,0x00},
{0x00,0x00,0xF8,0xFF,0xFF,0xE1,0x30,0x30,0x10,0x10,0x30,0x70,0xE0,0xC0,0x00,0x00},
{0x00,0x00,0x1F,0x7F,0xFF,0xE0,0x80,0x00,0x00,0x00,0x00,0x80,0xFF,0xFF,0x3F,0x00},
{0x00,0x00,0x00,0x00,0x01,0x01,0x03,0x03,0x02,0x02,0x03,0x03,0x01,0x00,0x00,0x00},/*"6",6*/

{0x00,0x00,0x80,0xF0,0xF0,0x30,0x30,0x30,0x30,0x30,0x30,0xB0,0xF0,0x70,0x30,0x00},
{0x00,0x00,0x03,0x03,0x00,0x00,0x00,0x00,0xC0,0xF8,0x3E,0x07,0x01,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0xF0,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"7",7*/

{0x00,0x00,0x80,0xC0,0xE0,0x70,0x30,0x10,0x10,0x10,0x30,0x70,0xE0,0xC0,0x80,0x00},
{0x00,0x00,0x0F,0x9F,0xBF,0xFC,0x70,0x70,0xE0,0xC0,0xE0,0xB0,0x9F,0x1F,0x07,0x00},
{0x00,0x7C,0xFF,0xFF,0x83,0x00,0x00,0x00,0x00,0x01,0x03,0x87,0xFF,0xFF,0x7C,0x00},
{0x00,0x00,0x00,0x01,0x01,0x03,0x02,0x02,0x02,0x02,0x03,0x01,0x01,0x00,0x00,0x00},/*"8",8*/

{0x00,0x00,0xC0,0xE0,0xE0,0x30,0x10,0x10,0x10,0x10,0x30,0xE0,0xC0,0x80,0x00,0x00},
{0x00,0x7F,0xFF,0xFF,0xC0,0x00,0x00,0x00,0x00,0x00,0x80,0xC0,0xFF,0xFF,0xFE,0x00},
{0x00,0x00,0xC0,0xC1,0xC3,0x03,0x02,0x02,0x02,0x83,0xC1,0xF9,0x7F,0x1F,0x07,0x00},
{0x00,0x00,0x01,0x01,0x03,0x02,0x02,0x02,0x03,0x03,0x01,0x00,0x00,0x00,0x00,0x00},/*"9",9*/

{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x78,0x78,0x78,0x30,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xC0,0xC0,0xC0,0x80,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x03,0x03,0x03,0x01,0x00,0x00,0x00,0x00,0x00},/*":",10*/

{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*" ",11*/

};

const unsigned char N40x20[][20]={
{0x00,0x00,0x00,0x00,0x80,0xC0,0xE0,0xE0,0x70,0x30,0x10,0x10,0x30,0xE0,0xE0,0xC0,0x80,0x00,0x00,0x00},
{0x00,0x00,0xF0,0xFE,0xFF,0xFF,0x0F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0xFF,0xFF,0xFE,0xF0,0x00},
{0x00,0x00,0x7F,0xFF,0xFF,0xFF,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0x7F,0x00},
{0x00,0x00,0x00,0x03,0x0F,0x1F,0x3F,0x38,0x60,0x60,0x40,0x40,0x60,0x38,0x3F,0x1F,0x0F,0x03,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"0",0*/

{0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x80,0x80,0xC0,0xE0,0xF0,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x40,0x40,0x40,0x60,0x7F,0x7F,0x7F,0x7F,0x60,0x40,0x40,0x40,0x40,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"1",1*/

{0x00,0x00,0x00,0xC0,0xC0,0x60,0x20,0x30,0x10,0x10,0x10,0x30,0x30,0xF0,0xE0,0xE0,0xC0,0x00,0x00,0x00},
{0x00,0x00,0x1F,0x1F,0x1F,0x1C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0xFF,0xFF,0x7F,0x1F,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xC0,0xE0,0x70,0x38,0x1E,0x0F,0x07,0x03,0x01,0x00,0x00,0x00,0x00},
{0x00,0x00,0x70,0x78,0x7C,0x6F,0x67,0x63,0x60,0x60,0x60,0x60,0x60,0x60,0x70,0x78,0x7E,0x0E,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"2",2*/

{0x00,0x00,0x80,0xC0,0xE0,0x60,0x30,0x10,0x10,0x10,0x30,0x30,0xF0,0xE0,0xE0,0xC0,0x00,0x00,0x00,0x00},
{0x00,0x00,0x07,0x0F,0x0F,0x0E,0x00,0x00,0x00,0x00,0x80,0x80,0xE0,0xFF,0x7F,0x3F,0x1F,0x00,0x00,0x00},
{0x00,0x00,0x00,0x80,0x80,0x80,0x00,0x01,0x01,0x01,0x01,0x03,0x07,0x0E,0xFE,0xFC,0xF8,0xE0,0x00,0x00},
{0x00,0x00,0x0F,0x1F,0x3F,0x37,0x60,0x40,0x40,0x40,0x40,0x60,0x70,0x38,0x3F,0x1F,0x0F,0x03,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"3",3*/

{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0xE0,0xF0,0xF0,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0xE0,0x78,0x1C,0x0F,0x03,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00},
{0x00,0x40,0x60,0x78,0x5C,0x4F,0x43,0x40,0x40,0x40,0x40,0x40,0xFF,0xFF,0xFF,0x40,0x40,0x40,0x40,0x40},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x40,0x40,0x40,0x7F,0x7F,0x7F,0x60,0x40,0x40,0x40,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"4",4*/

{0x00,0x00,0x00,0x00,0xF0,0xF0,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x00,0x00},
{0x00,0x00,0x00,0xF8,0xFF,0x07,0x80,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0x80,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x87,0x87,0x87,0x01,0x01,0x00,0x00,0x00,0x00,0x01,0x07,0xFF,0xFF,0xFE,0xF8,0x00,0x00},
{0x00,0x00,0x07,0x1F,0x3F,0x23,0x60,0x40,0x40,0x40,0x40,0x60,0x70,0x3C,0x3F,0x1F,0x0F,0x03,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"5",5*/

{0x00,0x00,0x00,0x00,0x00,0x80,0xC0,0xE0,0x60,0x30,0x10,0x10,0x10,0x10,0xF0,0xF0,0xE0,0xC0,0x00,0x00},
{0x00,0x00,0xE0,0xFC,0xFF,0xFF,0x07,0x80,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC1,0x81,0x81,0x01,0x00,0x00},
{0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x07,0x03,0x01,0x00,0x00,0x00,0x01,0x01,0x07,0xFF,0xFF,0xFE,0xF8,0x00},
{0x00,0x00,0x00,0x07,0x0F,0x1F,0x3E,0x38,0x60,0x60,0x40,0x40,0x40,0x60,0x38,0x3F,0x1F,0x0F,0x03,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"6",6*/

{0x00,0x00,0x00,0x80,0xF0,0xF0,0x70,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0xF0,0xF0,0x70,0x30,0x00},
{0x00,0x00,0x00,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0xF0,0x3C,0x0F,0x07,0x01,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF0,0xFC,0xFF,0x0F,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3F,0x7F,0x7F,0x7F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"7",7*/

{0x00,0x00,0x00,0x00,0xC0,0xE0,0xE0,0xF0,0x30,0x10,0x10,0x10,0x10,0x30,0xF0,0xE0,0xE0,0xC0,0x00,0x00},
{0x00,0x00,0x00,0x1F,0x7F,0xFF,0xFF,0xF0,0xE0,0xC0,0x80,0x80,0x00,0x80,0xE0,0xFF,0x7F,0x3F,0x1F,0x00},
{0x00,0x00,0xE0,0xF0,0xF8,0xFC,0x1E,0x07,0x03,0x03,0x07,0x07,0x0F,0x1F,0x3F,0xFC,0xFC,0xF0,0xE0,0x00},
{0x00,0x00,0x07,0x0F,0x1F,0x3F,0x38,0x60,0x60,0x40,0x40,0x40,0x40,0x60,0x38,0x3F,0x1F,0x0F,0x07,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"8",8*/

{0x00,0x00,0x00,0x80,0xC0,0xE0,0xE0,0x70,0x30,0x10,0x10,0x10,0x30,0x70,0xE0,0xC0,0x80,0x00,0x00,0x00},
{0x00,0x00,0xFE,0xFF,0xFF,0xFF,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x81,0xFF,0xFF,0xFF,0xF8,0x00},
{0x00,0x00,0x01,0x07,0x0F,0x0F,0x1F,0x1C,0x1C,0x18,0x18,0x18,0x0C,0x0E,0xC7,0xFF,0xFF,0xFF,0x1F,0x00},
{0x00,0x00,0x00,0x1C,0x3C,0x7C,0x7C,0x40,0x40,0x40,0x60,0x60,0x38,0x3C,0x1F,0x0F,0x07,0x01,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"9",9*/

{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0xE0,0xE0,0xE0,0xE0,0xC0,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x03,0x03,0x03,0x03,0x01,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x38,0x7C,0x7C,0x7C,0x7C,0x38,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*":",10*/

{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*" ",11*/
};

#endif

这个是用取模软件自己生成的,大家也可以自己动手生成自己的一份。看下数字显示函数。

/*********************************************************
** 自定义数字显示函数
** x: 列起始位置 0~(127-len) 取值从0到最后一个字符的起始位置
** y: 页起始位置 0~7
** num: 待显示的数字 0~9
** size: 字体大小枚举值
*********************************************************/
void OLED_Show_Num(u8 x, u8 y , u8 num , enum Num_Size size)
{
    u8 i,j ;
    u8 len = size >> 1 ;  // 字体宽度,这里直接处以2,因为字体宽度正好是高度的一半
    u8 page = size >> 3 ; // 这里处以8直接用移位操作,高度大于8就要翻页

    for(i = 0 ; i < page ; i ++)
    {
        OLCD_Set_Pos(x,y+i); // 设置显示位置
        switch(size)
        {
        case SMALL_NUM:
            for(j=0; j

下面是oled.h中的枚举定义。

// 定义字体枚举,值为字体的高度
enum Font_Size {SMALL = 6 , MEDIA = 12 , BIG = 16};
// 定义数字枚举,值为数字的高度
enum Num_Size {SMALL_NUM = 24 , MEDIA_NUM = 32 , BIG_NUM = 40};

看下自定义数字的显示效果

江涛带你玩0.96-OLED之实战stm32的RTC时钟(上)_第11张图片

如果想要其他风格或者大小不同的字体,大家可以自己去取模生成。 

6.3 自定义汉字显示

/*********************************************************
** 自定义汉字显示函数
** x: 列起始位置 0~(127-len) 取值从0到最后一个字符的起始位置
** y: 页起始位置 0~7
** index: 待显示的汉字序号
** size: 字体大小枚举值
*********************************************************/
void OLED_Show_Chinese(u8 x, u8 y , u8 index , enum Zh_Size size)
{
    u8 i,j ;
    u8 len = size  ;  // 字体宽度,等于字体高度
    u8 page = size >> 3 ; // 这里处以8直接用移位操作,高度大于8就要翻页

    for(i = 0 ; i < page ; i ++)
    {
        OLCD_Set_Pos(x,y+i); // 设置显示位置
        switch(size)
        {
        case ZH16:
            for(j=0; j

自定义汉字的显示其实和上面的显示数字类似,不过是数字的宽高是1:2,汉字的宽高是1:1,就只有这里有区别而已,我只取16*16的字体和32*32的字体给大家演示下。

6.4 自定义图标显示。

放上我的图标头文件

#ifndef __ICON_H
#define __ICON_H

//宽x高:16*16
const unsigned char ICON16[][16]={	  
	
{0xC0,0xF0,0x10,0xD0,0xD0,0xD0,0x10,0xD0,0xD0,0xD0,0x10,0xD0,0xD0,0xD0,0xF0,0x00},
{0x07,0x1F,0x10,0x17,0x17,0x17,0x10,0x17,0x17,0x17,0x10,0x17,0x17,0x17,0x1F,0x00},/*"电池",0*/

{0x08,0x18,0x28,0x48,0x88,0x48,0x28,0x18,0x08,0x00,0x00,0x00,0xE0,0x00,0xF8,0x00},
{0x00,0x00,0x40,0x40,0x7F,0x40,0x40,0x00,0x7C,0x00,0x7F,0x00,0x7F,0x00,0x7F,0x00},/*"信号",1*/

{0x00,0x00,0x00,0x08,0x10,0x20,0x40,0xFE,0x86,0xC4,0x68,0x30,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x10,0x08,0x04,0x02,0x7F,0x61,0x23,0x16,0x0C,0x00,0x00,0x00,0x00},/*"蓝牙",2*/

};

#endif

这些图标是我在取模软件中用图像模式,用鼠标一个一个点画出来的,可能有些丑,大家其实也可以自己画。至于图标的显示函数,把上面的字符显示的函数改一改就可以用了

** 自定义图标显示
** x: 列起始位置 0~(127-len) 取值从0到最后一个字符的起始位置
** y: 页起始位置 0~7
** index: 待显示的图标序号,图标大小为16*16
*********************************************************/
void OLED_Show_Icon(u8 x, u8 y , u8 index)
{
    u8 i,j ;
    u8 len = 16  ;
    u8 page = 2 ;

    for(i = 0 ; i < page ; i ++)
    {
        OLCD_Set_Pos(x,y+i); // 设置显示位置
        for(j=0; j

看下效果 

江涛带你玩0.96-OLED之实战stm32的RTC时钟(上)_第12张图片

7.功能显示函数

7.1 开关屏幕

/******************
**  开启OLED显示
*******************/
void OLED_On(void)
{
    OLED_Write_Byte(0X8D,OLED_CMD);  //SET DCDC命令
    OLED_Write_Byte(0X14,OLED_CMD);  //DCDC ON
    OLED_Write_Byte(0XAF,OLED_CMD);  //DISPLAY ON
}

/******************
**  关闭OLED显示
*******************/
void OLED_Off(void)
{
    OLED_Write_Byte(0X8D,OLED_CMD);  //SET DCDC命令
    OLED_Write_Byte(0X10,OLED_CMD);  //DCDC OFF
    OLED_Write_Byte(0XAE,OLED_CMD);  //DISPLAY OFF
}

 开关屏幕其实是根据SSD1306手册中的写命令来,这两个函数可以直接拷贝使用。

7.2 正常显示和反显

  • 全屏幕反显,使用一条写命令语句就可以了
OLED_Write_Byte(0xA6,OLED_CMD);// 开启正常显示 (0xA6 = 正常 / 0xA7 = 反显)
  • 字符本身反显,其实也很简单,将待写入的数据取反操作后写入
/*********************************************************
** 自定义汉字显示函数
** x: 列起始位置 0~(127-len) 取值从0到最后一个字符的起始位置
** y: 页起始位置 0~7
** index: 待显示的汉字序号
** size: 字体大小枚举值
** reverse: 是否反显 1=是,0=否
*********************************************************/
void OLED_Show_Chinese(u8 x, u8 y , u8 index , enum Zh_Size size , u8 reverse)
{
    u8 i,j ;
    u8 len = size  ;  // 字体宽度,等于字体高度
    u8 page = size >> 3 ; // 这里处以8直接用移位操作,高度大于8就要翻页

    for(i = 0 ; i < page ; i ++)
    {
        OLCD_Set_Pos(x,y+i); // 设置显示位置
        switch(size)
        {
        case ZH16:
            for(j=0; j

我改造了下汉字显示的函数,增加了是否反显的标志位 ,用来控制是否反显。

7.3 屏幕滚动

/************************************************************
** 滚动显示某一页
** start: 开始页 0 - 7
** end  : 结束页 0 - 7
** dirct:方向枚举,left向左,right向右
**************************************************************/
void OLED_Scroll_Display(u8 start,u8 end,enum DIRECTION dirct)
{
	if(start > 7 || end > 7) return; // 页码超出
	OLED_Write_Byte(0x2E ,OLED_CMD); // 关闭滚动
	OLED_Write_Byte(dirct,OLED_CMD); // 向左/右滚动
	OLED_Write_Byte(0x00 ,OLED_CMD); // 空制令
	OLED_Write_Byte(start,OLED_CMD); // 起始页
	OLED_Write_Byte(0x0F ,OLED_CMD); // 滚动间隔,0=不滚动,值越大,滚动越快
	OLED_Write_Byte(end  ,OLED_CMD); // 结束页
	OLED_Write_Byte(0x00 ,OLED_CMD); // 空指令
	OLED_Write_Byte(0xFF ,OLED_CMD); // 空指令,加两条空指令,不然不会生效
	OLED_Write_Byte(0x2F ,OLED_CMD); // 开启滚动
}

方向枚举的定义如下

// 滚动方向枚举
enum DIRECTION { LEFT = 0x27 , RIGHT = 0x26};

 看下演示效果。

 

如果有感兴趣的可以加群交流,以上的代码我会在群里分享。QQ群:1087285029

或者扫码

江涛带你玩0.96-OLED之实战stm32的RTC时钟(上)_第13张图片

 

 

 

你可能感兴趣的:(STM32)