OLED
简介
OLED
,即有机发光二极管(
Organic Light-Emitting Diode
),又称为有机电激光显示(
Organic
Electroluminesence Display
,
OELD
)。
OLED
由于同时具备自发光,不需背光源、对比度高、
厚度薄、视角广、反应速度快、可用于挠曲性面板、使用温度范围广、构造及制程较简单等优
异之特性,被认为是下一代的平面显示器新兴应用技术。
LCD
都需要背光,而
OLED
不需要,因为它是自发光的。这样同样的显示,
OLED
效果要
来得好一些。以目前的技术,
OLED
的尺寸还难以大型化,但是分辨率确可以做到很高。在本
章中,我们使用的是
ALINETEK
的
OLED
显示模块,该模块有以下特点:
1
)模块有单色和双色两种可选,单色为纯蓝色,而双色则为黄蓝双色。
2
)尺寸小,显示尺寸为
0.96
寸,而模块的尺寸仅为
27mm*26mm
大小。
3
)高分辨率,该模块的分辨率为
128*64。
(这里注意128和64单位均为:bit)。
4
)多种接口方式,该模块提供了总共
4
种接口包括:
6800
、
8080
两种并行接口方式、
4
线
SPI
接口方式以及
IIC
接口方式(只需要
2
根线就可以控制
OLED
了!)。
5
)不需要高压,直接接
3.3V
就可以工作了。(接5V容易烧毁)
OLED的四种模式:
买回来的一般都是BS1 和BS2都接的VCC,如果你要更还模式,需要用电烙铁进行修改。
8080并行通信:
8080
并行接口的发明者是
INTEL
,该总线也被
广泛应用于各类液晶显示器,
ALIENTEK OLED
模块也提供了这种接口,使得
MCU
可以快速
的访问
OLED
。
ALIENTEK OLED
模块的
8080
接口方式需要如下一些信号线:
CS
:
OLED
片选信号。
WR
:向
OLED
写入数据。
RD
:从
OLED
读取数据。
D[7
:
0]
:
8
位双向数据线。
RST(RES)
:硬复位
OLED
。
DC
:命令
/
数据标志(
0
,读写命令;
1
,读写数据)。
模块的
8080
并口读
/
写的过程为:先根据要写入
/
读取的数据的类型,设置
DC
为高(数据)
/
低(命令),然后拉低片选,选中
SSD1306
,接着我们根据是读数据,还是要写数据置
RD/WR
为低,然后:
在
RD
的上升沿, 使数据锁存到数据线(
D[7
:
0]
)上;
在
WR
的上升沿,使数据写入到
SSD1306
里面;
SPI串行通信:
CS
:
OLED
片选信号。
RST(RES)
:硬复位
OLED
。
DC
:命令
/
数据标志(
0
,读写命令;
1
,读写数据)。
SCLK
:串行时钟线。在
4
线串行模式下,
D0
信号线作为串行时钟线
SCLK
。
SDIN
:串行数据线。在
4
线串行模式下,
D1
信号线作为串行数据线
SDIN
。
模块的
D2
需要悬空,其他引脚可以接到
GND
。在
4
线串行模式下,只能往模块写数据而
不能读数据。
在
4
线
SPI
模式下,每个数据长度均为
8
位,在
SCLK
的上升沿,数据从
SDIN
移入到
SSD1306
,并且是高位在前的。
DC
线还是用作命令
/
数据的标志线。
模块显存:SSD1306
的显存总共为
128*64bit
大小,SSD1306将这些显存分为8页,每一页都是128字节,也就是128*8bit,总共就是128*64bit。
而OLED显示的原理也就是在这些每个位选择(1/0)的问题了。所以我们在OLED内部建立一个GRAM(128*8字节),每次修改的时候直接修改GRAM的数值就可以了,修改完后直接一次性进行更改。
SSD1306的命令介绍:
第一个命令就是设置屏幕的对比度,对比度越大,屏幕越亮。但是这个指令包括了两个字节 ,第一个发送的是命令,第二个才是设定的数值
OLED_WR_Byte(0x81,OLED_CMD); //对比度设置
OLED_WR_Byte(0xEF,OLED_CMD); //1~255;默认0X7F (亮度设置,越大越亮)
第二个命令为
0XAE/0XAF
。
0XAE
为关闭显示命令;
0XAF
为开启显示命令。
OLED_WR_Byte(0xAE,OLED_CMD); //关闭显示
OLED_WR_Byte(0xAF,OLED_CMD); //开启显示
第三个命令为
0X8D
,该指令也包含
2
个字节,第一个为命令字,第二个为设置值,第二
个字节的
BIT2
表示电荷泵的开关状态,该位为
1
,则开启电荷泵,为
0
则关闭。在模块初始化
的时候,这个必须要开启,否则是看不到屏幕显示的。、
//开启OLED显示
void OLED_Display_On(void)
{
//开启电荷泵(命令)
OLED_WR_Byte(0X8D,OLED_CMD);
//开启电荷泵的具体设置 0X14 = 10100
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命令
//关闭电荷泵的具体设置 0X10 = 10000
OLED_WR_Byte(0X10,OLED_CMD); //DCDC OFF
//关闭屏显
OLED_WR_Byte(0XAE,OLED_CMD); //DISPLAY OFF
}
这里的命令和设置分开,我的理解是:类似于排长给队长下达了一个命令(攻破碉堡),然后队长还要进行一个具体的实施。
第四个命令为
0XB0~B7
,该命令用于设置页地址,其低三位的值对应着
GRAM
的页地址。
第五个指令为
0X00~0X0F
,该指令用于设置显示时的起始列地址低四位。
第六个指令为
0X10~0X1F
,该指令用于设置显示时的起始列地址高四位。
io口的配置(8080)
#define OLED_CS PCout(9) //片选信号
#define OLED_RS PCout(8) //硬复位
#define OLED_WR PCout(7) //写数据到OLED
#define OLED_RD PCout(6) //读取OLED数据
//PB0~7,作为数据线
#define DATAOUT(x) GPIOB->ODR=(GPIOB->ODR&0xff00)|(x&0x00FF); //输出
这里的DATAOUT(X)就是将命令的八位字节看哪些位需要置高。
选择相应的通信模式
8080通信模式:
//向SSD1306写入一个字节。
//dat:要写入的数据/命令
//cmd:数据/命令标志 0,表示命令;1,表示数据;
void OLED_WR_Byte(u8 dat,u8 cmd)
{
//将命令传入后将对应的位拉高(推挽输出模式)
DATAOUT(dat);
//选择是否是命令或者数据
OLED_RS=cmd;
//拉低片选
OLED_CS=0;
//WR上升沿的情况下,将数据写入到SSD1306
OLED_WR=0;
OLED_WR=1;
OLED_CS=1;
//硬复位
OLED_RS=1;
}
SPI通信模式:
//向SSD1306写入一个字节。
//dat:要写入的数据/命令
//cmd:数据/命令标志 0,表示命令;1,表示数据;
void OLED_WR_Byte(u8 dat,u8 cmd)
{
u8 i;
OLED_RS=cmd; //写命令
//拉低片选
OLED_CS=0;
//因为一个命令是低八位
for(i=0;i<8;i++)
{
//拉低时钟线(才可以对数据进行改变)
OLED_SCLK=0;
//判断第八位是否为1,对数据线进行调整
if(dat&0x80)OLED_SDIN=1;
else OLED_SDIN=0;
//拉高时钟线
OLED_SCLK=1;
//将刚才的低八位左移一位,后面补零
dat<<=1;
}
OLED_CS=1;
OLED_RS=1;
}
初始化OLED(选择通信的条件是OLED_MODE == 1)
在这个代码中已经选择了8080。
//初始化SSD1306
void OLED_Init(void)
{
RCC->APB2ENR|=1<<3; //使能PORTB时钟
RCC->APB2ENR|=1<<4; //使能PORTC时钟
#if OLED_MODE==1 //使用8080并口模式
JTAG_Set(SWD_ENABLE);
GPIOB->CRL=0X33333333;
GPIOB->ODR|=0XFFFF;
GPIOC->CRH&=0XFFFFFF00;
GPIOC->CRL&=0X00FFFFFF;
GPIOC->CRH|=0X00000033;
GPIOC->CRL|=0X33000000;
GPIOC->ODR|=0X03C0;
#else //使用4线SPI 串口模式
GPIOB->CRL&=0XFFFFFF00;
GPIOB->CRL|=0XF0000033;
GPIOB->ODR|=0X03;
GPIOC->CRH&=0XFFFFFF00;
GPIOC->CRH|=0X00000033;
GPIOC->ODR|=3<<8;
#endif
//OLED_RST=0;
//delay_ms(100);
//OLED_RST=1;
OLED_WR_Byte(0xAE,OLED_CMD); //关闭显示
OLED_WR_Byte(0xD5,OLED_CMD); //设置时钟分频因子,震荡频率
OLED_WR_Byte(80,OLED_CMD); //[3:0],分频因子;[7:4],震荡频率
OLED_WR_Byte(0xA8,OLED_CMD); //设置驱动路数
OLED_WR_Byte(0X3F,OLED_CMD); //默认0X3F(1/64)
OLED_WR_Byte(0xD3,OLED_CMD); //设置显示偏移
OLED_WR_Byte(0X00,OLED_CMD); //默认为0
OLED_WR_Byte(0x40,OLED_CMD); //设置显示开始行 [5:0],行数.
OLED_WR_Byte(0x8D,OLED_CMD); //电荷泵设置
OLED_WR_Byte(0x14,OLED_CMD); //bit2,开启/关闭
OLED_WR_Byte(0x20,OLED_CMD); //设置内存地址模式
OLED_WR_Byte(0x02,OLED_CMD); //[1:0],00,列地址模式;01,行地址模式;10,页地址模式;默认10;
OLED_WR_Byte(0xA1,OLED_CMD); //段重定义设置,bit0:0,0->0;1,0->127;
OLED_WR_Byte(0xC0,OLED_CMD); //设置COM扫描方向;bit3:0,普通模式;1,重定义模式 COM[N-1]->COM0;N:驱动路数
OLED_WR_Byte(0xDA,OLED_CMD); //设置COM硬件引脚配置
OLED_WR_Byte(0x12,OLED_CMD); //[5:4]配置
OLED_WR_Byte(0x81,OLED_CMD); //对比度设置
OLED_WR_Byte(0xEF,OLED_CMD); //1~255;默认0X7F (亮度设置,越大越亮)
OLED_WR_Byte(0xD9,OLED_CMD); //设置预充电周期
OLED_WR_Byte(0xf1,OLED_CMD); //[3:0],PHASE 1;[7:4],PHASE 2;
OLED_WR_Byte(0xDB,OLED_CMD); //设置VCOMH 电压倍率
OLED_WR_Byte(0x30,OLED_CMD); //[6:4] 000,0.65*vcc;001,0.77*vcc;011,0.83*vcc;
OLED_WR_Byte(0xA4,OLED_CMD); //全局显示开启;bit0:1,开启;0,关闭;(白屏/黑屏)
OLED_WR_Byte(0xA6,OLED_CMD); //设置显示方式;bit0:1,反相显示;0,正常显示
OLED_WR_Byte(0xAF,OLED_CMD); //开启显示
OLED_Clear();
}
OLED_ShowString(x,y,"字符串",字节大小);
x,y指的是第一个bit的坐标,字节大小有12,16,24,字符大小为汉字的一半(高不变,宽变为1/2)
//显示字符串
//x,y:起点坐标
//size:字体大小
//*p:字符串起始地址
void OLED_ShowString(u8 x,u8 y,const u8 *p,u8 size)
{
while((*p<='~')&&(*p>=' ')) //判断是不是非法字符!
{
//当x超出范围,就进行换行(行高为字节大小)
if(x>(128-(size/2))){x=0;y+=size;}
//当y超出范围的时候,将会清屏
if(y>(64-size)){y=x=0;OLED_Clear();}
//将字符串转化为单个的字符
OLED_ShowChar(x,y,*p,size,1);
//展现一个字符就往后移动一个字节区域的宽度
x+=size/2;
//指针后移一位,指向下一个字符
p++;
}
}
void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size,u8 mode)
{
u8 temp,t,t1;
u8 y0=y;
u8 csize=(size/8+((size%8)?1:0))*(size/2); //得到字体一个字符对应点阵集所占的字节数
//减去空格,是因为数值的第零位存放的都是空格
chr=chr-' ';//得到偏移后的值
for(t=0;t<csize;t++)
{
if(size==12)temp=asc2_1206[chr][t]; //调用1206字体
else if(size==16)temp=asc2_1608[chr][t]; //调用1608字体
else if(size==24)temp=asc2_2412[chr][t]; //调用2412字体
else return; //没有的字库
//因为一个字节都是低八位
for(t1=0;t1<8;t1++)
{
//判断每一位是否为1,若为1,则进行填充,反之,则不
if(temp&0x80)OLED_DrawPoint(x,y,mode);
else OLED_DrawPoint(x,y,!mode);
//左移一位,将低位往上移动
temp<<=1;
//因为是列排列,所以是自上而下的连续八位,y++就是在起点的基础上进行下移,
//当超过你设定的字节高度时候(12,16,24),且退出循环,
//将会切换到下一列,继续从头开始
y++;
if((y-y0)==size)
{
y=y0;
x++;
break;
}
}
}
}
数组开头均是空格,当然我们也可以添加新的数组里面存放不同的东西。
const unsigned char asc2_1206[95][12]={
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*" ",0*/
const unsigned char asc2_1608[95][16]={
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*" ",0*/
nst unsigned char asc2_2412[95][36]={
{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},/*" ",0*/
字模配置
//画点
//x:0~127
//y:0~63
//t:1 填充 0,清空
void OLED_DrawPoint(u8 x,u8 y,u8 t)
{
u8 pos,bx,temp=0;
if(x>127||y>63)return;//超出范围了.
pos=7-y/8;
bx=y%8;
temp=1<<(7-bx);
//对相应的位置为1
if(t)OLED_GRAM[x][pos]|=temp;
else OLED_GRAM[x][pos]&=~temp;
}
一个通用的在点(x,
y
)置
1
表达式为: OLED_GRAM[x][7-y/8]|=1<<(7-y%8);