☰
STM32F103C8T6驱动ILI9341:2.8寸TFT LCD从点不亮到刷屏实战
2026/10/13 12:42:18 网站建设 项目流程

简介:本资源面向使用STM32F103C8T6最小系统板驱动2.8寸TFT液晶的嵌入式开发者与学习者,针对48脚芯片不具备FSMC功能这一限制,采用GPIO模拟方式实现ILI9341的16位并口显示,A端口0至15位作为数据总线,适合入门液晶驱动、练习时序控制与模块化硬件搭建的初中级工程师参考。压缩包共182个文件,约3.16MB,包含33个c源文件与37个h头文件构成驱动与STM32标准外设库主体,另有41个汇编启动文件、22个o编译产物、14个d依赖文件及uvproj、uvopt等Keil工程配置,还附带hex、axf、map等可直接烧录与调试的产物,目录结构完整便于二次开发。目前已有5092人学习下载,读者可获取一套可运行的ILI9341驱动代码与工程模板,理解模拟并口刷屏的时序实现,并在此基础上扩展图形显示与触屏功能(触屏输入暂未实现)。

1. 一块 2.8 寸屏点不亮,八成不是屏的问题

STM32F103C8T6 驱动 ILI9341 的 2.8 寸 TFT LCD,是嵌入式入门里最经典也最容易翻车的一组搭配。很多人拿到一块标着 2.8 寸、240×320 分辨率、带 XPT2046 触摸的模块,焊上排针、接好杜邦线、烧进例程,屏幕要么纯白、要么花屏、要么干脆一点反应没有。第一反应往往是“屏坏了”或者“买到了假货”,但实际排查下来,绝大多数问题出在供电、复位时序、FSMC 配置或者初始化命令序列上,屏本身反而是最不容易坏的那一环。

这篇笔记面向的是手上已经有这块最小系统板和这块屏、想把图形刷出来的开发者。我会把从硬件连线、并口时序、ILI9341 初始化、到刷屏和局部刷新验证的完整路径讲清楚,参数怎么设、坑在哪、失败时先看什么,都落到可复现的步骤上。看完你应该能自己判断:是接线错了、时序不对,还是初始化命令少发了一条。

2. 先搞清楚 ILI9341 到底怎么被“驱动”起来

2.1 8080 并口和 SPI 的选型差别

ILI9341 这颗驱动 IC 支持三种主机接口:8080 并口、SPI 三线/四线、以及 RGB 接口。2.8 寸模块上最常见的是 8080 并口和 SPI 两种引出方式。选哪种,直接决定你后面代码的写法和你 MCU 的引脚预算。

8080 并口分 8 位和 16 位两种数据宽度。16 位并口一次传一个像素的高低位,刷一屏 240×320 只需要 76800 次写操作,速度上明显占优,适合做动画或频繁刷新。代价是它要占用 16 根数据线加若干控制线,对 STM32F103C8T6 这种 48 脚封装来说,引脚会非常紧张,通常得靠 FSMC(可变静态存储控制器)来扛,否则软件模拟并口时序会慢到怀疑人生。

SPI 接口只需要 4 根线(SCK、MOSI、CS、DC,再加一根 RST 和 BLK),引脚压力小得多,接线也简单。代价是速度:SPI 一次只能传一位,刷一屏要传 240×320×16 个时钟位,即使跑到 18MHz,整屏刷新也在几十毫秒量级,做静态界面够用,做流畅动画就吃力。

我一般的判断是:如果只是显示文字、图标、简单界面,SPI 足够,接线省心;如果要做波形、视频帧、频繁局部刷新,优先上 FSMC 并口。下面两条路径我都会给到关键配置,你可以按自己的板子选。

2.2 供电和背光:最容易被忽略的第一道坎

ILI9341 模块的逻辑供电是 3.3V,但背光 LED 那一路经常是独立的。很多模块把背光 LED 的限流电阻做在板上,直接接 3.3V 就能亮;也有模块把背光引脚单独引出,需要你自己串电阻或者用三极管控制。如果你接的是 5V 逻辑,而模块没有电平转换,长期跑下来逻辑口容易受损,表现就是时好时坏。

背光不亮和屏幕不显示是两回事。背光不亮,你看到的是全黑;背光亮但没数据,你看到的是白屏或者灰屏。排查时先把这两个现象分开,能省掉一半时间。

提示:上电前用万用表量一下模块 VCC 和 GND 之间有没有短路,再量背光引脚对地电阻,确认限流电阻存在。这一步花两分钟,能避免烧掉整块屏。

2.3 用 SPI 跑通最小显示:接线与初始化

先给一条 SPI 路径的最小可跑通方案。以 STM32F103C8T6 的 SPI1 为例,引脚分配如下:

信号STM32 引脚说明
SCKPA5SPI1 时钟
MOSIPA7SPI1 数据输出
CSPA4片选,低有效
DCPA3数据/命令选择
RSTPA2复位,低有效
BLKPB0背光控制,可接 3.3V 常亮

初始化 SPI 的代码大致如下,用的是标准外设库风格,HAL 库把寄存器操作换成对应函数即可:

// SPI1 初始化,主机模式,8 位数据,模式 0 void LCD_SPI_Init(void) { GPIO_InitTypeDef gpio; SPI_InitTypeDef spi; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE); // SCK PA5 和 MOSI PA7 复用推挽 gpio.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7; gpio.GPIO_Mode = GPIO_Mode_AF_PP; gpio.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &gpio); // CS PA4、DC PA3、RST PA2 普通推挽输出 gpio.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_3 | GPIO_Pin_2; gpio.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOA, &gpio); SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Tx; // 只发不收 SPI_InitStructure.SPI_Mode = SPI_Mode_Master; SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; // 模式 0 SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4; // 72M/4=18M SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; SPI_Init(SPI1, &spi); SPI_Cmd(SPI1, ENABLE); }

这段代码里几个参数值得说清楚。SPI_BaudRatePrescaler_4把 72MHz 的 PCLK2 分频到 18MHz,这是 ILI9341 在 3.3V 下比较稳妥的上限,再往上跑有些批次会丢数据。SPI_CPOL_Low和SPI_CPHA_1Edge组合成模式 0,是 ILI9341 的默认采样方式,如果你接的是模式 3 的屏,这里要改成SPI_CPOL_High加SPI_CPHA_2Edge。SPI_Direction_1Line_Tx表示只发送不接收,省掉 MISO 引脚,因为写屏根本不需要读回数据。

写命令和写数据靠 DC 引脚区分,封装成两个函数:

// 写一个命令字节 void LCD_WriteCmd(uint8_t cmd) { LCD_DC_CLR(); // DC 拉低表示命令 LCD_CS_CLR(); SPI_WriteByte(cmd); LCD_CS_SET(); } // 写一个数据字节 void LCD_WriteData(uint8_t data) { LCD_DC_SET(); // DC 拉高表示数据 LCD_CS_CLR(); SPI_WriteByte(data); LCD_CS_SET(); }

SPI_WriteByte内部就是等 TXE 标志、写 DR、等 BSY 清零这三步。DC 的拉高拉低必须在 CS 有效之前完成,否则命令和数据会被屏当成同一种东西,表现就是初始化全乱。

2.4 ILI9341 初始化命令序列的关键几条

初始化序列网上一搜一大把,但真正决定能不能点亮的是前几条。下面这段是精简后的核心序列,省略了伽马校正等不影响点亮的项:

void LCD_Init(void) { LCD_RST_CLR(); Delay_ms(20); LCD_RST_SET(); Delay_ms(120); // 复位后必须等够 120ms LCD_WriteCmd(0x01); // 软复位 Delay_ms(120); LCD_WriteCmd(0x11); // 退出睡眠 Delay_ms(120); LCD_WriteCmd(0x36); // 内存访问控制 LCD_WriteData(0x48); // 横屏,BGR 顺序 LCD_WriteCmd(0x3A); // 像素格式 LCD_WriteData(0x55); // 16 位 RGB565 LCD_WriteCmd(0x29); // 开显示 }

0x11退出睡眠后那 120ms 不能省,很多白屏就是这里等得不够。0x36的 0x48 决定扫描方向和 RGB/BGR 顺序,如果你发现红色显示成蓝色,改这一位就行。0x3A的 0x55 是 16 位色,写 0x66 是 18 位,一般用 16 位省带宽。0x29开显示必须放在最后,前面配置没完成就开显示,屏会锁在异常状态。

3. 用 FSMC 并口把刷新速度拉起来

3.1 FSMC 映射到 ILI9341 的地址规划

STM32F103C8T6 的 FSMC 支持 NOR/PSRAM 模式,可以把 ILI9341 当成一个 16 位宽的 SRAM 来访问。关键是把 RS(也就是 DC)引脚接到某根地址线上,这样写命令和写数据就变成访问两个不同地址,不用再手动拉 DC。

常见接法是把 RS 接到 FSMC_A16,那么:

  • 写命令地址 = 基地址 + 0
  • 写数据地址 = 基地址 + (1 << 16) × 2

因为 FSMC 是 16 位宽,地址线每加 1 对应 2 字节,所以 A16 对应偏移是 0x20000。基地址选0x60000000(NE1 片选),于是命令地址0x60000000,数据地址0x60020000。

#define LCD_BASE ((uint32_t)0x60000000) #define LCD_CMD_ADDR (LCD_BASE) #define LCD_DATA_ADDR (LCD_BASE + 0x20000) #define LCD_WR_CMD(cmd) (*(__IO uint16_t *)LCD_CMD_ADDR = (cmd)) #define LCD_WR_DATA(dat) (*(__IO uint16_t *)LCD_DATA_ADDR = (dat))

这样写屏就是一条赋值语句,编译器直接生成 FSMC 总线写,速度比软件模拟快一个数量级。

3.2 FSMC 时序参数怎么算

FSMC 的读写时序由 BTCR 寄存器控制,核心是三个参数:地址建立时间 ADDSET、地址保持时间 ADDHLD、数据建立时间 DATAST。ILI9341 的 8080 时序要求是写脉冲宽度最小 66ns,读周期最小 355ns(读 ID 时)。

以 72MHz 的 HCLK 为例,一个时钟周期约 13.9ns。写时序取:

  • ADDSET = 1(约 14ns)
  • DATAST = 5(约 70ns,满足 66ns 最小写脉冲)

读时序要慢得多,取:

  • ADDSET = 15
  • DATAST = 60

配置代码:

// FSMC NOR/SRAM 模式配置,NE1 片选 FSMC_NORSRAMInitTypeDef fsmc; FSMC_NORSRAMTimingInitTypeDef timing; timing.FSMC_AddressSetupTime = 1; timing.FSMC_AddressHoldTime = 0; timing.FSMC_DataSetupTime = 5; timing.FSMC_BusTurnAroundDuration = 0; timing.FSMC_CLKDivision = 0; timing.FSMC_DataLatency = 0; timing.FSMC_AccessMode = FSMC_AccessMode_B; fsmc.FSMC_Bank = FSMC_Bank1_NORSRAM1; fsmc.FSMC_DataWidth = FSMC_DataWidth_16b; fsmc.FSMC_MemoryType = FSMC_MemoryType_SRAM; fsmc.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b; fsmc.FSMC_WriteOperation = FSMC_WriteOperation_Enable; fsmc.FSMC_ExtendedMode = FSMC_ExtendedMode_Disable; fsmc.FSMC_ReadWriteTimingStruct = &timing; FSMC_NORSRAMInit(&fsmc); FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM1, ENABLE);

FSMC_AccessMode_B表示 SRAM 模式,读写用同一套时序。如果你要读 ID 或者读显存,得开FSMC_ExtendedMode_Enable,给读操作单独配一套更慢的时序,否则读回来的数据是错的。

3.3 刷一屏纯色验证并口通路

并口配好后,用刷纯色做第一次验证:

void LCD_FillColor(uint16_t color) { uint32_t i; LCD_SetWindow(0, 0, 239, 319); // 设置全屏窗口 LCD_WR_CMD(0x2C); // 写显存命令 for (i = 0; i < 240 * 320; i++) { LCD_WR_DATA(color); } }

0x2C是写显存命令,发完之后连续写数据就会自动按窗口递增地址。LCD_SetWindow内部发的是0x2A(列地址)和0x2B(页地址),各带四个参数:起始高、起始低、结束高、结束低。窗口设错,颜色会只刷一部分或者刷到屏幕外。

如果刷出来是纯色但颜色不对,先查0x36的 BGR 位;如果是条纹或者雪花,查 FSMC 时序和接线;如果是全白,回到初始化序列,重点看0x11后的延时和0x29的位置。

4. 避坑与排查:点不亮时按这个顺序查

4.1 现象:背光亮但屏幕全白

原因通常是初始化序列没跑完,或者复位时序不对。ILI9341 上电后内部状态机需要完整的复位脉冲,RST 低电平至少要维持 10us,很多代码只拉低一两个指令周期就拉高,屏根本没复位。

解决:把 RST 拉低后延时 20ms 再拉高,拉高后再等 120ms 再发命令。这个延时宁长勿短,屏不会因为你多等而坏。

4.2 现象:SPI 模式下花屏、颜色错位

原因多半是 SPI 模式选错。ILI9341 默认模式 0,但有些模块因为走线或者上拉电阻,实际工作在模式 3。表现是数据错位、颜色块乱跳。

解决:把 CPOL 和 CPHA 换成模式 3 试一次。如果换完正常,说明模块的采样边沿和你原来的配置反了。另外检查 SPI 分频,18MHz 跑不稳就降到 9MHz 验证,确认是速度问题还是模式问题。

4.3 现象:FSMC 并口刷屏有随机噪点

原因是 FSMC 写时序太快,数据还没稳定就被屏采样。尤其是杜邦线连接、线长超过 10cm 的时候,信号完整性会明显变差。

解决:把 DATAST 从 5 加到 8 或 10,ADDSET 从 1 加到 2。如果噪点消失,说明就是时序余量不够。长期方案是缩短接线、加地线隔离,或者把屏直接插在转接板上。

4.4 现象:显示内容整体偏移或镜像

原因是0x36内存访问控制的扫描方向位设错。这一字节的 bit7 控制 MY、bit6 控制 MX、bit5 控制 MV、bit3 控制 BGR。横屏和竖屏切换就是改这几位。

解决:竖屏常用 0x48 或 0x08,横屏常用 0x28 或 0xE8,具体取决于你的排线方向和想要的坐标系。改完重新刷一屏,看原点在哪个角,再决定要不要翻转。

4.5 现象:局部刷新时旧内容残留

原因是没设窗口就直接写数据,或者窗口设了但没重新发0x2C。ILI9341 的显存地址在每次0x2C后从窗口起点开始,如果你连续写两次局部区域但中间没重设窗口,第二次会从上次结束的位置继续。

解决:每次局部刷新前都完整走一遍0x2A、0x2B、0x2C,不要图省事复用窗口。这个开销很小,但能避免大部分残影问题。

5. 把刷新率再压一压:局部刷新和 DMA 的配合

前面刷一屏纯色,SPI 路径大概要几十毫秒,FSMC 路径能压到几毫秒。但实际界面里你不需要每次都刷全屏,状态栏、按钮、数值这些区域是分开变的。把全屏刷新改成局部刷新,是提升响应最直接的手段。

局部刷新的核心是只对变化区域设窗口。比如一个 80×24 的数值显示区,每次只刷这 1920 个像素,SPI 下也就一两毫秒。做法是把要刷的区域坐标传进LCD_SetWindow,然后只写这块区域的数据。

// 只刷新 (x, y) 起、宽 w 高 h 的区域 void LCD_UpdateArea(uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint16_t *buf) { uint32_t i, n = (uint32_t)w * h; LCD_SetWindow(x, y, x + w - 1, y + h - 1); LCD_WR_CMD(0x2C); for (i = 0; i < n; i++) { LCD_WR_DATA(buf[i]); } }

buf是你在内存里维护的一块像素缓冲,界面逻辑只改这块缓冲,改完调一次LCD_UpdateArea推过去。这样界面逻辑和刷屏解耦,也方便后面接 DMA。

FSMC 路径下,如果 MCU 支持 DMA 到 FSMC 的通道,可以把buf直接交给 DMA,CPU 去处理下一帧逻辑。STM32F103C8T6 的 DMA 通道和 FSMC 的配合需要查具体通道映射,不是所有通道都能触发 FSMC 写。一个稳妥的验证方法是先用 CPU 轮询写,测出单区域耗时,再换 DMA 对比,看提升是否值得。

最后说一个我自己的习惯:每次改完初始化序列或者时序参数,先刷三屏纯色——红、绿、蓝,再刷一屏棋盘格。纯色验证颜色通道和窗口,棋盘格验证地址递增和边界。这四屏过了,再往上叠文字和图形,出问题也能快速定位是底层还是上层。这个习惯帮我省过很多次“以为是屏坏了”的返工。

希望帮到你。

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

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

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

立即咨询