Raspberry Pi Pico驱动0.96寸LCD全攻略:从SPI接口到LVGL图形界面实战

Raspberry Pi Pico驱动0.96寸LCD全攻略:从SPI接口到LVGL图形界面实战 1. 项目缘起为什么是Pico与0.96寸LCD的组合最近在捣鼓一些嵌入式的小玩意儿发现一个特别有意思的现象无论是刚入门的新手还是想快速验证想法的老手越来越多的人开始把目光投向Raspberry Pi Pico这块小板子。它价格便宜性能足够双核的RP2040芯片玩起来也很有弹性。但光有个主控还不够总得有个“窗口”来看看它在干什么对吧这时候一块小巧的0.96寸LCD屏幕就成了绝配。你可能要问屏幕那么多为什么偏偏是0.96寸这背后其实是个很实际的工程选择。首先它足够小几乎不占地方非常适合集成到那些对空间有苛刻要求的项目里比如迷你气象站、便携式游戏机、或者智能穿戴设备的原型。其次这类屏幕通常采用SPI或I2C接口接线简单只需要几根线就能驱动极大简化了硬件连接。最后它的功耗很低对于由电池供电的Pico项目来说这是个巨大的优势。所以当你看到“Pico LCD 0.96”这个组合时它背后代表的是一种高性价比、低门槛的嵌入式人机交互解决方案。我自己最初接触这个组合是想做一个能显示系统状态比如CPU温度、内存使用率的桌面小摆件。市面上现成的方案要么太贵要么太复杂。而Pico加上一块几块钱的0.96寸LCD成本控制在五十元以内完全符合DIY的精神。在折腾的过程中我踩过一些坑也总结出一些让屏幕稳定、高效工作的门道这篇文章就来聊聊这些实战经验。2. 核心器件选型与接口探秘驱动一块0.96寸LCD首先你得知道你在驱动什么。市面上常见的0.96寸LCD模块其核心驱动芯片大多是ST7735、ST7789或者SSD1306后者通常是OLED但外形尺寸类似。它们虽然都叫0.96寸但分辨率、色彩深度和驱动方式可能有天壤之别。2.1 驱动芯片的“门派”之别最常见的是ST7735和ST7789。ST7735通常用于128x160分辨率的屏幕而ST7789则常见于240x240或135x240分辨率的“方屏”或“竖屏”。SSD1306则是单色OLED的王者分辨率多为128x64。选择哪一款取决于你的项目需求如果需要显示彩色图片、图标ST77xx系列是首选如果只需要显示文字和简单图形且追求极致功耗和对比度SSD1306 OLED是更好的选择。我手头这块是ST7789驱动的240x240 IPS彩屏色彩和可视角度都相当不错下文也将以它为例。2.2 通信接口SPI与I2C的权衡这两种屏幕主要使用SPI或I2C接口与主控通信。SPI (Serial Peripheral Interface)速度快是全双工通信。对于刷新图片、动画等需要较高数据吞吐量的场景SPI是唯一的选择。ST7735/ST7789彩屏几乎都使用SPI接口。它的缺点是占用引脚较多通常需要SCK时钟、MOSI主机输出从机输入、DC数据/命令选择、RST复位和CS片选这5根线。I2C (Inter-Integrated Circuit)速度较慢是半双工通信。但它只需要两根线SDA数据和SCL时钟极大地节省了宝贵的IO引脚。SSD1306 OLED模块很多都提供了I2C接口的版本非常适合引脚紧张或仅需显示简单信息的项目。对于Pico驱动ST7789这类彩屏我们毫无疑问要使用SPI接口。Pico有硬件SPI接口使用它能极大减轻CPU负担实现更高的刷新率。2.3 硬件连接图与“坑点”预警连接本身不复杂但有几个细节容易出错。下面是一个典型的Pico与ST7789 SPI LCD的连接表Pico GPIO 引脚连接至 LCD 引脚作用说明GP2SCKSPI时钟线GP3MOSI (有时标SDA)SPI数据线主出从入GP4DC (或A0)数据/命令选择线。这是关键拉高时发送的是显示数据拉低时发送的是控制命令。GP5CS片选线低电平有效。如果模块上只有一个设备可以接GND常使能。GP6RST复位线低电平复位。通常上电复位即可也可由程序控制。3V3(OUT)VCC电源务必接3.3V接5V大概率烧屏。GNDGND电源地注意上表中的GPIO编号GP2, GP3...是Pico的物理引脚功能定义你在编程时使用的可能是数字编号如23...需根据你使用的开发框架进行映射。另一个大坑是电源绝大多数这类LCD模块的逻辑电平是3.3V与Pico的GPIO电平匹配但VCC必须接3.3V。我曾不小心接到5V上屏幕瞬间发烫并永久损坏。3. 软件驱动从MicroPython到C/C的实战硬件连好了接下来就是让屏幕亮起来。这里有两个主流选择MicroPython和C/C基于Arduino框架或Pico SDK。3.1 MicroPython方案快速原型验证对于快速验证和初学者MicroPython是福音。社区有丰富的库例如st7789.py驱动库。你只需要通过Thonny或rshell将库文件上传到Pico然后写一个简单的脚本。import machine import st7789 import time # 初始化SPI spi machine.SPI(1, baudrate40000000, polarity1, phase1, sckmachine.Pin(10), mosimachine.Pin(11)) # 初始化屏幕控制引脚 tft st7789.ST7789(spi, 240, 240, resetmachine.Pin(12, machine.Pin.OUT), dcmachine.Pin(13, machine.Pin.OUT), csmachine.Pin(14, machine.Pin.OUT)) # 初始化屏幕 tft.init() # 填充红色背景 tft.fill(st7789.RED) # 在指定位置显示文字 tft.text(“Hello Pico!“, 50, 100, st7789.WHITE) time.sleep(2)这段代码就能让屏幕显示红底白字。MicroPython的优势是交互性强调试方便但执行效率相对较低在刷全屏动画时可能会感到卡顿。3.2 C/C方案追求极致性能如果你需要流畅的动画、快速的图形渲染或者项目最终要固化产品C/C是必由之路。你可以使用Arduino IDE配合Adafruit ST7789库或者直接使用Raspberry Pi官方的Pico C/C SDK。使用Arduino框架相对简单库管理完善。而使用Pico SDK则更底层控制更精细性能也最高。这里以Pico SDK为例核心步骤是配置硬件SPI并按照ST7789的数据手册编写初始化序列和像素数据发送函数。// 简化的SPI初始化示例 (Pico SDK) #include “hardware/spi.h“ #include “pico/stdlib.h“ #define PIN_LCD_DC 4 #define PIN_LCD_RST 6 #define PIN_LCD_CS 5 void lcd_write_cmd(uint8_t cmd) { gpio_put(PIN_LCD_DC, 0); // DC线拉低表示发送命令 spi_write_blocking(spi_default, cmd, 1); } void lcd_write_data(uint8_t data) { gpio_put(PIN_LCD_DC, 1); // DC线拉高表示发送数据 spi_write_blocking(spi_default, data, 1); } void lcd_init() { // 初始化GPIO gpio_init(PIN_LCD_DC); gpio_set_dir(PIN_LCD_DC, GPIO_OUT); // ... 初始化RST, CS引脚 // 硬件复位屏幕 gpio_put(PIN_LCD_RST, 0); sleep_ms(100); gpio_put(PIN_LCD_RST, 1); sleep_ms(100); // 初始化SPI设置高速模式 spi_init(spi_default, 62500000); // 62.5 MHz gpio_set_function(2, GPIO_FUNC_SPI); // GP2 as SCK gpio_set_function(3, GPIO_FUNC_SPI); // GP3 as MOSI // 发送ST7789初始化命令序列 lcd_write_cmd(0x01); // 软件复位 sleep_ms(150); // ... 更多初始化命令如颜色模式、扫描方向等 lcd_write_cmd(0x11); // 退出睡眠模式 sleep_ms(120); lcd_write_cmd(0x29); // 开启显示 }在C语言环境下你可以通过DMA直接存储器访问来传输显示数据这是实现高性能刷新的关键。RP2040芯片的DMA功能非常强大可以在不占用CPU资源的情况下将内存中的一块图像数据“搬运”到SPI发送寄存器从而实现后台刷新CPU可以同时处理其他任务。这也是解决类似“stm32h750 dma 驱动 spi lcd 问题”中性能瓶颈的核心思路——合理配置DMA通道和传输完成中断。实操心得在MicroPython下如果遇到刷新慢的问题首先检查SPI波特率是否已设置到最高如80MHz。在C/C下使用DMA时要注意内存对齐和缓冲区管理。我曾因为DMA源地址不是32位对齐而导致传输失败调试了很久。一个稳妥的做法是使用malloc分配内存时确保对齐或者使用SDK提供的对齐内存分配函数。4. 图形化与高级应用让屏幕“活”起来让屏幕显示纯色和文字只是第一步我们通常需要显示更复杂的图形、图像甚至用户界面。4.1 构建图形库基础功能一个实用的图形库至少需要包含以下功能画点、画线、画矩形、画圆、填充、显示位图图片。这些功能都建立在最基础的画点(x, y, color)函数上。在SPI屏上画点本质上是向屏幕的GRAM图形存储器指定坐标写入一个颜色值通常是16位的RGB565格式。例如画一条线Bresenham算法或填充一个矩形就是循环调用画点函数。为了提高效率可以优化为一次发送多个连续像素的数据。ST7789支持设置“窗口”即一个矩形区域然后连续写入该窗口内的所有像素数据这比逐个像素设置要快几个数量级。4.2 显示图片从BMP到转换工具直接显示图片是刚需。通常的步骤是将图片在电脑上转换为代码可识别的格式。对于彩色屏常用RGB565格式的C语言数组。使用工具如LCD Image Converter、Img2Code在线工具将JPG/PNG图片转换为uint16_t类型的数组。在程序中通过设置窗口并连续发送数组数据来显示图片。// 假设有一个名为 gImage_pic 的图片数组 void lcd_draw_image(uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint16_t *data) { lcd_set_window(x, y, xw-1, yh-1); // 设置显示窗口 gpio_put(PIN_LCD_DC, 1); // 进入数据模式 spi_write_blocking(spi_default, (const uint8_t*)data, w*h*2); // 发送数据每个像素2字节 }4.3 引入GUI库LVGL的集成当项目需要按钮、列表、图表等交互元素时手动编码会变得极其繁琐。这时引入一个轻量级嵌入式GUI库是明智之选。LVGL是目前最受欢迎的选择之一它开源、免费、功能强大且对RP2040和ST7789有很好的支持。集成LVGL到Pico项目中的大致流程如下获取LVGL源码通常是一个lvgl文件夹。将其添加到你的工程中并配置lv_conf.h头文件根据你的屏幕240x240 RGB565和可用内存进行裁剪配置。实现LVGL要求的“显示驱动”和“输入设备驱动”回调函数。显示驱动函数的核心任务就是在LVGL需要刷新一块区域时调用我们前面写好的lcd_set_window和spi_write函数。在主循环中定期调用lv_timer_handler()和lv_task_handler()。一旦集成成功你就可以用LVGL提供的丰富控件和样式系统像在手机上开发APP一样构建你的嵌入式界面了效率提升不止一个档次。注意事项LVGL对内存有一定要求。RP2040虽然有264KB的SRAM但LVGL的绘图缓冲区会占用一部分。你需要根据界面复杂度在lv_conf.h中合理设置LV_MEM_SIZE和LV_COLOR_DEPTH。我的经验是对于240x240的16位色屏幕分配一个至少屏幕面积1/10大小的缓冲区如240*24像素并启用双缓冲区可以获得比较流畅的动画效果。如果内存紧张可以考虑降低颜色深度到8位LVGL支持或者使用单缓冲区。5. 性能优化与深度调试技巧当基础功能都实现后你会开始追求更极致的体验更快的刷新率、更低的功耗、更稳定的显示。这里有几个进阶技巧。5.1 超频Pico与SPI极速RP2040的标准主频是133MHz但它可以稳定超频到250MHz甚至更高。超频能直接提升CPU处理图形逻辑和驱动SPI的速度。在Pico SDK中只需在程序开头调用set_sys_clock_khz(250000, true)即可尝试超频至250MHz。更重要的是SPI时钟。理论上RP2040的SPI时钟可以达到系统时钟的一半。在超频到250MHz后SPI时钟可以设置为125MHz。在我的实测中ST7789模块在62.5MHz下工作非常稳定提升到125MHz时部分屏幕模块可能因布线质量出现数据错误需要降低波特率或缩短连接线。务必在提高SPI速率后运行完整的图形测试如刷屏、画线来验证稳定性。5.2 DMA与双缓冲机制这是消除画面撕裂、实现流畅动画的核心技术。原理是创建两个大小等于屏幕缓冲区的内存块Buffer A和Buffer B。CPU在后台向Buffer A绘制下一帧图像。当前屏幕显示的是Buffer B的内容。当Buffer A绘制完成启动一个DMA传输将Buffer A的内容搬运到屏幕。在DMA传输期间CPU可以立即开始向Buffer B绘制下一帧。如此循环往复。这样屏幕的刷新和CPU的绘图工作完全并行避免了在传输过程中修改缓冲区数据导致的画面撕裂。在Pico SDK中配置DMA需要关注dma_channel_config结构体正确设置传输数据大小、源地址和目标地址SPI DR寄存器并处理好传输完成中断以切换缓冲区。5.3 功耗管理与屏幕睡眠对于电池供电设备功耗至关重要。ST7789屏幕本身有较高的功耗。优化方法包括降低刷新率如果不是必须不要以最高频率如60Hz持续刷新。可以仅在数据更新时刷新一次。使用睡眠命令在长时间不显示时通过发送0x10进入睡眠模式命令关闭屏幕内部电路和背光。需要显示时再发送0x11唤醒。注意睡眠模式下屏幕GRAM内容通常会被保留唤醒后立即恢复显示。动态调整背光如果屏幕背光是PWM控制的可以根据环境光或内容亮度动态调整占空比。5.4 常见问题排查指南即使按照教程操作屏幕也可能不亮或显示异常。以下是一个系统的排查思路电源与连接这是第一位的。用万用表测量屏幕VCC引脚是否为稳定的3.3V所有GND是否都与Pico共地接线是否牢固特别是DC和RST这两个关键控制引脚。逻辑电平确认Pico的GPIO输出电平是3.3V与屏幕逻辑电平匹配。初始化序列不同批次的屏幕甚至同一型号不同厂家的屏幕其初始化命令序列可能有细微差别。最可靠的方法是找到屏幕卖家提供的资料或示例代码。如果找不到可以尝试通用的ST7789初始化序列并重点关注颜色格式RGB565 vs BGR565、扫描方向、 inversion on/off等命令。SPI模式与相位SPI有四种模式CPOL, CPHA。ST7789通常使用模式0CPOL0 CPHA0或模式3CPOL1 CPHA1。在代码中polarity和phase参数设置错误会导致通信完全失败。参考屏幕数据手册或示例代码设置。软件延时在发送复位命令、退出睡眠命令后必须给予足够的延时几十到几百毫秒让屏幕内部电路稳定。延时不足会导致后续初始化命令被忽略。使用逻辑分析仪如果以上都无效逻辑分析仪是终极武器。抓取SPI总线SCK MOSI和DC引脚上的波形对照ST7789数据手册逐条检查发送的命令和数据是否正确。这是定位软件驱动问题最直接的方法。6. 项目实战打造一个系统状态监视器理论说了这么多我们用一个综合项目来串联所有知识点一个基于Pico和0.96寸LCD的电脑系统状态监视器。它通过USB连接到电脑从电脑端的一个小服务程序获取CPU、GPU、内存的使用率和温度并实时显示在屏幕上。6.1 系统架构设计整个系统分为两部分PC端服务Python运行在电脑上使用psutil库获取系统状态信息通过串口USB虚拟串口定时如每秒一次将数据发送给Pico。数据格式可以设计为简单的文本协议例如CPU:45.2,GPU:62.1,MEM:76.5,TEMP:48。Pico端固件C/C负责接收串口数据、解析数据、驱动LCD并绘制界面。为了有更好的视觉效果我们集成LVGL来创建仪表盘式的界面。6.2 Pico端核心实现首先需要初始化两个外设UART用于通信和SPI用于驱动LCD。它们可以并行工作。// 初始化UART (使用GP0/GP1作为TX/RX) uart_init(uart0, 115200); gpio_set_function(0, GPIO_FUNC_UART); gpio_set_function(1, GPIO_FUNC_UART); // 初始化SPI和LCD代码如前文所述 spi_init(spi_default, 62500000); // ... lcd_init();然后初始化LVGL并创建一个简单的界面包含几个lv_bar进度条组件来显示使用率一个lv_label来显示温度。在主循环中我们需要做三件事处理LVGL定时器定期调用lv_timer_handler()。检查串口数据使用uart_is_readable()检查是否有数据到来然后读取并解析。当收到完整的一行数据后解析出各个数值。更新LVGL界面在LVGL的回调函数或主循环中将解析得到的数值设置到对应的lv_bar和lv_label对象上。LVGL会自动处理重绘。6.3 性能与稳定性考量数据解析要确保数据解析的鲁棒性处理可能的不完整数据包或错误格式。界面刷新由于数据更新频率不高1Hz完全不需要最高速刷新。可以将LVGL的刷新周期设置为100ms左右既能保证界面响应又不会过度消耗CPU。背光控制可以增加一个光敏电阻或通过串口命令控制屏幕背光在无人观看时自动调暗或关闭以节省功耗。通过这个项目你不仅实践了LCD驱动、SPI通信、串口通信还接触了嵌入式GUI开发和数据解析是一个非常好的综合性练手项目。当看到自己电脑的实时状态在这个小屏幕上跳动时那种成就感是无可替代的。