☰
MicroPython驱动OLED显示图像:从像素到视频播放的第一步
2026/10/15 17:35:15 网站建设 项目流程

1. 从一个“小屏幕”说起:为什么要在MicroPython上驱动OLED

很多人第一次接触MicroPython,都是因为一块几十块钱的开发板。它小巧、便宜、上手快,插上电脑就能跑Python代码,不用装一堆复杂的编译工具链。但玩了两天之后,你会发现一个尴尬的问题:板子跑起来了,代码也执行了,可你根本不知道它到底在干什么。没有屏幕,所有的输出都只能靠串口打印,一旦脱离电脑,设备就成了一个“黑盒”。

所以,给开发板配一块OLED屏幕,几乎是每个MicroPython玩家都会走的一步。它不贵,通常十几块钱就能买到一块0.96寸的128×64分辨率小屏,I2C接口,两根线就能点亮。更重要的是,它能让你实时看到传感器数据、程序状态、甚至显示简单的图像。这一步走通了,后面想做视频播放、图像处理、甚至做一个自己的小终端,才有了基础。

这篇文章要聊的,就是怎么在MicroPython环境下,把一块OLED屏幕驱动起来,并且让它显示图像。标题里提到了“看你喜欢看的视频”,这其实是一个更大的目标——用MicroPython做视频播放。但视频的本质是什么?是一帧一帧的图像快速切换。所以,驱动OLED显示图像,是通往视频播放的第一步,也是最关键的一步。这篇文章是“上篇”,我们先把图像显示这件事彻底搞明白。

适合谁看?如果你手里有一块支持MicroPython的开发板,比如ESP32、ESP8266、RP2040之类的,并且已经能跑基本的Python代码,那这篇文章就是为你准备的。如果你还没入门,也没关系,我会把每一步都拆开讲,尽量让第一次接触的人也能跟着做下来。

2. 硬件选型与接线:别小看这两根线

2.1 OLED屏幕怎么选:I2C还是SPI

市面上常见的0.96寸OLED模块,主要有两种接口:I2C和SPI。I2C只需要两根信号线(SDA、SCL),接线简单,速度够用,适合大多数场景。SPI需要四根线以上,速度快,但占用的引脚多。对于显示静态图像或者低帧率视频来说,I2C完全够用。我实测过,128×64分辨率下,I2C在400kHz速率下刷新一帧全屏图像大概需要几十毫秒,对于入门来说完全可以接受。

选屏的时候注意几个参数:分辨率128×64是主流,驱动芯片通常是SSD1306或SH1106。这两个芯片的初始化命令略有不同,SSD1306更常见,SH1106在某些廉价模块上会出现。如果你买到的屏幕显示有偏移或者花屏,很可能就是驱动芯片不匹配。另外,屏幕颜色有白色、蓝色、黄蓝双色几种,双色屏通常是上半部分黄色、下半部分蓝色,价格差不多,看个人喜好。

注意:有些模块标称I2C地址是0x3C,有些是0x3D。如果扫描不到设备,先检查地址。用I2C扫描工具跑一下,确认设备在线。

2.2 接线:两根线背后的门道

I2C接线很简单:VCC接3.3V,GND接GND,SDA接开发板的I2C数据引脚,SCL接时钟引脚。但这里有几个坑要注意。

第一,电压问题。大多数OLED模块支持3.3V和5V,但有些模块只支持3.3V。如果你用5V供电,可能会烧坏屏幕。保险起见,先查一下模块的规格书,或者直接用3.3V。

第二,上拉电阻。I2C总线需要上拉电阻,通常模块上已经自带了4.7kΩ或10kΩ的上拉电阻。如果开发板的I2C引脚内部上拉不够强,或者你接了很多设备,可能需要外接上拉电阻。我遇到过一种情况:单独接屏幕没问题,但同时接了两个I2C设备后,屏幕就不亮了。后来在SDA和SCL上各加了一个4.7kΩ电阻到3.3V,问题解决。

第三,引脚选择。不同开发板的I2C引脚不一样。ESP32默认的I2C引脚是GPIO21(SDA)和GPIO22(SCL),但你可以通过代码重新映射到任意GPIO。RP2040的I2C引脚更多,灵活性更高。接线之前,先确认你的开发板手册,别接错了。

2.3 开发板选择:够用就好

理论上,任何支持MicroPython并且有I2C接口的开发板都可以。ESP32是最常见的选择,因为它便宜、性能强、社区资源多。ESP8266也可以,但内存小一些,跑图像显示可能会有点吃力。RP2040(比如树莓派Pico)性能很好,双核,RAM也够,但MicroPython固件需要自己刷。

如果你还没买板子,我的建议是:入门选ESP32,性价比最高。如果已经有板子了,那就直接用,没必要为了这个项目专门换。

3. 软件环境搭建:从零到点亮屏幕

3.1 刷写MicroPython固件

拿到开发板的第一件事,是刷MicroPython固件。不同板子的刷写方法不一样,但基本流程是:下载固件文件,用烧录工具写入开发板。ESP32和ESP8266可以用esptool,RP2040可以直接拖拽uf2文件。

刷完之后,用串口工具连接开发板,应该能看到MicroPython的REPL提示符。这时候你可以输入简单的Python代码,比如print("hello"),确认环境正常。

提示:刷固件之前,先备份开发板上的原有数据。刷写会清空所有内容。

3.2 安装驱动库:ssd1306.py

MicroPython官方并没有内置OLED驱动库,但社区里有一个非常成熟的ssd1306.py,几乎成了标准。这个库很小,只有几百行代码,支持SSD1306和SH1106两种芯片。

获取方式有两种:一是从GitHub上下载,二是直接通过MicroPython的包管理工具安装。如果你用的是Thonny或者uPyCraft这类IDE,可以直接把文件上传到开发板。我习惯用mpremote工具,命令行操作更方便。

mpremote connect /dev/ttyUSB0 fs cp ssd1306.py :ssd1306.py

上传之后,在REPL里import ssd1306,如果没有报错,说明库已经就位。

3.3 I2C扫描:确认屏幕在线

在写显示代码之前,先确认屏幕能被扫描到。MicroPython的machine模块提供了I2C类,可以扫描总线上的设备。

from machine import I2C, Pin i2c = I2C(0, scl=Pin(22), sda=Pin(21), freq=400000) devices = i2c.scan() print(devices)

如果输出类似[60],说明设备地址是0x3C(60的十六进制)。如果输出空列表,说明接线有问题,或者屏幕没供电。这时候检查VCC和GND,确认SDA和SCL没有接反。

注意:有些开发板的I2C编号不是0,可能是1或者其他。如果I2C(0)报错,试试I2C(1)。

4. 驱动OLED显示图像:从像素到画面

4.1 理解OLED的显存结构

SSD1306的显存是一块128×64位的区域,总共1024字节。它按页(page)组织,每页8行,共8页。每个字节代表一列中的8个像素,低位在上,高位在下。这种结构决定了你不能直接往屏幕上写“像素”,而是要先在内存里构建一个缓冲区(framebuffer),然后一次性发送到屏幕。

MicroPython的ssd1306库提供了framebuf模块,它封装了基本的绘图操作,比如画点、画线、画矩形、显示文字。但framebuf本身不支持直接显示图像文件,你需要把图像转换成字节数组,然后写入缓冲区。

4.2 图像格式转换:从PNG到字节数组

这是整个流程中最关键的一步。你手头有一张图片,可能是PNG、JPG或者BMP,但OLED只认字节数组。怎么转换?

常用的工具是PC端的Python脚本,配合PIL(Pillow)库。基本思路是:打开图片,缩放到128×64,转换成黑白二值图像,然后按SSD1306的显存格式打包成字节数组。

from PIL import Image img = Image.open("test.png").convert("1") # 转黑白 img = img.resize((128, 64)) pixels = img.load() buf = bytearray(1024) for y in range(64): for x in range(128): if pixels[x, y]: buf[(y // 8) * 128 + x] |= 1 << (y % 8) with open("test.bin", "wb") as f: f.write(buf)

这段代码的逻辑是:遍历每个像素,如果像素是亮的,就把对应字节的对应位设为1。注意SSD1306的位顺序是低位在上,所以1 << (y % 8)。

生成的test.bin文件只有1024字节,非常小。你可以把它上传到开发板,然后用framebuf的blit方法显示。

4.3 在MicroPython中显示图像

上传test.bin到开发板后,用以下代码显示:

import ssd1306 from machine import I2C, Pin i2c = I2C(0, scl=Pin(22), sda=Pin(21)) oled = ssd1306.SSD1306_I2C(128, 64, i2c) with open("test.bin", "rb") as f: data = f.read() oled.fill(0) oled.blit(framebuf.FrameBuffer(bytearray(data), 128, 64, framebuf.MONO_HLSB), 0, 0) oled.show()

这里用到了framebuf.FrameBuffer,它把字节数组包装成一个可绘制的对象,然后blit到OLED的缓冲区。MONO_HLSB表示单色、水平字节、低位在前,正好匹配SSD1306的格式。

提示:如果显示出来的图像是反的或者上下颠倒,可能是位顺序或者扫描方向的问题。调整MONO_HLSB为MONO_HMSB试试,或者在转换脚本里翻转Y轴。

4.4 动态图像:从静态到动画

静态图像显示只是第一步。如果你想做动画,比如播放视频,就需要连续显示多帧图像。这时候有几个优化点:

第一,减少I2C传输次数。每次oled.show()都会把整个缓冲区发送到屏幕,耗时较长。如果帧率要求不高,可以接受。但如果想提高帧率,可以考虑只更新变化的部分,或者降低分辨率。

第二,使用双缓冲。在内存里维护两个缓冲区,一个用于绘制,一个用于显示,交替使用。这样可以避免绘制过程中的闪烁。

第三,压缩图像数据。1024字节一帧,如果存100帧就是100KB,对于ESP32来说不算大,但对于ESP8266就有点紧张了。可以考虑用RLE(游程编码)压缩,或者只存储关键帧。

我实测过,在ESP32上,I2C速率400kHz,全屏刷新一帧大约需要30-40毫秒,也就是25-30帧每秒。这个帧率对于简单的动画来说已经够用了。但如果想播放真正的视频,还需要进一步优化,比如降低颜色深度、使用SPI接口、或者用DMA传输。

5. 常见问题与排查技巧实录

5.1 屏幕不亮:从供电到地址的排查清单

屏幕不亮是最常见的问题。排查顺序如下:

现象可能原因解决方法
完全黑屏供电不足检查VCC是否接3.3V,用万用表量一下
完全黑屏接线错误确认SDA、SCL没有接反
完全黑屏I2C地址不对运行扫描代码,确认设备地址
花屏驱动芯片不匹配尝试SH1106驱动
显示偏移初始化参数不对调整列偏移量

我遇到过一种情况:屏幕在ESP32上不亮,但在另一块板子上正常。后来发现是ESP32的I2C引脚默认上拉不够,外接了两个4.7kΩ电阻就好了。

5.2 图像显示异常:位顺序与字节对齐

图像显示异常通常表现为:图像上下颠倒、左右镜像、或者出现条纹。这些问题大多和位顺序有关。

SSD1306的显存是按页组织的,每页8行。如果你的图像转换脚本没有正确处理页边界,就会出现条纹。比如,图像高度不是8的倍数时,最后一页会多出一些空行。解决方法是在转换时补齐到8的倍数。

另外,framebuf的MONO_HLSB和MONO_HMSB分别对应低位在前和高位在前。SSD1306通常是低位在前,但有些模块可能相反。如果图像看起来像“负片”,可能是位顺序反了。

5.3 内存不足:ESP8266的优化策略

ESP8266的RAM只有几十KB,跑图像显示很容易内存不足。优化策略包括:

  • 降低分辨率,比如用64×32
  • 减少缓冲区数量,只用一个
  • 使用gc.collect()手动回收内存
  • 把图像数据存在Flash里,而不是RAM里

我试过在ESP8266上显示128×64的全屏图像,勉强能跑,但帧率很低。如果要做动画,建议换ESP32或者RP2040。

5.4 刷新速度慢:I2C速率与优化技巧

I2C速率默认是100kHz,可以提高到400kHz甚至1MHz。在MicroPython里,初始化I2C时指定freq=400000即可。但要注意,不是所有模块都支持1MHz,400kHz是比较稳妥的选择。

另外,oled.show()是阻塞操作,会占用CPU时间。如果需要在显示的同时处理其他任务,可以考虑用定时器或者异步任务。MicroPython支持uasyncio,可以把显示操作放在协程里,避免阻塞主循环。

注意:提高I2C速率可能会导致通信不稳定,特别是接线较长的时候。如果出现随机花屏,降低速率试试。

6. 从图像到视频:下一步的思路

图像显示跑通之后,下一步就是让它动起来。视频的本质是连续帧,所以你需要解决三个问题:帧数据的存储、帧率的控制、以及音频的同步(如果需要声音)。

帧数据存储方面,可以把多帧图像打包成一个文件,每帧1024字节,按顺序读取。如果帧数很多,可以考虑压缩。简单的RLE压缩对于黑白图像效果不错,特别是大面积纯色的帧。

帧率控制方面,MicroPython的time.ticks_ms()可以获取毫秒级时间戳,用来计算帧间隔。如果一帧显示耗时40毫秒,那理论帧率就是25fps。你可以通过调整I2C速率、降低分辨率、或者只更新变化区域来提高帧率。

音频同步是更复杂的问题,MicroPython对音频的支持有限,通常需要外接DAC或者PWM输出。如果只是做无声动画,那就不需要考虑音频。

我在实际项目中的体会是:MicroPython做视频播放,瓶颈主要在I2C带宽和CPU处理能力。如果你真的想流畅播放视频,SPI接口的屏幕会更好,或者直接用硬件解码的方案。但作为学习和实验,I2C OLED已经足够让你理解视频显示的基本原理了。

最后分享一个小技巧:如果你想让图像显示更流畅,可以先把所有帧数据加载到内存里,然后用一个循环快速切换。这样避免了频繁的文件读取操作,帧率会明显提升。当然,前提是你的开发板内存够大。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询