OLED简介:
OLED,全称为Organic Light Emitting Diode,即有机发光二极管。它是一种新型的显示技术,与传统的LCD(液晶显示器)相比,OLED具有更快的响应速度、更高的对比度、更广的可视角度、更低的功耗等优点。
OLED的显示原理是利用有机材料在电场的作用下发光,它可以在每个像素点上独立控制亮度和颜色。因此,OLED可以实现更加细腻的图像和更加鲜艳的色彩效果。与此同时,OLED还具有更加薄、柔性和可制造性等优点,可以应用于各种场合,如智能手机、电视、车载显示器、手表、智能家居等。
OLED的缺点是寿命短、成本高、易受光照影响等。最重要的是现在还不能做出性能很好的大屏OLED显示屏
至于OLED原理嘛,我就不介绍了,可以参考部分OLED手册。
我用的是0.96 寸 OLED 显示屏,4脚IIC通讯模块。
OLED代码修改:
本人在中景园代码基础上做了微小的调整。
void IIC_Start()
{
OLED_SCLK_Set() ;
OLED_SDIN_Set();
OLED_SDIN_Clr();
OLED_SCLK_Clr();
}
void IIC_Stop()
{
OLED_SCLK_Set() ;
OLED_SCLK_Clr();
OLED_SDIN_Clr();
OLED_SDIN_Set();
}
void IIC_Wait_Ack()
{
//GPIOB->CRH &= 0XFFF0FFFF; //设置PB12为上拉输入模式
//GPIOB->CRH |= 0x00080000;
// OLED_SDA = 1;
// delay_us(1);
//OLED_SCL = 1;
//delay_us(50000);
/* while(1)
{
if(!OLED_SDA) //判断是否接收到OLED 应答信号
{
//GPIOB->CRH &= 0XFFF0FFFF; //设置PB12为通用推免输出模式
//GPIOB->CRH |= 0x00030000;
return;
}
}
*/
OLED_SCLK_Set();
OLED_SCLK_Clr();
}
void Write_IIC_Byte(unsigned char IIC_Byte)
{
unsigned char i;
unsigned char m,da;
da=IIC_Byte;
OLED_SCLK_Clr();
for(i=0;i<8;i++)
{
m=da;
//OLED_SCLK_Clr();
m=m&0x80;
if(m==0x80)
{OLED_SDIN_Set();}
else OLED_SDIN_Clr();
da=da<<1;
OLED_SCLK_Set();
OLED_SCLK_Clr();
}
}
void Write_IIC_Command(unsigned char IIC_Command)
{
IIC_Start();
Write_IIC_Byte(0x78); //Slave address,SA0=0
IIC_Wait_Ack();
Write_IIC_Byte(0x00); //write command
IIC_Wait_Ack();
Write_IIC_Byte(IIC_Command);
IIC_Wait_Ack();
IIC_Stop();
}
void Write_IIC_Data(unsigned char IIC_Data)
{
IIC_Start();
Write_IIC_Byte(0x78); //D/C#=0; R/W#=0
IIC_Wait_Ack();
Write_IIC_Byte(0x40); //write data
IIC_Wait_Ack();
Write_IIC_Byte(IIC_Data);
IIC_Wait_Ack();
IIC_Stop();
}
首先为了方便,把IIC这些函数全部用一个IIC.c和IIC.h封装,不将其放到oled.c里。其次,IIC_Start,IIC_Stop,Write_IIC_Byte,IIC_Wait_Ack这些函数做修改。
空闲状态下:SDA和SCL全为高电平。
开始信号:当SCL为高电平时,经过SDA由高到低的跳变,加上5us延时函数,表示数据传输的开始。
停止信号:当SCL为高电平时,经过SDA由低到高的跳变,加上5us延时函数,表示数据的结束。
注意:为啥我这里要加5us延时函数?这是因为我选择了标准模式(100Kb/s),所以周期为10us,开始和结束函数里面的延时为1个周期,这样感觉更加直观。毕竟是软件模拟嘛!
当然用官方写的也没问题。
接下来介绍如何写字节
或
void Write_IIC_Byte(uchar byte)
{
uchar i;
for(i = 0;i < 8;i++)
{
//
byte <<= 1;
IIC_SDA = CY;
/*
IIC_SDA = (byte & (0x80 >> i));
*/
IIC_SCL = 1;
Delay5us();
IIC_SCL = 0;
Delay5us();
}
}
数据传输:
在I2C总线上,数据传输是通过IIC_SDA引脚上的电平变化来实现的,而时钟同步则是通过IIC_SCL引脚上的电平变化来实现的。每当IIC_SCL引脚电平从低电平变为高电平时,IIC_SDA引脚上的数据才会被主机读取或写入(主机接收数据前必须要释放SDA) 。
且根据时序图可知,当SCL为高电平时SDA的数据必须要保持稳定(读取或写入),SCL为低电平时,此时SDA的数据才能有机会跳变。
由于在IIC通讯中,每一个字节的传输,传输的数据是从高位到低位依次传输的,即先传输最高位,最后传输最低位。因此,在将一个字节的数据写入IIC_SDA线中时,需要将最高位的数据先写入,才能保证数据的正确性,这个必须要了解!
所以在for循环里,
1、首先我们将发送的字节左移一位,将CY寄存器的值(也就是最高位)写入IIC_SDA引脚上。根据图,将SCL拉高半个周期(5us)此时是写入这个位,然后SCL拉低半个周期(5us),8次后从机控制应答,告诉主机是否成功读取数据。
2、也可按照第二种方法。图中说明非常清楚
至于应答,是在第九个周期里判断(假设就写入1个字节),将SCL拉高半个周期(5us),此时是读取这个字节,然后SCL拉低半个周期(5us)。
void IIC_Wait_Ack()
{
IIC_SCL = 1;
Delay5us();
IIC_SCL = 0;
Delay5us();
}
其实IIC里应该还有接收字节的函数,OLED这里没有用到,原因是因为我们不需要OLED读取数据,并触发额外的业务,比如在蓝桥杯单片机中,要是读取光敏电阻和电位器上的电压,这个时候要调用Read_IIC_Byte()这个函数。
uchar Read_IIC_Byte()
{
uchar i = 0;
uchar byte = 0x00;
IIC_SDA = 1;
for(i = 0;i < 8;i++)
{
IIC_SCL = 1;//
if(IIC_SDA)
{
byte |= 0x80 >> i;
}
IIC_SCL = 0;
}
return byte;
}
解释一下,这里很巧妙,请看,我将要读取的字节设成byte,并让它初始化为0x00,之后我通过八次扫描,一旦读取的数据线内的数据为1,那么我就将byte相对应的那一位变成1,因此经过八次循环我们最终的byte就是我们读到的真实数据。
有的朋友发现为啥要加IIC_SDA = 1;这句话?具体来说,IIC_SDA = 1语句是将SDA信号置为1,表示当前总线空闲,准备接收数据。同时,在读取数据的过程中,如果IIC_SDA = 1,表示从器件返回的数据位为1,主机需要将SDA置1,表示已经读取完了这个字节,可以进行下一个数据的读取了。
OLED代码分析:
IIC
void Write_IIC_Command(unsigned char IIC_Command)
{
IIC_Start();
Write_IIC_Byte(0x78);
IIC_Wait_Ack();
Write_IIC_Byte(0x00);
IIC_Wait_Ack();
Write_IIC_Byte(IIC_Command);
IIC_Wait_Ack();
IIC_Stop();
}
void Write_IIC_Data(unsigned char IIC_Data)
{
IIC_Start();
Write_IIC_Byte(0x78);
IIC_Wait_Ack();
Write_IIC_Byte(0x40);
IIC_Wait_Ack();
Write_IIC_Byte(IIC_Data);
IIC_Wait_Ack();
IIC_Stop();
}
Write_IIC_Command函数:
首先调用IIC_Start函数发送IIC总线的开始信号,数据开始发送。
然后向从设备发送地址字节0x78,表示要写入数据到从设备。在这个地址字节中,SA0=0,表示从设备的地址为0x78。然后,向从设备发送命令写入标志位0x00,表示接下来要写入的是一个命令而不是数据。
接着,向从设备发送要写入的IIC_Command命令数据。
最后,发送IIC总线的停止信号,表示数据停止发送
Write_IIC_Data函数:
首先调用IIC_Start函数发送起始信号,数据开始发送。
调用Write_IIC_Byte函数向I2C总线上写入从机地址和写数据命令,从机地址为0x78,写数据命令为0x40.
调用Write_IIC_Byte函数向I2C总线上写入要发送的数据(IIC_Data)。
调用IIC_Wait_Ack函数等待从机的应答信号。
调用IIC_Stop函数发送停止信号(Stop)。
OLED
void OLED_WR_Byte(unsigned dat,unsigned cmd)
{
if(cmd)
{
Write_IIC_Data(dat);
}
else
{
Write_IIC_Command(dat);
}
}
OLED_WR_Byte函数: 作用是通过IIC总线向OLED屏幕发送数据或命令。
参数dat表示要发送的数据,参数cmd表示数据类型,如果cmd为1,则表示要发送的是数据;如果cmd为0,则表示要发送的是命令。
在发送命令时,首先通过OLED_WR_Byte函数将命令类型设置为0,然后将具体的命令数据发送给OLED屏幕;在发送数据时,将数据类型设置为1,然后将具体的数据发送给OLED屏幕。
void fill_picture(unsigned char fill_Data)
{
unsigned char m,n;
for(m=0;m<8;m++)
{
OLED_WR_Byte(0xb0+m,0);
OLED_WR_Byte(0x00,0);
OLED_WR_Byte(0x10,0);
for(n=0;n<128;n++)
{
OLED_WR_Byte(fill_Data,1);
}
}
}
fill_picture函数:作用是将整个屏幕都填充为指定的数据。
使用for循环,循环8次,其中m表示页地址,范围为0到7。
在每次循环中,通过OLED_WR_Byte函数先向OLED发送页地址,然后向OLED发送列地址的低八位和高八位,从而确定每次循环要填充的位置。
使用for循环,循环128次,其中n表示列地址,范围为0到127。
在每次循环中,向OLED发送指定的填充数据fill_Data。
因此这种方式可以将整个屏幕都填充为指定的数据,从而清空屏幕或者初始化屏幕。
#define OLED_CMD 0 //写命令
//坐标设置
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
}
OLED_Set_Pos函数:作用是设置OLED屏幕上要显示的位置。即向OLED发送页地址和列地址的低八位和高八位,从而确定要显示的位置。
调用OLED_WR_Byte函数向OLED发送页地址,地址值为0xb0 + y,其中y表示页地址。
调用OLED_WR_Byte函数向OLED发送列地址的高八位,地址值为((x & 0xf0) >> 4) | 0x10,其中x表示列地址。
调用OLED_WR_Byte函数向OLED发送列地址的低八位,地址值为(x & 0x0f)。
OLED_Display_On函数:作用是开启OLED显示。
调用OLED_WR_Byte函数向OLED发送SET DCDC命令,地址值为0X8D。
调用OLED_WR_Byte函数向OLED发送DCDC ON命令,地址值为0X14。
调用OLED_WR_Byte函数向OLED发送DISPLAY ON命令,地址值为0XAF。
OLED_Display_Off函数:作用是关闭OLED显示。:
调用OLED_WR_Byte函数向OLED发送SET DCDC命令,地址值为0X8D。
调用OLED_WR_Byte函数向OLED发送DCDC OFF命令,地址值为0X10。
调用OLED_WR_Byte函数向OLED发送DISPLAY OFF命令,地址值为0XAE。
//清屏函数,清完屏,整个屏幕是黑色的!和没点亮一样!!!
void OLED_Clear(void)
{
u8 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);
} //更新显示
}
void OLED_On(void)
{
u8 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(1,OLED_DATA);
} //更新显示
}
OLED_Clear函数:作用是清空屏幕,将整个屏幕都填充为0,从而实现清屏效果。
调用for循环,循环8次,其中i表示页地址,范围为0到7。
在每次循环中,通过OLED_WR_Byte函数先向OLED发送页地址,然后向OLED发送列地址的低八位和高八位,从而确定每次循环要清空的位置。
调用for循环,循环128次,其中n表示列地址,范围为0到127。
在每次循环中,向OLED发送0,表示填充为黑色。
循环结束后,屏幕灭掉。
OLED_On函数:作用是将整个屏幕点亮,将整个屏幕都填充为1,从而实现屏幕点亮效果。
调用for循环,循环8次,其中i表示页地址,范围为0到7。
在每次循环中,通过OLED_WR_Byte函数先向OLED发送页地址,然后向OLED发送列地址的低八位和高八位,从而确定每次循环要填充的位置。
调用for循环,循环128次,其中n表示列地址,范围为0到127。
在每次循环中,向OLED发送1,表示填充为亮色。
循环结束后,屏幕整个被点亮,更新显示。
void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 Char_Size)
{
unsigned char c=0,i=0;
c=chr-' ';//得到偏移后的值
if(x > Max_Column-1){x = 0;y = y+2;}
if(Char_Size ==16)
{
OLED_Set_Pos(x,y);
for(i=0;i<8;i++)
OLED_WR_Byte(F8X16[c*16+i],OLED_DATA);
OLED_Set_Pos(x,y+1);
for(i=0;i<8;i++)
OLED_WR_Byte(F8X16[c*16+i+8],OLED_DATA);
}
else
{
OLED_Set_Pos(x,y);
for(i=0;i<6;i++)
OLED_WR_Byte(F6x8[c][i],OLED_DATA);
}
}
OLED_ShowChar函数: 作用在OLED屏幕上显示一个字符,支持两种字体大小:8x16和6x8。
x:横坐标 y:纵坐标 chr:显示的字符 Char_Size:字体大小。
计算出偏移后的字符值,即将ASCII码转化为F8X16或者F6x8字库中的索引值。
判断字符显示位置是否超出屏幕范围,如果超出则调整显示位置。
根据选择的字体大小,使用OLED_Set_Pos函数设置显示位置。
使用for循环,循环8次或者6次,向OLED发送对应的字符数据,以实现字符的显示。其中,F8X16和F6x8分别是8x16和6x8字体的字库数组。
u32 oled_pow(u8 m,u8 n)
{
u32 result=1;
while(n--) result*=m;
return result;
}
void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size2)
{
u8 t,temp;
u8 enshow=0;
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 + (size2 / 2) * t,y,' ',size2);
continue;
}else enshow = 1;
}
OLED_ShowChar(x + (size2 / 2) * t,y,temp + '0',size2);
}
}
OLED_ShowNum函数: 作用是在OLED屏幕上显示一个数字
x:横坐标 y:纵坐标 len :数字的位数 num:数值(0~4294967295) 比如我写123,就是3位数,对应len为3。 size2:还是字的大小,选16。
使用for循环,循环len次,其中t表示数字的位数。
计算出每一位数字的值,具体方法是将num除以10的len-t-1次方后取余数。
判断是否需要填充空格。当第一次出现非零数字后,后面的数字不再填充空格。
使用OLED_ShowChar函数,分别在不同位置显示每一位数字。
void OLED_ShowString(u8 x,u8 y,u8 *chr,u8 Char_Size)
{
unsigned char j=0;
while (chr[j] != '\0')
{ OLED_ShowChar(x,y,chr[j],Char_Size);
x += 8;
if(x > 120){x = 0;y += 2;}
j++;
}
}
void OLED_ShowCHinese(u8 x,u8 y,u8 no)
{
u8 t,adder=0;
OLED_Set_Pos(x,y);
for(t = 0;t < 16;t++)
{
OLED_WR_Byte(Hzk[2*no][t],OLED_DATA);
adder += 1;
}
OLED_Set_Pos(x,y+1);
for(t=0;t<16;t++)
{
OLED_WR_Byte(Hzk[2*no+1][t],OLED_DATA);
adder+=1;
}
}
OLED_ShowString函数: 作用是在指定位置显示一个字符串。
x:横坐标 y:纵坐标 *chr:(很明显)是常量字符串的首地址,所以这个参数写要发送的字符串 Char_Size:字的大小,选16就好(因为是16*16),也可以改。
使用while循环,将每个字符都显示出来。
使用OLED_ShowChar函数,在不同位置显示每个字符。
每个字符之间的间隔为8个列,如果当前行显示不下,则换行,调整显示位置。
其中,chr表示要显示的字符串,Char_Size表示字符的字体大小。
OLED_ShowChinese函数: 作用是在指定位置显示一个汉字
x:横坐标 y:纵坐标 no:第几个字 (0~7)
使用OLED_Set_Pos函数设置显示位置。
使用for循环,循环16次,分别向OLED发送汉字数据。
其中,no表示要显示的汉字在HZK数组中的索引值,HZK数组存储了所有汉字的字库数据。
每个汉字占用2行16列,如果当前行显示不下,则换行,调整显示位置。
void OLED_Init(void)
{
OLED_WR_Byte(0xAE,OLED_CMD);//--display off
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
OLED_WR_Byte(0xB0,OLED_CMD);//--set page address
OLED_WR_Byte(0x81,OLED_CMD); // contract control
OLED_WR_Byte(0xFF,OLED_CMD);//--128
OLED_WR_Byte(0xA1,OLED_CMD);//set segment remap
OLED_WR_Byte(0xA6,OLED_CMD);//--normal / reverse
OLED_WR_Byte(0xA8,OLED_CMD);//--set multiplex ratio(1 to 64)
OLED_WR_Byte(0x3F,OLED_CMD);//--1/32 duty
OLED_WR_Byte(0xC8,OLED_CMD);//Com scan direction
OLED_WR_Byte(0xD3,OLED_CMD);//-set display offset
OLED_WR_Byte(0x00,OLED_CMD);//
OLED_WR_Byte(0xD5,OLED_CMD);//set osc division
OLED_WR_Byte(0x80,OLED_CMD);//
OLED_WR_Byte(0xD8,OLED_CMD);//set area color mode off
OLED_WR_Byte(0x05,OLED_CMD);//
OLED_WR_Byte(0xD9,OLED_CMD);//Set Pre-Charge Period
OLED_WR_Byte(0xF1,OLED_CMD);//
OLED_WR_Byte(0xDA,OLED_CMD);//set com pin configuartion
OLED_WR_Byte(0x12,OLED_CMD);//
OLED_WR_Byte(0xDB,OLED_CMD);//set Vcomh
OLED_WR_Byte(0x30,OLED_CMD);//
OLED_WR_Byte(0x8D,OLED_CMD);//set charge pump enable
OLED_WR_Byte(0x14,OLED_CMD);//
OLED_WR_Byte(0xAF,OLED_CMD);//--turn on oled panel
}
这个初始化其实也没什么好说的,调用任何一个模块,都要对其进行初始化配置。
OLED_WR_Byte函数:作用是向OLED发送一系列命令,设置OLED的各种参数,包括显示方向、亮度、滚动等。
- 0xAE:关闭OLED显示。
- 0x00和0x10:设置列地址范围。
- 0x40:设置起始行地址。
- 0xB0:设置页地址范围。
- 0x81和0xFF:设置对比度。
- 0xA1:设置左右反转。
- 0xA6:设置正常/反转显示。
- 0xA8和0x3F:设置多路复用比率和扫描方式。
- 0xC8:设置COM扫描方向。
- 0xD3和0x00:设置显示偏移。
- 0xD5和0x80:设置时钟分频。
- 0xD8和0x05:设置区域颜色模式。
- 0xD9和0xF1:设置预充电周期。
- 0xDA和0x12:设置COM引脚配置。
- 0xDB和0x30:设置VCOMH。
- 0x8D和0x14:设置内部充电泵。
- 0xAF:打开OLED显示。
其中,OLED_CMD表示发送的是命令,OLED_DATA表示发送的是显示数据。
现象
显示汉字
我的汉字上下间距至少是2,不然上下的字会覆盖,这个是可以试出来的。
请看现象:
显示字符串
请看现象:
其他就不做示范了。
总结
别忘了,在主函数先初始化一次,来初步配置OLED屏幕,为了确保不受干扰,每一次界面切换内容,都要clear一遍(调用OLED_Clear)。
博主也是菜狗,当然不会写这个OLED代码,初学者要知道如何显示一个汉字、字符串、字符以及数字就好,了解他们的参数,会调用就可以了,毕竟博主以后也不会从事底层驱动开发的哈哈。就这样,希望你们有所收获!!!!