14.1 OLED显示屏的简单介绍
OLED
即有机发光二极管( Organic Light Emitting Diode )。OLED由于同时具备自发光,不需背光源、对比度高、厚度薄、视角广、反应速度快、可用于挠曲性面板、使用温度范围广、构造及制程较简单等优异之特性,被认为是下一代的平面显示器新兴应用技术。 LCD都需要背光,而OLED不需要,因为它是自发光的。这样同样的显示OLED 效果要来得好一些。以目前的技术,OLED 的尺寸还难以大型化,但是分辨率确可以做到很高。
发光原理
OLED属于一种电流型的有机发光器件,是通过载流子的注入和复合而致发光的现象,发光强度与注入的电流成正比。OLED在电场的作用下,阳极产生的空穴和阴极产生的电子就会发生移动,分别向空穴传输层和电子传输层注入,迁移到发光层。当二者在发光层相遇时,产生能量激子,从而激发发光分子最终产生可见光。
OLED显示屏特点
(下文OLED显示屏介绍都以中景园电子的OLED显示屏为例)。
1、0.96 寸OLED 有黄蓝,白,蓝三种颜色可选;其中黄蓝是屏上1/4部分为黄光,下3/4 为蓝;而且是固定区域显示固定颜色,颜色和显示区域均不能修改;白光则为纯白,也就是黑底白字;蓝色则为纯蓝,也就是黑底蓝字。
2、分辨率为128*64。
3、多种接口方式;OLED 裸屏总共种接口包括:6800、8080 两种并行接口方式、3线或4线串行SPI 接口方式、IIC 接口方式(只需要2根线就可以控制 OLED 了!),这五种接口是通过屏上的BS0~BS2 来配置的。
IIC 接口模块接口定义
1、GND:电源地。
2、VCC:电源正(3~5.5V)。
3、SCL:OLED 的D0 脚,在IIC 通信中为时钟管脚。
4、SDA:OLED 的D1 脚,在IIC 通信中为数据管脚。
0.96 寸OLED 驱动IC
0.96 寸OLED显示屏所用的驱动IC为SSD1306;其具有内部升压功能;所以在设计的时候不需要再专一设计升压电路;当然了本屏也可以选用外部升压。SSD1306的每页包含了128个字节,总共8页,这样刚好是 128*64 的点阵大小。这点与1.3寸OLED驱动IC(SSD1106)稍有不同,SSD1106 每页是132个字节,也是8页。所以在用0.96寸OLED 移植1.3 寸OLED程序的时候需要将0.96寸的显示地址向右偏移2,这样显示就正常了;否则在用1.3 寸的时候1.3寸屏右边会有4个像素点宽度显示不正常或是全白,这点大家注意一下。其它的SSD1306和SSD1106 区别不大。
SSD1306的引脚定义
C1P /C1N C2P /C2N : 保留引脚,应保持NC,这里并联电容器。
VBAT : 连接到VDD。VDD指的是高电平
VSS: 接地引脚。
VDD: 用于核心逻辑操作的电源引脚。
BS【2:0】: MCU总线接口选择引脚
CS#: 该引脚是芯片选择输入(低电平使能)。
RES#: 该引脚为复位信号输入。当引脚拉低时,执行芯片初始化。在正常操作期间,保持该引脚高(即连接到VDD)。
D/C#: 这是数据/命令控制引脚当它被拉高(即连接到VDD)时,D[7:0]处的数据被视为数当它被拉低时,D【7:0】处的数据将传输到命令寄存器。在I2C模式下,该引脚充当SA0,用于从地址选择。 选择3线串行接口时,该引脚必须连接到VSS。有关MCU接口信号的详细关系,请参阅时序特性图。
R/W#(WR#): 这是连接到MCU接口的读/写控制输入引脚。当连接到6800系列微处理器时,该引脚将用作读/写(R/W#)选择输入。当该引脚拉高(即连接到VDD)时,将执行读取模式;**当该引脚拉低时,将执行写入模式。**选择8080接口模式时,该引脚将作为写入(WR#)输入。当该引脚拉低并选择芯片时,启动数据写入操作。选择串行接口时,该引脚必须连接到VSS。
E(RD#): 当连接到6800系列微处理器时,该引脚将用作启用(E)信号。当该引脚拉高(即连接到VDD)并选择芯片时,启动读/写操作。当连接到8080系列微处理器时,该引脚接收读取(RD#)信号。当该引脚拉低并选择芯片时,启动读取操作。 选择串行接口时,该引脚必须连接到VSS。文章地址https://www.yii666.com/blog/354859.html
D【7:0】: 这些是连接到微处理器数据总线的8位双向数据总线。
IREF: 这是输出电流参考引脚。该引脚和VSS之间应连接一个电阻器,以将IREF电流保持在12.5 uA。
VCOMH: COM信号的引脚取消选择了电压电平。该引脚和VSS之间应连接电容器。
VCC: 输入电源电压引脚。
VLSS: 模拟接地引脚。它应该从外部连接到VSS。
14.2 OLED显示屏的驱动程序
首先拿到SSD1306驱动的显示屏后,接上GND,VCC,SCL,SDA四条线后,屏幕是不会自动亮起的,需要CPU写入数据驱动它显示。查阅SSD1306的手册,我们可以发现与其通信的方式。
I2C通信方式
因为IIC在前面我们详细的叙述过,这里就不再重复的介绍了,简单的分析就是,我们先将IIC的驱动程序写好,然后在去数据手册寻找显示屏如何具体的驱动。
//延时
void IIC_delay(void)
{
unsigned char t=1;
while(t--);
}
//起始信号
void I2C_Start(void)
{
OLED_SDA_Set();
OLED_SCL_Set();
IIC_delay();
OLED_SDA_Clr();
IIC_delay();
OLED_SCL_Clr();
}
//结束信号
void I2C_Stop(void)
{
OLED_SDA_Clr();
OLED_SCL_Set();
IIC_delay();
OLED_SDA_Set();
}
//等待信号响应
void I2C_WaitAck(void) //测数据信号的电平
{
OLED_SDA_Set();
IIC_delay();
OLED_SCL_Set();
IIC_delay();
OLED_SCL_Clr();
IIC_delay();
}
//写入一个字节
void Send_Byte(unsigned char dat)
{
unsigned char i;
for(i=0;i<8;i++)
{
OLED_SCL_Clr();//将时钟信号设置为低电平
if(dat&0x80)//将dat的8位从最高位依次写入
{
OLED_SDA_Set();
}
else
{
OLED_SDA_Clr();
}
IIC_delay();
OLED_SCL_Set();
IIC_delay();
OLED_SCL_Clr();
dat<<=1;
}
}
这是我们常规驱动IIC的方式
- 起始信号
- 写入芯片地址,一般为0x78或者0x7a
- 应答信号
- 写入控制命令(D/C#)选择数据模式(0x40)/控制模式(0x00)
- 应答信号
- 写入数据/控制字(一个字节)
- 应答信号
- 结束信号
//发送一个字节
//向SSD1306写入一个字节。
//mode:数据/命令标志 0,表示命令;1,表示数据;
void OLED_WR_Byte(unsigned char dat,unsigned char mode)
{
I2C_Start();
Send_Byte(0x78);
I2C_WaitAck();
if(mode){Send_Byte(0x40);}
else{Send_Byte(0x00);}
I2C_WaitAck();
Send_Byte(dat);
I2C_WaitAck();
I2C_Stop();
}
这里的)0X78或者0x7A是原理图决定的,我们查看原理图其实也不得而知,因为这个显示使用的比较多,多家厂商多次设计,所以我们先看一下那里决定的,翻到数据手册第二十页,可以看到如下。
在结合原理图我们再看中景园提供的原理图。
在这里我们可以看到控制D/C脚位的高低就可以完成地址的选择。
初始化SSD1306
由于一开始我们需要初始化SSD1306,因此我们需要先写入一些控制字来命令SSD1306的工作模式。鉴于指令集网上的手册中已有许多资料,这里不再赘述,只简单介绍一些用到的指令。
OLED_WR_Byte(0xAE,OLED_CMD);//开启OLED屏显示
以上是开启OLED屏关闭的命令,0xAE是控制字,OLED_CMD是写入命令。以下同理,便不再赘述
OLED_WR_Byte(0x00,OLED_CMD);//设置列起始地址的高位
OLED_WR_Byte(0x10,OLED_CMD);//设置列起始地址的低位
OLED_WR_Byte(0xB0,OLED_CMD);//设置目标显示位置页的起始地址
这三行代码用来控制显示的位置。这里就必须提一下SSD1306的内存地址模式,因为不同的模式下写入相同的数据输出的图像不同。SSD1306中有三种不同的内存地址模式:页地址模式,水平地址模式,垂直地址模式。这里我们使用的是页地址模式,其结构如下所示:
可以看到,这是128*64的显示屏,其中横向分为128段(列),竖向分为8页,每页有8行。选择页地址模式后,数据的填充如下所示:
在用户定义的地址写入数据后,列地址会自动加1,写完最后一列后,列地址指针会重置为列开始地址,而页地址不会变。至此,我们就很好理解上述的三行命令,其中0x00和0x10设置了列起始地址为0,而0xb0则设置了页起始地址为0。将上面代码结合我们一起写到初始化中:
//OLED的初始化
void OLED_Init(void)
{
OLED_WR_Byte(0xAE,OLED_CMD);//--打开OLED显示
OLED_WR_Byte(0x00,OLED_CMD);//---set low column address
OLED_WR_Byte(0x10,OLED_CMD);//---set high column address
OLED_WR_Byte(0x40,OLED_CMD);//--set start line address Set Mapping RAM Display Start Line (0x00~0x3F)
OLED_WR_Byte(0x81,OLED_CMD);//--设置显示的对比度(00H~FFH)
OLED_WR_Byte(0xFF,OLED_CMD); // 对比度为0xCF
OLED_WR_Byte(0xA1,OLED_CMD);//--Set SEG/Column Mapping 0xa0左右反置 0xa1正常
OLED_WR_Byte(0xC8,OLED_CMD);//Set COM/Row Scan Direction 0xc0上下反置 0xc8正常
OLED_WR_Byte(0xA6,OLED_CMD);//--设置正常显示
OLED_WR_Byte(0xA8,OLED_CMD);//--设置复用率(1~64)
OLED_WR_Byte(0x3f,OLED_CMD);//--1/64 duty
OLED_WR_Byte(0xD3,OLED_CMD);//-设置显示偏移 RAM Counter (0x00~0x3F)
OLED_WR_Byte(0x00,OLED_CMD);//-偏移量为0
OLED_WR_Byte(0xd5,OLED_CMD);//--设置时钟分频率(1~16),振荡器频率
OLED_WR_Byte(0x80,OLED_CMD);//--0~3位为时钟分频率(1),4~7位为振荡器频率(1000)
OLED_WR_Byte(0xD9,OLED_CMD);//--设置重充电周期
OLED_WR_Byte(0xF1,OLED_CMD);//Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
OLED_WR_Byte(0xDA,OLED_CMD);//--设置COM引脚硬件配置
OLED_WR_Byte(0x12,OLED_CMD);
OLED_WR_Byte(0xDB,OLED_CMD);//--设置Vcomh取消选择水平
OLED_WR_Byte(0x40,OLED_CMD);//Set VCOM Deselect Level
OLED_WR_Byte(0x20,OLED_CMD);//-Set Page Addressing Mode (0x00/0x01/0x02)
OLED_WR_Byte(0x02,OLED_CMD);//
OLED_WR_Byte(0x8D,OLED_CMD);//--set Charge Pump enable/disable
OLED_WR_Byte(0x14,OLED_CMD);//--set(0x10) disable
OLED_WR_Byte(0xA4,OLED_CMD);// Disable Entire Display On (0xa4/0xa5)
OLED_WR_Byte(0xA6,OLED_CMD);// Disable Inverse Display On (0xa6/a7)
OLED_Clear();
OLED_WR_Byte(0xAF,OLED_CMD); /*display ON*/
}
显示函数
这里我们直接上程序,先看程序在分析每个步骤的作用。
//坐标设置
void OLED_Set_Pos(unsigned char x, unsigned char y)
{
OLED_WR_Byte(0xb0+y,OLED_CMD);
OLED_WR_Byte(((x&0xf0)>>4)|0x10,OLED_CMD);
OLED_WR_Byte((x&0x0f),OLED_CMD);
}
//开启OLED显示
void OLED_Display_On(void)
{
OLED_WR_Byte(0X8D,OLED_CMD); //SET DCDC命令
OLED_WR_Byte(0X14,OLED_CMD); //DCDC ON
OLED_WR_Byte(0XAF,OLED_CMD); //DISPLAY ON
}
//关闭OLED显示
void OLED_Display_Off(void)
{
OLED_WR_Byte(0X8D,OLED_CMD); //SET DCDC命令
OLED_WR_Byte(0X10,OLED_CMD); //DCDC OFF
OLED_WR_Byte(0XAE,OLED_CMD); //DISPLAY OFF
}
//反显函数
void OLED_ColorTurn(unsigned char i)
{
if(i==0)
{
OLED_WR_Byte(0xA6,OLED_CMD);//正常显示
}
if(i==1)
{
OLED_WR_Byte(0xA7,OLED_CMD);//反色显示
}
}
//屏幕旋转180度
void OLED_DisplayTurn(unsigned char i)
{
if(i==0)
{
OLED_WR_Byte(0xC8,OLED_CMD);//正常显示
OLED_WR_Byte(0xA1,OLED_CMD);
}
if(i==1)
{
OLED_WR_Byte(0xC0,OLED_CMD);//反转显示
OLED_WR_Byte(0xA0,OLED_CMD);
}
}
//清屏函数,清完屏,整个屏幕是黑色的!和没点亮一样!!!
void OLED_Clear(void)
{
unsigned char i,n;
for(i=0;i<8;i++)
{
OLED_WR_Byte (0xb0+i,OLED_CMD); //设置页地址(0~7)
OLED_WR_Byte (0x00,OLED_CMD); //设置显示位置—列低地址
OLED_WR_Byte (0x10,OLED_CMD); //设置显示位置—列高地址
for(n=0;n<128;n++)OLED_WR_Byte(0,OLED_DATA);
} //更新显示
}
//在指定位置显示一个字符,包括部分字符
//x:0~127
//y:0~63
//sizey:选择字体 6x8 8x16
void OLED_ShowChar(unsigned char x,unsigned char y,unsigned char chr,unsigned char sizey)
{
unsigned char c=0,sizex=sizey/2;
unsigned int i=0,size1;
if(sizey==8)size1=6;
else size1=(sizey/8+((sizey%8)?1:0))*(sizey/2);
c=chr-' ';//得到偏移后的值
OLED_Set_Pos(x,y);
for(i=0;i<size1;i++)
{
if(i%sizex==0&&sizey!=8) OLED_Set_Pos(x,y++);
if(sizey==8) OLED_WR_Byte(asc2_0806[c][i],OLED_DATA);//6X8字号
else if(sizey==16) OLED_WR_Byte(asc2_1608[c][i],OLED_DATA);//8x16字号
// else if(sizey==xx) OLED_WR_Byte(asc2_xxxx[c][i],OLED_DATA);//用户添加字号
else return;
}
}
//m^n函数
unsigned int oled_pow(unsigned char m,unsigned char n)
{
unsigned int result=1;
while(n--)result*=m;
return result;
}
//显示数字
//x,y :起点坐标
//num:要显示的数字
//len :数字的位数
//sizey:字体大小
void OLED_ShowNum(unsigned char x,unsigned char y,unsigned int num,unsigned char len,unsigned char sizey)
{
unsigned char t,temp,m=0;
unsigned char enshow=0;
if(sizey==8)m=2;
for(t=0;t<len;t++)
{
temp=(num/oled_pow(10,len-t-1))%10;
if(enshow==0&&t<(len-1))
{
if(temp==0)
{
OLED_ShowChar(x+(sizey/2+m)*t,y,' ',sizey);
continue;
}else enshow=1;
}
OLED_ShowChar(x+(sizey/2+m)*t,y,temp+'0',sizey);
}
}
//显示一个字符号串
void OLED_ShowString(unsigned char x,unsigned char y,unsigned char *chr,unsigned char sizey)
{
unsigned char j=0;
while (chr[j]!='\0')
{
OLED_ShowChar(x,y,chr[j++],sizey);
if(sizey==8)x+=6;
else x+=sizey/2;
}
}
//显示汉字
void OLED_ShowCHinese(unsigned char x,unsigned char y,unsigned char *p)
{
unsigned char t,wordNum;
while(*p != '\0')
{
for(wordNum=0;wordNum<20;wordNum++)
{
if(Hzk[wordNum].Char[0]== *p && Hzk[wordNum].Char[1]== *(p+1))
{
OLED_Set_Pos(x,y);
for(t=0;t<16;t++){OLED_WR_Byte(Hzk[wordNum].Hex[t],1);}
OLED_Set_Pos(x,y+1);
for(t=0;t<16;t++){OLED_WR_Byte(Hzk[wordNum].Hex[t+16],1);}
break;
}
}
p += 2;
x+=16;
}
}
//显示图片
//x,y显示坐标
//sizex,sizey,图片长宽
//BMP:要显示的图片
void OLED_DrawBMP(unsigned char x,unsigned char y,unsigned char sizex, unsigned char sizey,unsigned char BMP[])
{
unsigned int j=0;
unsigned char i,m;
sizey=sizey/8+((sizey%8)?1:0);
for(i=0;i<sizey;i++)
{
OLED_Set_Pos(x,i+y);
for(m=0;m<sizex;m++)
{
OLED_WR_Byte(BMP[j++],OLED_DATA);
}
}
}
14.3 取模工具的使用
上述的显示汉字等都需要自己取模,或者使用标准的字库去实现因为STC89C52单片机的内存有限,所以我们直接取我们需要的话汉字模型。
汉字取模
首先打开软件设置我们的取模方式
打开设置之后我们就可以操作了
设置完成后,点击左下角的确认按钮,然后就可以再点击生成字模,即可完成。
再把这个复制到程序里面如下图所示
我们对原来厂商提供的程序进行了修改,这样的方式更加的方便。然后在主函数调用即可。
OLED_ShowCHinese(0,0,"特纳斯");
图片取模
打开取模软件
2.1 点击模式,选择图形模式
2.2 点击文件,打开要取模的图片
2.3点击选项,进行设置图片设置如下,然后点击确定。
3.最后点击生成字模。
4.将生成的字模复制到bmp.h的数组中