前言:
上一篇博客介绍了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字样
- IIC通信代码
- 写命令和写数据函数封装
- 获取数据代码
- 初始化函数封装
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
}
- 清屏函数封装
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);
}
}
}
- 显示字体函数封装
void display(char *adress)
{
int i;
for(i=0;i<8;i++){
Oled_Write_Data(adress[i]);
}
}
- 主函数
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。所以最好封装个函数,用个双重循环,代码会显得更简洁点。
结语
有什么问题的话,欢迎留言讨论!