3.7英寸电子纸HAT驱动指南:从SPI接口到低功耗应用实战
1. 项目概述:3.7英寸电子纸HAT,一块能“记住”画面的屏幕
如果你玩过树莓派、Arduino或者STM32,肯定对各种各样的显示屏不陌生。从LCD到OLED,它们都需要持续供电才能维持画面,一旦断电,屏幕就黑了。但今天要聊的这块3.7英寸电子纸(e-Paper)HAT,玩法完全不同。它最大的特点就是“双稳态”:只有在刷新画面的时候才耗电,一旦画面显示完成,你就算把电源线拔了,画面也能一直留在屏幕上,像一张打印出来的纸一样。这个特性让它特别适合那些需要长时间显示固定信息,但又对功耗极其敏感的场景,比如电子价签、公交站牌、温湿度监控显示,或者一个低功耗的电子时钟。
我手头这块3.7英寸的e-Paper HAT,分辨率是400x280像素,黑白红三色显示。它通过一个HAT(Hardware Attached on Top)接口,可以直接扣在树莓派的GPIO排针上,省去了飞线的麻烦。当然,它的驱动芯片(通常是SSD1675或类似型号)也支持通过SPI接口与Arduino、Jetson Nano甚至是STM32等主流微控制器通信,通用性很强。
网上关于它的资料不少,但很多教程要么过于简略,只给个示例代码;要么就是针对特定平台,换个环境就得从头摸索。这篇手册,我想结合自己从树莓派到STM32的踩坑经历,把它从硬件原理、驱动安装、基础显示到高级应用(比如Partial Update局部刷新)的完整流程梳理清楚。无论你是想在树莓派上做个信息看板,还是想用STM32做个超低功耗的传感器显示器,这里面的细节和坑点,希望能帮你一次搞定。
2. 硬件拆解与接口原理:不仅仅是“插上就能用”
拿到这块屏幕HAT,第一眼会觉得它很简单:一块屏,一块驱动板,一排排针。但要想玩得转,尤其是想跨平台使用,必须得搞清楚板子上的每一个芯片和引脚是干什么的。
2.1 核心芯片与电路分析
驱动板的核心是一颗e-Paper专用驱动IC,比如SSD1675。这颗芯片负责接收微控制器发来的图像数据,并生成复杂的时序电压波形,去驱动电子墨水胶囊里的黑白粒子移动,从而形成图像。它内部集成了RAM(帧缓存),所以我们只需要通过SPI把一整帧图片数据传给它,然后发个刷新指令,它就能自己忙活去了,此时主控MCU可以去休眠,非常省电。
除了主控IC,板上通常还有几个关键电路:
- 电平转换电路:树莓派GPIO是3.3V逻辑,而有些e-Paper驱动IC的IO口可能兼容5V或需要更高电压。板载的电平转换芯片(如74HC125D)确保了信号兼容。
- 电源管理电路:e-Paper刷新时需要多个电压(如VDD、VCOM等)。板载的电源芯片(可能是一颗DC-DC或LDO)负责从输入的3.3V或5V生成这些稳定电压。这里有个坑:有些HAT设计从树莓派的3.3V取电,但刷新时瞬时电流可能较大,导致树莓派3.3V电源被拉低,引起树莓派自身不稳定。稳妥的做法是,如果板子有独立的5V输入引脚,尽量从外部给5V供电。
- 温度传感器:高级一点的驱动板上会有一颗温度传感器(如NTC热敏电阻或数字传感器)。这是因为电子墨水的刷新效果(对比度、残影)受温度影响很大。驱动IC可以根据温度自动调整刷新电压波形,以达到最佳显示效果。如果你的项目环境温度变化大,这个功能至关重要。
2.2 引脚定义与跨平台连接指南
虽然它叫树莓派HAT,但引脚定义是标准的,我们可以把它用在任何有SPI接口的设备上。以下是核心引脚的功能解析:
| 引脚标号 | 名称 | 方向 | 功能说明 |
|---|---|---|---|
| VCC | 3.3V/5V | 输入 | 主电源输入。务必确认板子支持电压,接错可能烧毁。 |
| GND | 地 | - | 电源地。 |
| DIN / MOSI | SPI数据输入 | 输入 | 主设备输出,从设备输入。传输图像数据。 |
| CLK | SPI时钟 | 输入 | 时钟信号,由主设备产生。 |
| CS / SCE | 片选 | 输入 | 低电平有效,选中e-Paper驱动IC。 |
| DC | 数据/命令选择 | 输入 | 高电平表示传输的是数据(Data),低电平表示传输的是命令(Command)。这个引脚非常重要,是SPI控制此类屏幕的关键。 |
| RST | 复位 | 输出 | 低电平复位驱动IC。通常由主控控制,用于硬件复位。 |
| BUSY | 忙状态 | 输出 | 驱动IC输出,高电平表示忙(正在刷新),主控应等待。必须查询此引脚,否则在刷新过程中发送新指令会导致混乱。 |
注意:不同厂商的板子,引脚名称可能略有差异(如CS可能叫
SCE,DC可能叫D/C),但功能相同。连接前一定要找到对应的原理图或引脚图。
跨平台连接示例:
- 树莓派:直接插在40Pin GPIO排针上,通常板子标注了对应关系。确保方向正确(USB口方向一致)。
- Arduino Uno:需要杜邦线连接。例如:
DIN->D11(MOSI),CLK->D13(SCK),CS->D10,DC->D9,RST->D8,BUSY->D7。 - STM32(以STM32F103C8T6为例):使用硬件SPI1:
DIN->PA7(MOSI),CLK->PA5(SCK),CS->PA4(SS),其他DC、RST、BUSY接任意GPIO,如PB0,PB1,PB10。
3. 驱动与软件环境搭建:从树莓派到STM32
驱动这块屏幕,本质上是按照其时序要求,通过SPI发送一系列命令和数据。我们可以选择使用现有的库,也可以自己从头写驱动,后者对理解原理更有帮助。
3.1 树莓派平台:使用Waveshare官方库
对于树莓派用户,最快捷的方式是使用屏幕供应商(如Waveshare)提供的Python库。
启用SPI接口:
sudo raspi-config依次选择
Interface Options->SPI->Yes启用。重启或使用sudo raspi-config nonint do_spi 0命令快速启用。安装库与依赖:
sudo apt update sudo apt install python3-pip python3-pil python3-numpy git clone https://github.com/waveshare/e-Paper.git cd e-Paper/RaspberryPi_JetsonNano/python sudo pip3 install .安装后,在
examples目录下就能找到针对不同尺寸屏幕的示例程序。运行测试:
cd examples sudo python3 epd_3in7_test.py如果一切正常,屏幕会依次显示黑白红三色的测试图案。
树莓派上的坑点:
- 权限问题:直接运行Python脚本可能因无法访问GPIO而报错。务必使用
sudo或以root权限运行。 - 库版本冲突:如果系统已安装旧版
RPi.GPIO或spidev,可能导致问题。可以尝试在虚拟环境中安装。 - SPI速度:官方库可能设置了固定的SPI速度。如果屏幕出现乱码或刷新不全,可以尝试在代码中降低SPI速度(如修改
spidev的max_speed_hz参数)。
3.2 Arduino平台:通用库与底层驱动
Arduino生态下有像GxEPD2这样的优秀通用库,它支持众多型号的e-Paper屏幕。
安装库:在Arduino IDE中,点击
项目->加载库->管理库...,搜索GxEPD2并安装。选择正确的驱动类:这是最关键的一步。
GxEPD2库为不同驱动IC和屏幕尺寸定义了不同的类。对于3.7英寸400x280的SSD1675屏幕,通常对应的类是GxEPD2_370_GDEY037T03(具体需根据屏幕规格书确认)。你可以在库的src目录下查找或参考示例。编写基础代码:
#include <GxEPD2_BW.h> // 黑白显示类,如果支持红白黑则用_GxEPD2_3C #include <Fonts/FreeMonoBold9pt7b.h> // 定义连接引脚,根据你的实际接线修改 #define EPD_CS 10 #define EPD_DC 9 #define EPD_RST 8 #define EPD_BUSY 7 // 声明显示对象 GxEPD2_3C<GxEPD2_370_GDEY037T03, GxEPD2_370_GDEY037T03::HEIGHT> display(GxEPD2_370_GDEY037T03(EPD_CS, EPD_DC, EPD_RST, EPD_BUSY)); void setup() { display.init(115200); // 初始化串口用于调试,可省略 display.setRotation(1); // 设置旋转方向 display.setFont(&FreeMonoBold9pt7b); display.setTextColor(GxEPD_BLACK); display.firstPage(); do { display.setCursor(10, 30); display.print("Hello, e-Paper!"); } while (display.nextPage()); display.hibernate(); // 进入深度休眠,省电 } void loop() { }
Arduino上的注意事项:
- 内存消耗:全屏缓冲区的图像数据很占内存(400x280/8 = 14KB,三色则更大)。在内存较小的Arduino Uno上,使用全缓冲区可能导致内存不足。
GxEPD2库支持“分页”写入,可以边生成边发送,不占用完整缓冲区。 BUSY引脚处理:库内部会自动查询BUSY引脚,但你必须确保该引脚连接正确且模式设置为INPUT。
3.3 STM32平台:HAL库与自定义驱动
在STM32上使用,能让你对驱动过程有最深刻的理解。我们以STM32CubeMX和HAL库为例。
CubeMX配置:
- 配置一个SPI外设(如SPI1)为全双工主模式。
- 将
CS、DC、RST、BUSY配置为GPIO输出(RST、CS、DC)和输入(BUSY)。 - 注意SPI的时钟极性(CPOL)和相位(CPHA)。对于SSD1675,通常模式为
CPOL=0, CPHA=0(即模式0)。
编写底层驱动函数: 核心是三个函数:写命令、写数据、读忙状态。
// 引脚定义 #define EPD_CS_Clr() HAL_GPIO_WritePin(EPD_CS_GPIO_Port, EPD_CS_Pin, GPIO_PIN_RESET) #define EPD_CS_Set() HAL_GPIO_WritePin(EPD_CS_GPIO_Port, EPD_CS_Pin, GPIO_PIN_SET) // 类似定义 DC, RST 的宏 // 写命令 void EPD_WriteCmd(uint8_t cmd) { EPD_DC_Clr(); // DC置低,表示命令 EPD_CS_Clr(); // 片选拉低 HAL_SPI_Transmit(&hspi1, &cmd, 1, 100); EPD_CS_Set(); } // 写数据 void EPD_WriteData(uint8_t dat) { EPD_DC_Set(); // DC置高,表示数据 EPD_CS_Clr(); HAL_SPI_Transmit(&hspi1, &dat, 1, 100); EPD_CS_Set(); } // 读忙状态 void EPD_WaitUntilIdle(void) { while(HAL_GPIO_ReadPin(EPD_BUSY_GPIO_Port, EPD_BUSY_Pin) == 1) { HAL_Delay(10); } }实现初始化与刷新序列: 根据屏幕数据手册,初始化过程是一系列固定的命令和数据。你需要将厂商提供的初始化代码(通常是Arduino或树莓派库里的)移植过来,用上面的
EPD_WriteCmd和EPD_WriteData函数实现。void EPD_Init(void) { EPD_Reset(); // 硬件复位 EPD_WriteCmd(0x12); // 软件复位 EPD_WaitUntilIdle(); // ... 依次发送初始化命令序列,如设置分辨率、驱动波形等 EPD_WriteCmd(0x11); // 数据输入模式 EPD_WriteData(0x03); // ... 更多设置 }显示一帧图像,则是先发送写RAM的命令(0x24),然后连续发送整个帧缓冲区的数据(400*280/8 = 14000字节),最后发送刷新显示的命令(0x22)和0xC7,并等待
BUSY引脚变低。
STM32移植的核心挑战:
- 时序严格:某些命令后需要延时
ms级,必须严格按照数据手册操作。 - 数据量巨大:通过SPI发送14000字节数据需要时间。要优化SPI时钟速度(在CubeMX中设置),并确保使用
HAL_SPI_Transmit发送大数据时不会超时。 - 帧缓冲区管理:在STM32上开辟一个14000字节的数组(全局变量)作为帧缓冲区。绘制点、线、文字、图片的操作,最终都是修改这个缓冲区里的对应位。
4. 图像处理与显示优化:让内容更“好看”
直接显示黑白像素点阵太原始了。我们通常需要显示文字、图形甚至图片。这就涉及到图像处理。
4.1 创建与操作帧缓冲区
无论在哪个平台,核心都是一个一维字节数组(uint8_t buffer[400 * 280 / 8]),它代表了屏幕的每一个像素。数组的每个bit对应一个像素(1为黑,0为白)。对于三色屏,通常需要两个这样的缓冲区,分别代表黑色和红色图层。
设置一个像素点的函数是基础:
void SetPixel(int x, int y, int color) { if (x < 0 || x >= 400 || y < 0 || y >= 280) return; uint16_t addr = (x + y * 400) / 8; uint8_t bit = x % 8; if (color == BLACK) { frame_buffer[addr] |= (1 << (7 - bit)); // 位置1 } else { frame_buffer[addr] &= ~(1 << (7 - bit)); // 位清0 } }基于这个函数,你可以实现画线、画矩形、画圆等基本图形算法。
4.2 字体与文字显示
显示文字的本质是将字模(每个字对应的点阵数据)绘制到帧缓冲区。
- 使用现成字库:在树莓派Python中,PIL(Pillow)库内置了字体渲染功能,可以直接将文字转为图像。在Arduino的
GxEPD2库中,它适配了Adafruit GFX字体系统,可以直接使用.print()方法。 - 嵌入式平台取模:在STM32等资源受限平台,需要预先将用到的汉字、字母、数字的字模提取出来,保存为常量数组。可以使用PC端的取模软件(如PCtoLCD2002)来完成。在代码中,根据字符编码找到对应的字模数据,然后一位一位地画到缓冲区。
4.3 显示图片:从文件到点阵
在树莓派上,用PIL库可以轻松打开并处理图片:
from PIL import Image, ImageDraw, ImageFont image = Image.open('logo.png') # 调整图片大小和模式为'1'(1位像素,黑白) image = image.resize((400, 280)).convert('1')然后将图片数据传递给屏幕库的显示函数。
在STM32上,则需要先将图片转换成C语言数组。同样使用取模软件,将图片转换为十六进制数组。然后编写一个函数,将这个数组数据直接拷贝到帧缓冲区的指定位置。
重要技巧:电子纸刷新慢,且全刷有闪烁感。更新内容时,尽量只更新变化的部分。如果只是更新几个数字,可以只重绘那个区域,而不是刷新整个屏幕。这需要更精细的缓冲区管理和局部刷新命令的支持。
5. 低功耗设计与局部刷新:进阶玩家的必修课
电子纸的招牌特性是超低功耗,但要真正发挥这个优势,需要正确的使用方法。
5.1 全局刷新 vs. 局部刷新
- 全局刷新:清除整个屏幕的所有旧粒子,然后绘制新图像。过程耗时较长(约2-4秒),有明显的全屏闪烁(黑白反转过程),但显示效果最干净,无残影。适用于内容完全改变的场景。
- 局部刷新:只刷新屏幕上指定区域的内容。速度快(可能几百毫秒),无全局闪烁,功耗更低。但长期局部刷新会在屏幕边缘留下“鬼影”(残影),需要定期进行几次全局刷新来清除。
如何实现局部刷新:
- 发送命令,将屏幕设置为局部刷新模式。
- 指定要刷新的矩形区域(通过设置行、列地址命令)。
- 只向这个区域对应的帧缓冲区部分发送数据。
- 发送局部刷新命令。注意:并非所有e-Paper屏幕都支持硬件局部刷新,需要查阅具体驱动IC(如SSD1675)的数据手册。即使支持,其命令序列也与全局刷新不同。
5.2 深度睡眠与定时唤醒
在两次刷新之间,系统可以进入极低功耗的睡眠模式。
- 树莓派:虽然树莓派本身功耗不低,但你可以通过
gpiozero或RPi.GPIO库控制一个MOSFET开关,在刷新完成后彻底切断屏幕的电源(VCC),实现零待机功耗。同时树莓派自身可以进入轻度休眠。 - Arduino/STM32:这是发挥优势的地方。在调用
display.hibernate()(对于GxEPD2)或发送深度睡眠命令后,e-Paper驱动IC自身功耗可降至微安级。此时,主控MCU(如STM32)也可以进入Stop或Standby模式,整个系统仅靠RTC维持定时,功耗可以做到10微安以下。通过RTC定时唤醒或外部中断(如按键)唤醒,再更新屏幕,可以实现用纽扣电池运行数月甚至数年。
我的踩坑记录:曾经试图让STM32在刷新后立即进入Stop模式,但发现有时唤醒后屏幕通信失败。原因是BUSY引脚状态未妥善处理。正确的流程是:发送刷新命令 -> 循环查询BUSY引脚直到变低(刷新完成)-> 发送深度睡眠命令 -> 再将MCU置入低功耗模式。顺序错了,屏幕可能无法正确进入睡眠。
6. 项目实战:构建一个自动刷新的天气信息站
理论说再多,不如一个实际项目。我们来设计一个基于树莓派Zero W和3.7寸e-Paper的桌面天气站。
核心功能:每30分钟从网络API获取一次天气信息(温度、湿度、天气状况、预报),生成图片,刷新到屏幕上,然后树莓派进入休眠以省电。
硬件清单:
- 树莓派Zero W
- 3.7英寸e-Paper HAT
- 可选:外接5V电源适配器(为屏幕提供更稳定电源)
软件步骤:
- 系统与依赖:安装Raspberry Pi OS Lite,启用SPI,安装Python3、pip、PIL库以及
requests库。 - 获取天气数据:注册一个免费天气API服务(如OpenWeatherMap),编写Python脚本定时获取JSON数据。
- 生成显示图像:使用PIL库绘制背景、文字和图标。天气图标可以预先下载一套PNG素材,根据天气状况选择。
from PIL import Image, ImageDraw, ImageFont def create_weather_image(temp, humidity, desc): image = Image.new('1', (400, 280), 255) # 白色背景 draw = ImageDraw.Draw(image) font_large = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf', 48) draw.text((50, 50), f"{temp}°C", font=font_large, fill=0) # ... 绘制其他信息 return image - 控制屏幕刷新:集成e-Paper的Python库,将生成的图像发送到屏幕。关键点:为了减少残影,可以设定每刷新5次局部天气信息后,进行一次全屏的全局刷新。
- 自动化与省电:使用
cron定时任务每30分钟执行一次脚本。在脚本末尾,可以调用系统命令让树莓派进入暂停状态(sudo halt或使用rtcwake进入睡眠)。但更实用的方案是让树莓派保持运行,而仅关闭屏幕电源(如果硬件支持),因为树莓派从完全关机状态唤醒需要外部干预,不够“自动”。
可能遇到的问题与解决:
- 网络连接不稳定:脚本中需要增加网络检查重试机制。
- 图片刷新有残影:严格按照“多次局部+一次全局”的节奏,并确保全局刷新时使用正确的清屏命令(通常是先全白再全黑再显示新内容)。
- 功耗依然偏高:检查是否有其他进程在运行。可以考虑使用
systemd服务管理脚本,并禁用不需要的树莓派外设(如HDMI、蓝牙)。
这个项目综合运用了硬件驱动、网络通信、图像处理和定时任务,是一个很好的e-Paper入门到进阶的练手项目。你可以在此基础上增加更多传感器(如室内温湿度),或者改用ESP32+STM32的组合,由ESP32负责联网获取数据,通过串口发给STM32驱动屏幕,这样可以做出功耗更低的独立设备。
玩转e-Paper的关键在于耐心和理解其工作原理。它不像普通屏幕那样“实时”,每一次刷新都需要规划和等待。但正是这种特性,让它能在那些需要“静默显示”的场合大放异彩。从按照示例点亮屏幕,到自己驱动每一个像素,再到设计出低功耗的完整应用,这个过程本身,就是硬件编程最大的乐趣所在。
