12864 OLED屏幕因其体积小巧、功耗低、显示清晰等特点,常被用于树莓派等嵌入式设备的可视化项目中。其中IIC接口的0.96寸SSD1306协议屏幕尤为常见,本文将详细介绍如何在树莓派上通过Python驱动该屏幕,并实现GIF动画的显示功能。

1. 硬件准备

1.1 所需硬件

  • 树莓派(所有型号应该都是一样的)
  • IIC接口的0.96寸12864 OLED屏幕(SSD1306协议)
  • 杜邦线若干

1.2 硬件连接

12864 OLED屏幕通过IIC协议与树莓派通信,需连接4根线:

  • VCC:接树莓派3.3V或引脚
  • GND:接树莓派GND引脚
  • SDA:接树莓派GPIO2(SDA1)引脚
  • SCL:接树莓派GPIO3(SCL1)引脚

1.3 启用IIC接口

打开终端,输入以下命令进入配置界面:

1
sudo raspi-config

依次选择 Interface Options → I2C → Yes 启用 IIC 接口。

2. 关键代码实现

2.1 安装依赖库

驱动12864屏幕显示需用到luma.oled(驱动 OLED 屏幕)。此外,使用Pillow库实现图像处理,安装命令如下:

1
2
3
4
5
# 安装luma.oled库
pip3 install luma.oled

# 安装Pillow库(处理图像)
pip3 install pillow

2.2 代码实现

2.2.1 初始化硬件

通过i2c函数初始化 I2C 通信接口,指定通信端口为port=1(树莓派常用 I2C 端口),设备地址为0x3C(OLED 屏幕的 I2C 地址,需与实际硬件匹配)。基于初始化的 I2C 接口,使用ssd1306驱动创建 OLED 设备实例(注释提示可根据屏幕型号替换为ssd1325、ssd1331、sh1106等其他协议驱动)。通过sys.argv[1]从命令行参数中读取需要显示的 GIF 文件路径,代码如下:

1
2
3
4
serial = i2c(port=1, address=0x3C)
# 初始化设备,这里改ssd1306, ssd1325, ssd1331, sh1106
device = ssd1306(serial)
imagepath = str(sys.argv[1])

2.2.2 图像处理

使用Pillow库用于对图像进行缩放和布局调整,以适配128x64分辨率的显示区域(如 12864 OLED 屏幕)。首先计算图像的宽高比,为选择合适的缩放方式;之后根据目标宽度或高度,按原图像宽高比等比例计算新宽度和高度缩放,使图像高度符合目标值;最后创建一个 128x64 的透明画布,将缩放后的图像居中粘贴到画布上,确保图像在目标显示区域中处于居中位置,最终输出可直接用于显示的图像。相关代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
WIDTH = 128
HEIGHT = 64

def resize_image_by_height(image, target_height):
width, height = image.size
new_width = int(width * target_height / height)
resized_image = image.resize((new_width, target_height))
return resized_image

def resize_image_by_width(image, target_width):
width, height = image.size
new_height = int(target_width * height / width)
resized_image = image.resize((target_width, new_height))
return resized_image

def AspectRatio(image):
width, height = image.size
return width / height

def resize_and_paste(middle_img):
transparent_image = Image.new('RGBA', (128, 64), color = (0, 0, 0, 0))
middle_size = middle_img.size
x = (transparent_image.width - middle_size[0]) // 2
y = (transparent_image.height - middle_size[1]) // 2
transparent_image.paste(middle_img, (x, y))
return transparent_image

2.2.3 显示动画

首先打开目标 GIF 文件,通过ImageSequence.Iterator遍历 GIF 的所有帧,并将帧复制到frames列表中。之后调用上面的函数将gif当前帧转换为设备支持的显示模式后,即可通过device.display显示在 OLED 屏幕上。最后使用死循环,可确保GIF动画持续播放。代码如下:

1
2
3
4
5
6
7
8
9
while True:
with Image.open(imagepath) as gif:
frames = [frame.copy() for frame in ImageSequence.Iterator(gif)]
for frame in frames:
if AspectRatio(frame) > 2:
resized_image = resize_image_by_width(frame, WIDTH)
else:
resized_image = resize_image_by_height(frame, HEIGHT)
device.display(resize_and_paste(resized_image).convert(device.mode))

2.3 完整代码

完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# -*- coding: utf-8 -*-
from luma.core.interface.serial import i2c # , spi
from luma.oled.device import ssd1306 # , ssd1325, ssd1331, sh1106
from PIL import Image, ImageSequence
import sys

WIDTH = 128
HEIGHT = 64

def resize_image_by_height(image, target_height):
width, height = image.size
new_width = int(width * target_height / height)
resized_image = image.resize((new_width, target_height))
return resized_image

def resize_image_by_width(image, target_width):
width, height = image.size
new_height = int(target_width * height / width)
resized_image = image.resize((target_width, new_height))
return resized_image

def AspectRatio(image):
width, height = image.size
return width / height

def resize_and_paste(middle_img):
transparent_image = Image.new('RGBA', (128, 64), color = (0, 0, 0, 0))
middle_size = middle_img.size
x = (transparent_image.width - middle_size[0]) // 2
y = (transparent_image.height - middle_size[1]) // 2
transparent_image.paste(middle_img, (x, y))
return transparent_image

def main():
__version__ = 1.0
print("current version:", __version__)
# 初始化端口
serial = i2c(port=1, address=0x3C)
# 初始化设备,这里改ssd1306, ssd1325, ssd1331, sh1106
device = ssd1306(serial)
imagepath = str(sys.argv[1])
while True:
with Image.open(imagepath) as gif:
frames = [frame.copy() for frame in ImageSequence.Iterator(gif)]
for frame in frames:
if AspectRatio(frame) > 2:
resized_image = resize_image_by_width(frame, WIDTH)
else:
resized_image = resize_image_by_height(frame, HEIGHT)
device.display(resize_and_paste(resized_image).convert(device.mode))
print("next frame")
print("replay")

if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
pass

在终端中执行以下命令运行程序,屏幕会循环显示 GIF 动画,按Ctrl+C可停止程序:

1
python3 oled_gif.py test.gif

最后给一个使用12864显示摄像头画面的完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# -*- coding: utf-8 -*-
from luma.core.interface.serial import i2c # , spi
from luma.oled.device import ssd1306 # , ssd1325, ssd1331, sh1106
import cv2
from PIL import Image

cam = cv2.VideoCapture(0)
cam.set(cv2.CAP_PROP_FRAME_WIDTH, 320) # 设置宽度
cam.set(cv2.CAP_PROP_FRAME_HEIGHT, 240) # 设置高度
# 初始化端口‌知识产权师
serial = i2c(port=1, address=0x3C)
# 初始化设备,这里改ssd1306, ssd1325, ssd1331, sh1106
device = ssd1306(serial)
while True:
ret, image = cam.read()
cv2.imshow(("video"), image)‌知识产权师
print(image.shape)
image = cv2.resize(image, (128, 96))
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
h, w = gray_image.shape[:2]
start_y = h // 6
end_y = (h * 5) // 6
gray_image = gray_image[start_y:end_y, :]
gray_image = cv2.flip(gray_image, 1)
cv2.imshow(("gray"),gray_image)
pil_image = Image.fromarray(cv2.cvtColor(gray_image, cv2.COLOR_BGR2RGB))
print(pil_image.size)
device.display(pil_image.convert(device.mode))
k = cv2.waitKey(1)
if k != -1:
break
cam.release()
cv2.destroyAllWindows()