江科大OLED教程:01-快速上手(上集)_哔哩哔哩_bilibili
01 0.96寸OLED显示屏原理
1.1 0.96寸OLED显示屏简介
适用器件:
0.96寸OLED显示屏(驱动芯片:SSD1306 / SSD1315)
1.3寸OLED显示屏(驱动芯片:SH1106)
4针脚I2C接口
7针脚SPI接口
128*64像素
像素颜色不限
1.2 OLED模块结构
OLED模块实际上是由一块真正的屏幕和一块PCB底板组成的, 这个PCB底板上就是一些最小系统电路,加这个底板主要是方便插接电路,核心的东西其实都在玻璃屏幕上。单独的屏幕加软排线,一般称作裸屏。
黑色胶带下面就是驱动芯片SSD1306, 这款屏幕是128*64的屏幕,需要单独装一个专门负责扫描驱动的芯片,比如这里的SSD1306,它的任务就是扫描刷新,让这个屏幕始终都呈现出内容。这个芯片的接线:中间密密麻麻的引脚直接接到了点阵的128列,左边和右边,一边32个引脚,分别接到点阵的64行。下面还有一大排引脚通过软排线引出来,这些是通信引脚。我们想要显示什么就通过这些通信引脚,驱动芯片受到数据后,就会刷新显示到点阵上。下图是驱动芯片引脚位置图:
1.3 SSD1306简介
SSD1315和SSD1306是兼容的,区别是SSD1315没有并口,我们这个模块也没有用到并口,所以使用方法是一样的。
SSD1306是一款OLED/PLED点阵显示屏的控制器,可以嵌入在屏幕中,用于执行接收数据、显示存储、扫描刷新等任务
OLED是有机发光二极管,PLED是高分子发光二极管。显示的大概流程就是:第一步,通过引出来的通信引脚,把我们想显示的内容发给驱动芯片。第二步:驱动芯片受到数据,然后把它存起来,也就是芯片内部,自带的有RAM显示存储器,存储的是屏幕显示的内容。第三步:芯片内部有时钟和扫描电路,根据显示存储器的数据,它会自动对应刷新到屏幕。这样我们就把想显示的内容刷新到屏幕上了。这些步骤,我们只需要关心,如何通过通信协议,把数据写入到屏幕里的显示存储器即可。至于驱动芯片如何扫描刷新,不需要我们关心。
驱动接口:128个SEG引脚和64个COM引脚,对应128*64像素点阵显示屏
内置显示存储器(GDDRAM):128*64 bit (128*8 Byte)SRAM
供电:VDD=1.65~3.3V(IC 逻辑),VCC=7~15V(面板驱动)
VDD主要提供给IC逻辑电路,也就是内部的逻辑电路,存储器等;VCC主要用于点亮屏幕上的每隔像素点。使用SSD1306必须要提供这两个电源,屏幕厂商为了方便我们使用,在屏幕内部已经集成了一个升压电路,我们只需要开启这个升压电路,面板的VCC(7~15V)驱动电压就有了。这个VDD供电最大也不支持5V,但模块上可以接5V,这是因为,模块上有一个降压芯片,可以给5V,它可以降到3.3V给屏幕逻辑部分供电。这个降压电路是位于PCB底板上的,如果单独买这个裸屏,它的VCC是不能接5V的。
通信接口:8位6800/8080并行接口,3/4线SPI接口,I2C接口。
这些通信接口只能选择一种使用。6800/8080是并行传输的接口,也就是一次性发送一个字节,8个数据位,我们用的比较少,主要是并行传输的线非常多,接线很麻烦,一般并行传输用于数据量非常大的地方,比如单片机内部的数据总线,或者分辨率非常大的屏幕或摄像头等。对于我们这个模块,一般使用3/4线SPI接口,I2C接口,这两个都是串行传输的接口,也就是一次发送1个位,发送8次,才能发完一个字节。
1.4 SSD1306框图及引脚定义
最左边这里是MCU通信接口,所引出来的线,就是这个芯片所支持通信协议的各个引脚。我们想要发送给驱动芯片的各种数据和命令就是通过这个通信接口来接收,通信都是以字节为单位的,接收到一个字节之后呢,首先要进行分流,这个芯片的规定是,你发给它一个字节后,可以指定这个字节是作为【数据】,还是作为【命令】。命令会进入到下面的命令译码器(Command Decoder),用于控制内部电路的执行,比如开启显示,关闭显示,设置对比度,设置显示起始位置等等。芯片内部定义有一个命令表,参照命令表,就能知道这个命令是什么功能了。数据会进入到右边图形显示数据RAM存储器(Graphic Dispaly Data RAM(GDDRAM)),定义数据的字节,决定的就是屏幕上显示什么内容了,只要我们把想显示的内容,写入到GDDRAM里的相应位置,那么在屏幕上,就会显示对应的内容。所以,我们操控这个显示屏,只需要关注,怎么把这个数据写入到GDDRAM的正确位置就行了。显示控制器(Display Controller),它的任务就是读取GDDRAM,把里面的内容扫描刷新到屏幕上,所以显示控制器的输出,就是显示屏点阵的驱动器了。中间是段驱动器(Segment Driver),输出引脚是SEG0~SEG127,接到点阵的128列。上下两部分是公共端驱动器,输出引脚是COM0~COM63,接到点阵的64行,这部分电路,屏幕厂商已经接好了。下面还有两部分内容,一个是振荡器(Oscillator),用于提供刷新屏幕的时钟,CL(Clock)是时钟输入脚,CLS(Clock Select)是时钟源选择脚,这个芯片内部自带时钟,但也可以通过CLS选择,CLS接高电平,选择内部时钟,CL引脚不用,CLS接低电平,选择外部时钟,CL加一个外部时钟源。那我们一般都是使用内部时钟,使用简单。最后是电流控制和电压控制,VCOMH,是公共端的电压输出,一般在外面接个滤波电容就行了。IREF是驱动电流控制,在外面接个电阻,可以控制驱动屏幕的电流。因为不同厂商生产的屏幕,所需驱动电流可能不一样,这里通过电阻进行配置就行了。还有一个TE,这个是芯片的测试点,我们用不到。
单独用这个芯片的话,确实需要接两个电源,但是屏幕厂商已经在屏幕里面加了升压电路,所以VCC可以不接。
后面加#号,表示低电平是写, 高电平是读。
6800模式下,R/W#(WR#)和E(RD#)一个引脚管读写,一个引脚管时机,读写的时机,其实就是时钟线。8080是一个WR引脚,决定写和写的时机,一个RD引脚决定读和读的时机。
D/C#:在6800/8080/4线SPI下,高电平,表示D(Data)数据,低电平表示C(Command)命令,其中4线SPI,相比较3线SPI,就是多了一根D/C#线。I2C模式下:I2C支持总线挂载多设备,若一个总线同时挂载两个LED,就要配置一下这个SA0,防止地址冲突。在I2C和3线SPI模式下,没有单独的D/C引脚,来指定写入的字节数数据还是命令,它们是靠通信过程中,在数据线上指定的。
CS#:片选,低电平选中芯片。
RES#(Reset):是复位,低电平芯片复位。
1.5 通信接口选择及通信线定义
下图是BS引脚选择通信接口的说明,想选择哪种接口,就按照这个表的指示,接上高低电平就可以了。
下面这个表是不同模式下,通信引脚的具体功能。6800和8080模式下,D0~D7作为并行数据线,后面这些,用于控制读写,命令还是数据等。
3线或4线SPI,D1是数据输入,D0是时钟,当然,在串行模式下,是不支持对芯片进行数据读出的,也就是只能写不能读,所以这里,SPI没有数据输出口。下面的I2C即使有SDAout,实际上也无法读取数据,穿行模式下,写进去就没法读了。4线SPI比3线SPI多了单独的D/C引脚,所以3线指的是SDIN,SCLK,CS这3根,4线指的是SDIN,SCLK,CS,D/C这4根,RES是复位,不算通信线。最后一个I2C模式下,D2和D1接在一起,是SDA,D0是SCL,D/C引脚用于配置I2C从机地址最低位。
1.6 4针脚I2C接口模块原理图
右上角这一大块是OLED的裸屏,引出来30根线,当然有的裸屏,引出来的可能是31根。
引出来之后,首先是BS三个引脚,BS1接高电平,其它接低电平,正好对应I2C的配置,说明此时选中I2C的通信接口。
然后IREF,是电流控制,用于配置驱动屏幕的电流,手册里有计算公式,不过哦我们不用过多关心。
CS片选,接低电平,表示时钟选中芯片。
D/C,在I2C模式下,用于配置从机地址,两个电阻,一个接高电平,一个接低电平,这两个电阻只需要焊接一个,不能同时接。这里是如下图所示这样设计的。3个焊盘,焊在左边地址是3C,焊在右边地址是3D
这里78和7A和PCB上标的不一致,这是因为3C和3D,是从机地址的直接形式,78和和7A是从机地址左移一位后的形式。
RES:复位,接到上电复位电路。
D0:通过排针引出来。
D1和D2接在一起,是SDA,通过排针引出来。
SCL和SDA各接一个4.7K的上拉电阻,这是I2C通信的设计要求。
右边的引脚都没用接GND。
下面几个接电源滤波电容。
VCOMH接上滤波电容,VCC是面板驱动的供电脚。
1.7 7针脚SPI接口模块原理图
这4个电阻,只需要焊接2个,可以自己手动指定BS0和BS1的电平。因为这个模块,默认是4线SPI接口的,但同时它也支持3针脚SPI或者I2C。
7针脚可以手动默认为4线SPI,通过更改焊接电阻,可以手动更改为3线SPI和I2C。
1.8 字节传输-6800并口
1.9 字节传输-8080并口
1.10 字节传输-4线SPI
1.11 字节传输-3线SPI
1.12 字节传输-I2C
1.13 执行逻辑框图
1.14 命令表
通过写命令时序传输的字节,作为发送给SSD1306的一个命令
SSD1306查询命令表的定义,执行相应的操作
命令可以由一个字节或者连续的多个字节组成
命令可分为基础命令、滚屏命令、寻址命令、硬件配置命令、时间及驱动命令5大类
1.15 初始化过程(内部提供VCC)
1.16 ASCII码
ASCII码是一套数字到字符的映射标准,它规定了用什么数字表示什么字符
例如:char a = '#'; 等效于 char a = 0x23;
char a = '0'; 等效于 char a = 0x30;
char s[] = "hi"; 等效于 char s[] = {0x68, 0x69, 0x00};
1.17 汉字编码
汉字编码是一套数字到汉字的映射标准,它规定了用什么数字表示什么汉字
汉字编码有多种方案,常用的有GB2312/GBK/GB18030和Unicode/UTF-8
GB2312编码下:
char s[] = "好"; 等效于 char s[] = {0xBA, 0xC3, 0x00};
char s[] = "你好"; 等效于 char s[] = {0xC4, 0xE3, 0xBA, 0xC3, 0x00};
UTF-8编码下:
char s[] = "好"; 等效于 char s[] = {0xE5, 0xA5, 0xBD, 0x00};
char s[] = "你好"; 等效于 char s[] = {0xE4, 0xBD, 0xA0,
0xE5, 0xA5, 0xBD, 0x00};
编码查询:汉字字符集编码查询;中文字符集编码:GB2312、BIG5、GBK、GB18030、Unicode
1.18 任意位置显示任意尺寸图像
将下述15*15像素的Img图像显示到坐标(2, 3)的位置
uint8_t Img[] = {
0xFF,0x01,0xE1,0x11,0x49,0x25,0x45,0x05,0x45,0x25,0x49,0x11,0xE1,0x01,0xFF,
0x7F,0x40,0x43,0x44,0x48,0x51,0x52,0x52,0x52,0x51,0x48,0x44,0x43,0x40,0x7F};
02 江科大OLED显示屏驱动程序使用
电路接线:
4针脚:
7针脚:
2.1 江科大OLED显示屏程序移植流程
这里两种格式主要影响的是汉字的显示,各种编码格式,都可以兼容英文ASCII码字母。GB2312是汉字的国标码,主要支持汉字和英文;UTF-8是Unicode万国码的传输格式,支持所有国家的文字。
2.1.1 UTF-8格式的移植
(1)将江科大提供的OLED驱动文件添加进自己的工程里。
因为我们的程序里直接写了中文,所以需要进行如下设置:
--no-multibyte-chars
到这里我们的移植就成功了。
(2)使用
cpp
OLED_ShowString(0,0,"Hello World!",OLED_8X16); //显示字符串
OLED_ShowChinese(0,20,"你好,世界。"); //显示汉字,符号要为全角字符
OLED_Update(); //最后要进行刷新一下才会显示
所显示的汉字必需在字库定义。
2.1.2 GB2312格式的移植
(1)移植
这里要进行下编码格式转换,江科大提供的驱动是使用UTF-8编写的,所以要进行编码转换。
也可以直接使用整个文件夹进行转换,注意去掉只读的勾选。
然后把文件添加进工程里,跟移植UTF-8格式一样。
(2)使用
cpp
#include "stm32f10x.h" // Device header
#include "OLED.h"
//编码格式:GB2312
int main(void)
{
OLED_Init();
OLED_ShowString(0,0,"Hello World!",OLED_8X16); //显示字符串
OLED_ShowChinese(0,20,"你好,世界。"); //显示汉字,符号要为全角字符
OLED_Update(); //最后要进行刷新一下才会显示
while (1)
{
}
}
2.1.3 针对接不同引脚的OLED
因为使用软件模拟的I2C,所以SCL和SDA可以接任意空闲GPIO接口,在以下位置做对应更改成对应引脚即可。
2.2 常规数据的显示
字符、字符串、有无符号数、16进制、二进制、浮点数等等。
注:显示数的时候如果长度不够会在前面补0,但是千万不能自己在数的前面写0,因为这样代表八进制数。
cpp
OLED_Init();
// OLED_ShowChar(0,0,'A',OLED_8X16); //显示字符
// OLED_ShowChar(0,20,'B',OLED_6X8); //显示字符
// OLED_ShowString(20,0,"Hello World!",OLED_8X16); //显示字符串
// OLED_ShowString(20,20,"Hello World!",OLED_6X8); //显示字符串
OLED_ShowNum(0,0,12345,5,OLED_6X8); //显示无符号整数
OLED_ShowSignedNum(0,8,-66,2,OLED_6X8); //显示有符号整数
OLED_ShowHexNum(0,16,0xA5A5,4,OLED_6X8); //以16进制形式显示
OLED_ShowBinNum(0,24,0xA5A5,16,OLED_6X8); //以二进制形式显示
OLED_ShowFloatNum(0,32,-12.345,2,3,OLED_6X8); //显示浮点数
OLED_Update(); //最后要进行刷新一下才会显示
2.3 汉字的显示
(1)取模
使用江科大的OLED驱动都要保持这个配置。
cpp
OLED_ShowChinese(0,0,"哈喽,世界。"); //显示汉字,符号要为全角字符
2.4 图像的显示
cpp
OLED_ShowImage(0,0,16,16,Diode); //测试用,显示一个二极管
2.4.1 自定义图像的显示
cpp
const uint8_t Battery[] = {
0x00,0xFC,0x04,0xF4,0xF4,0x04,0xF4,0xF4,0x04,0xF4,0xF4,0x04,0xFC,0xF0,0x00,0x00,
0x1F,0x10,0x17,0x17,0x10,0x17,0x17,0x10,0x17,0x17,0x10,0x1F,0x07,0x00,
};
cpp
extern const uint8_t Battery[];
cpp
OLED_ShowImage(0,20,15,15,Battery); //显示电池符号
2.4.2 显示图片
取模软件只支持BMP格式图片,所以先使用PS对图片进行处理。
(1)先进行二值化
效果不是很好,所以可以先进行如下操作:
然后再进行二值化。
(2)调整图片大小
(3)保存
下图默认即可:
(4) 取模
然后点击生成字模。
cpp
const uint8_t Lenna[] = {
0x00,0x00,0x80,0x80,0x00,0x00,0x80,0xC5,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x80,0xC0,0xC0,0x00,0x00,0x20,0x20,0x20,0x20,0x20,0x60,0x60,0x60,0xC0,
0x80,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,
0x0E,0x3B,0xEE,0x9C,0xF0,0xE0,0x60,0x30,0x18,0x0C,0x06,0x03,0x0E,0x07,0x79,0x7F,
0x00,0x00,0x80,0xEB,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xF0,0x18,0x83,0x81,0x00,
0x40,0xE0,0xF0,0xE0,0xE0,0xF0,0x5B,0x7F,0x2C,0x86,0xC6,0x02,0x03,0x03,0x83,0x86,
0x0C,0x38,0x70,0xE0,0x00,0x00,0xFC,0xFE,0xCE,0xF8,0x70,0x30,0xF0,0xF8,0x0E,0x07,
0x01,0x00,0xC0,0x60,0x38,0x1C,0x06,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x34,0xFF,
0x00,0x00,0x00,0x00,0x00,0x3C,0xFF,0xFF,0xFC,0xF8,0x6C,0xF6,0x1A,0x0D,0xD6,0xDB,
0xFF,0xFC,0xFE,0xF3,0xFB,0xF9,0xFC,0xF0,0xF3,0x71,0x31,0x38,0x18,0x0C,0x06,0x86,
0xC3,0xC3,0xE1,0x71,0xF0,0xFC,0x7C,0x3C,0x0F,0x81,0xE0,0x78,0x1E,0x07,0x01,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF6,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x80,
0xCC,0xEC,0xB1,0xFF,0xFF,0xF3,0xFC,0xFE,0x7F,0xFF,0xFF,0xC3,0xBE,0xFF,0x7F,0xBC,
0xED,0xE7,0xFF,0xFF,0xF0,0xF4,0x64,0x00,0x80,0xC0,0xE0,0xF7,0xFF,0x1F,0xFF,0xFF,
0xE1,0x00,0x80,0xF0,0x7C,0x0F,0x03,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,
0x00,0x00,0xE0,0xFF,0x00,0x00,0x00,0xFF,0x38,0xBC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xE3,0xF7,0xFF,0xFF,0xFF,0xF8,0xF8,0x7E,0xDF,0xFF,0x47,0x01,0x00,0x01,0x01,0x01,
0x01,0x01,0x60,0xE7,0xFF,0x39,0x81,0xE1,0x3F,0xCC,0xFF,0xFF,0xFF,0xF8,0x1F,0x0B,
0x00,0x00,0x00,0x00,0x00,0xF0,0x38,0x0C,0x02,0x03,0x01,0x01,0x40,0xD8,0xBF,0xE7,
0x80,0x00,0x40,0xFF,0xFF,0x7F,0xFF,0xDF,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEF,
0xC3,0xE1,0xE3,0x00,0x03,0x07,0x8F,0xD8,0x78,0x30,0x30,0x23,0x03,0x03,0x0F,0x27,
0x3B,0x6F,0x43,0xD0,0xFE,0xFF,0x0E,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x80,
0xFF,0x3F,0x00,0x80,0xC0,0x60,0x30,0x30,0xBF,0xFE,0xFF,0xFF,0xFF,0x00,0x00,0xFF,
0xFF,0x20,0xB3,0xDD,0x4F,0x1F,0x3F,0x77,0xFF,0xFF,0xFF,0xFF,0x7F,0x1F,0x1E,0x90,
0x62,0x9E,0xCF,0xFF,0x3F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x01,0x07,0x3E,0xFF,0x63,0x40,0x40,0xC0,0xC0,0xD0,0xFC,0xE7,0xE3,0xFC,0x0F,0x83,
0x01,0x00,0x00,0x40,0x03,0x0F,0x0F,0x0F,0x0F,0x00,0x08,0x0F,0x0B,0x08,0x02,0x0C,
0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x08,0x0C,0x06,0x07,0x03,0x03,0x01,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,
0x0F,0x08,0x00,0x00,0x01,0x03,0x03,0x03,0x01,0x00,0x01,0x0C,0x08,0x08,0x00,0x08,
};
cpp
extern const uint8_t Lenna[];
cpp
OLED_ShowImage(0,0,60,60,Lenna); //显示图片
2.5 格式化字符串显示
cpp
OLED_Printf(0,0,OLED_6X8,"Num1:%d,Num2:%d",123,-456);
OLED_Printf(0,8,OLED_6X8,"%X",0xA5A5);
OLED_Printf(0,16,OLED_6X8,"%f",123.456);
OLED_Printf(0,24,OLED_6X8,"%05d",-66); //固定显示5个长度,不足部分补0
OLED_Printf(0,32,OLED_6X8,"%06.2f",12.3456);
OLED_Printf(0,32,OLED_6X8,"%+06.2f",12.3456);
2.7 绘图函数
cpp
// OLED_DrawPoint(0,0); //绘制一个点
// OLED_GetPoint(50,20); //返回这个像素点是否被点亮,点亮返回1,未点亮返回0
// OLED_DrawLine(0,0,127,63); //通过两点绘制一条直线
// OLED_DrawRectangle(0,0,50,30,OLED_UNFILLED); //绘制一个矩形,指定起点,宽度和高度,最后一个参数指定是否填充,也可以用0/1表示
// OLED_DrawTriangle(0,0,40,10,30,50,OLED_UNFILLED); //绘制未填充三角形
// OLED_DrawTriangle(50,0,90,10,80,50,OLED_FILLED); //绘制填充三角形
// OLED_DrawCircle(30,30,20,OLED_UNFILLED); //绘制空心圆
// OLED_DrawCircle(80,30,10,OLED_FILLED); //绘制实心圆
// OLED_DrawEllipse(30,30,20,10,OLED_UNFILLED); //绘制未填充椭圆,圆心,横轴,纵轴,(长轴、短轴),填充参数
// OLED_DrawEllipse(80,30,10,20,OLED_FILLED); //绘制未填充椭圆,圆心,横轴,纵轴,(长轴、短轴),填充参数
OLED_DrawArc(30,30,20,45,-45,OLED_UNFILLED); //绘制圆弧
OLED_DrawArc(80,30,20,-45,45,OLED_FILLED); //绘制扇形
2.8 更新和显示控制函数
cpp
/*更新函数*/
void OLED_Update(void); //刷新整个屏幕
void OLED_UpdateArea(int16_t X, int16_t Y, uint8_t Width, uint8_t Height); //刷新屏幕局部区域
/*显存控制函数*/
void OLED_Clear(void); //清空整个屏幕
void OLED_ClearArea(int16_t X, int16_t Y, uint8_t Width, uint8_t Height); //清空部分区域
void OLED_Reverse(void); //翻转整个屏幕
void OLED_ReverseArea(int16_t X, int16_t Y, uint8_t Width, uint8_t Height); //翻转屏幕部分区域
2.9 初始化函数
OLED只能倒着插的时候,就可以更改一下上面两个参数。
上述参数可更改亮度。