公司动态
树莓派Pico驱动1.44寸SPI彩屏:从ST7735S原理到实战优化
1. 项目概述Pico-LCD-1.44 是什么如果你手头有一块树莓派 Pico 或者其它 RP2040 开发板想让它在不连接电脑的情况下显示点信息比如传感器数据、系统状态或者做个简单的小游戏那么一块小巧的 LCD 屏几乎是必备的。Pico-LCD-1.44 指的就是一块对角尺寸为 1.44 英寸专门设计用于与树莓派 Pico尤其是其 RP2040 微控制器对接的彩色液晶显示屏模块。这块屏的核心驱动芯片通常是 ST7735S而它与 Pico 之间的“对话语言”就是我们在热搜词里反复看到的 SPI。简单来说这个项目就是让一块原本“沉默”的 Pico 开发板通过几根 GPIO 口以 SPI 协议驱动一块彩屏让它“开口说话”显示图形和文字。这听起来像是嵌入式开发里的“Hello World”但实际做下来从接线、理解 SPI 时序、到最终让一个像素点正确发光中间每一步都藏着不少细节。网上资料虽多但往往要么过于简略只给代码要么过于深入陷入寄存器配置的海洋对于刚上手的朋友不太友好。我折腾过好几块类似的屏也踩过不少坑这次就结合 Pico-LCD-1.44 和 ST7735S把 SPI 驱动 LCD 这件事从头到尾、掰开揉碎了讲清楚目标是让你看完就能动手并且知道每一步为什么这么做。2. 核心硬件与通信协议解析驱动一块 LCD 屏首先得搞清楚你在和谁打交道以及你们之间用什么方式沟通。这决定了你的代码框架和硬件连接。2.1 显示屏模块ST7735S 驱动芯片Pico-LCD-1.44 屏的本质其实是一块由 ST7735S 这颗驱动芯片控制的液晶面板。我们微控制器MCU的所有指令和数据最终都是发给 ST7735S由它来操作屏幕上的每一个像素点。ST7735S 内部有一个显存GRAM大小对应着屏幕的分辨率。对于常见的 1.44 寸屏分辨率通常是 128x128 像素。每个像素的颜色深度可以是 12位RGB444、16位RGB565或 18位RGB666最常用的是 16位 RGB565 模式因为它平衡了色彩表现和传输数据量。这意味着你要在屏幕上显示一个红色点实际上是通过 SPI 向 ST7735S 的显存对应位置写入一个 16 位的数字比如 0xF800。这颗芯片支持两种主要接口8位/9位/16位/18位的并行接口以及我们项目里用的串行 SPI 接口。SPI 接口引脚少节省 GPIO非常适合 Pico 这种 GPIO 资源虽然丰富但项目可能需求更多的场景。2.2 通信基石SPI 协议深度解读SPISerial Peripheral Interface是一种高速、全双工、同步的串行通信总线。说人话就是通信双方有一根时钟线来同步节奏数据可以同时收和发速度很快。驱动 ST7735S我们主要利用其 SPI 写操作。SPI 通常涉及四根线SCK (Serial Clock)时钟线由主机Pico产生用来同步数据位。MOSI (Master Out Slave In)主机输出从机输入。Pico 通过这根线发送命令或数据给屏幕。MISO (Master In Slave Out)主机输入从机输出。屏幕通过这根线返回数据给 Pico。注意在单纯驱动 ST7735S 显示时我们往往只写不读所以这根线有时可以不接但某些初始化序列或读屏状态如读ID时可能需要。CS/CSX (Chip Select)片选线低电平有效。当 Pico 拉低这根线就表示“屏幕我现在要跟你说话了”。一个主机可以接多个 SPI 从设备用不同的 CS 线选择。此外对于 LCD 驱动还有两根至关重要的控制线DCX (Data/Command)数据/命令选择线。这是 ST7735S 等显示驱动芯片的关键引脚。它告诉芯片当前通过 MOSI 发送过去的一个字节是命令如设置显示方向、开显示还是数据如要写入显存的像素颜色值。通常高电平为数据低电平为命令。RESX (Reset)复位线低电平有效。用于对 ST7735S 进行硬件复位确保其从一个确定的状态开始工作。SPI 有四种时钟模式CPOL 和 CPHA 的组合决定了时钟空闲时的电平和数据采样的边沿。ST7735S 通常工作在Mode 0(CPOL0 CPHA0) 或Mode 3(CPOL1 CPHA1)。这意味着时钟空闲时为低电平Mode 0或高电平Mode 3数据在时钟的第一个边沿上升沿或下降沿被采样。你必须在代码中正确配置否则数据传过去屏幕也识别不了。大多数例程使用 Mode 0。2.3 微控制器树莓派 Pico 的 GPIO 与 SPI 外设树莓派 Pico 的核心是 RP2040 双核 Cortex-M0 微控制器。它有两个硬件 SPI 外设SPI0 和 SPI1每个都可以配置为主机。使用硬件 SPI 的好处是速度快不占用 CPU 时间进行位翻转即“模拟 SPI”。RP2040 的 GPIO 功能是复用的。一个物理引脚可以通过编程将其功能切换到 SPI的 SCK、MOSI、MISO 等。Pico 的引脚排列图Pinout上已经标明了哪些引脚可以用于 SPI0 和 SPI1。例如SPI0: SCKGP18 MOSIGP19 MISOGP16SPI1: SCKGP10 MOSIGP11 MISOGP12你可以灵活选择但通常建议优先使用硬件 SPI 引脚以获得最佳性能。剩下的 DCX、RESX 和 CSX 引脚则使用普通的 GPIO 口通过软件控制其高低电平即可。注意很多便宜的 LCD 模块为了兼容性可能标注的是“DIN”对应 MOSI、“CLK”对应 SCK。接线时务必对照模块说明书和 Pico 引脚图。3. 硬件连接与电路准备理论清楚了现在开始动手。第一步是把屏幕和 Pico 正确地连接起来。3.1 引脚对应关系与接线图假设我们使用 Pico 的 SPI0并选择一组方便的 GPIO 作为控制线。下面是一个经典的接线方案Pico 引脚 (GPIO)引脚名称连接至 LCD 模块引脚说明GP18SPI0 SCKSCK / CLK时钟线GP19SPI0 MOSISDA / DIN / MOSI数据线主机输出GP16SPI0 MISO(可选)数据线主机输入初始化后通常可不接GP17任意 GPIODC / DCX / A0数据/命令选择线GP20任意 GPIORES / RESX复位线GP21任意 GPIOCS / CSX片选线3V3(OUT)3.3V PowerVCC电源 (3.3V)GNDGroundGND地线(可选)-LED / BLK背光控制接 3.3V 常亮或接 GPIO 控制接线实操要点电源务必正确Pico 和 LCD 模块必须共地GND 接一起。LCD 的 VCC 必须接 Pico 的 3.3V 输出3V3(OUT)绝对不能接 5V否则会烧毁屏幕或 RP2040。背光处理如果屏幕有独立的背光引脚LED为了省事可以直接接 3.3V 让它常亮。如果想控制亮度可以接一个 GPIO 并通过 PWM 控制或者串联一个合适电阻后接 3.3V。上拉电阻有些模块内部已经为 RESX、CSX 等引脚集成了上拉电阻。如果没有在干扰较大的环境中可以考虑在 Pico 侧为这些控制线添加 10kΩ 的上拉电阻到 3.3V确保默认状态稳定。短线连接尽量使用短杜邦线连接长线可能引入干扰导致 SPI 通信不稳定。3.2 硬件 SPI 与模拟 SPI 的选择RP2040 有硬件 SPI为什么还有人用 GPIO 模拟即“软件 SPI”或“位碰撞 SPI”呢主要有两个原因引脚灵活性硬件 SPI 的 SCK、MOSI、MISO 引脚是固定的。如果你的项目里这些固定引脚已经被其他功能占用就可能需要把 SPI 信号“映射”到其它 GPIO 上这时就只能用模拟 SPI。调试与理解模拟 SPI 的代码直观你可以清楚地看到每一位数据是如何在时钟控制下送出的对于学习 SPI 时序非常有帮助。如何选择追求性能和省心无脑选硬件 SPI。RP2040 的硬件 SPI 速率可以轻松达到几十 MHz刷屏流畅。引脚冲突或初学者学习可以选择模拟 SPI。虽然速度慢通常几百 kHz 到几 MHz但对于 1.44 寸小屏的静态或简单动画显示也足够用了。我个人的建议是第一次尝试先用硬件 SPI 确保基本功能跑通。理解了整个流程后如果想挑战自己可以再写一个模拟 SPI 的版本作为练习。下文将以硬件 SPI 为例进行讲解。4. 软件驱动从初始化到显示图像硬件连接妥当后真正的挑战在软件。我们需要编写代码与 ST7735S 芯片对话。4.1 开发环境与库选择对于树莓派 Pico主流开发环境有MicroPython / CircuitPython上手极快语法简单有丰富的社区库。对于快速原型验证、教育或简单应用非常友好。通常有现成的st7735.py或st7735s.py库。C/C (使用 Pico SDK)性能最优资源控制最精细适合对实时性、内存或功耗有严格要求的项目。需要自己编写或移植底层驱动。为了深入理解原理我们将以C/C (Pico SDK)为例因为从这里你能看到最本质的寄存器操作和时序控制。理解了 C 版本的驱动再看 MicroPython 的库就会觉得一目了然。4.2 ST7735S 初始化序列详解屏幕上电后ST7735S 处于一个未知状态我们需要通过一系列命令Command对其进行配置这就是初始化序列。这些命令通常可以在 ST7735S 的数据手册Datasheet里找到网上也有很多针对不同屏幕的现成序列。一个典型的初始化流程如下硬件复位拉低 RESX 引脚至少 10ms然后拉高等待 120ms 以上让芯片稳定。发送软件复位命令发送命令0x01(SWRESET)并等待 150ms。退出睡眠模式发送命令0x11(SLPOUT)等待 120ms。设置颜色模式发送命令0x3A(COLMOD)后面跟数据0x05表示 RGB565 模式16位/像素。设置显示方向旋转发送命令0x36(MADCTL)后面跟一个数据字节来控制显示方向、颜色顺序等。例如0xC0可能表示旋转 180 度。设置列和行地址范围通常用0x2A(CASET) 和0x2B(RASET) 命令来设置你将要写入的显存区域。全屏刷新就是设置整个屏幕范围。打开显示发送命令0x29(DISPON)。在代码中我们需要为“发送命令”和“发送数据”编写两个基础函数// 假设已定义好 spi, dc_pin, cs_pin 等 void lcd_write_cmd(uint8_t cmd) { gpio_put(dc_pin, 0); // DC 拉低表示接下来是命令 spi_write_blocking(spi, cmd, 1); } void lcd_write_data(uint8_t data) { gpio_put(dc_pin, 1); // DC 拉高表示接下来是数据 spi_write_blocking(spi, data, 1); }初始化时就是调用一系列lcd_write_cmd()和lcd_write_data()的组合。实操心得不同厂家、不同批次的 1.44 寸屏其初始化序列可能略有差异尤其是 MADCTL 参数和伽马校正参数。如果遇到屏幕颜色不对、花屏或者显示方向错误第一个要检查的就是初始化序列。最好能找到卖家提供的示例代码或资料。4.3 像素写入与区域填充初始化完成后就可以向显存写数据了。基本操作是用 CASET 和 RASET 命令设定一个要写的矩形区域可以是单个像素一行或整个屏幕。发送0x2C(RAMWR) 命令告诉芯片“接下来我要开始写显存数据了”。连续发送像素颜色数据RGB565格式每个像素2个字节。数据会自动填充到步骤1设定的区域内。例如要画一个从 (x0, y0) 到 (x1, y1) 的红色矩形void lcd_set_address(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { lcd_write_cmd(0x2A); // CASET lcd_write_data(x0 8); lcd_write_data(x0 0xFF); lcd_write_data(x1 8); lcd_write_data(x1 0xFF); lcd_write_cmd(0x2B); // RASET lcd_write_data(y0 8); lcd_write_data(y0 0xFF); lcd_write_data(y1 8); lcd_write_data(y1 0xFF); } void lcd_fill_rect(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color) { lcd_set_address(x0, y0, x1, y1); lcd_write_cmd(0x2C); // RAMWR uint32_t num_pixels (x1 - x0 1) * (y1 - y0 1); // 优化一次性发送多个相同颜色的字节而不是循环调用单字节函数 for(uint32_t i 0; i num_pixels; i) { uint8_t buf[2] {color 8, color 0xFF}; spi_write_blocking(spi, buf, 2); } } // 调用填充全屏红色 (RGB565: 0xF800) lcd_fill_rect(0, 0, 127, 127, 0xF800);性能关键spi_write_blocking是阻塞函数刷全屏几万个像素点会占用大量时间。优化方法包括使用 DMA直接存储器访问来搬运颜色数据到 SPICPU 可以同时干别的事。构建一个屏幕大小的帧缓冲区Framebuffer在内存中所有绘图操作先在内存里完成最后一次性 DMA 传输到屏幕。这是最流畅的方式但会消耗较多内存128x128x2 32KB。4.4 显示文字与图形屏幕能显示单色块了接下来就是显示文字和图形。这需要用到字库和画点函数。画点函数这是最基础的图形函数。基于上面的lcd_set_address可以实现一个画点函数但效率较低。更高效的做法是直接操作帧缓冲区。字库英文字母和数字可以用位图字库如 8x16, 12x24 像素。将字符的位图数据每个比特代表一个像素是否点亮存储为数组显示时遍历位图遇到1就画前景色遇到0就画背景色。画线、画圆、填充这些是基本的图形算法如 Bresenham 算法可以在画点函数的基础上实现。显示图像将图片转换为 C 语言数组RGB565 格式然后调用区域填充函数将数组数据发送到屏幕对应区域。有很多在线工具可以完成图片转换。对于复杂 UI可以考虑移植轻量级的图形库如u8g2、LVGL等。它们提供了按钮、标签、图表等高级控件但需要更多的内存和处理器资源。5. 常见问题排查与调试技巧驱动 SPI LCD 的过程很少一帆风顺以下是几个我踩过的坑和解决方法。5.1 屏幕无任何显示背光亮但无内容这是最常见的问题。排查思路如下检查电源和背光用万用表测量屏幕 VCC 和 GND 之间电压是否为稳定的 3.3V。确认背光引脚如果有已正确供电。检查复位时序确保 RESX 引脚执行了正确的低电平脉冲10ms。可以在代码中增加延时或者尝试手动用导线短接一下 RESX 到 GND 再断开。检查 SPI 模式与速率确认代码中 SPI 的时钟模式CPOL, CPHA与屏幕要求一致通常是 Mode 0。初始阶段请将 SPI 波特率设低一些比如 1 MHz 或更低确保通信稳定。高速率可能导致时序错乱。检查 DCX 和 CSX 引脚用逻辑分析仪或示波器观察波形。在发送命令时DCX 应为低电平发送数据时DCX 应为高电平。CSX 在整个通信期间应保持低电平。检查初始化序列核对每一个命令和数据是否与你的屏幕型号匹配。尝试注释掉部分非关键初始化命令如伽马校正只保留最基本的复位、睡眠退出、颜色模式、开显示。逻辑分析仪是神器如果条件允许用逻辑分析仪连接 SCK、MOSI、DCX 三根线可以清晰地看到发送的命令和数据流与数据手册的时序图对比一目了然。5.2 屏幕花屏、错位或颜色异常花屏满屏彩色噪点大概率是 SPI 时钟模式不对或者初始化序列完全没执行成功。重点检查复位和基本命令。显示错位图像偏移、分裂检查CASET和RASET设置的起始和结束地址是否正确。有些屏幕在初始化后默认的显示窗口Display Window可能不是从 (0,0) 开始需要额外命令设置。也可能是 MADCTL 命令设置的方向不对。颜色异常红蓝互换、颜色失真红蓝互换检查 MADCTL 命令中关于 RGB/BGR 顺序的位通常是一个标志位。ST7735S 默认可能是 BGR 顺序而你的颜色数据是 RGB 顺序导致红蓝对调。整体颜色失真检查COLMOD命令设置的颜色深度是否与你发送的数据格式匹配。你发送的是 RGB5652字节但屏幕可能被设置成了 RGB444 或其他模式。伽马校正不同的屏幕面板可能需要不同的伽马校正参数。错误的伽马值会导致颜色发白、发暗或对比度异常。尝试调整初始化序列中的伽马相关命令如0x26,0xF2等。5.3 通信不稳定或速度慢通信不稳定偶尔花屏、数据错误接线问题检查杜邦线是否接触不良尤其是时钟线 SCK。SCK 频率高对线路敏感。电源噪声在 Pico 的 3.3V 和 GND 之间并联一个 10uF 和一个 0.1uF 的电容可以滤除电源噪声。未使用引脚确保 SPI 的 MISO 引脚即使不用在代码中初始化为正确的功能或者设置为输入上拉/下拉不要悬空。刷新速度慢优化 SPI 速率在确保稳定的前提下逐步提高 SPI 波特率。RP2040 的 SPI 在 3.3V 下可以跑到几十 MHz。使用 DMA这是提升刷屏速度最有效的方法。将需要发送的颜色数据数组通过 DMA 通道直接搬运到 SPI 的发送 FIFOCPU 几乎不参与。使用帧缓冲区避免频繁设置地址和发送 RAMWR 命令。在内存中开辟一块和屏幕一样大的缓冲区所有绘图操作都在这个缓冲区进行最后一次性用 DMA 将整个缓冲区数据发送到屏幕。这实现了“双缓冲”能完全消除闪烁。5.4 进阶技巧降低功耗与提升体验关闭背光如果屏幕有独立的背光控制引脚在不需要显示时通过 GPIO 将其拉低以关闭背光这是省电的大头。利用睡眠模式在系统空闲时可以向 ST7735S 发送0x10(SLPIN) 命令使其进入睡眠模式显著降低功耗。唤醒时发送0x11(SLPOUT)。局部刷新如果 UI 只有一小部分变化如一个数字只刷新那一小块区域而不是全屏刷新可以极大提高效率并降低功耗。字体与图形优化使用单色位图字体并在绘制时进行裁剪只绘制屏幕可见区域内的部分可以减少不必要的绘图计算和 SPI 传输。驱动一块小小的 SPI LCD 屏几乎涵盖了嵌入式开发中硬件接口、通信协议、驱动编写、性能优化和问题调试的所有核心环节。从看到第一个像素点亮起的兴奋到实现流畅动画的成就感这个过程本身就是极好的学习路径。希望这份结合了原理和实战经验的指南能帮你少走弯路更快地让你的 Pico 项目“亮”起来。