赞
踩
前言:OLED模块作为人们日常生活中常见屏幕类型之一,使用的受众面非常广阔。例如:显示各个传感器数值,显示精美界面,多级化菜单系统等等都不离不开他的身影。可以说学会OLED模块是嵌入式开发必须掌握的驱动开发技能之一,同时,也是嵌入式开发调试配置的重要手段与技巧!(文章结尾会有代码开源)
实验硬件:STM32F103C8T6;0.96寸OLED
OLED,即有机发光二极管(Organic Light-Emitting Diode),又称为有机电激光显示(Organic Electroluminesence Display, OELD)。OLED 由于同时具备自发光,不需背光源、对比度高、厚度薄、视角广、反应速度快、可用于挠曲性面板、使用温度范围广、构造及制程较简单等优异之特性,被认为是下一代的平面显示器新兴应用技术。
LCD 都需要背光,而 OLED 不需要,因为它是自发光的。这样同样的显示,OLED 效果要来得好一些。以目前的技术,OLED 的尺寸还难以大型化,但是分辨率确可以做到很高。市场上常见OLED模块有以下特点:
(1)模块有单色和双色两种可选,单色为纯蓝色,而双色则为黄蓝双色。
(2)尺寸小,显示尺寸为 0.96 寸,而模块的尺寸仅为 27mm*26mm 大小。
(3)高分辨率,该模块的分辨率为 128*64。
(4)多种接口方式,该模块提供了总共 5 种接口包括:6800、8080 两种并行接口方式、3线或 4 线的穿行 SPI 接口方式,、IIC 接口方式(只需要 2 根线就可以控制 OLED 了!)。
(5)不需要高压,直接接 3.3V 就可以工作了。
特别注意,市面上有部分的OLED屏幕不可以直接接5.0v电压,否则可能烧坏!
总结:目前市面上常用的0.96寸OLED屏幕通讯方式主要有SPI和I2C两种!SPI为4线制较多,而I2C为2线制。2种通讯协议较为浅显的区别:总所周知,SPI的通讯速度明显快于I2C的通讯速度,所以通常使用SPI通讯协议的OLED屏幕可以实现更高的帧数显示,画面更为流畅丝滑。
当然,OLED屏幕显示的帧数高低不仅取决于通讯协议的不同,DMA (直接存储器访问)的使用也可以大幅提升OLED显示帧数。这一点笔者会在之后一篇博客文章专门介绍,有兴趣的读者可以关注一下!
本次实验所采用的0.96寸OLED屏幕为I2C通讯方式,故在此稍微给读者介绍一下I2C通讯原理。
IIC(Inter-Integrated Circuit)总线是一种由 PHILIPS 公司开发的两线式串行总线,用于连接微控制器及其外围设备。它是由数据线 SDA 和时钟 SCL 构成的串行总线,可发送和接收数据。在 CPU 与被控 IC 之间、IC 与 IC 之间进行双向传送,高速 IIC 总线一般可达 400kbps 以上。
I2C 总线在传送数据过程中共有三种类型信号, 它们分别是:开始信号、结束信号和应答信号。
开始信号:SCL 为高电平时,SDA 由高电平向低电平跳变,开始传送数据。
结束信号:SCL 为高电平时,SDA 由低电平向高电平跳变,结束传送数据。
应答信号:接收数据的 IC 在接收到 8bit 数据后,向发送数据的 IC 发出特定的低电平脉冲,
表示已收到数据。CPU 向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU 接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为受控单元出现故障。
这些信号中,起始信号是必需的,结束信号和应答信号,都可以不要。
目前大部分 MCU 都带有 IIC 总线接口,STM32 也不例外。但是这里我们不使用 STM32的硬件 IIC ,而是通过软件模拟。STM32 的硬件 IIC 非常复杂,更重要的是不稳定,故不推荐使用。所以我们这里就通过模拟来实现了。
本文的主要目的是为实现OLED各种显示(含动态),I2C的通讯原理只给大家稍微科普梳理一下。如果有需要进一步了解的读者,可以移步笔者的其他文章进行详细学习。
为了缩短开发周期和方便后续讲解,这里使用HAL库编程。这部分的Cubex配置较为简单,具体过程如下:
1、SYS配置:Debug选择Serial Wire(否则芯片可能自锁)
2、RCC配置:选择外部高速晶振
3、I2C配置:选择I2C2,Parameter Settings为默认即可
4、时钟树配置:
oled.c中的代码:
- #include "oled.h"
- #include "asc.h" //字库(可以自己制作)
- #include "main.h"
-
- void WriteCmd(unsigned char I2C_Command) //写命令利用I2C通讯
- {
- HAL_I2C_Mem_Write(&hi2c1,OLED0561_ADD,COM,I2C_MEMADD_SIZE_8BIT,&I2C_Command,1,100);
- }
-
- void WriteDat(unsigned char I2C_Data) //写数据利用I2C通讯
- {
- HAL_I2C_Mem_Write(&hi2c1,OLED0561_ADD,DAT,I2C_MEMADD_SIZE_8BIT,&I2C_Data,1,100);
- }
-
- void OLED_Init(void)
- {
- HAL_Delay(100); //这里的延时很重要
-
- WriteCmd(0xAE); //display off
- WriteCmd(0x20); //Set Memory Addressing Mode
- WriteCmd(0x10); //00,Horizontal Addressing Mode;01,Vertical Addressing Mode;10,Page Addressing Mode (RESET);11,Invalid
- WriteCmd(0xb0); //Set Page Start Address for Page Addressing Mode,0-7
- WriteCmd(0xc8); //Set COM Output Scan Direction
- WriteCmd(0x00); //---set low column address
- WriteCmd(0x10); //---set high column address
- WriteCmd(0x40); //--set start line address
- WriteCmd(0x81); //--set contrast control register
- WriteCmd(0xff); //亮度调节 0x00~0xff
- WriteCmd(0xa1); //--set segment re-map 0 to 127
- WriteCmd(0xa6); //--set normal display
- WriteCmd(0xa8); //--set multiplex ratio(1 to 64)
- WriteCmd(0x3F); //
- WriteCmd(0xa4); //0xa4,Output follows RAM content;0xa5,Output ignores RAM content
- WriteCmd(0xd3); //-set display offset
- WriteCmd(0x00); //-not offset
- WriteCmd(0xd5); //--set display clock divide ratio/oscillator frequency
- WriteCmd(0xf0); //--set divide ratio
- WriteCmd(0xd9); //--set pre-charge period
- WriteCmd(0x22); //
- WriteCmd(0xda); //--set com pins hardware configuration
- WriteCmd(0x12);
- WriteCmd(0xdb); //--set vcomh
- WriteCmd(0x20); //0x20,0.77xVcc
- WriteCmd(0x8d); //--set DC-DC enable
- WriteCmd(0x14); //
- WriteCmd(0xaf); //--turn on oled panel
- }
-
- void OLED_SetPos(unsigned char x, unsigned char y) //设置起始点坐标
- {
- WriteCmd(0xb0+y);
- WriteCmd(((x&0xf0)>>4)|0x10);
- WriteCmd((x&0x0f)|0x01);
- }
-
- void OLED_Fill(unsigned char fill_Data)//全屏填充
- {
- unsigned char m,n;
- for(m=0;m<8;m++)
- {
- WriteCmd(0xb0+m); //page0-page1
- WriteCmd(0x00); //low column start address
- WriteCmd(0x10); //high column start address
- for(n=0;n<128;n++)
- {
- WriteDat(fill_Data);
- }
- }
- }
-
-
- void OLED_CLS(void)//清屏
- {
- OLED_Fill(0x00);
- }
-
- void OLED_ON(void)
- {
- WriteCmd(0X8D); //设置电荷泵
- WriteCmd(0X14); //开启电荷泵
- WriteCmd(0XAF); //OLED唤醒
- }
-
- void OLED_OFF(void)
- {
- WriteCmd(0X8D); //设置电荷泵
- WriteCmd(0X10); //关闭电荷泵
- WriteCmd(0XAE); //OLED休眠
- }
以上代码都是OLED初始化等必不可少的基础函数,目的在于配置和方便以后使用OLED。用户可以直接移植使用,注意笔者这里是选用了I2C2接口。
其实从本质上来说OLED等一众屏幕显示都是基于每个像素点的点亮,这里的像素点就可以等效于一个极小的LED灯(当然,LED可以是单色的,也可以是基于三原色的RGB灯),我们利用取模工具将我们表现得汉字,字符串,图片,动画等做成字库,进而利用字库去点亮规定好得LED灯及像素点,即可完成OLED得显示目的。
字符串的显示是在字符显示基础上实现的,是OLED显示中常见的API函数之一。
- // Parameters : x,y -- 起始点坐标(x:0~127, y:0~7); ch[] -- 要显示的字符串; TextSize -- 字符大小(1:6*8 ; 2:8*16)
- // Description : 显示codetab.h中的ASCII字符,有6*8和8*16可选择
- void OLED_ShowStr(unsigned char x, unsigned char y, unsigned char ch[], unsigned char TextSize)
- {
- unsigned char c = 0,i = 0,j = 0;
- switch(TextSize)
- {
- case 1:
- {
- while(ch[j] != '\0')
- {
- c = ch[j] - 32;
- if(x > 126)
- {
- x = 0;
- y++;
- }
- OLED_SetPos(x,y);
- for(i=0;i<6;i++)
- WriteDat(F6x8[c][i]);
- x += 6;
- j++;
- }
- }break;
- case 2:
- {
- while(ch[j] != '\0')
- {
- c = ch[j] - 32;
- if(x > 120)
- {
- x = 0;
- y++;
- }
- OLED_SetPos(x,y);
- for(i=0;i<8;i++)
- WriteDat(F8X16[c*16+i]);
- OLED_SetPos(x,y+1);
- for(i=0;i<8;i++)
- WriteDat(F8X16[c*16+i+8]);
- x += 8;
- j++;
- }
- }break;
- }
- }
main代码:
OLED_ShowStr(20,3,"hello world",2);
显示效果:
汉字显示也是我们实际工程运用中非常常见的一种显示要求,这里我们通常会用运用到取模工具。
笔者选用了PCtoLCD2002完美版,取模过程如下:
注意:读者使用的取模设置方式一定要和程序所写画点的方式对应起来,否则可能会出现乱码现象。
单个汉字显示的API函数:
- // Parameters : x,y -- 起始点坐标(x:0~127, y:0~7); N:汉字在.h中的索引
- // Description : 显示ASCII_8x16.h中的汉字,16*16点阵
- void OLED_ShowCN(unsigned char x, unsigned char y, unsigned char N)
- {
- unsigned char wm=0;
- unsigned int adder=32*N;
- OLED_SetPos(x , y);
- for(wm = 0;wm < 16;wm++)
- {
- WriteDat(F16x16[adder]);
- adder += 1;
- }
- OLED_SetPos(x,y + 1);
- for(wm = 0;wm < 16;wm++)
- {
- WriteDat(F16x16[adder]);
- adder += 1;
- }
- }
汉字串显示的API函数:
- // 这是自己写的显示中文字符串的函数,要先把中文字符串“共阴——列行式——逆向输出”取字模后存入asc.h相应的位置(连续存入)
- //传入参数分别为:x:起始横坐标
- // y:纵坐标(填入0-7)
- // begin:填入的中文字符串的第一个字在我们asc.c字库里面的序号
- // num:我们要填写几个字
- // 比如要填“测试”,取完字模存入后这两个字在字库中序号为0,1,横坐标0,纵坐标第二行,就填:x:0,y:2,begin:0,num:2
- void OLED_ShowCN_STR(u8 x , u8 y , u8 begin , u8 num)
- {
- u8 i;
- for(i=0;i<num;i++){OLED_ShowCN(i*16+x,y,i+begin);} //OLED显示标题
- }
main代码:
OLED_ShowCN_STR(10,3,0,7);
显示效果:
这里的图片可以是PCtoLCD2002完美版软件下的灵魂画师模式,也可以是将图片转换为变成单色(黑与白)——数组形式,而且大小应该在128*64内。
灵魂画师:
本次重点讲解如何把读者所需要的图片显示到OLED上,使用的软件为Img2Lcd2.9。
打开Img2Lcd2.9软件后如下进行设置,之后点击保存,可以得到一个大数组。
样例图片(分辨率50*59):
- unsigned char BMP1[] = { 0X32,0X01,0X00,0X3B,0X00,0X3B,
- 0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X80,0XF8,
- 0XFC,0X78,0X70,0X30,0X00,0X08,0X00,0X04,0X04,0X04,0X04,0X00,0X00,0X00,0X00,0X00,
- 0X00,0X00,0X00,0X00,0X00,0X80,0XC8,0XC8,0XE8,0XF0,0XF0,0XF0,0X00,0X20,0X40,0X40,
- 0X80,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
- 0X00,0X00,0X00,0X00,0X60,0X10,0X0C,0X02,0X01,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
- 0X00,0X00,0X00,0X00,0X00,0X00,0X70,0X00,0X00,0X00,0X80,0X00,0X00,0X00,0X00,0X00,
- 0X00,0X01,0X01,0X03,0X03,0X07,0X00,0X00,0X00,0X00,0X00,0X00,0X01,0X06,0X08,0X30,
- 0XC0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X18,0X13,0X10,
- 0X08,0X0C,0X0E,0X20,0X00,0X10,0X10,0X08,0X04,0X06,0X01,0X20,0X20,0X10,0X08,0X04,
- 0X01,0X00,0X00,0X00,0X00,0X01,0X06,0X18,0X20,0X60,0X78,0X00,0X00,0X00,0X40,0X00,
- 0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X7E,0X00,0X00,0X00,
- 0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X7F,0X80,0X00,
- 0X00,0X00,0X00,0X00,0X07,0X07,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
- 0X0C,0X1C,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X02,0X01,0X01,
- 0X42,0X7C,0X00,0X80,0X20,0X10,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
- 0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X01,0X06,0X04,0X08,0X10,0X30,0X20,
- 0X20,0X40,0X41,0XC0,0XC0,0X80,0X81,0X81,0X80,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
- 0X00,0X20,0X20,0X18,0X08,0X04,0X00,0X00,0X0C,0X04,0X02,0X01,0X03,0X03,0X00,0X00,
- 0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
- 0X80,0XC0,0X40,0X60,0X20,0X10,0X10,0X08,0X04,0X04,0X02,0X02,0X02,0X01,0X03,0X03,
- 0X02,0X03,0X03,0X02,0X02,0X04,0X04,0X0C,0X08,0X10,0X30,0X20,0X40,0X80,0X80,0X00,
- 0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X40,0X70,0XFC,0XF0,0X60,
- 0X00,0X00,0X00,0X00,0X00,0X00,0X10,0X08,0X04,0X02,0X03,0X01,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,0X01,0X03,0X06,0X08,0X30,0X60,0XC0,
- 0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X80,0XC0,0X61,0X3F,0X0C,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,0X01,0X01,
- 0X01,0X01,0X00,0X00,0X00,0X00,0X00,0X00,};
注意:Img2Lcd2.9可以处理的图片类型有限,如果大家图片不能被打开处理,可能是图片类型不对,可以借助PNG转JPG - 在线转换图像文件 (aconvert.com)进行处理转换。
显示图片的API函数:
- // Parameters : x0,y0 -- 起始点坐标(x0:0~127, y0:0~7); x1,y1 -- 起点对角线(结束点)的坐标(x1:1~128,y1:1~8)
- // Description : 显示BMP位图
- void OLED_DrawBMP(unsigned char x0,unsigned char y0,unsigned char x1,unsigned char y1,unsigned char BMP[])
- {
- unsigned int j=0;
- unsigned char x,y;
-
- if(y1%8==0)
- y = y1/8;
- else
- y = y1/8 + 1;
- for(y=y0;y<y1;y++)
- {
- OLED_SetPos(x0,y);
- for(x=x0;x<x1;x++)
- {
- WriteDat(BMP[j++]);
- }
- }
- }
main函数代码:
OLED_DrawBMP(30,0,89,8,BMP1);
特别说明:值得注意的是函数的第一个(30)和第三个参数(89),起始列地址和终止列地址,如果和图片的分辨率对不上,那就会显示出来一坨散沙,看不出样子。(89-30=59)
显示效果:
一般情况下GIF动态图片的大小都是不满足在OLED显示的,所以需要进行大小亦或是帧数变化操作。这里读者推荐使用:GiFResizerChs
处理过程如下:
之后采用GIF动态图片分批处理软件进行每张图片的提取,笔者这里使用的是:zhs9
批量提取后的图片:
之后就是反复枯燥的取模过程了,这里就给大家省略了。
GIF动态图片显示API函数:
- /*
- @brief 显示动图
- @param x0:起始列地址
- y0:起始页地址
- x1:终止列地址
- y1:终止页地址
- k: 帧个数
- m: 单帧数组大小
- BMP[][m]:存放动图代码的数组
- @retval 无
- */
- void OLED_DrawGIF(unsigned char x0, unsigned char y0,unsigned char x1, unsigned char y1, unsigned char k, int m, unsigned char GIF[][m])
- {
- unsigned int j=0; //定义变量
- unsigned char x,y,i; //定义变量
-
- if(y1%8==0) y=y1/8; //判断终止页是否为8的整数倍
- else y=y1/8+1;
- for (i=0;i<k;i++) //从第一帧开始画
- {
- j = 0;
- for(y=y0;y<y1;y++) //从起始页开始,画到终止页
- {
- OLED_SetPos(x0,y); //在页的起始列开始画
-
- for(x=x0;x<x1;x++) //画x1 - x0 列
- {
-
- WriteDat(GIF[i][j++]); //画图片的点
- }
- }
- //delay_ms(80);//人为制造卡顿???
-
- }
- }
main函数:
OLED_DrawGIF(30,2,78,8,12,294,BMP2);
这里取模的帧数过程笔者偷懒了,取模实在太烦了,所以就少取了很多帧数的模。如果诸位有批量取模的软件请务必告知,感谢!
显示效果:
OLED动态火苗
源码地址:链接:https://pan.baidu.com/s/1nDVko8Iux71b2lnenUKy9g 提取码:83bi
如果读者对笔者文章所述的开发软件有需要的可以私聊笔者,笔者可以提供,这里就仅提供源码了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。