第一次拿到0.96寸OLED模块时,我对着那四根线发了好一会儿呆。这种小尺寸显示屏在智能手表、便携设备上很常见,但要用89C52单片机驱动它,需要先理解几个关键点。OLED(有机发光二极管)和LCD最大的区别在于它不需要背光,每个像素都能独立发光,这让它的对比度更高、响应速度更快,而且可视角度能达到170度以上。
我手头这块中景园电子的OLED模块,分辨率是128x64,内置SSD1306驱动芯片。有意思的是,它支持多种接口方式:SPI、IIC、6800并口、8080并口。为了简化接线,我选择了IIC接口,只需要SDA(数据线)和SCL(时钟线)两根信号线,加上VCC和GND,总共四根线就能搞定。
89C52是经典的8位单片机,虽然现在看性能一般,但驱动这种小OLED绰绰有余。实际接线时要注意:IIC总线需要上拉电阻(通常4.7kΩ),模块本身可能已经集成,如果没有就需要自己外接。我第一次调试时就因为没加上拉电阻,导致通信失败,屏幕一片漆黑。
IIC协议是驱动OLED的核心,但很多新手会觉得时序控制很头疼。这里我分享一个实战技巧:用示波器抓取波形。当你看到SCL时钟线上整齐的方波,SDA数据线上同步变化的信号,就能直观理解IIC的工作方式。
SSD1306的IIC地址通常是0x78(写模式)或0x79(读模式),具体取决于模块的SA0引脚电平。通信流程分为三步:起始信号→发送地址/命令/数据→停止信号。起始信号在SCL高电平时SDA产生下降沿,停止信号则是SCL高电平时SDA产生上升沿。
写命令和写数据的区别在于控制字节的D/C#位:命令对应0x00,数据对应0x40。比如初始化时要先发送一系列命令设置显示参数,再发送显示数据。我常用的几个关键命令:
先来看IIC起始信号函数,用89C52的GPIO模拟:
c复制void IIC_Start() {
SDA = 1; // 数据线高
SCL = 1; // 时钟线高
Delay_us(5);
SDA = 0; // 下降沿
Delay_us(5);
SCL = 0; // 拉低时钟
}
发送单字节的函数需要按位操作,注意数据在SCL高电平时必须稳定:
c复制void Write_IIC_Byte(unsigned char dat) {
unsigned char i;
for(i=0; i<8; i++) {
SDA = (dat & 0x80) ? 1 : 0;
dat <<= 1;
SCL = 1;
Delay_us(2);
SCL = 0;
}
// 等待ACK
SDA = 1;
SCL = 1;
Delay_us(2);
SCL = 0;
}
OLED初始化函数需要严格按照时序配置SSD1306。有个坑我踩过:上电后必须延时500ms再初始化,否则配置可能不生效。初始化命令序列包括设置对比度(0x81)、扫描方向(0xC8)、显示偏移(0xD3)等20多个参数。
显示ASCII字符相对简单,但要做得好需要优化字库。我推荐使用PCtoLCD2002取模软件,生成6x8、8x16等不同尺寸的字模数组。比如显示字符串函数:
c复制void OLED_ShowStr(unsigned char x, y, char *str) {
while(*str) {
OLED_ShowChar(x, y, *str++);
x += 6; // 6x8字体宽度
if(x > 122) { x=0; y++; } // 自动换行
}
}
显示汉字更复杂,需要16x16点阵。我建立了一个汉字库数组,通过Unicode编码索引。显示时要注意:一个汉字占用32字节数据,分上下两部分显示。
对于图形显示,我常用的优化方法:
显示BMP图片需要先将图片转为二进制数组。我用Image2LCD工具转换,设置宽度128像素,高度64像素,垂直扫描模式。显示函数如下:
c复制void OLED_DrawBMP(unsigned char x0,y0,x1,y1,unsigned char BMP[]) {
unsigned int j=0;
unsigned char x,y;
for(y=y0; y<y1; y++) {
OLED_SetPos(x0,y);
for(x=x0; x<x1; x++) {
OLED_WriteData(BMP[j++]);
}
}
}
实现动画时要注意:
我做过一个电子表项目,指针动画就是这样实现的:先擦除旧指针,再绘制新位置,中间加2ms延时消除残影。
调试OLED时最常见的问题是白屏或无显示,按这个顺序排查:
如果显示内容错乱,可能是:
有个隐蔽的坑:部分模块的RES引脚需要硬件复位。我在一个项目上折腾半天,最后发现是漏接了10kΩ的上拉电阻。