使用 MicroPython 在树莓派 Pico 上驱动微雪墨水屏显示器
墨水屏(又称 "电子墨水")显示器非常适合低功耗和日光下可读的应用。在这篇文章中,我们将展示如何将 800 × 480 墨水屏连接到树莓派 Pico,然后通过 MicroPython 代码显示图像。
硬件设置
我们假设:
- 一个具有 SPI 接口的微雪风格 7.5″ 墨水屏模块(分辨率 800×480)。
- 一个运行 MicroPython 的树莓派 Pico 2。
我们需要使用 Pico 的 SPI 接口。Pico 2 有两个 SPI 接口:SPI0 和 SPI1。我们将使用 SPI0。请参见下面的引脚图。
Pico 2 Pinout
以下是我的引脚连接。墨水屏通常有以下信号:
墨水屏引脚 | 描述 | Pico SPI0 示例引脚 |
---|---|---|
VCC | 3.3 V 电源 | 3V3 (例如 引脚 36) |
GND | 地线 | GND (例如 引脚 38) |
DIN | MOSI (SPI TX) | SPI0 TX / GP7 (引脚 10) |
CLK | SCLK (SPI CLK) | SPI0 SCK / GP6 (引脚 9) |
CS | SPI CS | SPI0 CSn / GP5 (引脚 7) |
DC | 数据 / 命令 | GP8 (引脚 11) |
RST | 复位 | SPI0 RST / GP9 (引脚 12) |
BUSY | 忙信号 | GP10 (引脚 14) |
GPIO 说明
对于 DC、RST、BUSY,你实际上可以使用任何你想要的 GPIO。我只是随机选择了 GP8、GP9、GP10。当然,如果你想使用其他 GPIO,你需要相应地修改代码。
Pico 上的 MicroPython 脚本
将以下脚本(例如 epaper_800x480.py)通过 Thonny 或任何其他方法保存到你的树莓派 Pico 上。它定义了一个驱动类,初始化显示器,并可以显示图像。
import machine
import time
# -------------------------------------------------------------------------
# 1) Display resolution for 7.5" e-paper (800 x 480)
# -------------------------------------------------------------------------
EPD_WIDTH = 800
EPD_HEIGHT = 480
# -------------------------------------------------------------------------
# 2) Configure the Pico pins and SPI0
# -------------------------------------------------------------------------
# GP6 = SCK
# GP7 = MOSI
# GP4 = MISO (not used but must assign a pin)
# GP5 = CS
# GP8 = DC
# GP9 = RST
# GP10 = BUSY
spi = machine.SPI(
0,
baudrate=2_000_000,
polarity=0,
phase=0,
sck=machine.Pin(6),
mosi=machine.Pin(7),
miso=machine.Pin(4)
)
cs_pin = machine.Pin(5, machine.Pin.OUT, value=1)
dc_pin = machine.Pin(8, machine.Pin.OUT, value=0)
rst_pin = machine.Pin(9, machine.Pin.OUT, value=1)
busy_pin = machine.Pin(10, machine.Pin.IN)
def delay_ms(ms):
time.sleep_ms(ms)
def digital_write(pin, val):
pin.value(val)
def digital_read(pin):
return pin.value()
def spi_write_block(data_block):
spi.write(data_block)
# -------------------------------------------------------------------------
# 3) E‐Paper Driver Class
# -------------------------------------------------------------------------
class EPD_800x480:
def __init__(self):
self.width = EPD_WIDTH
self.height = EPD_HEIGHT
self.reset_pin = rst_pin
self.dc_pin = dc_pin
self.busy_pin = busy_pin
self.cs_pin = cs_pin
def hardware_reset(self):
digital_write(self.reset_pin, 1)
delay_ms(200)
digital_write(self.reset_pin, 0)
delay_ms(2)
digital_write(self.reset_pin, 1)
delay_ms(200)
def send_command(self, cmd):
digital_write(self.dc_pin, 0) # Command
digital_write(self.cs_pin, 0)
spi_write_block(bytes([cmd]))
digital_write(self.cs_pin, 1)
def send_data(self, data):
digital_write(self.dc_pin, 1) # Data
digital_write(self.cs_pin, 0)
spi_write_block(bytes([data]))
digital_write(self.cs_pin, 1)
def send_data_block(self, data_block):
digital_write(self.dc_pin, 1)
digital_write(self.cs_pin, 0)
spi_write_block(data_block)
digital_write(self.cs_pin, 1)
def read_busy(self):
while digital_read(self.busy_pin) == 0:
delay_ms(20)
def turn_on_display(self):
# Refresh
self.send_command(0x12)
delay_ms(100)
self.read_busy()
def init(self):
"""Initialize the display (power on, set registers, etc.).
Make sure to match your e‐paper's datasheet or Waveshare example code!
"""
self.hardware_reset()
# Example sequence (replace with your display's official sequence):
# - POWER SETTING
# - PANEL SETTING
# - TRES (resolution)
# - etc.
# This is a placeholder; adapt to your hardware:
self.send_command(0x01) # POWER SETTING
self.send_data(0x07)
self.send_data(0x07)
self.send_data(0x3f)
self.send_data(0x3f)
self.send_command(0x04) # POWER ON
delay_ms(100)
self.read_busy()
self.send_command(0x00) # PANEL SETTING
self.send_data(0x1F)
# TRES: set resolution 800x480
self.send_command(0x61)
self.send_data(0x03) # 800 >> 8
self.send_data(0x20) # 800 & 0xFF
self.send_data(0x01) # 480 >> 8
self.send_data(0xE0) # 480 & 0xFF
self.send_command(0x15)
self.send_data(0x00)
self.send_command(0x50)
self.send_data(0x10)
self.send_data(0x07)
self.send_command(0x60)
self.send_data(0x22)
# -- CHUNKED SENDING to save memory --
def _send_zeros_in_chunks(self, total_size, chunk_size=512):
zero_chunk = b'\x00' * chunk_size
sent = 0
while sent < total_size:
remain = total_size - sent
if remain >= chunk_size:
self.send_data_block(zero_chunk)
sent += chunk_size
else:
self.send_data_block(b'\x00' * remain)
sent += remain
def _send_inverted_data_in_chunks(self, data, chunk_size=512):
idx = 0
length = len(data)
while idx < length:
end = min(idx + chunk_size, length)
slice_data = data[idx:end]
inverted = bytearray(len(slice_data))
for i, b in enumerate(slice_data):
inverted[i] = ~b & 0xFF
self.send_data_block(inverted)
idx = end
def display(self, black_buffer):
"""black_buffer is a bytes/bytearray of 48,000 bytes (800x480 // 8).
We'll invert each byte on the fly, because the hardware often
expects 1=white and 0=black.
"""
size = (self.width * self.height) // 8
# 1) Send old data (all white) in chunks
self.send_command(0x10)
self._send_zeros_in_chunks(size)
# 2) Send new data (inverted)
self.send_command(0x13)
self._send_inverted_data_in_chunks(black_buffer)
self.turn_on_display()
def clear(self):
"""Clear the screen to white."""
size = (self.width * self.height) // 8
self.send_command(0x10)
self._send_zeros_in_chunks(size)
self.send_command(0x13)
self._send_zeros_in_chunks(size)
self.turn_on_display()
def sleep(self):
"""Deep sleep / power off the display."""
self.send_command(0x02) # POWER OFF
self.read_busy()
self.send_command(0x07) # DEEP_SLEEP
self.send_data(0xA5)
delay_ms(2000)
# -------------------------------------------------------------------------
# EXAMPLE USAGE
# -------------------------------------------------------------------------
def main():
epd = EPD_800x480()
epd.init()
# Clear the screen
epd.clear()
# Suppose you uploaded an 800x480 BIN file to the Pico (flash),
# containing 48,000 bytes of 1-bit data:
with open("image_800x480.bin", "rb") as f:
image_data = f.read()
# Display it
epd.display(image_data)
# Wait a few seconds, then sleep
delay_ms(5000)
epd.sleep()
if __name__ == "__main__":
main()
准备图像
你需要一个 800×480 像素的 1 位(黑白)图像。在你的桌面计算机上(常规 Python),你可以使用 Pillow 转换任何彩色图像:
#!/usr/bin/env python3
import argparse
from PIL import Image
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--input", "-i", required=True, help="Input image (png/jpg)")
parser.add_argument("--output", "-o", required=True, help="Output bin file")
args = parser.parse_args()
# 1) Open and resize/crop your image to 800x480 if needed
img = Image.open(args.input).convert("RGB")
img = img.resize((800, 480))
# 2) Convert to 1-bit
bw_img = img.convert("1") # uses dithering or pass dither=Image.NONE
pixels = bw_img.load()
width, height = bw_img.size
buf = bytearray(width*height//8)
idx = 0
for y in range(height):
for x_block in range(0, width, 8):
b = 0
for bitpos in range(8):
px_val = pixels[x_block+bitpos, y]
# px_val is 0 (black) or 255 (white) typically
if px_val >= 128:
# White => bit=1
b |= (1 << (7 - bitpos))
buf[idx] = b
idx += 1
with open(args.output, "wb") as f:
f.write(buf)
print(f"Saved {len(buf)} bytes to {args.output}")
if __name__ == "__main__":
main()
运行方式:
python image_to_bin.py --input myphoto.png --output image_800x480.bin
将 image_800x480.bin 复制到 Pico(例如,使用 Thonny 的 视图 → 文件)。
在 Pico 上运行
- 打开 Thonny(或你喜欢的 MicroPython IDE)。
- 将
epaper_800x480.py
复制到你的桌面。 - 同时将
image_800x480.bin
复制到 Pico 上。 - 运行
epaper_800x480.py
。它应该初始化显示器,清除它,然后显示你转换的图像!
如果一切正常工作,你将在墨水屏上看到你的黑白图像。
常见问题 / 技巧
- 内存错误:如果你看到
MemoryError: memory allocation failed
,这通常意味着你试图创建一个巨大的数组。上面代码中的分块方法避免了这个问题。 - 颜色反转:如果你的显示器颜色反转了(白色是黑色,黑色是白色),看看是否需要移除或修改
~b & 0xFF
逻辑。 - 局部更新:一些墨水屏支持局部刷新。你需要更高级的序列来进行局部更新。
- 缓慢:墨水屏刷新可能需要 2-3 秒或更长时间,特别是在大型显示器上。这是正常的。
结论
就是这样!你已经成功地用 MicroPython 在树莓派 Pico 上驱动了一个大型 7.5″、800×480 墨水屏。关键是将数据分小块发送以节省内存,并在硬件需要时即时反转位。
墨水屏提供了一种华丽的、低功耗的方式来显示静态图像、标牌或仪表板。既然你有了基础知识,你可以扩展到绘制文本、形状或局部更新。

E‐paper display
祝你在墨水屏和 Pico 的编程中玩得愉快!
© LICENSED UNDER CC BY-NC-SA 4.0