资讯动态

STM32驱动TFT LCD 3.5寸屏:从SPI时序到排错全解析

发布时间:2026/9/2 0:31:41 来源:尧图企业网站定制
简介这是一套基于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时钟→ PA5SPI1_SCKMOSI主出从入→ PA7SPI1_MOSICS片选→ 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的引脚基本都是FT5V容忍引脚但为了稳妥还是按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官方例程里用的基本都是模式0CPOL0CPHA0即空闲时钟为低电平第一个边沿采样数据。如果你用的是模式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×2307200字节也就是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。我个人更推荐的做法是一开始就用LittlevGLLVGL这类开源图形库。LVGL底层有现成的显示驱动接口把flush函数对接好上面就能跑控件、跑动画开发效率比裸写画点函数高太多。但LVGL对RAM有一定要求F103C8这种只有20KB RAM的芯片跑起来很吃力至少得F407或F429起。如果是F103又不想上GUI那就老老实实裸机驱动把基础画图和字体显示写扎实做几个固定界面的项目也完全够用。说实话TFT LCD驱动算不上什么高深技术但它是一个特别典型的“会者不难难者不会”的坑。你花一下午把它调通了后面再遇到任何SPI接口的屏幕哪怕是2.4寸、4.3寸换一套初始化序列就能秒上手。反过来如果每次都是复制粘贴改个引脚就收工下次遇到问题依然两眼一抹黑。我把这套东西完整写下来就是想让你少走一点弯路。第一步先确认控制器型号第二步核对SPI模式第三步模块化移植第四步再谈优化——按这个顺序来屏幕点亮只是时间问题。如果你在调的时候卡住了欢迎把具体现象和控制器型号记下来对照上面的排查表一项项过多半能找到答案。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价