0.71英寸LCD驱动全攻略:从SPI接口到低功耗显示的嵌入式实践

📅 2026/8/1 11:02:26 👁️ 阅读次数 📝 编程学习
0.71英寸LCD驱动全攻略:从SPI接口到低功耗显示的嵌入式实践

1. 项目缘起:为什么是0.71英寸LCD?

最近在做一个需要超小尺寸显示界面的嵌入式项目,找了一圈市面上的显示屏,要么太大,要么功耗太高,要么接口复杂。最后,一块0.71英寸的LCD模块进入了我的视线。你可能觉得,这么小的屏幕能干什么?显示个图标都费劲吧。但恰恰是这种“极致的小”,让它在一些特定场景下成了不可替代的“神器”。

比如,可穿戴设备(智能戒指、微型耳机仓)、微型仪器仪表、作为大型设备的辅助状态指示屏,或者是一些极客DIY项目里,需要在不显眼的地方塞进一个信息窗口。0.71英寸,对角线长度大约18毫米,比一枚一元硬币的直径还要小一点。在这个尺寸下,如何驱动它、能显示什么内容、实际效果如何,就成了非常具体且有趣的技术问题。

这块屏通常分辨率是128x128或128x160(RGB排列),采用SPI或I2C接口,功耗极低。市面上常见的驱动芯片有ST7735S、ST7789V等。我手头这块就是基于ST7735S的128x128分辨率SPI屏。接下来,我就从硬件连接到软件驱动,再到实际应用中的那些“坑”和技巧,完整地复盘一遍。

2. 硬件连接与电源设计的门道

拿到模块,第一步肯定是把它接起来。别看它小,连接上的讲究一点不少。

2.1 引脚定义与核心连接

大多数0.71英寸LCD模块的引脚都大同小异,以我用的这款为例,共有8个引脚(有些是7个,少一个背光控制):

  1. VCC:电源正极,通常是3.3V。这里第一个坑就来了:一定要确认模块的供电电压。虽然很多标称3.3V,但有些老版本或特定驱动芯片的模块可能兼容5V输入(内部有LDO),而有些则非常娇贵,5V上去立刻冒烟。最稳妥的方法是查阅具体模块的数据手册,如果没有,就用3.3V。
  2. GND:电源地。
  3. SCL/SCLK:SPI时钟线。
  4. SDA/MOSI:SPI主设备输出、从设备输入数据线。
  5. RES/RST:复位引脚,低电平有效。这是必须连接的引脚,很多初始化失败的问题都源于复位时序不对或根本没接。
  6. DC/RS:数据/命令选择引脚。这是SPI屏区别于I2C屏的关键。高电平时,SPI总线上传的是要显示的像素数据;低电平时,传的是命令(如设置扫描方向、开显示等)。
  7. CS:片选引脚,低电平有效。
  8. BLK:背光控制,通常高电平点亮。有些模块背光直接接VCC,那就常亮。

连接微控制器(以常见的STM32F103C8T6为例):

  • VCC -> 3.3V
  • GND -> GND
  • SCLK -> PA5 (SPI1 CLK)
  • MOSI -> PA7 (SPI1 MOSI)
  • RES -> PA0 (任意GPIO)
  • DC -> PA1 (任意GPIO)
  • CS -> PA4 (SPI1 NSS) 或 任意GPIO(如果软件控制片选)
  • BLK -> PA2 (任意GPIO)

注意:RES、DC、CS、BLK这些控制引脚,强烈建议不要直接悬空或上拉到不合适的电平。最好由MCU的GPIO直接驱动,确保上电瞬间和运行中的状态可控。

2.2 电源与去耦的细节

模块虽小,但对电源纹波比较敏感。在驱动芯片全速刷新屏幕时,电流会有瞬间波动。如果电源不稳,可能导致显示花屏、横线或局部乱码。

  • 必加滤波电容:在模块的VCC和GND引脚之间,尽可能靠近引脚焊接一个10uF的钽电容或电解电容,再并联一个0.1uF的陶瓷电容。10uF负责应对低频电流波动,0.1uF负责滤除高频噪声。这个步骤能解决一大半莫名其妙的显示干扰问题。
  • 背光限流:背光LED通常也是直接接在VCC上。如果模块本身没有限流电阻,你需要在外围电路上串联一个电阻。阻值需要计算:R = (VCC - Vf_led) / I_led。假设VCC=3.3V,LED正向电压Vf=2.8V,期望电流I=10mA,则R=(3.3-2.8)/0.01=50欧姆。选择一个接近的标准值,如51欧姆。不加限流电阻,背光可能过亮、发热甚至早衰。

3. 驱动软件:从底层SPI到图形库

硬件连好了,接下来就是让屏幕亮起来并显示内容。这个过程可以分为三层:硬件SPI驱动、LCD驱动芯片命令层、以及应用层的图形绘制。

3.1 SPI配置与速率选择

对于STM32,使用硬件SPI能极大减轻CPU负担。配置要点:

  • 模式:SPI模式0或模式3。ST7735S通常使用模式0(CPOL=0, CPHA=0)。这意味着时钟空闲时为低电平,数据在时钟的第一个边沿(上升沿)采样。
  • 数据大小:8位。
  • 速率:这是第二个容易踩坑的地方。初始化和传输数据可以用不同的速率。初始化时,尤其是发复位序列和一系列配置命令时,建议用较低的速率,比如小于10MHz。因为上电后驱动芯片内部振荡器还没稳定,高速通信容易失败。初始化完成后,再切换到高速进行刷图。我通常这样设置:
// 初始化阶段 hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_32; // 约2.25MHz (72MHz/32) HAL_SPI_Init(&hspi1); LCD_Init(); // 自定义的初始化函数 // 初始化完成后,切换到高速 hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8; // 约9MHz HAL_SPI_Init(&hspi1);
  • 片选(CS)管理:如果硬件SPI的NSS引脚被其他设备占用,或者想更灵活控制,可以把CS当成普通GPIO,在传输前后手动拉低和拉高。每次传输命令或数据前拉低CS,传输完成后拉高。

3.2 驱动芯片命令层封装

这是与LCD模块对话的核心。你需要根据ST7735S的数据手册,编写发送命令和发送数据的函数。

void LCD_WriteCommand(uint8_t cmd) { LCD_DC_CMD(); // 设置DC引脚为低电平,表示命令 HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY); } void LCD_WriteData(uint8_t dat) { LCD_DC_DATA(); // 设置DC引脚为高电平,表示数据 HAL_SPI_Transmit(&hspi1, &dat, 1, HAL_MAX_DELAY); } void LCD_WriteData_Burst(uint16_t *pData, uint32_t len) { LCD_DC_DATA(); // 注意:SPI传输通常以8位为单位,但像素数据是16位(RGB565) // 需要将16位数据拆分成两个8位连续发送 HAL_SPI_Transmit(&hspi1, (uint8_t*)pData, len * 2, HAL_MAX_DELAY); }

然后,就是一系列初始化命令序列。这个序列很长,包括退出睡眠模式、设置颜色格式、内存访问控制、帧率控制等等。这里藏着第三个大坑:初始化序列可能因屏幕批次、厂商不同而有细微差别。网上找到的代码不一定完全适用。最可靠的方法是找卖家索取具体的初始化代码,或者根据数据手册的“典型初始化流程”自己调试。如果初始化后屏幕全白、全黑或有规律条纹,多半是初始化命令不对。

一个关键命令是CASETRASET(设置列地址和行地址),以及RAMWR(写内存)。在局部刷新时,正确设置窗口范围能显著提高效率。

3.3 图形绘制基础与“内存屏”概念

对于128x128的分辨率,如果每个像素用16位色(RGB565),那么整屏缓冲区需要128 * 128 * 2 = 32768字节,即32KB。这对于一些RAM较小的MCU(比如只有20KB RAM的STM32F103)来说,是负担不起的。因此,通常采用“直接写屏”模式,即计算好要画的内容,直接通过SPI流式发送到屏幕的GRAM中。

但这会带来一个问题:任何图形的改变,都需要重新计算并发送整个变化的区域,对于动态界面,SPI总线会非常忙碌。因此,诞生了一个折中方案:在MCU内部开辟一个小于全屏的缓冲区,比如只缓冲一行或一个字符区域的数据,组装好后再一次性发送。这可以避免频繁地设置窗口地址,提升部分场景下的效率。

绘制基本图形(点、线、矩形、圆)的算法是基础。例如画线用Bresenham算法,画圆用中点圆算法。这些算法需要自己实现或移植。对于文字显示,需要字库。由于屏幕小,通常使用点阵字库,比如12x12, 16x16。可以将字库以数组形式存储在代码区(Flash)或外部Flash中。

4. 性能优化与显示效果调校

当基础显示功能实现后,你会发现刷新速度慢、颜色不对、有残影等问题。这时就需要深入调优。

4.1 SPI全双工与DMA加速

默认的HAL_SPI_Transmit是阻塞式的,CPU要等待整个传输完成。刷新一整屏(32768字节)数据,即使SPI时钟9MHz,理论时间也要约29ms,加上命令开销,实际可能超过30ms,帧率只有30多帧。但这已经阻塞CPU 30ms,期间无法处理其他任务。

优化方案1:使用DMA(直接存储器访问)。让DMA控制器自动将内存中的数据搬运到SPI的发送数据寄存器,CPU在此期间可以被释放出来处理其他事情。

// 配置SPI的TX DMA流 // 在显示函数中 HAL_SPI_Transmit_DMA(&hspi1, (uint8_t*)frame_buffer, BUFFER_SIZE); // 然后等待DMA传输完成中断或回调函数

使用DMA后,CPU占用率几乎降为0,刷屏时间就只受限于SPI时钟速度。

优化方案2:提高SPI时钟。在确保信号质量(导线不长,干扰小)的前提下,可以尝试将SPI时钟提高到MCU SPI外设支持的最高分频。STM32F103的SPI1在PCLK2为72MHz时,最高可达36MHz。但要注意,屏幕驱动芯片有最高时钟限制,ST7735S通常支持到15MHz(写)10MHz(读)。盲目提高可能导致数据错误。我一般保守地设置在15-18MHz左右,已经比之前快了一倍。

4.2 颜色管理与Gamma校正

你可能会发现屏幕上显示的颜色和你在代码里设定的RGB565值看起来有偏差。比如纯红色(0xF800)看起来有点发橙。这涉及到两个问题:

  1. 驱动芯片的颜色格式:ST7735S支持多种颜色格式,如RGB565、RGB444等。你发送的数据格式必须和芯片内部设置的格式一致。初始化命令COLMOD就是用来设置这个的。务必确保你发送的16位数据格式与COLMOD命令匹配。
  2. Gamma校正:LCD屏幕的亮度响应通常不是线性的。低灰度时电压变化对亮度影响大,高灰度时影响小。为了让人眼感知到的亮度变化是均匀的,需要对输出数据进行非线性校正(Gamma校正)。ST7735S提供了一系列Gamma校正命令(GMCTRP1,GMCTRN1等),里面是一组预设的校正值。通常,直接应用数据手册或示例代码中的Gamma校正序列,就能获得更准确、更柔和的色彩过渡。如果颜色感觉“生硬”或“断层”,可以尝试调整这些Gamma值。

4.3 解决残影与闪烁

残影,即图像切换后,上一幅图的痕迹还隐约可见。这主要是由于液晶分子的扭转和恢复需要时间。ST7735S有一个FRMCTR1帧率控制命令,可以调整帧速率。适当降低帧率有时反而能减轻残影,因为给了液晶分子更充分的响应时间。但帧率太低会导致动画卡顿,需要权衡。

闪烁,可能是由于电源纹波,也可能是软件问题。如果是在绘制过程中屏幕局部闪烁,检查一下你的绘制逻辑:是否在每次局部更新时,都先正确设置了窗口(CASET/RASET),然后发送RAMWR,再发送数据?错误的窗口设置可能导致数据写到了非预期的区域,造成显示混乱,视觉上像闪烁。

另一个高级技巧是使用局部刷新。如果界面上只有一小部分区域(如一个数字)需要更新,那么只刷新这一小块区域,而不是全屏。这需要你精确管理屏幕上每个元素的区域。例如,一个数字从“1”变成“2”,你只需要计算这两个数字图形的差异区域,然后只更新这个差异区域。这能极大减少数据量和刷新时间,从而降低整体功耗和视觉闪烁感。

5. 实战应用:构建一个微型信息显示器

理论说了这么多,我们把它用起来。假设我们要做一个显示时间、温度和电量的微型状态显示器。

5.1 界面布局与内存管理

128x128的屏幕,空间极其宝贵。我们需要精心规划:

  • 顶部20像素:显示标题(如“Status”),用12号字体。
  • 中间区域:80像素高,用于显示大号的时间数字(如32像素高的字体)或主要的温度数据。
  • 底部28像素:显示电量图标和百分比,以及一些状态图标。

由于没有全屏缓冲区,我们需要一个“脏矩形”机制。为屏幕上每个可能变化的区域定义一个结构体,包含其坐标和是否需要更新的标志。

typedef struct { uint16_t x; uint16_t y; uint16_t width; uint16_t height; bool is_dirty; } display_region_t; display_region_t region_time = {10, 30, 108, 32, false}; display_region_t region_battery = {90, 100, 30, 20, false};

当时间需要更新时,region_time.is_dirty = true。在主循环中,定期检查所有区域,如果is_dirty为真,则调用对应的绘制函数更新该区域,更新完成后将标志置为false

5.2 字体与图标处理

为了节省Flash空间,我们只提取需要的字符和图标。例如,时间显示只需要数字0-9和冒号“:”。我们可以制作一个精简的字库数组。图标(如电池、WiFi信号)可以用位图数组表示,每个像素1位(单色)或16位(彩色)。网上有很多在线工具可以将小图片转换成C语言数组。

绘制图标时,注意颜色深度。如果你的图标是单色的,但屏幕是16位色,你需要一个颜色查找表,将图标的1位数据映射到前景色和背景色。

5.3 低功耗策略

0.71英寸屏本身功耗很低(全亮时几个mA),但在电池供电设备中,每一微安都值得计较。

  • 动态背光控制:环境光暗时,可以降低背光亮度(通过PWM控制BLK引脚)或直接关闭背光。
  • 睡眠模式:ST7735S有睡眠命令(SLPIN)。当长时间不需要显示时,发送此命令,芯片进入低功耗睡眠模式,电流可以降到微安级。需要显示时,再发送唤醒命令(SLPOUT)。注意,从睡眠中唤醒后,通常需要重新初始化一部分寄存器(或者直接发送完整的初始化序列最保险)。
  • 降低刷新率:如果显示的是静态或缓慢变化的信息,可以大幅降低刷新频率。比如每秒只更新一次时间。在不需要刷新的时候,让MCU和SPI都进入低功耗状态。

6. 调试过程中遇到的典型问题与解决方案

实际开发不可能一帆风顺,下面是我踩过的一些坑和解决办法。

6.1 屏幕初始化后无任何显示(白屏或黑屏)

这是最常见的问题。排查步骤:

  1. 查电源和背光:用万用表测量VCC和GND引脚电压是否为稳定的3.3V?背光引脚(BLK)电压是否被拉高(如果可控)?最简单的方法,用手电筒斜着照屏幕,看能否看到非常暗的图案。如果能,说明屏幕有显示但背光没亮。
  2. 查复位时序:复位引脚(RES)的时序非常关键。正确的顺序是:上电后,保持RES为低电平至少10ms,然后拉高,再延迟至少120ms(等待内部振荡稳定),再进行初始化命令序列。很多驱动库的LCD_Init函数开头就包含了这个硬件复位过程,确保你的代码执行了它。
  3. 查SPI通信:用逻辑分析仪或示波器抓取SPI总线(SCLK, MOSI)的波形。看初始化命令序列是否被正确发出。重点检查第一个命令SWRESET(软件复位)是否发出。没有逻辑分析仪的话,可以尝试将SPI速率降到很低(比如100kHz),并逐个命令发送,在每个命令后加长延时,用最笨的方法排除通信速度问题。
  4. 查初始化序列:这是最可能的原因。不同厂家、不同批次的屏幕,初始化序列可能有细微差别。特别是那些用于调节电压、Gamma、功率控制的参数。尝试换一个来源的初始化代码(比如Adafruit的ST7735库、Arduino的TFT_eSPI库中的配置),或者仔细核对数据手册中的推荐序列。

6.2 显示颜色异常或花屏

  1. 颜色格式不匹配:确认你发送的像素数据格式(RGB565还是其他)与通过COLMOD命令设置的格式完全一致。RGB565是0b101(5-6-5),如果你的数据是别的格式,颜色肯定会错乱。
  2. 数据位序问题:SPI是MSB(最高位)先发送。但RGB565的16位数据中,是先发送高8位还是低8位?这需要看驱动芯片的要求。ST7735S通常是先发高8位。如果你把字节顺序弄反了,颜色就会完全不对。尝试交换发送的两个字节顺序。
  3. 内存访问控制(MADCTL)设置错误:这个命令控制显示旋转、镜像和颜色顺序(BGR/RGB)。如果你设置了镜像或旋转,但送数据的顺序没变,就会花屏。更常见的是RGB顺序。ST7735S默认可能是BGR顺序。这意味着你发送的RGB565数据中的R和B分量会被交换。如果你发现红色和蓝色反了,在初始化时修改MADCTL命令中的RGB位,或者在你的代码里交换R和B分量。
  4. 电源噪声:回到硬件,检查电源滤波电容是否焊接良好,尽量靠近模块引脚。可以用示波器观察VCC上的纹波。

6.3 刷新速度慢,动画卡顿

  1. SPI时钟太低:检查SPI初始化代码中的分频系数,在屏幕允许范围内尽量提高。
  2. 没有使用DMA:如果CPU被SPI传输阻塞,其他任务(如读取传感器、处理逻辑)就会被耽误,感觉整体卡顿。启用DMA是质变。
  3. 无效的全屏刷新:检查你的代码逻辑,是否在只有局部变化时,也执行了全屏刷新?引入“脏矩形”机制,只更新需要更新的区域。
  4. 复杂的图形绘制:在MCU上实时计算抗锯齿的圆或斜线,非常耗时。对于静态或很少变化的复杂图形,可以预先计算好位图存在Flash里,显示时直接发送位图数据。

6.4 长时间运行后显示异常或死机

  1. 看门狗复位:如果SPI传输是阻塞式的,且刷新一屏时间超过看门狗超时时间,会导致系统复位。优化刷新速度或调整看门狗超时时间。
  2. 堆栈溢出:绘制函数或显示驱动中使用了较大的局部数组,可能导致栈溢出。将大数组定义为静态变量或全局变量。
  3. 电磁干扰(EMI):在电机、继电器等强干扰源附近,SPI信号可能被干扰。尝试缩短连接线,使用双绞线,或在信号线上串联小电阻(如22欧姆)并并联到地的小电容(如10pF),以改善信号完整性。

经过这一整套从硬件到软件,从原理到调试的梳理,这块小小的0.71英寸LCD模块就不再是一个黑盒了。你可以精准地控制它的每一个像素,让它在你手中发挥出最大的价值。无论是作为产品中的一个低调的指示器,还是作为一个极客玩具的核心显示部件,它都能以极低的成本和功耗,提供可靠的信息输出。最关键的是,通过啃下这块小屏幕,你掌握的这套显示驱动、优化和调试的方法论,可以无缝迁移到更大、更复杂的显示设备上。