Bootstrap

基于IIC通信的显示器OLED编程详解(一篇搞懂)

前言:

上一篇博客介绍了IIC通信,这篇我们就来玩玩oled模块。当然选用的是IIC接口,因为市面上还有一种是SPI接口的。对于oled长啥样,采用了什么材料,工艺怎么怎么样等等这里就不作任何介绍,搞得眼花缭乱的,对我们用它做开发也没任何帮助,同时节省读者阅读时间。为什么会有这种想法,就是因为发现一些博客在无关紧要的东西方面写了大半篇幅,而在实现原理方面草草了之,再放点代码,贴个效果实现图就算完事了,虚的一批。所以想自己写点东西,话不多说,下面直接开干,上技术活。

强调: 相关IIC通信代码请参考我的上篇博客

1、写模式——写命令与写数据

当我们拿到一个硬件产品准备做开发时,毫无疑问首先要做的就是参考产品说明书。找到我们实现某项功能需要参考的点。OLED是一个电子显示屏,我们用它无非就是显示一些数字,文字,图片。归根到底这些都属于数据,单片机通过IIC协议与oled进行通信,实现数据传输。这就需要按照IIC总线数据格式(如图)进行,有一种“在道上走,就要按照道上的规矩办事"的感觉。
在这里插入图片描述

(1)说明:

  • S:开始状态 即:start();
  • Slave Address:从地址,如上图红色框内,根据说明书可以为b0111100或b0111101,当R\W为0时为写模式。所以可以选择从地址:01111000,即0x78。
  • ACK 应答信号
  • 上图第四部分(蓝色框内),根据说明书从地址发送完后,发送的就是控制字节和数据字节。当控制字节Co位为0时,发送的信息只包含数据字节。当D/C位为0时数据字节作为命令,为1时作为数据。所以图中控制字节可以为:00000000=0x00(后面数据字节为写入指令),01000000=0x40(后面数据字节为写入数据)。
  • 写入指令/数据
  • ACK应答信号
  • STOP 终止信号

(2)编码:

void Oled_Write_Cmd(char Cmd)
{
	IIC_Start();
	IIC_Send_Byte(0x78);
	IIC_AcK();
	IIC_Send_Byte(0x00);
	IIC_Ack();
	IIC_Send_Byte(Cmd);
	IIC_Ack();
	IIC_Stop();
}
void Oled_Write_Data(char Data)
{
	IIC_Start();
	IIC_Send_Byte(0x78);
	IIC_AcK();
	IIC_Send_Byte(0x40);
	IIC_Ack();
	IIC_Send_Byte(Data);
	IIC_Ack();
	IIC_Stop();
}
//IIC通信相关代码参考上篇博客

2、寻址模式

向oled写入了命令也写入了数据。那还有一个就是我要在哪个位置显示。OLED是分辨率为128*64的点矩阵,或是说嵌入式128 x 64位SRAM显示缓冲区。关于它的寻址模式有三种,分别为页寻址模式,水平寻址模式和垂直寻址模式。常用的是页寻址模式,所以对于后面两种在这里就不作介绍。页地址模式如下图所示:
在这里插入图片描述
在页面寻址模式下,完成内存中的读或写后,列地址指针自动增加1。如果列地址指针到达列结束地址(COL 127),则列地址指针将被重置为列起始地址(COL 0),而页面地址指针将不会被更改(依然为某个PAGE)。用户必须设置新的页面和列地址,才能访问下一页的RAM内容。
通过看上图我们也可以发现,需要注意三点

  • 如何配成页寻址模式
    前面提到,oled的寻址模式有三种,因此我们需要做些配置,硬件才知道我们选用的是哪种模式。查阅说明书可知如图:
    在这里插入图片描述

通过上图我们可以发现,页寻址模式是默认的,也就是可以不用配置。但如果我们要配也就是把A1=1,A0=0(根据紫色框内);通过黄框内的内容我们知道需要发送指令地址:1、0x20;2、0x02(星号任意,全配为0方便)

  • 页地址(PAGE)
    在这里插入图片描述
    如上图,X2,X1,X0总共有2的3次方种组合,对应PAGE0—PAGE7。以PAGE0为例,则X2X1X0=000,D7~D0=10110000,对应指令地址0xB0。
  • 列地址(COL)
    在这里插入图片描述
    如上图红框内,列地址有两个,一个高四位,一个低四位。高四位(0x10~0x1f),
    低四位(0x00~0x0f)。高四位16种可能,低四位也有16种,两者结合总共16*16超过了128(oled总共128列)。所以我们一般使用高四位0x10 ~0x17。可以这样理解,128列分为高位管8个大部分,每一个大部分管低位16位(如图)
    在这里插入图片描述
    比如我们想在最后一列显示,配置如下指令地址。先是确定高四位:00010111=0x17,后是确定低四位:00001111=0x0f。(是不是有种在二维平面坐标内,要同时确定x和y的坐标,才能准确确定一个点的位置的味道?)

3、获得我们想要的数据:

这里要使用到字模软件(咋用的?去找百度,不是介绍重点)。我用的是C51单片机,取模方式选择C51模式。以在Oled上显示Hello为例。得到的点阵为:

/*--  文字:  H  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,0x20,0x3F,0x21,0x01,0x01,0x21,0x3F,0x20,
/*--  文字:  e  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x1F,0x24,0x24,0x24,0x24,0x17,0x00,
/*--  文字:  l  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x10,0x10,0xF8,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,
/*--  文字:  l  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x10,0x10,0xF8,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,
/*--  文字:  o  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00,

上述显示每个字体对应点阵高皆为16。而通过前面的分析我们发现:OLED分辨率
为128 * 64=128 * 8 * 8。也就是一个PAGE共有128列,一个字节对应8位,剩下的高就为8了。很明显上述点阵一个字体需要占用两个PAGE。所以也就是在我们编程时候要去注意字体的高占用了多少个PAGE。用代码定义上述点阵如下:

/*--  文字:  H  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
char H1[8] = {0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08};
char H2[8] = {0x20,0x3F,0x21,0x01,0x01,0x21,0x3F,0x20};
/*--  文字:  e  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
char e1[8] = {0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00};
char e2[8] = {0x00,0x1F,0x24,0x24,0x24,0x24,0x17,0x00};
/*--  文字:  l  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
char l1[8] = {0x00,0x10,0x10,0xF8,0x00,0x00,0x00,0x00};
char l2[8] = {0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00};
/*--  文字:  o  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
char o1[8] = {0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00};
char o2[8] = {0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00};

4、代码编写

功能实现:在OLED屏幕中央显示一个Hello字样

  1. IIC通信代码
  2. 写命令和写数据函数封装
  3. 获取数据代码
  4. 初始化函数封装
void Oled_Init(void){
	Oled_Write_Cmd(0xAE);//--display off
	Oled_Write_Cmd(0x00);//---set low column address
	Oled_Write_Cmd(0x10);//---set high column address
	Oled_Write_Cmd(0x40);//--set start line address  
	Oled_Write_Cmd(0xB0);//--set page address
	Oled_Write_Cmd(0x81); // contract control
	Oled_Write_Cmd(0xFF);//--128   
	Oled_Write_Cmd(0xA1);//set segment remap 
	Oled_Write_Cmd(0xA6);//--normal / reverse
	Oled_Write_Cmd(0xA8);//--set multiplex ratio(1 to 64)
	Oled_Write_Cmd(0x3F);//--1/32 duty
	Oled_Write_Cmd(0xC8);//Com scan direction
	Oled_Write_Cmd(0xD3);//-set display offset
	Oled_Write_Cmd(0x00);//
	Oled_Write_Cmd(0xD5);//set osc division
	Oled_Write_Cmd(0x80);//
	Oled_Write_Cmd(0xD8);//set area color mode off
	Oled_Write_Cmd(0x05);//
	Oled_Write_Cmd(0xD9);//Set Pre-Charge Period
	Oled_Write_Cmd(0xF1);//
	Oled_Write_Cmd(0xDA);//set com pin configuartion
	Oled_Write_Cmd(0x12);//
	Oled_Write_Cmd(0xDB);//set Vcomh
	Oled_Write_Cmd(0x30);//
	Oled_Write_Cmd(0x8D);//set charge pump enable
	Oled_Write_Cmd(0x14);//
	Oled_Write_Cmd(0xAF);//--turn on oled panel		
}
  1. 清屏函数封装
void Oled_Clear()
{
	unsigned char i,j;
	for(i=0;i<8;i++){
		Oled_Write_Cmd(0xB0 + i);//PAGE0 -- PAGE7
		//每个page从0列
		Oled_Write_Cmd(0x00);
		Oled_Write_Cmd(0x10);
		for(j=0;j<128;j++){
			Oled_Write_Data(0);
		}
}
}
  1. 显示字体函数封装
void display(char *adress)
{
	int i;
	for(i=0;i<8;i++){
				Oled_Write_Data(adress[i]);
		}
}
  1. 主函数
void main()
{
	//1、OLED初始化
	Oled_Init();
	//2. 选择一个位置
	//2.1 确认页寻址模式
	Oled_Write_Cmd(0x20);
	Oled_Write_Cmd(0x02);
	Oled_Clear();
	//2.2 选择PAGE3   1011 0011
	//0xB3
	Oled_Write_Cmd(0xB3);
	Oled_Write_Cmd(0x00);
	Oled_Write_Cmd(0x13);
	display(H1);
	display(e1);
	display(l1);
	display(l1);
	display(o1);
	
	Oled_Write_Cmd(0xB4);
	Oled_Write_Cmd(0x00);
	Oled_Write_Cmd(0x13);
	display(H2);
	display(e2);
	display(l2);
	display(l2);
	display(o2);
	while(1);
}

效果展示:
在这里插入图片描述
当然了你想要显示一张图片操作原理是一样的,就是要选择图片取模,这里就不再赘述了。效果如下:
不打广告,顺带用的软件自带素材
关于这个图片128*64。所以最好封装个函数,用个双重循环,代码会显得更简洁点。

结语
有什么问题的话,欢迎留言讨论!

;