树莓派使用 Python 驱动 SSD1306(IIC/SPI 通信)

本文详细介绍了如何在树莓派上使用 python 驱动 SSD1306 OLED 屏幕,并提供完整的驱动包,真正的开箱即用。支持 IIC 和 SPI。

先介绍一下SSD1306屏幕,这个屏幕的主控型号是使用的 SSD1306,分辨率 12864 和 12832 两种是最常见的。它是 OLED 屏幕,不需要背光,单个像素自己就能发光,所以屏幕的厚度能做到很薄,功率也能做到很小。SSD1306 中嵌入了对比度控制器、显示 RAM 和晶振,因此减少了外部器件和功耗。它有 256 级灰度控制器。接口支持 6800/8080 并口、IIC 接口和 SPI 接口。

上面的图片是本店中出售的 SSD1306 IIC 接口的屏幕,分辨率是 128*64。为了方便DIY玩家使用和调试,模块固定了一种接口,需要其它接口的请进店查看或浏览【神秘商店】页面。电源支持 3.3v - 5v,可用在树莓派、51单片机、stm32、arduino、ESP系列上。

值得注意的是,该屏幕因为没有背光,所以在接上电之后屏幕不会有任何反应,需要使用驱动让屏幕显示后才能看到效果。很多用户不知道这个,以为屏幕是坏了。蓝黄双色指的是上面黄色,下面蓝色,并不是整个屏幕有蓝黄两种颜色。

【ssd1306 数据手册下载】 【luma-oled 英文文档】 【luma-oled Github】

下面我们使用 Python 的 luma.oled 库来驱动这个屏幕。感谢开源社区和作者给我们提供这么好用的库。

安装驱动库

这个库在 Python 2.7, 3.4, 3.5 和 3.6 上测试通过。

我们在树莓派上默认使用 Python2.7 进行安装和使用。

$ sudo apt-get install python-dev python-pip libfreetype6-dev libjpeg-dev build-essential
$ sudo -H pip install --upgrade luma.oled

打开硬件 IIC 和 SPI

屏幕需要使用 IIC 进行通信,所以树莓派先要打开硬件 IIC 功能。首先检查下是否打开了 IIC 功能。执行下面的命令:

$ ls /dev/i2c-*

如果看到有 /dev/i2c-1 或者 /dev/i2c-0 就打开了,这步可以跳过。如果什么也没看到,则按下面的方法打开 IIC。

在 raspbian 系统上使用下面命令打开配置菜单:

$ sudo raspi-config

选择 Interfacing options

然后选择 I2C

最后选择 ,按回车。需要打开其他接口也是同样的方式。选择好后,会回到主菜单,选择 finish,回车,然后重启生效。

硬件接线

IIC 仅需要 4 根线就可以,其中 2 根是电源,另外 2 根是 SDA 和 SCL。我们使用 IIC-1 接口。下面是树莓派的 GPIO 引脚图。

注意:请一定以屏幕的实际引脚编号为准,电源反接必烧毁,屏幕会产生大量热量,注意烫伤手。

下面开始接电源。将屏幕的 GND 接到树莓派的 9 号引脚,将屏幕的 VCC 接到树莓派的 1 号引脚。如果你接了风扇,风扇可以接其它的电源口,树莓派有 2 个 3v 电源和 2 个 5v 电源。学会变通,不要死盯着这一个电源接口,只要不短路和过载,一般都没什么问题。在本店购买的屏幕支持 3v-5v 宽电压,接 5v 也不会烧坏的,其它地方购买的屏幕,请一定注意电源,烧坏本人不负责。

下面开始接数据线。将屏幕的 SDA 接到树莓派的 3 号引脚,将屏幕的 SCL(有些也叫SCK) 接到树莓派的 5 号引脚。至此,屏幕的线接好了,下面开始写程序让屏幕显示。

hello world! 代码

新建 ssd1306-iic.py 文件,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from luma.core.interface.serial import i2c, spi
from luma.core.render import canvas
from luma.oled.device import ssd1306


# 创建 IIC 设备
serial = i2c(port=1, address=0x3C)

# 如果使用 SPI,换成这个
# serial = spi(device=0, port=0)

# 创建屏幕的驱动实例
device = ssd1306(serial)

# 开始往屏幕上绘图。draw 是 Pillow 的实例,它里面还有非常多的绘图 API。
with canvas(device) as draw:
  draw.rectangle(device.bounding_box, outline="white", fill="black")
  draw.text((30, 40), "Hello World!", fill="white")

# 这行是为了阻止程序退出,因为退出的时候会调用析构函数,清空屏幕。防止一闪而过,什么也看不到。
while (True):
  pass

然后运行它,就能看到屏幕有显示了。

$ python ssd1306-iic.py

如果屏幕没有显示,检查屏幕的接线是否正确。如果程序有报错,能看懂的错误自己解决,看不懂的请在文章下面留言,或使用页面右下角的站内信邮件。在本店购买的用户,有问题请直接联系旺旺客服或售后技术群,将会第一时间给你解答。

SPI 驱动

上面是 IIC 接口的,IIC 的好处是只需要 4 根线,接线简单,可以少占用 GPIO,但是也有缺点:刷新率只有 10FPS 不到。读者可以自己做一个实验,在垂直方向上画一根直线,从 0 扫描到 127,你们就会看到直线移动的非常慢,并且在刷新的时候,从上到下直线会发生断裂。产生这个现象的根本原因是 IIC 传输数据效率太慢了(相对屏幕来说,并不是绝对的慢)。屏幕刷新的时候,驱动默认使用的是水平页刷新方式,也就是从左到右,从上到下,一页一页刷新。而刷新整个屏幕,耗费的时间需要几百毫秒,所以能看到上面的线已经往右移动了,而下面的线还在原来的位置,从而看到断裂现象。

对于只显示静态的图形来说,或者是对刷新率要求不高的场景,IIC 是比较合适的。接线和驱动简单,少占用 GPIO。但是对于刷新率要求高的场景,比如做动画效果,那 IIC 就不太适合了,这个时候就要使用 SPI 通信了。本人做过实验,在树莓派3B上,使用本店购买的 SPI 屏幕,配合本店的 nodejs SPI 驱动,刷新率最大能达到 2200FPS。动画效果看起来非常流畅。这里我们暂时还是使用 luma.oled 驱动来驱动 SPI 的屏幕。

接线

电源线请参照上面 IIC 的接线。这里开始接 SPI 的线。将屏幕的 D0(有的也叫SCLK) 接树莓派的 23 号引脚,将屏幕的 D1(有的也叫MOSI) 接到树莓派的 19 号引脚,将屏幕的 RST 接到树莓派的 22 号引脚,将屏幕的 DC 接到树莓派的 18 号引脚,将屏幕的 CS 接到树莓派的 24 号引脚。

hello world! 程序

使用 SPI 需要先打开硬件 SPI 功能,请参照上面的方法确认打开。

新建 ssd1306-spi.py 文件,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from luma.core.interface.serial import i2c, spi
from luma.core.render import canvas
from luma.oled.device import ssd1306

# 创建 SPI 设备,使用 SPI-0
serial = spi(device=0, port=0)

# 创建屏幕的驱动实例
device = ssd1306(serial)

# 开始往屏幕上绘图。draw 是 Pillow 的实例,它里面还有非常多的绘图 API。
with canvas(device) as draw:
  draw.rectangle(device.bounding_box, outline="white", fill="black")
  draw.text((30, 40), "Hello World!", fill="white")

# 这行是为了阻止程序退出,因为退出的时候会调用析构函数,清空屏幕。防止一闪而过,什么也看不到。
while (True):
  pass

然后运行它,就能看到屏幕有显示了。

$ python ssd1306-spi.py

驱动库源码

以上是基础用法,一般的也够用了,但是 luma.oled 这个库很强大,还有其它的用法,下面讲解一下源码,这样,你们就能完全掌握它了。

# 文件 https://github.com/rm-hull/luma.oled/blob/master/luma/oled/device/__init__.py#L128

def __init__(self, serial_interface=None, width=128, height=64, rotate=0, **kwargs):
# ······
# Supported modes
  settings = {
    (128, 64): dict(multiplex=0x3F, displayclockdiv=0x80, compins=0x12),
    (128, 32): dict(multiplex=0x1F, displayclockdiv=0x80, compins=0x02),
    (96, 16): dict(multiplex=0x0F, displayclockdiv=0x60, compins=0x02),
    (64, 48): dict(multiplex=0x2F, displayclockdiv=0x80, compins=0x12),
    (64, 32): dict(multiplex=0x1F, displayclockdiv=0x80, compins=0x12)
  }.get((width, height))

上面是 SSD1306 的构造函数定义部分代码,从参数名就能一眼看出各个参数是什么意思了。默认是 128*64,如果有其它分辨率的屏幕,则可以自己指定。再往下可以看到所有支持的分辨率。这里说明一下,SSD1306 这个主控支持多种分辨率的屏幕,只需要替换玻璃面板就行了。但是有一点,不同的分辨率需要设置不同的参数,要不然会出现花屏的问题。

rotate 可以设置屏幕的方向,有 4 个值,分别是 0,1,2,3,分别对应 0度,90度,180度,270度,有需要不同方向的读者可以设置这个参数。

再来看看这个 draw 是个什么东西。

# 文件 https://github.com/rm-hull/luma.core/blob/master/luma/core/render.py#L32

from PIL import Image, ImageDraw

def __enter__(self):
  self.draw = ImageDraw.Draw(self.image)
  return self.draw

上面是 render 函数的部分代码,很简单,只有2行,一眼就能看出 draw 是 pillow ImageDraw 的实例对象。知道了这个就好办了,这样我们就能知道这个对象里面所有的绘图函数了。下面我们再去看看 pillowImageDraw 是什么东西。

pillow 是 Python 的一个图形库,提供各种图形操作的 API,和 JavaScript 中的 canvas 差不多。pillow 文档

找到 pillow 的文档,定位到 ImageDraw 模块,可以看到它的类定义:

# 文档:https://pillow.readthedocs.io/en/latest/reference/ImageDraw.html#PIL.ImageDraw.PIL.ImageDraw.Draw

# Creates an object that can be used to draw in the given image.

class PIL.ImageDraw.Draw(im, mode=None)

其实就是返回了一个包装过的对象,这个对象可以在给定的 image 对象上进行绘图操作。既然它是类,那么再往下看看这个类里面有哪些函数。

# 文档:https://pillow.readthedocs.io/en/latest/reference/ImageDraw.html#PIL.ImageDraw.PIL.ImageDraw.ImageDraw.rectangle

# Draws a rectangle.

PIL.ImageDraw.ImageDraw.rectangle(xy, fill=None, outline=None, width=0)

是不是很熟悉?这个函数就是我们之前 hello world!里面用到了的。好了,到这里就真相大白了,这个库的源码也差不多摸透了。文档中所有可用的绘图 API 都给出了说明,大家可以自行去查看其它 API,诸如画点、线、面、矩形、圆形、三角形、bitmap等等这些都有文档。后期如果有读者需要的话,本站会出一份中文的 API 文档,让大家更好阅读和使用。

这个库主要分 2 部分,一部分是刚才上面我们分析的图形库,图形库是使用 pillow 的。另一部分是硬件,因为本文的目的是让大家了解软件的用法,硬件这块这里就不讲了,本文篇幅也较长了,本来源码这块也不打算讲的,很多新手可能连屏幕都不会使用,一上来就源码,怕会吓到小朋友,即使没有小朋友,吓到花花草草也是不好的。但是本站的主旨就是认真,本着授人以鱼不如授人以渔的原则,篇幅长也要写完。能看到这里的都是提莫队长的蘑菇。

本人 Python 水平有限,属于小菜鸡,以上如有错误之处,欢迎大家指正。

硬件这块涉及到的知识点和软件不太一样,对硬件感兴趣的软件工程师读者们,如果想了解的话,后期另写一篇硬件篇的文章。

再看看其它同类文章吧!

有想说的评论一句吧!