树莓派Pico驱动2.13寸电子墨水屏:从硬件对接到天气站项目实战
1. 项目缘起为什么选择Pico e-Paper 2.13最近在折腾一些低功耗、常显的桌面小玩意儿比如天气预报站、待办事项清单或者一个能显示系统状态的小屏幕。市面上能选的方案不少但综合下来树莓派Pico搭配一块2.13英寸的电子墨水屏e-Paper成了一个非常迷人的组合。你可能也刷到过一些相关的帖子比如“luckfox pico焊上一块”或者讨论“pico 虚幻引擎 vr示例”的虽然后者是另一个维度的应用但都说明了围绕Pico的生态和创意非常活跃。而我今天想聊的就是如何把Pico和这块2.13寸的墨水屏玩转打造一个属于自己的、超省电的“信息窗口”。选择这个组合核心原因有几个。首先是极致的低功耗电子墨水屏只在刷新图像时耗电显示静态内容时功耗几乎为零这对于用电池供电、希望长期运行的项目来说是决定性的。其次Pico特指Raspberry Pi Pico这款微控制器价格亲民、性能足够其RP2040芯片的双核ARM Cortex-M0处理器和丰富的GPIO驱动一块SPI接口的屏幕绰绰有余。最后2.13英寸这个尺寸很“黄金”它足够显示几行文字和一些简单的图标但又不会太大以至于让整个设备变得笨重非常适合做桌面摆件或者嵌入到一些小装置里。网上关于“deovr pico安装包”的搜索可能有点偏差那通常是给Pico VR设备用的和我们这里的微控制器不是一回事。这恰恰说明当你搜索“Pico”时明确上下文很重要——我们说的是Raspberry Pi Pico而不是VR设备。所以如果你也对用硬件创造一个小而美的、永不断电的显示终端感兴趣那么跟着我一步步来从硬件连接到软件驱动再到实际应用开发这篇内容会给你一个完整的指南。2. 硬件全解析Pico与2.13寸墨水屏的对接动手之前我们得先搞清楚手头有什么以及它们该如何连接。这个环节看似基础但很多后续的“坑”都埋在这里。2.1 核心硬件介绍Raspberry Pi Pico这是我们的大脑。重点关注其40针的GPIO排针。对于驱动屏幕我们需要用到其中的SPI串行外设接口引脚、一些通用的GPIO用于控制以及电源引脚。Pico有两个SPI接口通常我们使用默认的SPI0其对应的引脚是GP16SPI0 RX、GP17SPI0 CSn、GP18SPI0 SCK、GP19SPI0 TX。当然软件上可以重映射但用默认的最省事。2.13英寸e-Paper显示屏模块这是我们的脸面。市面上常见的2.13寸墨水屏模块分辨率通常是250x122像素也有212x104等变种购买时需确认。它内部包含了显示屏面板和必要的驱动电路。模块通常会引出大约10个左右的引脚。最关键的是它需要一个3.3V的电源逻辑电平也是3.3V这与Pico的GPIO电平完美匹配无需电平转换。2.2 引脚连接详解屏幕模块的引脚定义可能因供应商而异但核心信号线通常如下。请务必以你购买屏幕时附带的资料为准。下面是一个最常见的连接方式以使用SPI0为例Pico GPIO引脚Pico 引脚名称连接至屏幕模块引脚信号说明GP16SPI0 RX / UART0 TXDIN / MOSISPI数据输入主出从入。Pico通过此线向屏幕发送图像数据。GP17SPI0 CSnCS / SDASPI片选。低电平有效告诉屏幕“我现在要跟你通信了”。GP18SPI0 SCKCLK / SCLSPI时钟。提供数据传输的同步时钟。GP19SPI0 TXBUSY忙信号。这是一个输入信号到Pico。屏幕刷新时需要时间此引脚拉高表示屏幕正忙Pico必须等待。GP20通用GPIODC / D/C数据/命令选择。告诉屏幕当前发送的是命令低电平还是数据高电平。GP21通用GPIORST复位。用于硬件复位屏幕控制器低电平有效。3V3(OUT)3.3V电源输出VCC电源正极3.3V。GND地GND电源地。注意这里有一个非常关键的细节表格中我将Pico的GP19 (SPI0 TX)连接到了屏幕的BUSY引脚。这是因为BUSY是Pico的输入而SPI0 TX通常用作输出。我之所以这样连接是基于一个常见的硬件设计考量在代码中我们可以将GP19重新初始化为一个普通的数字输入引脚而不是SPI功能。这样布线在物理上整齐SPI引脚排在一起软件上稍作调整即可。另一种更清晰的接法是将BUSY接到任何一个未被占用的GPIO上比如GP22。两种方式都可以在代码中对应修改引脚定义就行。电源连接至关重要务必使用Pico的3V3(OUT)引脚为屏幕供电。不要使用VBUS5V会烧毁屏幕Pico的3V3(OUT)引脚可以提供足够的电流驱动这块小屏幕。2.3 焊接与检查对于“luckfox pico焊上一块”这种操作其实原理相通。无论是Raspberry Pi Pico还是Luckfox Pico核心都是理解引脚定义并进行正确连接。如果你用的是排针可以使用杜邦线连接如果追求稳固和美观建议直接焊接排针然后用焊接或排线连接屏幕。连接完成后在上电前请务必用万用表通断档检查3V3与GND之间是否短路所有信号线是否连接到了正确的Pico引脚VCC和GND有没有接反确认无误后再通电。通电后屏幕可能会快速闪动一下全黑变全白再变全黑这是正常的初始化过程。如果没有反应立即断电重新检查。3. 软件环境搭建与驱动库选择硬件连好了接下来就是让Pico“动”起来。我们需要在电脑上搭建开发环境并选择合适的库来驱动屏幕。3.1 开发环境选择MicroPython vs. C/C对于快速原型开发MicroPython是绝佳选择。它语法简单交互性强库丰富非常适合初学者和需要快速迭代的项目。我们将以MicroPython为例进行说明。如果你追求极致的性能和存储空间控制可以选择C/C使用Raspberry Pi官方的Pico SDK但那会复杂不少。步骤1烧录MicroPython固件按住Pico板上的BOOTSEL按钮同时通过Micro USB线将其连接到电脑。电脑会识别出一个名为RPI-RP2的可移动磁盘。从 MicroPython官网 下载最新的rp2-pico固件文件.uf2格式。将该.uf2文件拖入RPI-RP2磁盘。Pico会自动重启并成为一块运行MicroPython的开发板。步骤2安装代码编辑器/IDE推荐使用Thonny。它是一款简单易用的Python IDE内置了MicroPython支持可以方便地连接Pico、上传文件和运行代码。从 Thonny官网 下载并安装。打开Thonny在右下角选择解释器为“MicroPython (Raspberry Pi Pico)”。如果连接正常Shell窗口会显示MicroPython的版本信息。3.2 驱动库的获取与理解我们不需要从零开始编写屏幕的底层驱动。社区已经有优秀的开源库。最常用的是来自waveshare微雪的库他们生产了很多种墨水屏其库代码结构清晰适配性强。如何获取库访问微雪的GitHub仓库例如搜索“waveshare e-Paper MicroPython”或者直接从他们产品页面的资料下载中找到MicroPython示例代码包。在示例代码包中你会找到几个关键文件epd2in13.py(或类似名称)这是2.13英寸屏幕的主驱动文件。它包含了初始化、清屏、发送缓冲区、进入休眠等所有底层函数。epdconfig.py这是引脚配置和硬件抽象层。这是我们最需要关注和修改的文件它定义了上面表格中提到的所有引脚BUSY, RST, DC, CS, SCK, MOSI对应到Pico的哪个GPIO编号。font*.py一些点阵字体文件。main.py示例主程序。核心修改epdconfig.py用Thonny打开这个文件你会看到类似下面的代码段# Raspberry Pi Pico Pin Define BUSY_PIN 17 RST_PIN 21 DC_PIN 20 CS_PIN 18 SCK_PIN 19 MOSI_PIN 16请根据你实际的硬件连接我们上面表格的方案修改这些引脚编号。例如如果你按照我的建议将BUSY接在GP19上那么BUSY_PIN 19。RST_PIN 21,DC_PIN 20,CS_PIN 17接GP17SCK_PIN 18,MOSI_PIN 16。实操心得很多驱动失败的问题都出在这个配置文件上。务必仔细核对一个引脚错了屏幕就可能毫无反应或者显示乱码。另外确保这个文件和你主程序在同一个目录下或者被正确导入。4. 从“Hello World”到图像显示核心代码剖析环境就绪库也准备好了让我们写第一个程序让屏幕显示点东西。4.1 基础显示流程电子墨水屏的刷新有其特殊流程不同于LCD即时刷新。基本流程是初始化 - 清空帧缓冲区 - 向缓冲区写入内容 - 将缓冲区内容发送到屏幕刷新 - 进入休眠。下面是一个最简单的显示文本的例子import machine import utime from epd2in13 import EPD_2in13 # 导入驱动类 from framebuf import MONO_HLSB # 导入帧缓冲区格式 # 初始化屏幕对象 epd EPD_2in13() # 这会自动调用epdconfig.py中的配置 epd.init() # 硬件初始化 epd.Clear(0xFF) # 清屏为白色0xFF代表白色0x00代表黑色 # 创建一个帧缓冲区FrameBuffer大小与屏幕分辨率一致 # 这里以250x122为例使用1位色深黑白 buf bytearray(250 * 122 // 8) # 计算缓冲区字节数 fb framebuf.FrameBuffer(buf, 250, 122, framebuf.MONO_HLSB) # 在帧缓冲区中操作 fb.fill(0xFF) # 填充白色背景 fb.text(Hello World!, 10, 50, 0x00) # 在坐标(10,50)处写黑色文字 # 将帧缓冲区的内容发送到屏幕并刷新显示 epd.display(epd.getbuffer(buf)) # getbuffer函数可能需要对buf进行格式转换 epd.sleep() # 让屏幕进入低功耗休眠模式 print(Display done.)代码解读与避坑epd.Clear(0xFF)这一步很重要。墨水屏有“残影”特性刷新前最好用一次全白或全黑清屏以确保显示干净。FrameBuffer这是MicroPython中处理图像内存的核心对象。我们所有绘图操作画点、线、矩形、文字都是先在这个内存缓冲区里完成。MONO_HLSB这是帧缓冲区的格式。表示单色1位水平扫描最低有效位优先。这是最易出错的地方之一。不同的屏幕驱动库可能要求不同的格式如MONO_VLSB。如果显示的文字是横向断裂或倒置的很可能就是格式不对。需要查阅驱动库epd2in13.py里display()函数对输入数据格式的要求。epd.display(epd.getbuffer(buf))getbuffer方法通常不是标准的FrameBuffer方法而是驱动库提供的用于将FrameBuffer的缓冲区转换成屏幕控制器需要的字节序列。务必使用驱动库提供的方法。epd.sleep()刷新完成后立即让屏幕休眠。这是实现低功耗的关键如果不调用屏幕控制器可能仍在耗电。4.2 显示图片与图形显示图片需要先将图片处理成适合墨水屏的1位黑白位图并转换为字节数组。步骤1准备图片使用像Photoshop、GIMP或在线工具将你的图片转换成黑白二值图尺寸调整为250x122像素。保存为BMP格式未经压缩的位图通常最容易处理。步骤2转换图片数据我们可以写一个简单的Python脚本在电脑上运行将BMP图片转换为MicroPython能用的字节数组格式。这里提供一个思路# 这是一个在电脑上运行的Python脚本pic2byte.py from PIL import Image import numpy as np img Image.open(your_image.bmp).convert(1) # 转换为1位黑白模式 width, height img.size # 确保尺寸匹配 assert width 250 and height 122 # 将像素数据转换为字节数组注意位顺序可能需要调整 # 一种常见的格式每行从左到右的像素每8个像素打包成一个字节高位在前 data bytearray() for y in range(height): for x in range(0, width, 8): byte 0 for bit in range(8): if x bit width: pixel img.getpixel((x bit, y)) # 假设黑色(0)为1白色(255)为0根据屏幕驱动调整 if pixel 0: byte | (1 (7 - bit)) # 高位在前 data.append(byte) # 将字节数组保存为.py文件方便导入 with open(image_data.py, w) as f: f.write(image_data repr(data))步骤3在Pico上显示将生成的image_data.py文件上传到Pico然后在主程序中import image_data from epd2in13 import EPD_2in13 epd EPD_2in13() epd.init() epd.Clear(0xFF) # 直接使用驱动库的显示方法如果库支持直接显示字节数组 epd.display(epd.getbuffer(image_data.image_data)) # 注意这里用了getbuffer # 或者如果库提供了显示部分区域的功能也可以将数据放入FrameBuffer再显示 epd.sleep()注意事项图片转换过程中的位顺序高位在前/低位在前和颜色极性黑是1还是0必须与屏幕驱动库的期望严格匹配。这通常需要反复测试。一个稳妥的方法是先用驱动库自带的示例图片进行测试确保其转换工具和显示代码是通路然后模仿其转换逻辑处理自己的图片。5. 打造实用项目网络天气显示站让屏幕显示静态内容只是第一步。让我们结合Pico的无线功能如果你用的是Pico W做一个能自动从网络获取天气信息并显示的桌面站。这才是它的完全体。5.1 硬件升级使用Pico WRaspberry Pi Pico W 在基础Pico上增加了英飞凌CYW43439无线芯片支持2.4GHz Wi-Fi 4 (802.11n)和蓝牙5.2。我们需要用它来连接网络。连接变化Pico W的GPIO引脚定义与Pico完全一致所以之前连接屏幕的线路无需任何改动。我们只是额外利用了其内部的Wi-Fi功能。5.2 软件架构与网络连接项目需要两个核心功能1. 连接Wi-Fi2. 通过HTTP请求获取天气数据JSON格式。MicroPython的network和urequests或requests库可以帮我们轻松实现。首先将Wi-Fi凭证保存在一个单独的配置文件config.py中避免将密码硬编码在主程序里# config.py WIFI_SSID 你的Wi-Fi名称 WIFI_PASSWORD 你的Wi-Fi密码主程序核心逻辑 (main.py)import network import time import json from epd2in13 import EPD_2in13 import framebuf from config import WIFI_SSID, WIFI_PASSWORD # --- 1. Wi-Fi连接函数 --- def connect_wifi(): wlan network.WLAN(network.STA_IF) wlan.active(True) if not wlan.isconnected(): print(Connecting to network...) wlan.connect(WIFI_SSID, WIFI_PASSWORD) # 等待连接最多10秒 max_wait 10 while max_wait 0: if wlan.isconnected(): break max_wait - 1 print(Waiting..., max_wait) time.sleep(1) if not wlan.isconnected(): raise RuntimeError(Network connection failed) print(Network connected. IP:, wlan.ifconfig()[0]) return wlan # --- 2. 获取天气数据函数 --- def get_weather(api_key, city): # 这里以OpenWeatherMap免费API为例 url fhttp://api.openweathermap.org/data/2.5/weather?q{city}appid{api_key}unitsmetric try: import urequests resp urequests.get(url) if resp.status_code 200: data json.loads(resp.text) resp.close() # 解析我们需要的数据 temp data[main][temp] humidity data[main][humidity] desc data[weather][0][description] icon data[weather][0][icon] # 例如 01d 表示晴天 return {temp: temp, humidity: humidity, desc: desc, icon: icon} else: print(API Error:, resp.status_code) resp.close() return None except Exception as e: print(Request failed:, e) return None # --- 3. 在屏幕上绘制天气信息 --- def draw_weather(epd, fb, weather_info): if not weather_info: fb.text(No Data, 50, 50, 0x00) return fb.fill(0xFF) # 清空缓冲区为白底 # 绘制城市名 fb.text(Beijing, 10, 10, 0x00) # 绘制温度大字体需要自己实现或使用大号字体文件 temp_str f{weather_info[temp]:.1f}C # 假设我们有一个大字体这里先用小字体示例 fb.text(temp_str, 10, 40, 0x00) # 绘制湿度和天气描述 fb.text(fH: {weather_info[humidity]}%, 10, 70, 0x00) fb.text(weather_info[desc], 10, 90, 0x00) # 这里可以添加根据icon代码显示简单图标的逻辑 # 例如判断icon是否包含01d然后在特定位置画一个太阳图形 # --- 主循环 --- def main(): epd EPD_2in13() wlan None weather_api_key 你的OpenWeatherMap API Key city Beijing try: wlan connect_wifi() epd.init() epd.Clear(0xFF) buf bytearray(250 * 122 // 8) fb framebuf.FrameBuffer(buf, 250, 122, framebuf.MONO_HLSB) while True: weather get_weather(weather_api_key, city) draw_weather(epd, fb, weather) epd.display(epd.getbuffer(buf)) epd.sleep() # 显示后休眠 # 每隔30分钟更新一次1800秒 time.sleep(1800) # 更新前唤醒屏幕 epd.init() epd.Clear(0xFF) except Exception as e: print(Main loop error:, e) finally: if wlan: wlan.disconnect() epd.sleep() # 进入深度睡眠以省电Pico W支持 # machine.deepsleep(30 * 60 * 1000) # 深度睡眠30分钟 if __name__ __main__: main()5.3 优化与省电策略这个简单的循环已经能工作但不够健壮和省电。错误处理与重试网络请求可能失败。应该在get_weather函数中加入重试机制并在屏幕上显示“更新中...”或“网络错误”的提示。更智能的刷新电子墨水屏全刷一次需要时间约2-4秒且会有闪烁。如果只有部分信息如温度变化可以考虑使用屏幕驱动的局部刷新功能如果支持。局部刷新更快、无闪烁但对残影控制要求高不宜连续使用太多次。极致省电屏幕每次刷新显示后立即调用epd.sleep()。Pico W在两次更新的间隔可以断开Wi-Fi连接wlan.disconnect()wlan.active(False)。甚至可以调用machine.deepsleep()让整个MCU进入深度睡眠通过RTC定时器或外部中断唤醒。深度睡眠下电流可降至几十微安级别。电源管理如果使用电池供电可以考虑在Pico的EN使能引脚上增加一个MOSFET开关由另一个低功耗定时器控制实现周期性的完全断电重启。数据缓存与离线显示可以在Pico的文件系统中缓存上一次成功的天气数据。如果本次网络更新失败则继续显示缓存的数据避免屏幕空白。6. 进阶技巧与深度避坑指南玩转这套组合有些细节和坑只有实际动手才会遇到。6.1 解决“残影”与显示优化电子墨水屏的物理特性导致其存在“残影”Ghosting。即上一次显示的内容会隐约留在屏幕上影响新内容的对比度。应对策略定期全刷每进行数次局部刷新后例如5-10次必须执行一次全屏刷新epd.Clear() 全屏显示。驱动库的init()函数通常也包含一次全刷。波形文件LUT屏幕的刷新依赖于一个叫“查找表”LUT的波形文件。这个文件定义了从黑到白、白到黑等状态转换时需要施加的电压脉冲序列。不同的屏幕、甚至不同温度下最优的LUT可能不同。微雪的驱动库里通常内置了一个通用的LUT。如果你发现残影特别严重或者刷新速度慢可以尝试寻找或自己优化LUT。这是一个高级话题需要对屏幕数据手册有深入研究。避免频繁刷新从设计上就避免需要每秒更新多次的应用。墨水屏适合分钟级甚至小时级更新的场景。6.2 字体与图形处理内置的fb.text()字体很小8x8像素。要显示大字号或中文需要自定义字体。使用自定义点阵字体找到或制作点阵字库文件通常是.py或.bin格式里面包含了每个字符的像素矩阵数据。在代码中加载这个字库并实现一个draw_text函数该函数从字库中取出字符数据用fb.pixel()或fb.blit()方法画到帧缓冲区。微雪的一些示例代码中包含了中文字库font12.py,font24.py可以参考其实现。绘制简单图形FrameBuffer提供了line,rect,fill_rect,ellipse等基本绘图方法足以绘制简单的图标和图表比如温度计图标、湿度百分比条等。6.3 多屏幕协作与SPI冲突一个Pico的SPI总线可以挂载多个设备通过不同的片选CS引脚控制。理论上你可以同时驱动多块相同的墨水屏。但需要注意总线负载SPI总线有速度上限挂载太多设备可能导致通信不稳定。引脚冲突确保每个设备的BUSY、DC、RST引脚使用独立的GPIO。驱动库修改需要修改驱动库的epdconfig.py或者实例化多个驱动对象每个对象传入不同的引脚配置字典。刷新时序避免同时刷新多块屏幕因为刷新时电流较大。最好分时操作刷新完一块再操作下一块。6.4 固件与内存管理MicroPython运行在有限的RAM上Pico有264KB。当处理图片、大字体或复杂逻辑时可能遇到内存分配失败MemoryError。优化建议使用bytearray而非bytesbytearray是可变的有时能避免创建不必要的中间对象。及时释放大对象例如在完成一张图片的显示后使用del语句删除相关的图像数据变量或者将其赋值为None提示垃圾回收器。冻结模块将常用的库如驱动库、字体库编译成MicroPython的“冻结字节码”Frozen Bytecode并将其烧录到固件中。这样它们会存储在Flash中而非占用宝贵的RAM。这需要从源码编译MicroPython固件属于进阶操作。使用gc.collect()在关键操作后手动触发垃圾回收。7. 项目扩展思路不止于天气站掌握了基础这个Pico e-Paper组合可以化身无数创意项目智能家居信息屏通过MQTT协议订阅家庭自动化平台如Home Assistant的消息显示室内温湿度、空调状态、安防报警等信息。日程/任务看板与日历API如Google Calendar同步显示今日日程。或者做一个本地任务列表通过物理按钮或手机App来添加/完成任务。系统监控副屏通过USB串口或网络接收来自电脑主机的数据CPU占用率、内存使用量、网络速度实时显示在桌面上。慢速微博/新闻阅读器定时抓取RSS订阅或特定社交媒体的更新以“慢阅读”的方式逐条显示。电子标签/价格牌利用其超低功耗特性制作仓库物品标签或商店电子价格牌通过蓝牙或无线网络批量更新内容。最后一点个人体会玩Pico和墨水屏最大的乐趣在于在物理世界的限制低功耗、慢刷新、黑白两色下用代码创造出有用、有趣的东西。它迫使你思考如何简化信息、如何设计交互、如何优化能耗。每一次成功的点亮每一次刷新出清晰的内容带来的成就感远超在高清屏幕上渲染华丽的界面。从这个小小的2.13英寸窗口开始你可以连接到整个世界却只消耗微不足道的电力这本身就是一种极致的优雅。