树莓派GPIO模拟SPI驱动ST7565液晶屏详解
1. 这个项目到底在做什么以及为什么值得折腾先把这个项目说清楚用树莓派的普通 GPIO 引脚配合 wiringPi 这个 C 语言库按照软件模拟 SPI 的时序把一块基于 ST7565 控制芯片的 128x64 黑白点阵液晶屏点亮并在上面显示文字、数字、线条和简单图形最终可以做成一个系统状态监视屏、传感器读数仪表或者小型仪表盘。ST7565 是一颗非常经典的单色液晶驱动控制芯片在早期的手机副屏、工业仪表、小尺寸 COG 液晶模组上特别常见。它最多能驱动 128x64 分辨率的点阵屏内部自带显示 RAM外部只需要 SPI 接口就能写入数据。市面上现在还能买到很多基于这颗芯片的液晶模块排针直接引出面包板就可以接。如果你手里恰好有一块这种老屏又不想用 Python 的现成封装库直接把它“当黑盒用掉”而是想把底层时序、控制器初始化、页地址寻址这些东西彻底弄明白那么顺着这个项目走一遍是最快的路径。这个项目最适合三类人第一类是有 C 语言基础想在树莓派上接触底层 GPIO 控制的玩家第二类是手头有闲置 ST7565 屏想废物利用做成小工具的人第三类是做小型嵌入式产品需要低成本、高可靠性图形显示方案的开发者。做完这个项目你掌握的其实是一整套“带控制器的点阵屏驱动方法论”包括如何初始化芯片、如何理解页地址与列地址、如何把数据从内存映射到屏幕像素、如何设计字符字模和图形函数。这套思路学会了以后换其他同架构的液晶屏比如某些 128x32 的小屏或者市面上常见的 128x64 的 COG 屏基本可以平移过去。为什么要用 wiringPi而不是用 Python 的 RPi.GPIO 或者 gpiozero原因是这个库虽然在几年前就已经停止维护但它作为教学工具的价值到现在依然很高。wiringPi 的函数接口写得非常简洁底层直接操作树莓派的 GPIO 寄存器延时控制可以精确到微秒级别这对需要模拟 SPI 时“每一位都要翻转时钟线”的场景特别关键。用 Python 做当然也能做但解释型语言在循环里翻转 GPIO 的稳定性和速度都不如 C 来得扎实尤其是当你需要连续发送几百个字节、还要保证时序不错乱的时候C 语言的优势非常明显。此外wiringPi 支持 BCM 编号和自身编号两种方式调试起来很灵活项目代码也短非常适合用来理解“时序和位操作”这两个嵌入式基础概念。还要解释一下 ST7565 的接口模式。这颗芯片本身支持并口和串口两种连接方式并口需要 8 根数据线加若干控制线接线多、速度快而本文用串口 SPI 方式只需要 SCK、SDA、A0、RST、CS 五根信号线接线简单刷新一屏文本也完全够用。整个过程不依赖树莓派硬件 SPI 外设直接用 GPIO 按位模拟时钟这样做最大的好处是引脚随便挑你喜欢哪几个引脚就接哪几个引脚代码里改定义就行坏处是 CPU 占用会高一点但 128x64 这种黑白屏正常应用场景下每秒刷十次以内就够了这点开销树莓派完全扛得住。2. 硬件准备与接线以及新手最容易翻车的几个点2.1 物料清单与模块引脚识别照着下面这个清单准备东西基本就不会缺件了树莓派一块任意型号都可以我用的是 4B但 3B、2B、Zero W 都能跑。ST7565 液晶模块一块。注意看模块 PCB 上是否自带对比度调节电位器不带的话后面要通过命令调节对比度问题也不大。面包板一块母对母杜邦线若干。建议多备几根有些线接触不良会坑死人。一个 100 到 200 欧姆的电阻用于背光限流。万用表一个调试阶段量电压、查通断离不开它。接线之前先把模块引脚逐个确认。市面上 ST7565 模块引脚排布不太统一常见丝印有 VCC/VDD、GND、SCL/SCK、SDA/SI、A0/RS/DC、RST/RESET、CS/片选、BL/LED。其中 A0 在不同模块上标注不同但作用完全相同A0 为低电平时SPI 线上发送的是命令字节A0 为高电平时发送的是显示数据字节。这个一定要弄清楚不然程序怎么改都是花屏。2.2 引脚对应关系和接线示意我下面用的全是 BCM 编号因为代码里只要调用wiringPiSetupGpio()函数里就能直接用 BCM 编号不用再去做 wiringPi 专用编号和 BCM 编号之间的换算省掉一个超容易出错的环节。如果你确实想用 wiringPi 原始编号也不是不行先执行gpio readall看一眼映射表再写代码。液晶模块引脚接树莓派引脚说明VCC3.3V物理引脚 1模块逻辑电源不要接 5VGNDGND物理引脚 6必须共地SCKBCM 11SPI 时钟线SDABCM 10SPI 数据线A0/DCBCM 24命令/数据选择线RSTBCM 25复位线CSBCM 8片选线BL3.3V 串 120Ω 电阻后接背光正极或者串电阻后接 GPIO 控制这里有几个容易踩的坑我逐个说。第一VCC 绝对不能直接接 5V。ST7565 主控的工作电压范围通常是 2.7V 到 3.6V树莓派 GPIO 输出高电平也就是 3.3V。如果你脑子一热把模块 VCC 接到 5V 引脚上模块长期过压轻则发热、工作不稳定重则直接烧掉内部稳压电路和控制器。只有当模块板上明确印着支持 5V、且自带稳压转换电路时才允许接 5V。判断不了的时候一律接 3.3V这是最稳妥的选择。第二背光不能直接挂 GPIO。ST7565 模块的背光一般是 LED 灯串工作电流比逻辑电路大不少树莓派 GPIO 引脚最大输出电流有限直接驱动容易把引脚搞坏。正确做法是背光正极接 3.3V负极串一个限流电阻到 GND。电阻取多少可以算一下假设背光 LED 压降约 3.0V如果串 120Ω 电阻电流就是 (3.3V - 3.0V) / 120Ω ≈ 2.5mA室内环境这个亮度够了而且电阻几乎不发热。如果你嫌暗可以换成 100Ω电流约 3mA还在安全范围。想用程序控制背光开关的话可以把电阻接在背光负极和某个 GPIO 之间GPIO 输出低电平点亮、高电平熄灭。第三所有的 GND 必须连在一起。如果你的模块是外接电源供电那么模块的 GND 和树莓派的 GND 必须可靠相连否则 SPI 信号没有统一参考地数据线上出现乱码几乎是必然的。2.3 接完线之后的硬件自检线接完之后别急着写代码花五分钟做两个检查。第一背光是否正常亮。如果背光单独引线先用跳线临时接到 3.3V 试一下能亮说明背光通路没问题。第二用万用表直流电压档量模块 VCC 引脚和 GND 引脚之间的电压正常应该在 3.2V 到 3.4V 之间。如果电压不对检查是不是杜邦线接触不良。这两个检查能过滤掉大量“程序没问题但屏幕就是不动”的硬件故障千万别跳过。3. 环境准备系统与 wiringPi 的安装验证3.1 安装 wiringPi 的两种方式wiringPi 官方仓库已经停止更新不同版本的树莓派系统安装方式不太一样我把它拆成两条路任选一条能走通就行。第一条路适用于老版本系统。如果你的树莓派 OS 版本比较旧直接执行软件源安装命令系统会自动装好 wiringPi 工具和库文件。装完后执行gpio -v如果能输出版本号就说明成了。第二条路适用于新版本系统。树莓派官方软件源已经把 wiringPi 移除了这时候需要自己编译一份老版本源码。准备工作是先确保系统里有 git 和编译工具链然后从官方仓库克隆源码进入目录后执行构建脚本编译完成后把生成的库文件安装到系统目录。整个编译过程不算长树莓派 4B 上几分钟之内就能完成。这里我不写具体仓库地址网上搜索 wiringPi GitHub 就能找到原项目仓库下载最新源码编译即可。两个坑提前说一下。第一个坑是编译时遇到缺少头文件的报错说明系统没有安装编译工具链先安装build-essential之类的软件包再编译。第二个坑是某些内核版本下旧版 wiringPi 无法识别新树莓派型号表现为gpio readall显示引脚映射不完整这种情况可以换用社区维护的兼容分支基本都能解决。3.2 验证库是否正常工作的两个命令装好之后先执行gpio -v看版本再执行gpio readall。后者会打印出一张完整的引脚功能表你要重点看两件事表格是否正常显示了 BCM 编号和物理引脚的对应关系程序是否提示权限不足。gpio readall也是后续排查问题的重要工具。比如你怀疑某个引脚被其他功能占用了通过这个表就能看出来。树莓派的引脚复用经常出问题尤其是你可能把屏幕的信号线不小心接在了板载 I2C 或者 PCM 功能引脚上这时屏幕表现会非常诡异。3.3 权限问题的处理建议执行 GPIO 操作时如果提示unable to open /dev/mem或者wiringPiSetup: Permission denied那不是库没装好而是当前用户没有操作 GPIO 的权限。两个解决办法第一所有命令和程序都用sudo运行第二把当前用户加入 gpio 用户组重新登录后生效。我建议学习期直接用 sudo省得在权限上浪费时间。等你的项目要开机自启动、做服务的时候再考虑用 systemd 配置里指定用户的方式去规避权限问题。4. 驱动原理ST7565 初始化命令与页面寻址机制4.1 SPI 时序与命令/数据的区分先把这个项目最底层的概念讲明白。SPI 串行通信本质上就是三根线配合SCK 提供时钟脉冲SDA 在时钟作用下一位一位地传输数据CS 负责选中设备。ST7565 的协议规定数据位在 SCK 的上升沿被采样。也就是说发送一个 bit 的逻辑是先把 SDA 引脚设置成目标电平然后在 SCK 引脚上产生一个从低到高的跳变保持片刻再把 SCK 拉低准备发送下一位。A0 引脚用来区分当前字节是命令还是数据。A0 为低控制器会把收到的字节当作命令解释A0 为高会把字节写入显示 RAM。这个设计在很多液晶控制芯片上都能看到所以驱动代码里写命令和写数据其实是同一套字节发送函数只是 A0 的电平不同。在实际写代码时建议把底层发送封装成两个函数send_command(cmd)和send_data(data)它们内部都调用同一个 SPI 发送函数只是 A0 引脚高低不同。这样上层业务逻辑就不会被底层时序细节干扰。4.2 初始化命令串逐个解释ST7565 上电后的状态是随机的不初始化就直接写数据显示什么都可能不对。初始化命令串是整个驱动的地基我列一份实测稳定可用的序列并解释每条命令的作用。常用初始化序列如下0xE2软复位让控制器内部逻辑回到确定的初始状态。0xA2设置偏压比。偏压比决定 LCD 驱动电压的分压比例常用值有 1/7 和 1/9 两种不同液晶玻璃屏对偏压比要求不同。0xC8设置 SHL 方向。这个位控制垂直方向的扫描输出是否反转如果屏幕上下颠倒就改成0xC0。0xA0设置 ADC 方向。这个位控制水平方向的扫描输出是否反转如果屏幕左右镜像就改成0xA1。0x2F打开内部电源电路包括稳压器、电压跟随器等。0x26设置稳压电阻比。它影响内部电压放大倍数需要和对比度配合调整。0x81加参数0x20设置对比度寄存器。对比度范围通常是 0 到 63部分模块用 0 到 31。这里可以先给个中间值之后再微调。0xAF打开显示。与此对应的是0xAE关闭显示。在初始化过程中如果屏幕全黑但背光亮先不要怀疑代码逻辑优先怀疑偏压比和对比度配置。常见现象是偏压比选错导致驱动电压不足屏幕几乎看不到内容对比度配太高导致整个屏幕糊成一片黑。调整思路就是三个参数轮流试偏压比、稳压电阻比、对比度寄存器。每调一次用清屏后写入全 0xFF 的方式观察屏幕变黑的程度。4.3 页面寻址8 页 × 8 行不是线性内存ST7565 的显示 RAM 有 128 列 × 64 行正好对应屏幕的 128x64 像素。但它的 RAM 组织方式不是线性的而是把 64 行分成 8 个页每页占 8 行所以一共是 8 页每页 128 字节。换句话说地址模型是这样的水平方向是列地址 0 到 127垂直方向是页地址 0 到 7每一页内部的 8 行由字节内的 8 个 bit 控制。操作时序为先发送页地址命令0xB0 页号再发送列地址高四位命令0x10 (列号 4)接着发送列地址低四位命令0x00 (列号 0x0F)最后连续发送数据字节。每次发送一个数据字节后列地址会自动加 1写满一行 128 个字节后列地址会回到 0但页地址不会自动跳到下一页。很多人第一次写清屏函数就在这里翻了车连续写了 1024 个字节结果只有第一页被填满其他页全是旧数据屏幕上会出现重复的横条纹。理解这种寻址方式之后清屏和画像素函数就很好设计了。清屏就是对每一页循环设置页地址设置列地址为 0连续写 128 个 0。画像素则需要根据坐标计算出页号和列号再在对应字节的 bit 上置位。4.4 屏幕方向、镜像与列地址偏移ST7565 有两个方向控制位分别是 ADC 选择命令和 SHL 选择命令。ADC 位控制列扫描方向取值 0 或 1屏幕会左右镜像SHL 位控制行扫描方向取值 0 或 1屏幕会上下倒置。组合起来就是四种屏幕方向。当你发现画一条从左到右的线结果变成从右到左或者文字整体上下颠倒不要想着在代码里把坐标公式反着算直接改初始化命令里的这两个位就行改完之后一劳永逸所有绘图函数都正常。还有一个隐藏很深的列地址偏移问题。某些 ST7565 模块的 RAM 列地址和屏幕物理列之间存在偏差差值一般是 4 个像素左右。具体表现是你按坐标 0 画一条竖线它不出现在屏幕最左边而是偏了几个像素。这个时候检查你的列地址设置命令是否包含偏移补偿或者在初始化命令里设置了正确的列偏移值。不同模块的情况不完全一样解决办法就是用全屏填充法观察先把屏幕刷成黑色如果左边或者右边出现一条亮线说明列地址起始位置没对齐补一个偏移即可。5. 核心代码实现从 SendByte 到显示字符串5.1 引脚宏定义和底层字节发送代码全部基于 BCM 编号程序最开始调用wiringPiSetupGpio()。引脚宏定义如下#include stdio.h #include string.h #include wiringPi.h #define PIN_SCK 11 #define PIN_SDA 10 #define PIN_A0 24 #define PIN_RST 25 #define PIN_CS 8底层字节发送函数是所有显示操作的基础。ST7565 要求数据在 SCK 上升沿被采样所以发送每一位时必须先把 SDA 设置好再产生一次上升沿最后拉低 SCK 准备下一轮。这里我还通过is_data参数控制 A0 电平让同一个函数既支持命令发送也支持数据发送。static void lcd_write_byte(unsigned char val, int is_data) { int i; digitalWrite(PIN_A0, is_data ? HIGH : LOW); digitalWrite(PIN_CS, LOW); for (i 7; i 0; i--) { digitalWrite(PIN_SCK, LOW); digitalWrite(PIN_SDA, (val (1 i)) ? HIGH : LOW); delayMicroseconds(1); digitalWrite(PIN_SCK, HIGH); delayMicroseconds(1); } digitalWrite(PIN_CS, HIGH); } static void lcd_command(unsigned char cmd) { lcd_write_byte(cmd, 0); } static void lcd_data(unsigned char dat) { lcd_write_byte(dat, 1); }注意delayMicroseconds(1)。这个延时不是必须的但对老型号模块和杜邦线比较长的场景很有帮助能显著降低信号干扰。你完全可以把两处延时都去掉程序也能跑但如果屏幕出现偶发花屏第一步就是把这个延时加上再试。5.2 复位与初始化函数复位函数很简单先把 RST 拉低 10 毫秒以上再拉高。此时控制器完成内部复位接下来才能发送初始化命令。static void lcd_reset(void) { digitalWrite(PIN_RST, LOW); delay(10); digitalWrite(PIN_RST, HIGH); delay(10); }初始化函数就是把上一节的命令串依次发送。为了调试方便我把初始化命令放到数组里用循环逐条发送static void lcd_init(void) { static const unsigned char init_cmds[] { 0xE2, 0xA2, 0xC8, 0xA0, 0x2F, 0x26, 0x81, 0x2A, 0xAF }; int i; for (i 0; i sizeof(init_cmds); i) { lcd_command(init_cmds[i]); } }这段初始化里0x81后面的0x2A是对比度参数具体值可以按你的屏幕调整。如果屏幕全黑把这个值往小调如果屏幕太淡往大调。0xA2是偏压比如果你的屏显示很灰可以改成0xA3试一下。遇到镜像问题就改0xC8为0xC0或者改0xA0为0xA1。5.3 清屏与画像素建立显存缓存为什么要建显存缓存因为 ST7565 不支持读回 RAM 内容。如果你直接在屏上画一个像素需要把该像素所在整页的旧数据先读出来才能修改单个 bit但芯片没有读命令所以实际做法是维护一块 8×128 字节的内存数组作为显存镜像所有绘图操作先改数组再把整个数组或者变化的那一段推送到屏幕。显存定义和清屏函数如下static unsigned char lcd_buffer[8][128]; static void lcd_clear(void) { memset(lcd_buffer, 0, sizeof(lcd_buffer)); lcd_refresh(); } static void lcd_refresh(void) { int page, col; for (page 0; page 8; page) { lcd_command(0xB0 | page); lcd_command(0x10 | 0); lcd_command(0x00 | 0); for (col 0; col 128; col) { lcd_data(lcd_buffer[page][col]); } } }lcd_refresh()负责把显存数组完整推到屏幕。操作顺序是先设置页地址然后把列地址清为 0接着连续写 128 个数据字节。循环八次整屏刷新完成。画像素函数的坐标映射关系是页号 y / 8页内行号 y % 8列号 x。对应代码static void lcd_set_pixel(int x, int y, int on) { if (x 0 || x 128 || y 0 || y 64) { return; } if (on) { lcd_buffer[y / 8][x] | (1 (y % 8)); } else { lcd_buffer[y / 8][x] ~(1 (y % 8)); } }画完像素后需要调用lcd_refresh()才能显示出来。如果你画很多点后每次都刷新效率偏低可以等一批图元画完再一次刷新这也是我用显存方案的重要原因。5.4 画线、画矩形的基本函数有了lcd_set_pixel基本图元就很好写了。水平线固定 y对 x 从 x0 到 x1 循环画点垂直线固定 x对 y 循环。矩形就是四条边组合如果要填充就按行从上到下填充。static void lcd_draw_line(int x0, int y0, int x1, int y1) { int dx, dy, steps, k; float x_inc, y_inc, x, y; dx x1 - x0; dy y1 - y0; steps (abs(dx) abs(dy)) ? abs(dx) : abs(dy); x_inc (float)dx / steps; y_inc (float)dy / steps; x x0; y y0; for (k 0; k steps; k) { lcd_set_pixel((int)(x 0.5), (int)(y 0.5), 1); x x_inc; y y_inc; } }浮点计算在树莓派上完全不是问题如果做到更低性能的 MCU 上再改成整数算法。矩形绘制只需要在四条边之间调lcd_draw_line就行这里不单独贴代码了。5.5 5x7 字模与字符串显示字符显示的本质是把字符拆成点阵逐点写入显存。最常用的 ASCII 点阵字模是 5x7每个字符用 5 个字节表示每字节的低 7 位从上到下对应 7 个像素行。举个例子字符A的字模可以这样写static const unsigned char char_A[5] { 0x3E, 0x05, 0x09, 0x05, 0x3E };完整字模表很长建议直接找一份标准 5x7 点阵字库拷进工程。下面给出字符串显示的核心思路static void lcd_draw_char(int x, int y, char c) { int col, row; int page y / 8; int bit y % 8; const unsigned char *glyph; glyph font_5x7[(int)c]; for (col 0; col 5; col) { if (x col 128) { break; } for (row 0; row 7; row) { if (glyph[col] (1 row)) { lcd_set_pixel(x col, y row, 1); } } } } static void lcd_draw_string(int x, int y, const char *str) { int cur_x x; while (*str) { lcd_draw_char(cur_x, y, *str); cur_x 6; if (cur_x 5 128) { cur_x 0; y 8; } } }写字符串时最容易犯的错是坐标越界。因为 5x7 字体加 1 列间距每字符宽度是 6 像素128 像素宽最多显示 21 个字符。如果你的字符串超过这个长度不处理换行就会在屏幕边缘出现乱码。上面代码里已经做了简单换行判断行高按 8 像素步进。5.6 主函数示例把上面所有函数串起来主函数完整流程如下int main(void) { wiringPiSetupGpio(); pinMode(PIN_SCK, OUTPUT); pinMode(PIN_SDA, OUTPUT); pinMode(PIN_A0, OUTPUT); pinMode(PIN_RST, OUTPUT); pinMode(PIN_CS, OUTPUT); digitalWrite(PIN_SCK, LOW); digitalWrite(PIN_CS, HIGH); lcd_reset(); lcd_init(); lcd_clear(); lcd_draw_string(0, 0, Hello ST7565); lcd_draw_string(0, 16, 128x64 LCD); lcd_draw_line(0, 40, 127, 40); lcd_draw_line(0, 40, 0, 63); lcd_refresh(); delay(5000); return 0; }这段程序执行后的预期结果屏幕背光亮起左上角两行文字显示中下部有一条水平线、一条垂直线。如果你看到这个画面说明驱动已经全部通了接下来就可以按自己的需求画各种界面了。6. 常见问题与调试思路速查6.1 屏幕全黑或全白先查不是代码的部分屏幕全黑、背光亮这种现象在调试第一天出现率最高。第一步用万用表量模块 VCC 和 GND 之间有没有 3.3V 左右的电压别只靠“模块没冒烟”来判断供电正常。第二步检查 RST 引脚复位时序不完整会导致控制器一直处于复位状态屏幕当然全黑。第三步看对比度配置把初始化命令里的偏压比和对比度参数往多个方向调一遍全黑往往就是对比度参数过高或者偏压比设置不对导致驱动电压失控。如果屏幕全白优先怀疑数据线。用万用表测 SDA 和 SCK 在执行lcd_refresh()期间有没有电平跳变。如果没有任何跳变说明程序可能根本没跑起来或者引脚定义和实际接线不一致。再加上 A0 也工作异常的话控制器会把所有命令当成数据或者把数据当成命令最终显示内容就是一片空白。6.2 乱码、错位、镜像的排查逻辑花屏和乱码先用最简单的归因法把 SPI 延时的两个delayMicroseconds(1)改成delayMicroseconds(4)如果乱码消失说明问题几乎可以锁定为时序太快或线材干扰。这种问题在面包板长跳线上太常见了长线加了电容电感高频信号质量变差芯片收到的数据就错位了。字符错八成是页地址和列地址问题。比如你想在第二行显示文字结果第二行文字跑到第一行附近重叠了那就是页地址没从 0xB1 开始。再比如文字整体左移或者右移了几个像素就是列地址设置或者偏移没处理。建议临时在显示函数里打印页号、列号观察每一步操作是否和预期一致。镜像问题最简单改初始化命令里的两个方向位。屏幕上下颠倒改0xC8为0xC0左右镜像改0xA0为0xA1。一次改一个位、刷新观察不要两个一起改不然你都不知道是哪个方向生效了。6.3 程序能跑但屏无反应程序编译能过、运行也不报错但屏幕一动不动这种问题往往不是屏幕坏了而是权限或引脚冲突。先确认是不是用 sudo 运行的很多时候非 root 用户打开 GPIO 失败是静默的。再执行gpio readall对照你的接线确认引脚没有被标注为其他功能。如果某个引脚被系统占用程序写它不会有任何效果。还有一种隐蔽情况是代码里引脚初始化顺序出错。比如没有先把 SCK 拉低也没有把 CS 拉高系统上电后这些引脚处于随机状态第一帧数据可能在无意义的时钟中丢失。所以主函数初始化后先设置初始电平这个习惯很有用。6.4 几个实用的排查技巧我调试这种屏时常用三个技巧效果很好。第一个是全屏填充法清屏时写入 0xFF如果屏幕整个变黑说明写数据通路完全正常如果只有局部变黑说明列地址或者页地址循环有问题。第二个是单像素扫描法写一个小循环让一个亮点从左上角向右下角逐渐移动每次移动前清屏。如果亮点移动轨迹出现跳变或者重影能帮你精确定位地址切换的问题。第三个是延长初始化延时法一开始调试时把复位后延时改到 50 毫秒以上初始化命令之间也加 1 毫秒延时等确认稳定后再逐步减小能滤掉很多上电瞬间的问题。7. 扩展把屏幕变成系统状态监视器7.1 采集系统状态并格式化显示点亮屏幕、显示字符串只是起点。我把它改造成系统监视器的思路很简单每隔一秒采集一次树莓派的 CPU 温度、内存使用率、当前 IP 和运行时间然后拼成字符串显示到屏幕上。CPU 温度读取系统文件或者执行vcgencmd measure_temp都能拿到C 语言里用popen调用命令也行。内存使用量可以从/proc/meminfo解析出总内存和剩余内存简单做个除法得到百分比。IP 地址用getaddrinfo或者直接调用hostname -I都行。格式化好之后调用lcd_draw_string分几行显示。显示内容多了以后有个问题每次刷新如果都先整屏清空再重绘屏幕会有明显的闪烁。我的做法是只对变化的区域做局部刷新记录上一帧的字符串内容和当前帧做对比变化的部分才更新到显存然后只把那几行对应的区域推送到屏幕。7.2 减少闪烁的刷新策略因为 ST7565 是并行寻址、数据逐字节发送整屏刷新需要发送 1024 字节在软件 SPI 下虽然耗时不大但频繁整刷会有“闪屏”观感。实践中把刷新频率控制在 1 到 2 秒一次并且每次只更新变化的行闪烁感就会大幅降低。另外亮度正常的黑白屏其实对刷新率要求不高界面设计成“数据变化才刷新”比“每秒强制刷新”体验好得多。要想再进一步可以把显存数组和实际屏幕内容做差异比较只推送变化的字节。这样做还有一个额外好处减少 SPI 总线上的无效传输降低对树莓派 CPU 的占用后台跑其他服务更稳。7.3 还可以往哪些方向玩我后来还做过一个简单的电阻电压表界面用电位器分压接入模拟输入引脚把 ADC 采样结果换算成电压值调用字符显示函数实时刷新数字。类似思路可以做温度计、定时器、系统日志滚动屏。如果要显示汉字需要准备 16x16 点阵字库原理和 5x7 字符完全一样只是字模数组更大、取模更麻烦。如果想让动画更流畅可以申请多个显存页做双缓冲先画好一帧再整体切换效果会比逐点刷新好很多。8. 最后再说几句踩坑后的经验这个项目我前前后后折腾过不止一遍最大的体会就是“点亮”和“显示正确”之间隔着很长的排查路径。很多第一次玩这种屏的人第一眼看到背光亮起特别兴奋紧接着就陷入花屏、镜像、错位、半明半暗这些问题的泥潭里。其实回过头看绝大部分都是非常基础的原因电源接错、共地没做好、页地址没切对、对比度配错。只要按着先查硬件、再查时序、再查地址映射的顺序走一般都能在半小时内解决。还有个小建议调试这种老式液晶屏手边一定放个万用表再准备一条尽量短的杜邦线。线越短SPI 信号越干净花屏概率越低。如果条件允许逻辑分析仪能直接抓取 SPI 波形一眼就能看出字节内容对不对省下大量瞎猜的时间。最后说个很实用的细节初始化命令里的对比度参数保持在一个中间值不要调得太极端这样无论室内光线怎么变屏幕内容都看得清。祝你的 ST7565 早日正常显示享受那种“底层时序完全掌握在自己手里”的踏实感。