OLED(Organic Light-Emitting Display, 有机自发光显示)是非常常见的显示技术,其通过自发光的有机二极管材料实现显示,与使用恒定背光的LCD(Liquid Crystal Display,液晶显示)相区分。现在绝大多数中端及以上手机全部使用OLED屏幕,其具备厚度薄、视角广、功耗低、对比度高、响应速度快等等优势。
单片机使用的OLED模块最常见的型号是SSD1306,分辨率128×64,具有I²C(Inter-Integrated Circuit,集成电路间通信总线)和SPI(Serial Peripheral Interface,串行外设接口)两个协议的版本,今天以前者为例(因为我买到的初学者套装送的是前者)。两个协议需要了解的是,I²C速率较低,资源占用少,只用两根线传输,通常用于OLED、传感器等便携模块;而SPI速率高,资源占用多,要用四根线传输,通常用于屏幕、存储器等高速大型模块。对于STM32功能层开发,其他更加深入的原理我们完全几乎不需要了解,因为虽然底层驱动不同(网上可以轻易获取),但上层接口和使用方式是完全相同的。
接线
对于I²C版本的SSD1306,除了VCC和GND以外,有SCL(Serial Clock,时钟线)和SDA(Serial Data,数据线)两个引脚。STM32的I²C通用默认引脚是SCL对应PB6,SDA对应PB7,因此需要这样接线:
STM32 ──────── SSD1306
3.3V ─────── VCC
PB6 ───────> SCL
PB7 ───────> SDA
GND ───────── GND
导入驱动与基本点阵原理
根据协议和模块版本不同,对应的OLED驱动实现和文件名称可能也不同,但总体分为OLED.h、oledfont.h、BMP.h三个头文件和oled.c一个C源文件。OLED.h负责声明所有功能层接口,oledfont.h负责声明要输出的英文和中文以及其16进制点阵数组,BMP.h负责声明要输出的BMP图像的16进制点阵数组,oled.c则是驱动的核心底层逻辑(这我们反而不需要关心)。
OLED屏显示需要的唯一数据就是16进制点阵数组,每个16进制数代表一列的八个像素,即一个字节。例如十六进制数0x48,其二进制为01001000,则代表一个八像素的纵列第二和第五个像素亮,其余像素暗,依次类推:
|  |  | 
因此,上面这个二维数组就是2个16列,每列8像素的点阵。前6列在上方,后6列紧接着正下方,故构成的本质上是16列16像素的点阵,即我们常见的一个16×16汉字,这个字是“我”。
SSD1306的分辨率128×64。以上面的逻辑,每列8像素为一个16进制数字(1字节),我们可以把每列8像素的横向集合定义成新的行,这样的行称为页(page)。那么我们就可以归纳出一个新的坐标系,这个坐标系是128×8的,正是我们在接口中实际使用的字节坐标系。
对于一个英文字母或标点,是8×8点阵,翻译成字节坐标系就占据一个8×1的区域。因此,横向排布英文字母或标点的坐标,从左上角第一个字母(0,0)开始依次向右8列,第2个字母(8,0),第3个字母(16,0)直到第16个字母(120,0);纵向排布则就是一页一个。横坐标保持8的整数倍,纵坐标保持整数,如果填满英文字母或标点,右下角最大坐标即为(120,8)。
对于一个汉字,是16×16点阵,翻译成字节坐标系就占据一个16×2的区域,同理,横向排布16列一个,纵向排布2页一个。横坐标保持8的整数倍,纵坐标保持2的整数倍,如果填满汉字,右下角最大坐标即为(112,8)。
对于图像,如果要填满整个屏幕,就是一个长度为128×8=1024的16进制点阵大数组,当然接口也支持在特定坐标绘制局部图像。将BMP图像、汉字、英文字母或标点转换为16进制点阵数组的工具称为字模转换器,Windows平台上,PCtoLCD2002是最经典的。
接口使用
接口中使用的坐标系都是字节坐标系,而非像素坐标系。
基本
|  |  | 
这两个函数负责初始化和清屏,不多赘述。
显示英文字母或标点
|  |  | 
前两个参数是初始坐标,第三个是需要显示的字符串;第四个字体大小(有些版本的驱动不存在这个参数),只有当其为16时会使用8×16(字节坐标系8×2)字体,否则其他任意值都输出默认的8×8(字节坐标系8×1)字体。
显示汉字
需在oledfont.h中填入需要显示的汉字的点阵数据,这里以“你好”为例:
|  |  | 
函数用法:
|  |  | 
前两个参数是初始坐标,第三个是需要显示的汉字在Hzk中的索引,例如显示“你好”:
|  |  | 
显示图像
和汉字一样,也需要把图像的点阵数据提前填入BMP.h的BMP1数组中,函数用法:
|  |  | 
前两个参数是初始坐标,后两个参数是图像范围,第五个则是图像点阵数组,实际使用时需要用extern字段引入头文件里定义的BMP1,例如铺满全屏:
|  |  | 
以上所述所有文件、函数、变量名可能因协议与驱动而异,但功能都几乎相同,可以举一反三。
