1. 项目缘起:为什么是Pico e-Paper 2.9 (B)?
如果你和我一样,对嵌入式开发、物联网设备或者桌面小摆件有点兴趣,大概率会听说过电子墨水屏。这东西的魔力在于,它只在刷新内容时才耗电,一旦显示完成,哪怕你把电源拔了,画面也能一直“挂”在那里。这种特性让它成了制作低功耗信息牌、电子标签、甚至是桌面天气站的绝佳选择。市面上能驱动墨水屏的开发板不少,但Raspberry Pi Pico(简称Pico)的出现,让这件事的门槛和成本都降到了一个新低点。
Pico e-Paper 2.9 (B)这个标题,指的就是用一块树莓派Pico微控制器,去驱动一块2.9英寸、黑白红三色的电子墨水屏模块。这里的“(B)”通常指代屏幕的版本或变体,比如可能是带边框的版本,或者使用了特定驱动芯片(如SSD1680)的型号。我手头这块屏,就是典型的“三色”屏,能显示黑、白、红三种颜色,比单纯的黑白屏多了那么一点表现力,用来做天气预报(晴天白色、阴天灰色、高温预警红色)或者待办事项列表(已完成划红线)就特别合适。
选择这个组合,核心原因就三个:便宜、简单、好玩。一块Pico开发板几十块钱,一块2.9寸的三色墨水屏模块也是几十块,加起来百元以内就能搞定一个可以自定义显示任何内容的终端。相比用树莓派Zero或者ESP32,Pico的RP2040芯片性能足够驱动这块屏,而且其MicroPython/C++生态完善,社区资源丰富,遇到问题容易找到解决方案。更重要的是,整个过程从硬件连接到软件编程,涉及的都是嵌入式开发最基础的知识,非常适合想入门硬件交互或者给自己做个实用小工具的爱好者。
2. 硬件准备与连接:别小看那几根线
拿到Pico和墨水屏模块,第一步不是急着写代码,而是把线接对。这一步看似简单,却是后续所有工作的基础,接错了轻则没显示,重则可能损坏设备。
2.1 认识你的屏幕模块
常见的2.9英寸三色e-Paper模块,背面通常会有一个引脚定义标签。关键引脚通常包括:
- VCC:电源正极,接3.3V。非常重要:绝大多数这类屏幕模块的工作电压是3.3V,绝对不要接到5V上!
- GND:电源地线,与Pico的GND相连。
- DIN (MOSI):串行数据输入,用于从Pico向屏幕发送图像数据。
- CLK (SCK):串行时钟,同步数据通信。
- CS (Chip Select):片选引脚,低电平有效,用于选中当前这块屏幕(如果系统中有多个SPI设备)。
- DC (Data/Command):数据/命令选择引脚。用于告诉屏幕控制器,当前发送的是命令(如初始化指令)还是数据(如图像数据)。
- RST (Reset):复位引脚,低电平有效,用于硬件复位屏幕控制器。
- BUSY:忙状态引脚。墨水屏在刷新时,内部控制器正在工作,此时该引脚会输出高电平(或低电平,取决于模块设计),告诉主控“我正在忙,别打扰”。这个引脚至关重要,程序必须检测它,并在屏幕忙时等待,否则会导致通信混乱和显示异常。
我用的这块屏,其引脚逻辑是:BUSY引脚高电平表示忙碌。你需要根据自己屏幕的数据手册或卖家提供的资料确认这一点。
2.2 与Pico的正确连接
树莓派Pico有多组GPIO,我们通常使用其硬件SPI接口以获得最佳性能。Pico默认的SPI0接口引脚是:
- GP16 (SPI0 RX)- 通常不用,因为屏幕只接收数据。
- GP17 (SPI0 CSn)- 可作为片选CS。
- GP18 (SPI0 SCK)- 时钟CLK。
- GP19 (SPI0 TX)- 主设备输出,接屏幕的DIN。
但是,CS、DC、RST、BUSY这些引脚不是SPI标准引脚,我们可以将它们连接到Pico上任意的通用GPIO上。下面是一个经过我实测可用的连接方案(使用SPI0):
| Pico GPIO 引脚 | 连接至 e-Paper 模块 | 说明 |
|---|---|---|
| GP3.3V (Pin 36) | VCC | 电源,务必是3.3V |
| GND (Pin 38) | GND | 电源地 |
| GP18 (Pin 24) | CLK (SCK) | SPI时钟 |
| GP19 (Pin 25) | DIN (MOSI) | SPI数据输出 |
| GP17 (Pin 22) | CS | 片选(可自定义,但需与代码对应) |
| GP20 (Pin 26) | DC | 数据/命令(可自定义) |
| GP21 (Pin 27) | RST | 复位(可自定义) |
| GP22 (Pin 29) | BUSY | 忙状态检测(可自定义) |
注意:Pico有多个GND和3.3V引脚,任选一组连接即可。引脚编号参照Pico的物理引脚图。
连接时,建议使用杜邦线。如果追求稳定和美观,可以考虑焊接排针后用排线连接,或者直接使用Pico的焊接孔洞进行焊接。确保连接牢固,避免接触不良导致的随机故障。
3. 软件环境搭建与驱动库选择
硬件连好后,就要让Pico“动”起来。我们需要给Pico刷入固件,并准备好控制屏幕的代码库。
3.1 给Pico刷入MicroPython固件
对于快速原型开发,MicroPython比C++更友好。首先,去树莓派基金会官网下载最新的Pico MicroPython固件文件(通常是一个.uf2文件)。然后:
- 按住Pico板上的BOOTSEL按钮不放。
- 将Pico通过Micro USB线连接到电脑。
- 待电脑出现一个名为
RPI-RP2的可移动磁盘后,松开按钮。 - 将下载好的
.uf2固件文件拖入这个磁盘。Pico会自动重启,此时它就变成了一个MicroPython设备。
接下来,你需要一个代码编辑器或IDE来和Pico交互。Thonny是一个极佳的选择,它免费、轻量,且内置了MicroPython支持。安装Thonny后,在右下角选择解释器为“MicroPython (Raspberry Pi Pico)”,并选择正确的串口。连接成功后,Shell界面会出现>>>提示符。
3.2 寻找并安装e-Paper驱动库
MicroPython社区有很多开源驱动库。对于SSD1680驱动的2.9寸三色屏,micropython-epaper或micropython-ili9xxx等库的衍生版本可能适用。但更常见的做法是,屏幕卖家或社区爱好者会提供针对特定屏幕的驱动文件。
通常,你需要两个核心文件:
epaper2in9b.py: 这是主驱动文件,里面定义了EPD类,包含了初始化、清屏、显示图像、休眠等所有底层函数。文件名可能因屏幕型号略有不同,如epd2in9b.py。font*.py或图片处理工具: 用于显示文字和图片的辅助文件。
如何获取?三个途径:
- 卖家资料:购买屏幕的店铺详情页或客服通常会提供百度网盘链接。
- GitHub:搜索关键词“micropython pico 2.9 e-paper”或“SSD1680 micropython”。
- 基于已知库修改:如果你找到的库不完全匹配,可能需要根据你的屏幕数据手册,调整驱动文件中的初始化命令序列(LUT,查找表)、分辨率(通常是128x296像素)和BUSY引脚逻辑。
将找到的epaper2in9b.py等文件,通过Thonny的“文件”->“保存到”功能,上传到Pico的根目录下。这样,在你的主程序里就可以用import epaper2in9b来调用它了。
4. 核心驱动原理与代码逐行解析
拿到驱动库不是终点,理解它如何工作才能灵活运用和排错。我们以一个典型的epaper2in9b.py驱动为例,拆解几个关键函数。
4.1 初始化:唤醒沉睡的屏幕
__init__或init函数是起点。它主要做以下几件事:
def init(self): self.reset() # 1. 硬件复位 self.send_command(0x12) # 2. 软件复位 self.read_busy() # 3. 等待复位完成 # 4. 发送一系列配置命令 self.send_command(0x01) # 驱动器输出控制 self.send_data(0x27) self.send_data(0x01) self.send_data(0x00) # ... 更多配置,如设置LUT、边框控制等 self.set_lut() # 5. 加载波形表(LUT) # ... 后续配置- 复位:先拉低RST引脚再拉高,进行硬件复位,确保控制器从已知状态开始。
- 发送命令/数据:
send_command和send_data是底层函数,区别在于先拉低还是拉高DC引脚。它们通过SPI将字节数据发送出去。 - 读BUSY:
read_busy()函数内部是一个循环,不断检测BUSY引脚状态,直到屏幕就绪。这是避免显示错乱的关键,任何主要操作(初始化、清屏、显示)后都应等待BUSY变低。 - 加载LUT:
set_lut()函数发送一长串十六进制数。这是电子墨水屏的核心——波形查找表。它定义了如何控制每个像素点从当前颜色切换到目标颜色所需的电压脉冲序列。不同的屏幕、甚至不同的温度补偿模式,LUT都可能不同。如果你发现显示残影严重或刷新不正常,很可能需要调整这里的值。
4.2 图像数据发送:黑白红如何区分?
三色屏(黑、白、红)通常用1位(1bit)深度来表示两种颜色,但这里需要表示三种状态(黑、白、红)。常见的实现方式是使用两个位平面(Bitplane)。
- 第一个位平面: 定义黑白区域。1代表黑色,0代表白色。
- 第二个位平面: 定义红色区域。1代表红色,0代表非红色(即由第一个位平面决定是黑还是白)。
在内存中,我们需要为128x296的屏幕创建两个缓冲区(buffer),每个缓冲区的大小是(height * width) // 8字节,因为1个字节可以表示8个像素点(1bit per pixel)。
display函数的工作流程:
- 分别将“黑白缓冲区”和“红色缓冲区”的数据,通过
send_data()发送给屏幕。 - 发送特定的刷新命令(如
0x22和0x20)。 - 调用
read_busy()等待漫长的全屏刷新完成(通常需要2-4秒)。 - 发送休眠命令(
0x10)以降低功耗。
4.3 绘图与文字显示:在缓冲区上作画
驱动库通常只提供最基础的set_pixel函数。要显示文字或图片,我们需要在缓冲区数组上进行操作。
- 显示文字:需要字库文件。字库定义了每个字符对应的点阵数据。流程是:根据字符编码找到点阵数据,然后按位计算,将该字符的像素点“画”到缓冲区的对应位置。例如,如果字库数据某位是1,我们就在黑白缓冲区的对应字节的对应位上设为1(黑色)。
- 显示图片:需要先将图片(如PNG)转换为二值化的位图数据。可以使用电脑上的工具(如ImageMagick、Python PIL库)预先转换,生成一个字节数组,然后直接把这个数组赋值给缓冲区。
- 局部刷新:全屏刷新慢且闪屏。有些驱动支持局部刷新(Partial Refresh),它只更新变化的部分,速度更快且无全局闪烁。但这需要更复杂的LUT支持和精确的区域控制,很多低成本模块的驱动并未实现。
5. 实战:打造一个桌面天气站
理解了原理,我们来做一个实际项目:用Pico和墨水屏显示实时天气与时间。这需要Pico连接Wi-Fi获取数据。
5.1 网络连接与API获取
我们将使用ESP8266/ESP32常用的urequests库在MicroPython中获取网络数据。首先,确保你的Pico连接了Wi-Fi模块(如ESP-01S)或者使用带有Wi-Fi功能的Pico W。这里以Pico W为例。
import network import time import urequests import json # 1. 连接Wi-Fi wlan = network.WLAN(network.STA_IF) wlan.active(True) wlan.connect('你的Wi-Fi名', '你的密码') print("正在连接Wi-Fi...") max_wait = 10 while max_wait > 0: if wlan.isconnected(): break max_wait -= 1 time.sleep(1) if not wlan.isconnected(): raise RuntimeError('网络连接失败') print('网络连接成功!IP:', wlan.ifconfig()[0]) # 2. 获取天气数据(以和风天气为例,需要申请免费KEY) city_id = "你的城市ID" api_key = "你的API_KEY" url = f"https://devapi.qweather.com/v7/weather/now?location={city_id}&key={api_key}" response = urequests.get(url) data = json.loads(response.text) response.close() weather_text = data['now']['text'] # 天气状况,如“晴” temp = data['now']['temp'] # 温度,如“25” humidity = data['now']['humidity'] # 湿度,如“60”5.2 整合显示与主循环
接下来,将获取到的数据与墨水屏驱动结合。我们需要一个主循环,定期(如每30分钟)更新天气,每分钟更新时间。
from epaper2in9b import EPD from machine import Pin, SPI import utime # 初始化屏幕(引脚定义与之前硬件连接一致) spi = SPI(0, baudrate=2000000, polarity=0, phase=0, sck=Pin(18), mosi=Pin(19)) epd = EPD(spi, cs=Pin(17), dc=Pin(20), rst=Pin(21), busy=Pin(22)) epd.init() # 清屏并创建画布(这里需要自己实现或使用库中的FrameBuffer) # 假设我们有一个FrameBuffer类fb,它内部管理着两个缓冲区 fb.clear() # 在fb上绘制边框、标题“天气站” # 调用字体渲染函数,将weather_text, temp, humidity绘制到fb的指定位置 # 获取当前时间并绘制 # 将fb的缓冲区数据传给epd并显示 epd.display_frame(fb.buffer_black, fb.buffer_red) # 假设fb有这两个属性 # 进入深度睡眠以省电(Pico W支持),或者简单的延时循环 # 这里简化处理,使用延时 while True: update_weather() # 封装好的更新天气函数 update_time() # 封装好的更新时间函数(可能只更新分钟部分) # 重新绘制并刷新屏幕 epd.display_frame(fb.buffer_black, fb.buffer_red) utime.sleep(1800) # 休眠30分钟5.3 优化与省电策略
- 局部刷新:如果驱动支持,在更新时间和日期时,只刷新屏幕的一小部分区域,可以极大提升刷新速度并减少闪烁。
- 深度睡眠:对于电池供电的场景,在两次刷新间隔,可以让Pico进入深度睡眠模式,仅靠RTC唤醒,这将把功耗从mA级降至μA级。
- 帧缓冲管理:避免每次刷新都重新绘制全部静态元素(如边框、标题)。只在初始化时绘制一次静态背景,后续只更新动态数据区域。
6. 常见问题排查与避坑指南
在实际操作中,你几乎一定会遇到下面这些问题。
6.1 屏幕一片空白或全黑/全白
- 检查电源:首先确认VCC接的是3.3V,并且电源能提供足够的电流(峰值可能需几十mA)。用万用表测量一下电压。
- 检查接线:逐根检查杜邦线是否松动,特别是SPI的CLK和DIN线。可以尝试降低SPI波特率(如降到1MHz)测试。
- 检查复位和初始化:在代码初始化部分后,添加一个
epd.clear()函数调用,并确保后面跟了epd.display()和read_busy。有时初始化序列不完整会导致屏幕处于未知状态。 - 检查驱动兼容性:确认你的
epd2in9b.py驱动文件是否完全匹配你的屏幕型号和驱动IC。不同批次的屏幕,其初始化命令序列可能有细微差别。最直接的验证方法是找到卖家提供的示例程序,如果能运行,再对比其驱动文件与你的有何不同。
6.2 显示乱码、残影或刷新不正常
- BUSY引脚处理不当:这是最常见的原因。确保在每一个可能让屏幕工作的命令(
init,clear,display)之后,都调用了read_busy()并等待其完成。在read_busy()函数中加一些调试打印,确认它确实在等待。 - SPI通信干扰:如果SPI线上还接了其他设备,确保CS片选信号管理正确。尽量让屏幕独占一组SPI。
- LUT不匹配:残影严重(上一幅图像痕迹去不掉)通常与波形查找表LUT有关。屏幕在不同温度下需要不同的驱动波形。有些高级驱动库会内置温度传感器并动态切换LUT。如果你的库没有,尝试在较温暖的环境下(20-25°C)测试,或者寻找带有温度补偿参数的驱动版本。
- 刷新太快:全屏刷新后,必须等待足够长的时间(通常2秒以上)再进行下一次操作。即使BUSY信号已解除,屏幕粒子也需要时间稳定。
6.3 无法显示红色或颜色错乱
- 缓冲区数据错误:确认你是在正确的缓冲区(红色缓冲区)里设置了红色像素。红色是独立控制的,你在黑白缓冲区里将某个像素设为黑色,同时在红色缓冲区里将同一个像素设为红色,最终显示可能是黑色(优先级问题,取决于驱动IC)。
- 驱动IC理解错误:仔细阅读驱动代码或屏幕数据手册,理解它是如何解释两个缓冲区的。常见规则是:
buffer_black某位为1是黑,为0是白;buffer_red某位为1是红,为0则看buffer_black。但也可能存在其他映射关系。
6.4 MicroPython程序内存不足
Pico的RP2040有264KB RAM,但对于MicroPython和帧缓冲区来说并不算大。如果同时加载大字体、多张图片,很容易出现MemoryError。
- 优化字体:只加载需要的字号和字符集。使用点阵字体而非矢量字体。
- 压缩图片:确保图片已是1位色深,并且尺寸不超过屏幕分辨率。
- 使用外部存储:如果图片资源很大,可以考虑使用Pico的板载Flash(通过文件系统)或者外接SD卡,动态加载需要显示的部分数据。
- 帧缓冲区复用:避免创建多个全屏大小的缓冲区。
7. 进阶玩法与扩展思路
当基础显示搞定后,可以尝试更多有趣的应用。
- 多屏幕拼接:使用多块相同的屏幕,由一块Pico控制,通过不同的CS片选引脚来选择。可以制作一个长条形的信息看板。
- 利用Pico的PIO:RP2040独有的可编程输入输出单元(PIO)可以用来模拟复杂的通信协议。理论上,你可以用PIO来模拟SPI甚至并行接口,以驱动分辨率更高或接口不同的屏幕,解放CPU资源。
- 制作电子日历/待办清单:结合实时时钟模块(如DS3231),制作一个离线工作的电子日历。通过按钮切换查看月份、周视图和待办事项。
- 传感器数据可视化:连接温湿度传感器(如DHT22)、空气质量传感器,将数据历史以简易图表的形式绘制在屏幕上。
- 低功耗远程信息牌:结合LoRa或NB-IoT模块,制作一个电池供电、数月才需更换一次电池的远程野外信息采集显示终端。
从点亮第一幅图像,到完成一个稳定运行的天气站,整个过程你会反复经历连接、调试、崩溃、查资料、再调试的循环。这正是嵌入式开发的常态,也是乐趣所在。Pico e-Paper 2.9 (B)这个组合,就像一个微型的画布,硬件连接是固定好的框架,而你的代码则是画笔。它成本低廉,反馈直观(成功或失败一眼可见),涉及的知识点全面而不深奥,是踏入硬件编程世界一个非常舒适的起点。我最开始也卡在BUSY引脚检测上整整一个下午,但当屏幕终于按我的指令显示出清晰的文字时,那种成就感是纯软件项目难以比拟的。建议你在成功驱动屏幕后,不要止步于示例代码,试着去修改驱动文件里的LUT参数看看效果,或者尝试用PIO去模拟通信时序,这些深入的折腾会让你对底层硬件的理解加深一个层次。