1.69英寸LCD模块选型、驱动与优化全攻略
1. 项目缘起:为什么是1.69英寸LCD?
最近在捣鼓一个需要显示交互界面的小玩意儿,比如智能家居的温控面板、便携式数据记录仪,或者一个桌面小摆件。选屏幕的时候,我直接跳过了那些傻大黑粗的旧款,也绕开了成本高昂的OLED,最终把目光锁定在了1.69英寸的LCD模块上。你可能要问,市面上屏幕尺寸那么多,为啥偏偏是1.69英寸?这背后其实是一系列工程实践和成本效益权衡的结果。
首先,1.69英寸这个尺寸非常“黄金”。它比常见的0.96英寸、1.3英寸屏大了不少,能显示更多内容,比如几行文字加个小图标,或者一个简单的进度条和数值,做个小UI界面绰绰有余。同时,它又不像2.4英寸、3.5英寸那样“占地为王”,在保持信息显示能力的同时,对整机尺寸和功耗的控制非常友好。对于很多嵌入式、物联网或者创客项目来说,设备本身可能就巴掌大,1.69英寸屏既能提供足够的视觉信息,又不会喧宾夺主,是功能与体积的完美平衡点。
其次,从驱动和接口来看,这类模块通常已经高度集成化。你拿到手的基本都是一个完整的“显示子系统”,包含了LCD玻璃、驱动芯片、背光,甚至FPC排线都已经焊好。主控芯片(比如我们常用的STM32、ESP32或者树莓派Pico)只需要通过SPI或者I2C这类简单的串行接口发送数据,就能点亮它。这意味着开发者可以把精力集中在应用逻辑上,而不是去折腾复杂的LCD时序和电源管理。对于个人项目或者小批量产品,这种“开箱即用”的特性极大地降低了开发门槛和周期。
最后,成本是绕不开的话题。相比于同尺寸的OLED,LCD模块的价格要亲民得多。虽然OLED有对比度高、色彩鲜艳、可弯曲等优点,但对于很多显示固定图标、文字、简单图形的应用场景,LCD的显示效果完全够用。在保证基本可视角度和背光均匀度的前提下,选择一个性价比高的1.69英寸LCD模块,能让整个项目的BOM成本(物料清单成本)得到有效控制。所以,当你需要一个不大不小、够用就好、容易驱动且成本可控的显示屏时,1.69英寸LCD模块往往会成为那个“刚刚好”的选择。
2. 模块初探:硬件接口与核心参数解读
拿到一块1.69英寸LCD模块,第一件事不是急着通电,而是先把它“看透”。这就像交朋友,得先了解对方的脾气秉性。模块的硬件接口和核心参数,直接决定了它能否与你的主控芯片“愉快玩耍”,以及最终显示效果的上限。
2.1 常见的接口类型:SPI与“模拟”SPI
绝大多数1.69英寸LCD模块都采用SPI(Serial Peripheral Interface)接口,因为它引脚少、速度快、协议简单。但这里有个关键细节需要区分:硬件SPI和软件模拟SPI(常被简称为“模拟SPI”)。
模块的驱动芯片(比如ST7789、ST7735、ILI9341等)本身支持标准的四线SPI(SCLK时钟、MOSI主出从入、MISO主入从出、CS片选),外加DC(数据/命令)和RST(复位)引脚。如果你的主控芯片有富余的硬件SPI外设,那么强烈建议使用硬件SPI。它的优势是速度极快,数据传输不占用CPU资源,刷屏流畅,尤其是在需要动画或快速更新数据的场景下,体验天差地别。
然而,很多入门级开发板(比如某些Arduino型号)硬件SPI引脚可能被其他功能占用,或者你想把屏幕接在更灵活的GPIO上。这时就需要使用“模拟SPI”,即用普通的GPIO口,通过软件逐位模拟出SCLK和MOSI的时序。这种做法非常灵活,不挑引脚,但代价是速度慢,CPU占用率高。对于1.69英寸这种分辨率(常见为240x280或240x320)的屏幕,用模拟SPI刷一整屏图像,可能会有肉眼可见的延迟感。
注意:在查阅模块资料时,务必确认其支持的SPI模式(通常是模式0或模式3),以及最高时钟频率。过高的时钟频率在长线或面包板连接时可能导致数据错误,如果出现显示花屏,可以尝试降低SPI时钟速度。
2.2 关键电气参数与屏幕特性
除了接口,下面几个参数关乎屏幕的“身体素质”:
- 分辨率:这是屏幕的像素网格大小,直接决定了显示内容的精细度。常见的1.69英寸模块分辨率有240x280、240x320等。240x280意味着横向240个像素,纵向280个像素。在确定UI设计时,所有图标、文字的尺寸都必须基于这个分辨率来规划。
- 色彩深度:通常支持16位色(RGB565格式),即每个像素用16位二进制数表示,红色占5位,绿色占6位,蓝色占5位,总共能显示65536种颜色。对于大多数非照片级的图形界面,这个色彩数完全足够。有些模块也支持18位色(RGB666)或通过抖动算法模拟更丰富的色彩,但需要驱动芯片和驱动程序支持。
- 背光类型与控制:绝大多数是LED背光。关键点在于背光是常亮还是可控。如果模块的背光引脚(BLK或LED)直接接在了VCC上,那么上电即亮,无法通过软件调节亮度。更理想的模块会将背光引脚独立引出,这样你就可以通过主控的PWM(脉宽调制)功能来无极调节亮度,实现省电或适应不同环境光。
- 供电电压:常见的是3.3V,与主流微控制器逻辑电平匹配。务必确认模块的VCC引脚电压要求,接错电压可能永久损坏屏幕或驱动芯片。
- 驱动芯片型号:这是屏幕的“大脑”,决定了底层指令集。ST7789v、ST7735s、ILI9341是常见的型号。在寻找或编写驱动程序时,必须针对具体的驱动芯片型号。虽然它们的功能相似,但初始化序列和部分命令寄存器可能存在差异,用错驱动会导致显示异常。
3. 驱动实战:从点亮到显示第一幅图像
理论了解得再多,不如动手点亮点亮。这个过程可以分解为硬件连接、底层驱动移植、高级图形库应用三个层次。我们以一款典型的采用ST7789v驱动芯片、240x280分辨率、SPI接口的1.69英寸LCD模块为例,搭配STM32系列微控制器进行说明。
3.1 硬件连接与引脚定义
首先,根据模块的数据手册或卖家提供的引脚图,完成物理连接。一个典型的连接表示例如下:
| LCD模块引脚 | 功能说明 | 连接至STM32引脚 | 备注 |
|---|---|---|---|
| VCC | 电源正极 (3.3V) | 3.3V | 切勿接5V! |
| GND | 电源地 | GND | |
| SCL | SPI时钟线 | PA5 (SPI1_SCK) | 硬件SPI时钟引脚 |
| SDA | SPI数据线 (MOSI) | PA7 (SPI1_MOSI) | 硬件SPI主出从入引脚 |
| RES | 复位信号 | PA0 (任意GPIO) | 低电平有效,用于硬复位 |
| DC | 数据/命令选择 | PA1 (任意GPIO) | 高电平=数据,低电平=命令 |
| CS | 片选信号 | PA4 (SPI1_NSS) | 低电平选中该设备 |
| BLK | 背光控制 | PA2 (任意GPIO) | 接PWM引脚可实现调光 |
连接时,建议使用杜邦线并确保接触牢固。如果屏幕距离主控较远,或者SPI时钟设置得较高,可以考虑在SCLK和MOSI线上串联一个几十欧姆的电阻,以减弱信号反射。
3.2 底层驱动:SPI通信与初始化序列
硬件连接好后,我们需要在STM32的工程中实现最底层的通信。这包括两个部分:SPI外设初始化和LCD驱动芯片的初始化序列。
SPI初始化:在STM32CubeMX或直接寄存器编程中,配置SPI1为主机模式、全双工、数据位宽8位。时钟极性(CPOL)和时钟相位(CPHA)需要根据ST7789v的数据手册设置,通常为CPOL=0, CPHA=0(即SPI Mode 0)。时钟预分频器(BaudRatePrescaler)可以先设置一个较低的值,如256分频,确保通信稳定,后续可以再提高。
LCD初始化序列:这是点亮屏幕最关键的一步。ST7789v芯片上电后处于一个未知状态,必须通过一系列特定的命令(Command)和参数(Data)进行配置,才能正常显示。这些命令包括设置扫描方向、颜色格式、电源控制、伽马校正等。初始化序列通常由屏幕模块厂商提供,或者可以从开源驱动库(如Arduino的Adafruit_ST7789库)中找到。
在代码中,我们需要实现两个最基本的函数:
LCD_WriteCommand(uint8_t cmd): 拉低DC引脚(表示发送命令),通过SPI发送一个字节的命令。LCD_WriteData(uint8_t data): 拉高DC引脚(表示发送数据),通过SPI发送一个字节的数据。
然后,将整个初始化序列(可能包含几十条命令和数据)依次调用这两个函数发送出去。一个常见的坑是复位时序。必须在发送初始化序列前,先操作RESET引脚:拉低至少10ms,然后拉高,并延时至少120ms,等待芯片内部稳定。
// 示例:发送初始化序列中的一条命令(设置像素格式为16位) LCD_WriteCommand(0x3A); // COLMOD: Interface Pixel Format LCD_WriteData(0x55); // 参数0x55代表16位/pixel (RGB565)3.3 核心操作:设置窗口与写入像素数据
初始化完成后,屏幕是空白的(可能为某种纯色)。要显示内容,核心是理解“窗口(Window)”机制。我们不需要逐个像素地告诉屏幕每个点画什么颜色,那样效率极低。驱动芯片允许我们定义一个矩形的显示区域(窗口),然后连续地向这个区域“灌入”像素数据,芯片会自动按顺序填充。
- 设置列地址(X方向):发送命令
0x2A,后面跟4个字节数据,分别代表起始列地址的高8位、低8位,和结束列地址的高8位、低8位。例如,要设置从第10列到第230列,就需要发送0x2A,0x00,0x0A,0x00,0xE6。 - 设置行地址(Y方向):发送命令
0x2B,后面跟4个字节数据,代表起始行和结束行地址。 - 写入GRAM(显存):发送命令
0x2C。此后,所有通过LCD_WriteData发送的数据,都会被芯片解释为像素颜色值,并依次填充到刚才设置的窗口内。每个像素点需要2个字节(RGB565格式)。
因此,画一个矩形色块或显示一张位图的基本流程就是:设置窗口 -> 发送0x2C命令 -> 循环发送像素数据。为了提高效率,我们可以利用STM32 SPI的DMA(直接存储器访问)功能,将存储好的图像数据数组直接通过DMA搬运到SPI发送寄存器,完全解放CPU。
3.4 借助图形库:LVGL与u8g2的快速上手
自己操作底层像素对于画方块、线条还行,但要构建一个带有按钮、图表、动画的复杂GUI,就力不从心了。这时,就需要引入嵌入式图形库。对于1.69英寸这类资源有限的微控制器,LVGL和u8g2是两个绝佳的选择。
LVGL (Light and Versatile Graphics Library):功能极其强大,是一个完整的GUI框架。它支持各种控件(按钮、滑块、列表、图表)、动画、主题、多语言,甚至还有模拟器可以在PC上先开发UI。它的缺点是资源消耗相对较大,需要更多的RAM和Flash。但对于STM32F4系列(带FPU和足够内存)或ESP32来说,运行LVGL驱动1.69英寸屏是完全可以的。集成LVGL的关键是提供好几个“移植函数”,主要是disp_flush函数(用于将LVGL画好的图像区域刷新到物理屏幕)和touchpad_read函数(如果你有触摸屏的话)。LVGL内部会处理好所有的绘图逻辑,你只需要调用它的API创建界面即可。
u8g2:这是一个更专注于单色、OLED/LCD点阵屏的图形库。虽然它最初为单色设计,但其“全缓冲模式”也完全支持彩色LCD。u8g2的API非常简洁,画点、线、圆、文字、位图都很方便。它比LVGL轻量得多,适合在资源更紧张的芯片(如STM32F1、Arduino Uno)上驱动LCD显示一些信息界面,但不适合做复杂的交互式GUI。使用u8g2,你需要根据你的驱动芯片型号(如ST7789)选择对应的构造函数,并实现底层的u8x8_byte_4wire_hw_spi通信回调函数。
选择哪个库,取决于你的项目需求。要酷炫的交互界面,选LVGL;只是显示些数据和简单图形,u8g2更快捷省资源。
4. 性能优化与显示效果调校
当屏幕能正常显示后,我们往往会追求更快的刷新速度和更好的视觉体验。这就进入了优化和调校阶段,这里面的门道也不少。
4.1 提升刷屏速度:硬件SPI与DMA的威力
如果你用的是软件模拟SPI,感觉刷屏慢,那么切换到硬件SPI是提升速度最直接有效的方法。在STM32上,将SPI时钟提升到芯片和屏幕都能承受的最高频率(比如STM32F1的SPI可以到18MHz,STM32F4可以到37.5MHz),刷屏速度会有质的飞跃。
更进一步,使用DMA。DMA可以在不打扰CPU的情况下,自动将内存中的图像数据搬运到SPI数据寄存器。设置流程是:配置好SPI和DMA通道,设置传输数据量(像素数*2),然后启动DMA传输。在此期间,CPU可以去处理其他任务(如传感器读取、逻辑计算)。对于全屏刷新,使用DMA能极大提高系统效率,并保证刷屏过程的稳定。一个实测对比:对于240x280的屏幕,用软件SPI刷全屏可能需要上百毫秒,用高速硬件SPI可能降到几十毫秒,而用硬件SPI+DMA,这个时间可以进一步缩短,并且CPU占用率几乎为零。
4.2 解决显示瑕疵:撕裂效应与动态分配
当屏幕刷新(比如动画)和你的图形渲染不同步时,可能会看到屏幕上半部分和下半部分显示的是不同帧的图像,中间一条线上下内容“撕裂”了。这就是“撕裂效应(Tearing Effect)”。
ST7789等驱动芯片提供了一个非常有用的功能来对抗撕裂:撕裂效应输出信号(TE, Tearing Effect Output)。如果模块引出了TE引脚(有时标为TEAR),可以将其连接到MCU的一个外部中断引脚。驱动芯片会在每一帧扫描完成后,通过这个引脚输出一个脉冲。MCU收到这个中断信号后,知道当前帧已显示完毕,可以安全地开始更新下一帧的显存数据。这样就保证了图像更新的完整性,避免了撕裂。在LVGL等库中,可以配置这个TE信号来实现同步刷新。
另一个常见问题是内存不足。双缓冲(Double Buffering)是解决闪烁和提升流畅度的经典技术,但它需要两倍于屏幕的缓冲区内存(2402802 bytes ≈ 131KB)。对于内存紧张的MCU,这可能是无法承受的。此时可以采用**局部刷新(Partial Refresh)**策略。只更新屏幕上发生变化的那部分区域,而不是每次都重绘全屏。LVGL库天生就支持局部刷新,它内部会跟踪哪些区域需要重绘,并只调用disp_flush函数更新这些“脏区域”,极大地减少了数据传输量。
4.3 色彩与背光调校:让显示更舒适
默认的显示色彩可能偏色、发白或对比度不佳。这可以通过调整驱动芯片的伽马校正(Gamma Correction)和亮度/对比度控制寄存器来改善。这些调整没有固定值,需要根据屏幕观感反复试验。通常可以在初始化序列后,发送一系列伽马校正参数(命令0x26和0x27用于正负伽马校正)。网上可以找到针对不同屏幕面板的调校参数,可以作为起点。
对于可调背光的模块,PWM调光是必备功能。将背光引脚连接到MCU的PWM输出,通过改变PWM的占空比来调节亮度。这里有个细节:人眼对亮度的感知不是线性的。直接线性改变占空比,可能会感觉低亮度时变化太快,高亮度时变化太慢。更好的做法是使用伽马曲线来映射PWM值,使亮度变化更符合人眼感知。例如,你可以建立一个查找表,将目标亮度百分比(0-100%)映射到一个经过伽马校正的PWM计数值上。
5. 项目集成与常见问题排查
将屏幕集成到实际项目中,总会遇到一些意想不到的问题。下面是一些典型的“坑”和排查思路。
5.1 上电无显示或花屏的排查链路
这是最让人头疼的问题。请按照以下步骤系统性排查:
- 电源与连接:首先用万用表测量VCC和GND之间的电压,确保是稳定的3.3V。检查所有连接线是否牢固,有无虚焊、断线。特别是SPI的时钟和数据线,接错或接触不良是花屏的常见原因。
- 复位时序:确认复位信号(RESET)的时序是否正确。必须有一个从高到低再到高的跳变过程,且低电平保持时间足够(参考数据手册,通常>10ms)。可以在代码中增加复位后的延时(如
HAL_Delay(120))。 - SPI模式与速率:确认MCU的SPI模式(CPOL, CPHA)与LCD驱动芯片要求的一致。最稳妥的方法是先将SPI时钟速率设到最低(如108kHz),确保能通信,再逐步提高。花屏、错位往往是时钟速率过高或模式不对导致数据采样错误。
- 初始化序列:逐条核对初始化序列,确保命令和参数都正确。有时不同批次的屏幕或不同厂商的兼容模块,初始化序列可能有细微差别。尝试注释掉部分非核心初始化命令(如伽马校正、电源控制等),只保留最基础的设置(如扫描方向、像素格式、显示开),看是否能显示纯色。
- 驱动芯片型号:再次确认你使用的驱动程序是否完全匹配屏幕上的驱动芯片。ST7789v和ST7789vi可能就有差异。用错驱动,轻则颜色不对,重则完全无显示。
5.2 显示内容错位或镜像
如果文字、图片显示的位置不对,或者上下/左右颠倒,这通常不是硬件问题,而是**扫描方向(Rotation)和显示偏移(Offset)**设置不正确。
驱动芯片可以通过命令(如ST7789的0x36命令,即MADCTL)来设置内存访问控制。这个命令的参数控制了行列地址是否互换、行列的扫描顺序(正序/反序)、颜色字节顺序(RGB/BGR)等。当你调用setRotation(0)、setRotation(1)这类库函数时,底层就是在修改这个寄存器。
如果UI整体正确但偏离了屏幕中央,可能是存在显示偏移(Display Offset)。有些屏幕面板的物理像素阵列其实比驱动芯片支持的要小,或者为了适配面板,需要在驱动芯片中设置一个起始偏移量。这需要通过特定的命令(如0x37设置垂直滚动起始地址,或初始化序列中的一些偏移参数)来校正。这部分参数通常需要从屏幕供应商那里获取,或者通过反复试验确定。
5.3 功耗与电磁干扰(EMI)考量
在电池供电的项目中,屏幕背光往往是耗电大户。除了使用PWM调低亮度,在不需要显示时,可以彻底关闭背光(将BLK引脚拉低)。更进一步,可以发送命令让驱动芯片进入睡眠模式(Sleep Mode),此时芯片内部大部分电路关闭,功耗可以降到微安级别。需要显示时再唤醒它。
高速SPI信号线也是潜在的电磁干扰源。如果项目中有模拟传感器(如麦克风、精密ADC),屏幕刷新时可能会在传感器读数中引入噪声。解决方案包括:在屏幕的电源引脚就近放置一个10uF和一个0.1uF的电容进行去耦;使用屏蔽线或绞合线连接SPI信号;在软件上,避免在关键数据采样期间进行大面积的屏幕刷新;或者降低SPI时钟频率。
