ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

嵌入式LCD屏驱详解:主流芯片识别、初始化与调试指南

嵌入式LCD屏驱详解:主流芯片识别、初始化与调试指南

1. 屏幕不亮了先别急着查接线,先确认它用的是哪颗屏驱

做嵌入式项目的人应该都有过这种经历:一块LCD模块拿在手里,四五个引脚、一个背光、一根排线,看着和网上教程里的一模一样,照着别人的代码接上去,结果要么白屏、要么花屏、要么颜色完全不对。大多数人的第一反应是查接线、量电平、怀疑自己的SPI配置有问题。但我后来发现,至少有三分之一的情况,问题压根不在你的代码,而在屏幕背面那颗不起眼的芯片——LCD驱动IC(屏驱)。

屏驱这个东西,简单说就是屏幕的“大脑”兼“翻译官”。主控芯片(MCU)并不直接控制屏幕上的每一个像素,它只需要按照某种协议把像素数据发给屏驱,由屏驱把数据转成玻璃基板上对应的扫描信号、灰阶电压、刷新时序。换句话说,MCU和屏幕之间隔着一个屏驱,所有你写给屏幕的“话”都是说给屏驱听的,而不同屏驱的“听力习惯”完全不一样。

这就是为什么同样2.4寸、同样240x320分辨率的屏幕,用A驱动芯片的代码去驱动B芯片的模块,大概率跑不起来或显示异常。市面上大量廉价模块外形相似,但背后用的驱动芯片五花八门:ST7735、ILI9341、ST7789、ILI9488、GC9A01……它们在命令集、初始化序列、接口定义上各有各的习惯。今天这篇文章就围绕“常用LCD屏驱”这个主题,把我实际调屏过程中用过的、见过的、踩过坑的驱动芯片和对应经验梳理一遍。适合正在入门显示模块的同学,也适合已经会点基础的开发者系统补一遍背后的原理。

我的建议是:拿到任何一块新屏,先别急着接线,花两分钟搞清楚屏驱型号,再动手。下面就从这个动作说起。

2. 市面常见的几颗屏驱,各自是什么脾气

2.1 一张表看清主流屏驱

先把我用过的几颗常见屏驱列出来,方便做选型和对比。这里的“常见”指的是在嵌入式DIY、开源硬件、小型产品里出现频率最高的那几颗,它们覆盖了从0.96寸到3.5寸的绝大多数小尺寸LCD场景。

屏驱型号常见分辨率典型尺寸主要接口色彩格式备注
ST7735128x160、80x1600.96寸/1.8寸SPIRGB444/565/666小尺寸TFT的老将
ST7789240x240、135x2401.3寸/1.54寸SPIRGB565/666近几年小屏新宠
ILI9341240x3202.4寸/2.8寸/3.2寸SPI/并行RGB565/666最经典的通用屏驱
ILI9488320x4803.5寸SPI/并行RGB666大分辨率、颜色要求高的屏
GC9A01240x2401.28寸圆形SPIRGB565圆形表盘类屏幕常见
NT35510720x1280等手机屏级别MIPI/并行RGB888移动设备市场的常见型号
SSD1306128x640.96寸OLEDI2C/SPI单色OLED,严格来说不算LCD

这张表里ST7735和ILI9341是真正的“老资历”,网上能搜到的显示类教程起码有一半是围绕它们写的。ST7789是近年来的后起之秀,很多1.3寸、1.54寸小方屏在用,单颗价格比同尺寸的ST7735方案还便宜,而且分辨率更高。GC9A01则几乎成了圆形智能手表、电子胸牌类产品的默认选择。

2.2 为什么不能“一套代码走天下”

很多人不理解:都是SPI接口,都是发一串命令,凭什么换颗芯片就要改初始化?这里的关键在于,屏驱芯片内部的寄存器地址和命令码并没有一个统一的标准。比如ST7735的“开显示”命令是0x29,ILI9341也一样是0x29,但你一深入就发现差异很大:颜色深度设置命令在ILI9341上是0x3A,在ST7735上虽然有0x3A但可用的色彩格式不一样;屏幕方向控制命令0x36(MADCTL)各家都有,但某些保留位的行为可能不同,尤其是不同批次、不同厂商生产的同型号芯片,初始化序列甚至存在细微差别。

就算主控的驱动程序写得再优雅,最终还是要适配“这一颗屏驱”的寄存器行为。所以结论很直接:先用芯片型号反查数据手册,再写初始化。省掉这一步,后面八成要返工。

3. 拿到新屏先“验明正身”:从物理型号到数据手册的关键信息

3.1 三步认清屏驱

第一步,看模块背面的PCB丝印。绝大多数独立模块上会印着驱动芯片型号,比如一块小板子上印着“ST7789V”或者“ILI9341C”,基本就是答案。但丝印位置不固定,有时候字很小,建议直接手机拍照放大看。我踩过的那个坑就是在1.8寸屏上,PCB丝印只印了模组型号,真正标注驱动芯片的地方藏在FPC排线根部,用放大镜才看清。

第二步,查驱动芯片型号对应的数据手册(Datasheet)。搜索引擎输入型号加“datasheet”字样,很容易找到,但要注意版本,同一个型号会分“PRELIMINARY”和“FINAL”版本,命令定义可能有修订。以英文版PDF为准,中文二次翻译的资料建议只当参考。

第三步,也是我自己常用的办法:如果模块卖家提供了示例代码,直接看示例里的初始化数组和命令注释,通常能反推芯片型号。初始化数组里出现频率最高的那几个命令,比如0x36、0x3A、0x2A、0x2B、0x2C,是TFT驱动家族的通用“骨架”,但具体的参数值会因芯片而异。

3.2 数据手册里只抓这五件事

数据手册动辄几十页,大部分内容是这个芯片内部的栅极驱动逻辑和电压生成电路,对做应用层的人来说不需要逐页啃。我调屏时的习惯是只看五个部分:

  1. 接口选择:确认屏驱支持哪几种通信方式,有哪些配置引脚决定工作模式。比如ILI9341支持三线/四线SPI、8位/16位并行接口,这些模式靠模块上的电阻或引脚跳线决定。如果你的模块只引出了SPI引脚,那就不用管并行部分。

  2. 命令总表和关键命令:看命令号、命令名称、参数个数和参数含义。重点抓0x36(MADCTL,内存访问方向)、0x3A(像素格式)、0x2A/0x2B/0x2C(窗口地址和写显存),这几条是所有TFT屏都绕不开的基础。

  3. 电源与复位时序:芯片对VDD上电、复位释放和退出睡眠之间的延时都有要求。有些屏一上电就有短暂的“高电平复位”过程,如果MCU的复位脚时序和屏驱不匹配,芯片可能处于异常状态。

  4. 显存布局和偏移:很多屏驱内部的GRAM坐标和物理可显示区域并不是从(0,0)开始的,这就是“偏移量”问题。比如ST7789的某些1.54寸屏,真正可显示的像素区域在GRAM里偏移了(34,52),写代码时必须把这个偏移算进去,否则画面会偏。

  5. 帧率和背光控制:如果做动态显示,需要了解屏驱支持的扫描方向、撕裂检测(TE)引脚以及帧率设置范围。这些决定你后面是否需要做防撕裂处理。

3.3 用最小系统验证“通讯通没通”

在写完整初始化之前,我强烈建议先做一次“读ID”测试。绝大多数屏驱支持读取芯片ID,比如ILI9341的0xD3命令会返回0x93,ST7789的0x04命令会返回一系列ID字节。如果读回来的值和预期一致,说明接线、SPI模式、主控配置全对;如果读不到,大概率是SPI的时钟极性(CPOL)或相位(CPHA)不对,或者DC引脚(数据/命令切换)接反了。

这一步只花十分钟,却能排除掉一半以上的低级问题。很多调屏教程直接跳过了读ID这一步,结果后面颜色不对时不知道是屏驱问题还是参数问题,排查起来特别痛苦。

4. 初始化序列实战:以ILI9341为例从零跑通一块屏

4.1 初始化到底在“初始化”什么

屏驱内部的寄存器在上电后的默认值,往往处于“低功耗待机”状态,画面是关闭的,像素格式也不是我们常用的RGB565,甚至扫描方向是倒的。初始化序列的作用,就是把这堆默认状态一项一项改成我们要的状态。有人迷信网上抄来的几十行初始化数组,觉得“只要照抄就行”。我建议至少理解其中几行在干什么,一旦屏驱动不了,你才能判断是数组问题还是接线问题。

一个典型的初始化流程包含这几步:硬复位 → 软件复位 → 退出睡眠 → 设置像素格式 → 设置扫描方向 → 设置窗口地址 → 开显示 → 清除画面。下面这段代码是我在一个模拟项目X上用SPI方式驱动ILI9341时整理的简化版,可以直接抄进自己的工程里改引脚编号使用。

4.2 完整初始化代码

#include <stdint.h> // 假设已经实现以下底层函数 void LCD_SPI_SendByte(uint8_t data); void LCD_DC_HIGH(void); // 数据模式 void LCD_DC_LOW(void); // 命令模式 void LCD_CS_LOW(void); void LCD_CS_HIGH(void); void LCD_RST_HIGH(void); void LCD_RST_LOW(void); void Delay_ms(uint32_t n); // 发送一条命令,参数列表以0x00结尾 static void lcd_write_cmd(uint8_t cmd) { LCD_CS_LOW(); LCD_DC_LOW(); LCD_SPI_SendByte(cmd); LCD_CS_HIGH(); } static void lcd_write_data(uint8_t data) { LCD_CS_LOW(); LCD_DC_HIGH(); LCD_SPI_SendByte(data); LCD_CS_HIGH(); } // 带参数的命令 static void lcd_write_cmd_data(uint8_t cmd, uint8_t *data, uint8_t len) { lcd_write_cmd(cmd); for (uint8_t i = 0; i < len; i++) { lcd_write_data(data[i]); } } // 复位 static void lcd_reset(void) { LCD_RST_HIGH(); Delay_ms(50); LCD_RST_LOW(); Delay_ms(50); LCD_RST_HIGH(); Delay_ms(120); } // ILI9341 初始化 void ili9341_init(void) { lcd_reset(); lcd_write_cmd(0x01); // 软件复位 Delay_ms(120); lcd_write_cmd(0x11); // 退出睡眠模式 Delay_ms(150); uint8_t cmd36 = 0x48; // MADCTL: MX+MV? 实际根据屏幕方向定 lcd_write_cmd_data(0x36, &cmd36, 1); uint8_t cmd3A = 0x55; // RGB565格式 lcd_write_cmd_data(0x3A, &cmd3A, 1); lcd_write_cmd(0x20); // 关闭反显(部分屏默认开) lcd_write_cmd(0x29); // 打开显示 Delay_ms(50); // 清屏为黑色 lcd_set_window(0, 0, 239, 319); for (uint32_t i = 0; i < 240u * 320u; i++) { lcd_write_data(0x00); lcd_write_data(0x00); } }

这段代码里最重要的几个命令:

  • 0x01(软件复位)和0x11(退出睡眠)是开屏前必须满足的时序条件。上电后屏驱默认处于睡眠态,不退出睡眠你发后面所有命令都没用。
  • 0x36的0x48是我这块模拟屏需要的一个扫描方向值。它的含义是:MY=0,MX=1,MV=1,BGR=1,表示从左到右、从上到下的扫描再加上RGB/BGR颜色顺序调整。如果你发现颜色反了,就是这个字节里的BGR位在起作用。
  • 0x3A设置像素格式,0x55等于RGB565(5位红、6位绿、5位蓝)。如果设成0x66就是RGB666,颜色空间和显存占用都不一样,不能乱配。
  • 清屏操作实际上就是不断往0x2C(写显存)命令后面灌像素数据,灌多少取决于窗口大小。这里的窗口函数用到了0x2A和0x2B,我会在下面补充说明。

4.3 窗口设置函数:理解屏幕的“画布裁剪”

void lcd_set_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { uint8_t addr[4]; addr[0] = x0 >> 8; addr[1] = x0 & 0xFF; addr[2] = x1 >> 8; addr[3] = x1 & 0xFF; lcd_write_cmd_data(0x2A, addr, 4); // 列地址范围 addr[0] = y0 >> 8; addr[1] = y0 & 0xFF; addr[2] = y1 >> 8; addr[3] = y1 & 0xFF; lcd_write_cmd_data(0x2B, addr, 4); // 行地址范围 lcd_write_cmd(0x2C); // 进入写显存模式 }

窗口机制可以理解成“画布裁剪”。系统默认的GRAM是一个完整的帧缓冲,窗口命令的作用是把后续写入的数据限定在一个矩形区域里。比如做UI时只需要更新数字区域,设置窗口后再写少量像素,MCU的SPI总线压力就能大幅降低,刷新率也跟着上来。

5. 移植调屏最容易翻车的五个瞬间

这一节讲几类我实际踩过、也帮别人排查过的高频问题,每一个都有明确的排查路径,而不是直接给你“改一个参数”的结论。

5.1 问题一:颜色整体反相,红色变蓝色

典型症状:屏幕上显示红色,肉眼看到的却是蓝色,绿色变成紫色。

根因:0x36命令里的BGR位设置错了。LCD的RGB条子分RGB排布和BGR排布两种,很多屏驱同时支持两种,靠MADCTL里第3位来切换。如果位值设反,颜色就整体互换。排查办法是先显示纯红、纯绿、纯蓝三个色块,看到哪两个对调了,就能确认是BGR方向问题。

我自己的排查经验是:不要用彩色照片测试,那样根本分不清是颜色偏还是反。用三原色块测,一眼定位。

5.2 问题二:画面偏上或偏左,像被裁了一刀

典型症状:图像整体不在屏幕中央,有的方向甚至有一圈黑边。

根因:屏驱内部的GRAM物理布局和LCD面板的可见区域并不完全重合。比如ST7789这颗芯片本身GRAM是240x320,但很多1.54寸面板实际只显示240x240区域,驱动数据写入时必须设置GRAM偏移,让可见区域从(0,0)开始。更麻烦的是,某些模块的FPC连接方式还会改变偏移方向。

排查办法:读模块规格书里关于“Display Area”和“GRAM Offset”的表格。一般厂商会把偏移量写成一个明确数值,比如“Column offset: 34, Row offset: 52”,直接在窗口设置时把坐标加上这个偏移即可。这里要特别提醒:网上很多ST7789驱动代码针对某一款1.54寸圆角屏写死了偏移量,你换一家供应商的屏幕时偏移可能就变了,所以自己一定要会验证。

5.3 问题三:白屏但背光亮,SPI数据发不进去

典型症状:背光正常亮,屏幕全白,MCU发了命令没反应。

根因:优先怀疑DC引脚(命令/数据选择)和CS片选时序。有些模块把DC引脚接到了固定电平上,导致屏幕永远认为自己收到的是数据而不是命令。还有一种情况是SPI的CPHA配置错,数据采样沿不对,屏幕上收到的字节全是错的。

排查顺序:先用逻辑分析仪抓SPI波形,看CS、DC、SCLK、MOSI四个信号是否和代码逻辑一致。没有逻辑分析仪的话,可以把DC引脚暂时固定在高电平或低电平,再发“清屏”命令,观察画面变化,能快速判断DC接线是否有问题。

5.4 问题四:画面闪烁、刷新时有撕裂感

典型症状:动态画面刷新时出现横向的割裂感,像两帧画面拼在一起。

根因:屏驱内部GRAM写入和面板扫描是异步的。当MCU正在往GRAM里写新一帧数据时,面板扫描刚好扫描到同一区域,就会把新旧数据各显示一部分,形成撕裂。屏驱一般会提供撕裂检测(TE)引脚,配合垂直同步可以解决,但很多低端模块没引出这个引脚。

如果是SPI接口的简单应用,更常见的做法是把刷新率降到肉眼可接受的范围,或者把刷新时机固定到一帧完整写完后再触发。我实测下来,对于240x320分辨率的SPI屏,如果刷新频率在30-60Hz之间,配合0x37(垂直滚动起始地址)做整帧一次性写入,撕裂感会小很多。

5.5 问题五:初始化代码一样,但不同批次屏幕显示效果不同

典型症状:同型号屏驱换了供应商,显示发白、发暗或者颜色偏。

根因:屏幕厂商为了匹配自家面板的玻璃特性,会在出厂时推荐一套“Gamma校正值”和“电压设置值”,这些值以寄存器命令形式存在初始化序列里。同一颗ILI9341芯片,A厂商面板和B厂商面板的Gamma寄存器参数往往不一样,网上抄来的初始化数组可能只适配某一家的面板。

排查办法:向模块供应商索要针对具体面板的初始化代码,尤其是那些以0xE0、0xE1开头的Gamma设置命令。不要迷信“ILI9341通用的初始化数组”,通用数组能点亮,但画质不一定是最佳状态。

6. 实际选型时的几条经验

6.1 不同场景适合的屏驱

选LCD屏驱,本质上是在分辨率、接口速度、价格、资料丰富度之间做权衡。根据我手里的项目经验,大概可以这样归类:

项目场景推荐屏驱理由
低成本小屏显示文字ST7735/ST7789单价低、资料多、SPI速度快
普通仪表盘、菜单UIILI9341240x320性价比高、并行接口可选
圆形表盘界面GC9A01专为圆形显示优化,边缘处理方便
彩色图片展示、3.5寸大屏ILI9488分辨率高、支持RGB666色彩
黑白低功耗显示SSD1306(OLED)对比度高、功耗低、直接支持I2C
工业级大尺寸屏带内置控制器的大屏驱(如RA8876类)自带显存管理和图层功能

需要提醒的是,很多小屏模块虽然“看起来一样”,但引脚定义可能完全不同。同一颗ST7735,有的模块引脚顺序是VCC-GND-CLK-DIN-DC-CS-RST-BL,有的会多一个MISO引脚。买之前一定要确认引脚排列,别拿另一块板的排线直接插,容易烧背光。

6.2 关于“便宜屏”的一个实在建议

市面上二三十块钱的小屏,驱动芯片可能是原厂正品,也可能是兼容IC。兼容IC的命令集大部分和原厂一致,但某些细节会有差异,比如ID读取命令返回值不同、初始化时序要求更严格。我的建议是:商业产品尽量选成熟方案,至少先验证同一型号的屏驱一致性;DIY项目则无所谓,反正便宜的坏了不心疼。

从我个人经验来看,如果项目对刷新率和可靠性要求不高,SPI接口的ILI9341方案最稳妥,资料密度高、踩坑的人多、可参考代码成熟;如果做的是追求外观的小尺寸产品,ST7789加1.54寸圆角屏的组合在视觉上确实更讨喜。

最后分享一个我自己保留的习惯:每调通一块新屏,就把完整的初始化数组、窗口偏移、MADCTL方向值、坑点备注整理到工程注释里,然后保存一份带日期的副本。因为同一型号的屏驱动芯片在不同批次上真的可能“性格”不同,这份记录能帮你下次遇到同型号屏幕时直接避坑。调屏这件事,说难不难,但细节决定成败,多掌握一个排查方向,就少熬一个晚上。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进