STM32驱动TFT LCD 3.5寸屏:从SPI时序到排错全解析
2026/9/2 0:31:23 网站建设 项目流程

简介:这是一套基于STM32的3.5寸TFT LCD(分辨率320×240)驱动程序与演示例程,主要面向使用GLCD库进行嵌入式显示开发的工程师、学生及电子爱好者。例程基于MDK4.0及以上版本,工程文件路径明确,下载调试工具为ULINK,使用时将配套LCD模块插至板子CN4接口,烧录HEX后即可看到屏幕演示画面,适合快速验证显示功能并深入学习GLCD初始化、绘图与刷屏流程。资源包共219个文件,大小约4.19MB,包含c/h源文件、o/s编译中间文件、uvproj工程文件、hex烧录文件、map/sct链接脚本以及txt说明等,目录结构完整,便于定位与分析。目前已有2150人浏览学习,对于刚接触STM32液晶驱动或需要参考官方例程排查问题的开发者,这是一份可直接借鉴的实用资料。 做嵌入式开发的朋友,手头最不缺的大概就是各种屏幕了。而TFT LCD 3.5寸屏,绝对算得上出镜率最高的一款:尺寸不大不小,分辨率从480x320到800x480都有,既能做手持仪表,又能做桌面小盒子的人机交互界面。不过,屏幕买回来只是第一步,真正让人头大的是驱动——尤其是你手里同时握着STM32开发板和一块裸屏,想让它跑起来的时候。

我这些年帮不少人调过这类屏,也见过太多人卡在同一道坎上:例程下载了,引脚也对上了,编译也没报错,但屏幕就是死活不亮。所以今天干脆把这套东西完整梳理一遍,基于STM32平台把TFT LCD 3.5寸屏的驱动思路、代码结构和排错方法全讲透,顺便把一些常规文档里不会写的经验也抖出来。

1. 先说清楚驱动这块屏到底难在哪

1.1 市面上3.5寸屏的控制器差异很大

很多初学者容易忽略一个关键事实:TFT LCD 3.5寸只是一个物理尺寸规格,它背后的驱动IC并不是统一的。同一家淘宝店卖的3.5寸屏,可能这批次用的是ILI9488,下批次就是ST7796,再换一家可能又是HX8357D。而这些IC的初始化寄存器序列是不同的,写错型号直接白屏或者花屏。

判断控制器型号通常有几种途径:看商家商品详情页、看屏幕背面丝印、或者直接问客服。最稳妥的办法是买屏的时候让商家同时提供对应控制器的例程。如果实在拿不到,就靠后面提到的初始化序列试探法来判断。反正我自己踩坑的经验是,手里这批320x480的屏用ILI9488驱动最多,其次是ST7796,两者命令集大部分兼容,但细节上有差异。

1.2 为什么例程必须自己动手移植

网上的例程千千万,但几乎没有一个能直接烧进去就用。原因很简单——引脚分配不一样。同一个SPI接口,有人把CS接PA4,有人接PB12;有人用硬件SPI,有人用GPIO模拟SPI。再加上不同的STM32型号、不同的HAL库版本,直接拿来编译不出错才稀奇。

所以驱动屏这件事的核心能力,不是背代码,而是能看懂时序,能对照数据手册改引脚,能把一段MCU型号A的代码搬到MCU型号B上。说白了,你得掌握移植的方法,而不是复制粘贴的结果。这也是为什么我强烈建议你在做任何屏幕项目前,先把SPI通信的底层摸一遍,哪怕只是翻翻参考手册里的时序图,后面调屏能少走一半弯路。

2. 驱动方案选型与硬件连接

2.1 到底用SPI还是并口

3.5寸屏的分辨率如果是320x480,驱动IC大多支持SPI、8位并口、16位并口甚至RGB接口。很多人上来就纠结:SPI那么慢,刷个全屏要半天,是不是必须上并口?

我的建议是,先别急,根据应用场景来取舍。以ILI9488为例,如果是标准SPI 4线模式,理论上时钟跑到40MHz时可以勉强刷个短视频,但实际上因为SPI传输还要穿插命令字节、CS片选切换这些开销,全屏填充的速度也就十几帧每秒。如果只是显示静态界面、仪表盘、菜单,这个速度完全够用;但如果要做动画、刷视频、跑GUI特效,SPI确实力不从心,那种场景建议上FSMC或RGB接口。

FSMC并口方式的优势在于,MCU把屏幕映射成一段内存地址,往这个地址写数据就跟往内存写一样,刷屏速度能快一个数量级。缺点是需要占用大量引脚,板子连线也麻烦。从稳妥和通用角度看,我先以SPI接口方案为例展开,这也是网上绝大多数例程采用的方式。

2.2 引脚分配和硬件连接

以最常见的STM32F103C8T6(蓝色Pill板)为例,我一般这样分配引脚:

  • SCK(时钟)→ PA5(SPI1_SCK)
  • MOSI(主出从入)→ PA7(SPI1_MOSI)
  • CS(片选)→ PA4(软件控制)
  • DC(数据/命令选择)→ PA6(软件控制)
  • RST(复位)→ PA3(软件控制)
  • BLK(背光)→ PA2(软件控制,或直接接3.3V)

这里有个细节:很多屏幕模块的DC引脚也叫RS或A0,指的都是同一根线,接线前看一眼丝印确认。另外,3.5寸屏的背光LED电流不小,尽量不要直接从STM32的GPIO口取电,标准做法是GPIO控制一个MOS管或三极管,再由外部电源给背光供电。如果你买的模块已经带了背光驱动电路,那BLK引脚可以直接接GPIO,但判断不了的时候,先量一下模块原理图,别硬猜。

我自己供电的习惯是:屏幕的VCC接5V或3.3V取决于模块说明,逻辑电平统一3.3V,千万别用5V去怼GPIO。STM32的引脚基本都是FT(5V容忍)引脚,但为了稳妥,还是按3.3V逻辑来。连线这块看着简单,恰恰是翻车重灾区。

3. 核心代码拆解:从初始化到点亮一个像素

3.1 STM32硬件SPI配置与底层函数

如果只是点亮屏幕,其实模拟SPI的代码更通用、更好理解,随便换引脚都行。但如果追求刷新速度,建议还是用硬件SPI。以STM32的HAL库为例,初始化代码可以这样写:

SPI_HandleTypeDef hspi1; void SPI1_Init(void) { hspi1.Instance = SPI1; hspi1.Init.Mode = SPI_MODE_MASTER; hspi1.Init.Direction = SPI_DIRECTION_2LINES; hspi1.Init.DataSize = SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; hspi1.Init.NSS = SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4; hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; HAL_SPI_Init(&hspi1); }

这里有几个点需要展开讲一下。

时钟极性和相位是SPI最容易出错的地方。现在常见的TFT LCD驱动IC,比如ILI9488、ST7789、ST7796,官方例程里用的基本都是模式0(CPOL=0,CPHA=0),即空闲时钟为低电平,第一个边沿采样数据。如果你用的是模式1、2、3初始化,屏幕大概率不出图像或者干脆白屏。有个笨办法,就是把四种模式都试一遍,总有一种能点亮,但最好还是看数据手册的时序图确认。

波特率预分频器也值得说道。F103的SPI1挂在APB2总线上,时钟是72MHz,预分频4就是18MHz。理论上ILI9488支持到更高的SPI时钟,但实测下来,杜邦线连接、没做阻抗匹配的状态下,超过20MHz就容易出现花屏或色彩错乱。所以我建议先用预分频8甚至16把屏幕点亮,稳定之后再试着提高频率。跑通了功能再优化速度,这个顺序永远不要颠倒。

3.2 写命令和写数据的核心函数

屏幕驱动的本质就是两件事:发送命令、发送数据。命令和数据通过DC引脚来区分:DC拉低时发送的是命令,DC拉高时发送的是数据。这个函数写好了,后面所有操作都建立在这两个函数之上:

void LCD_WriteCmd(uint8_t cmd) { LCD_CS_LOW(); LCD_DC_LOW(); HAL_SPI_Transmit(&hspi1, &cmd, 1, 10); LCD_CS_HIGH(); } void LCD_WriteData(uint8_t data) { LCD_CS_LOW(); LCD_DC_HIGH(); HAL_SPI_Transmit(&hspi1, &data, 1, 10); LCD_CS_HIGH(); }

还有一个高频操作是连续发送数据,比如填充一片颜色区域。如果每个字节都调用一次HAL_SPI_Transmit,效率会低得离谱。更好的做法是直接把整个数据缓冲区一次性发出去:

void LCD_WriteDataBuffer(uint8_t *buf, uint32_t len) { LCD_CS_LOW(); LCD_DC_HIGH(); HAL_SPI_Transmit(&hspi1, buf, len, 100); LCD_CS_HIGH(); }

这里补充一个经验:CS引脚在整个传输过程中应该保持低电平,不要在每个字节之间都拉高再拉低。很多初学者写循环,每次发送一个字节就切换一次CS,屏幕虽然能点亮,但速度会慢很多,而且某些严格的屏还会出现兼容性问题。正确的做法是,一次完整操作(比如一次画矩形填充)期间CS一直拉低,操作结束再拉高。

3.3 初始化序列:屏幕的灵魂

屏幕点亮的关键全在初始化序列。这串寄存器配置代码才真正体现“例程”的价值。网上流传的初始化代码五花八门,有的能亮,有的不能亮,根源就在于初始化序列是否完整、顺序是否正确。

以ILI9488为例,一小段最基本的初始化流程(简化版)大致是这样:

LCD_WriteCmd(0x01); // 软件复位 HAL_Delay(120); LCD_WriteCmd(0x11); // 退出睡眠模式 HAL_Delay(120); LCD_WriteCmd(0x36); // 内存访问控制(扫描方向) LCD_WriteData(0x48); // 根据屏幕接线方向调整 LCD_WriteCmd(0x3A); // 像素格式 LCD_WriteData(0x66); // 18位颜色,RGB666 LCD_WriteCmd(0x29); // 打开显示

这里最让人迷惑的是0x36寄存器,也就是MADCTL。它控制显示扫描方向,值不同会导致图像上下颠倒、左右镜像,甚至颜色分量错位。0x00是默认左上角到右下角,0x80是行方向反转,0x40是列方向反转,0x20是行列交换。具体选哪个值,完全取决于你的屏幕排线方向和安装方向,改一改这个值就能解决90%的显示方向问题。

还有一个容易踩坑的点是0x3A像素格式设置。ILI9488是RGB666的屏,0x66表示18位颜色,但很多人在代码里用的是RGB565的0x55格式,然后发送16位颜色数据。这样也能显示,但色彩深度不同,最高位会被截断,显示效果偏色或者有噪点。我一般在3.5寸屏上直接写0x66,然后发送数据时每个像素用3个字节(R、G、B各6位),这样色彩最准确。

完整的初始化序列远比上面这段复杂,会包含电源设置、伽马校正、显示时序调整等。这些寄存器值和顺序直接抄官方例程或靠谱商家提供的即可,没必要每个都去深究——但你要明白一点:初始化序列不是随便抄抄就完事,它跟屏幕型号强相关,换一块屏就得换一套序列。

3.4 画点、矩形填充和显存计算

有了初始化,接下来就是最基础的画点了。TFT LCD的操作思路是:先设置显示窗口(即要操作的像素区域),然后往窗口内连续写像素数据。以ILI9488为例,设置窗口的代码如下:

void LCD_SetWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { LCD_WriteCmd(0x2A); // 列地址设置 LCD_WriteData(x0 >> 8); LCD_WriteData(x0 & 0xFF); LCD_WriteData(x1 >> 8); LCD_WriteData(x1 & 0xFF); LCD_WriteCmd(0x2B); // 行地址设置 LCD_WriteData(y0 >> 8); LCD_WriteData(y0 & 0xFF); LCD_WriteData(y1 >> 8); LCD_WriteData(y1 & 0xFF); LCD_WriteCmd(0x2C); // 写内存开始 }

然后填充颜色,如果是RGB666的18位色,每个像素是3字节:

void LCD_FillRect(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint8_t r, uint8_t g, uint8_t b) { uint32_t len = (uint32_t)(x1 - x0 + 1) * (y1 - y0 + 1); LCD_SetWindow(x0, y0, x1, y1); LCD_CS_LOW(); LCD_DC_HIGH(); for (uint32_t i = 0; i < len; i++) { uint8_t color[3] = {r, g, b}; HAL_SPI_Transmit(&hspi1, color, 3, 10); } LCD_CS_HIGH(); }

这里必须提醒一个隐蔽的问题:如果一次性填充大量像素,用上面这种逐像素发送3字节的方式,速度极慢。刷个320x480的全屏,可能要好几秒钟。优化方案是构建一个足够大的缓冲区,把颜色数据一次性塞进去,再通过DMA或大块传输发送。

我之前算过一笔账:320x480的分辨率,如果每个像素按RGB565的2字节计算,全屏数据量是320×480×2=307200字节,也就是300KB。而STM32F103C8T6的SRAM只有20KB,根本塞不下全屏缓冲区。所以对于小内存MCU,要么分块填充(比如每次刷一行或几行),要么改用3字节发送+加速优化,没有别的捷径。

4. 实操中常见的坑与排查技巧

4.1 白屏问题:先查硬件,再查初始化

白屏是TFT LCD调试中最常见的问题,几乎每个人都会遇到一次。我按排查顺序整理了一张清单:

现象可能原因排查方法
白屏且屏幕无任何反应背光没亮或背光电路故障确认BLK引脚电平,用万用表量背光LED供电
白屏但有背光RST复位信号不对确认RST引脚初始化后拉高,且初始化序列前有足够延时
白屏但MCU程序在跑SPI模式不对确认CPOL/CPHA是否设成模式0
白屏且第一次上电偶尔坏电源供电不足3.5寸屏瞬间电流较大,用示波器看VCC是否跌落

按这个顺序排查,基本能覆盖绝大多数白屏情况。尤其是供电问题,很多人换一块屏幕就白屏,结果发现是电源模块输出电流不够,屏幕一启动背光就把电压拉垮了。

4.2 花屏和偏色:多和时序与格式有关

花屏的表现形式很多:有的是满屏彩色噪点,有的是图像错位重影,有的是颜色明显偏紫或偏绿。

噪点类花屏,八成是SPI时钟太快或者线路干扰。杜邦线一长,信号的反射和串扰就明显了,我实测过,超过20cm的杜邦线在SPI时钟18MHz时就开始出现间歇性花屏。解决办法:降低SPI分频系数,或者缩短导线,最好用排线连接。如果非要高速,就得考虑PCB板设计了。

图像错位重影,多半是像素格式设置和实际发送的数据宽度不匹配。比如IC是RGB666,你设置0x55用16位色,又用18位打包发送,数据就会错位。解决方法是统一格式:要么寄存器选0x66,每像素发3字节;要么寄存器选0x55,每像素发2字节,然后自己转换。

颜色偏绿偏紫,一般是RGB的字节顺序反了。ILI9488的显示数据写入顺序是R、G、B,但有些派生的ST7796屏可能实际是B、G、R。遇到这种情况,最简单的处理办法是修改0x36寄存器的BGR位(bit 3),或者直接在颜色转换时交换R和B分量。这两个方案我都试过,改寄存器更省事,不用动上层代码。

4.3 显示方向不对:一个寄存器搞定

屏幕装进外壳后发现上下颠倒或者左右镜像,是特别常见的事。很多人遇到这个问题就开始改画点函数,其实完全没必要。

回到前面提到的MADCTL寄存器(0x36),它有一个完整的映射表:

bit7 MYbit6 MXbit5 MV效果
000默认方向
100上下翻转
010左右翻转
110旋转180度
001X/Y轴互换

确定安装方向后,根据这个表格挑一个组合值就行,不用改任何其它代码。这个经验在处理各种屏幕安装角度时特别管用。

4.4 关于触摸功能:别和显示驱动混在一起

很多3.5寸屏模块是带触摸的,常见的是XPT2046电阻触摸芯片,通过SPI接口独立通信。一个特别容易犯的错是:把触摸芯片的片选和屏幕驱动的片选接到同一个引脚上。这两个设备虽然都叫SPI,但物理上是两个独立的从机,片选必须分开。如果共用一个CS,触摸和显示的数据会互相串扰,屏幕闪、触摸乱跳都是这么来的。

触摸部分的驱动思路和显示完全不同:它不需要初始化序列,只要不断读取坐标数据并做滤波处理。所以无论代码结构还是调试思路,都应该把触摸驱动和显示驱动分开,各自独立成文件。

5. 移植例程的通用方法论

5.1 拿到例程先做什么

每次从网上Down一个LCD例程,我的习惯是先不动代码,先做三个动作:打开原理图确认引脚、打开底层驱动函数确认SPI模式、打开初始化序列确认屏幕控制器型号。这三个信息对齐了,再谈编译和烧录。

移植的时候最省事的方法是分层。把代码分成三层:最底层是硬件抽象(SPI传输、GPIO控制),中间层是屏幕驱动的命令封装(写命令、写数据、设置窗口),最上层才是画点、画线、显示图片、显示文字这些应用函数。移植到新平台时,只需要改最底层,中间层和上层基本不用动。很多人移植失败,就是所有代码揉成一团,改起来牵一发动全身。

5.2 如何快速判断屏幕的控制IC型号

有些商家不提供型号,或者屏幕是二手拆机件,完全没资料。这时候可以靠初始化序列试探法:网上找ILI9488、ST7796、HX8357D等几份常见初始化代码,逐个试。不用全屏显示,只需要初始化完填充一个纯色,能正确显示纯色的就是对应型号。

这个方法听起来土,但非常有效。我帮朋友调试的时候,就碰到过一张屏幕印着ILI9341的丝印,实际内部却是ST7789V——老库存屏在这个圈子里并不罕见。所以丝印只能参考,点亮才是硬道理。

5.3 从点亮到项目落地的一点建议

最后再聊点实际的。如果你只是调试屏幕,跑个例程,看到彩条或者图片就收工,那其实还差得远。真正把一个屏幕驱动用到项目里,至少要做到三件事:第一,把屏幕驱动封装成模块,跟业务逻辑解耦;第二,建立一套简单的字体显示和图标显示机制,别每次都临时画像素;第三,考虑刷新机制,静态界面和动态数据用不同的刷新策略,避免整屏刷新浪费CPU。

我个人更推荐的做法是,一开始就用LittlevGL(LVGL)这类开源图形库。LVGL底层有现成的显示驱动接口,把flush函数对接好,上面就能跑控件、跑动画,开发效率比裸写画点函数高太多。但LVGL对RAM有一定要求,F103C8这种只有20KB RAM的芯片跑起来很吃力,至少得F407或F429起。如果是F103又不想上GUI,那就老老实实裸机驱动,把基础画图和字体显示写扎实,做几个固定界面的项目也完全够用。

说实话,TFT LCD驱动算不上什么高深技术,但它是一个特别典型的“会者不难,难者不会”的坑。你花一下午把它调通了,后面再遇到任何SPI接口的屏幕,哪怕是2.4寸、4.3寸,换一套初始化序列就能秒上手。反过来,如果每次都是复制粘贴,改个引脚就收工,下次遇到问题依然两眼一抹黑。

我把这套东西完整写下来,就是想让你少走一点弯路。第一步先确认控制器型号,第二步核对SPI模式,第三步模块化移植,第四步再谈优化——按这个顺序来,屏幕点亮只是时间问题。如果你在调的时候卡住了,欢迎把具体现象和控制器型号记下来,对照上面的排查表一项项过,多半能找到答案。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询