从零开始玩转 ILI9341:手把手教你点亮一块 TFT 彩屏
你有没有试过,手里的 STM32 或 ESP32 板子已经能跑 FreeRTOS、连上 Wi-Fi、读传感器数据了,却唯独“说不出话”——没有屏幕显示?
这时候,加一块TFT-LCD 屏幕,立刻让项目“活”起来。而其中最经典、资料最多、最适合入门的驱动芯片,非ILI9341莫属。
这块小小的 IC,藏在很多 2.4 英寸到 3.5 英寸的彩色屏幕上,支持 320×240 分辨率、1670 万色显示,还能通过 SPI 接口用 5 根线就驱动起来。对初学者友好得不像话。
今天我们就抛开那些晦涩术语,不堆砌参数表,带你一步步搞懂:
如何真正把一块 ILI9341 驱动的屏幕点亮,并为后续图形界面打下基础。
为什么是 ILI9341?它到底强在哪?
先别急着写代码。我们得明白——为什么这么多年过去,大家还在用这块“老将”?
因为它做到了三个字:稳、省、快。
- 稳:初始化序列成熟,各大平台(Arduino、STM32、ESP-IDF)都有现成可用的驱动;
- 省:SPI 模式下仅需 SCK、MOSI、CS、DC 四根信号线 + 电源,引脚紧张也不怕;
- 快:支持最高 10~15MHz 的 SPI 速率,刷一屏图片也就几十毫秒。
更重要的是,它的生态太完善了。
Adafruit GFX 库原生支持它,LVGL 官方示例直接拿它做演示,GitHub 上随便搜 “ILI9341 driver”,跳出几百个开源项目任你选。
换句话说:别人踩过的坑,你基本不用再踩一遍。
它是怎么工作的?一个“翻译官”的角色
你可以把 ILI9341 想象成 MCU 和 LCD 面板之间的“翻译官”。
MCU 只会说:“我要在第 (x,y) 点画个红色。”
但 LCD 面板需要的是精确的时序信号:行同步、场同步、像素时钟……这些复杂逻辑全由 ILI9341 内部处理。
它主要干四件事:
- 听命令:比如 “我要开始写图像了”(命令
0x2C); - 收数据:接着传过来的就是一个个像素的颜色值;
- 存进显存:内部有 320×240×2 = 176KB 的 GRAM 缓冲区暂存画面;
- 自动刷新:只要开启显示,它就会周而复始地从 GRAM 读取数据,生成驱动波形点亮屏幕。
整个过程不需要 MCU 持续干预,非常省心。
📌 小知识:GRAM 是什么?
就像电脑显卡的显存一样,它是专用于存放当前要显示内容的一块内存。ILI9341 自带这块存储,所以主控只需要“喂”一次数据,剩下的交给它自己扫屏就行。
通信接口怎么选?SPI 还是并行?
ILI9341 支持多种接口,但对我们大多数人来说,答案很明确:用 SPI,尤其是四线模式。
| 接口类型 | 所需引脚数 | 速度 | 适合场景 |
|---|---|---|---|
| SPI 四线 | 4~5 | 中高 | 多数 MCU,资源紧张首选 |
| 8080 并行(8位) | ~10+ | 高 | 性能要求高,引脚富余 |
| RGB 接口 | 更多 | 极高 | 视频类应用 |
对于 STM32F1/F4、ESP32、树莓派 Pico 这类常见开发板,SPI 是最优解。即使没有硬件 SPI,也能软件模拟(bit-bang),兼容性极强。
而且你会发现,市面上绝大多数“插即用”的 ILI9341 模组都是走 SPI 的,背面标着:SCL、SDA、CS、RST、DC、LED、VCC、GND—— 其实这里的 SDA 就是 MOSI,SCL 是 SCK。
关键信号线详解:每一根都不可忽视
虽然只用了几根线,但每一条都有它的使命:
| 引脚名 | 功能说明 |
|---|---|
| SCK | SPI 时钟,由 MCU 输出,决定传输速率 |
| MOSI | 主发从收,传输命令和数据 |
| CS | 片选,低电平有效,每次通信前拉低 |
| DC | 数据/命令选择:DC=0发命令,DC=1发数据 |
| RST | 复位引脚,可硬件复位或软复位 |
| BL / LED | 背光控制,有些模块需要单独供电或 PWM 调光 |
⚠️ 常见翻车点:
- 忘接 RST 或悬空 → 屏幕无法正常启动
- DC 和 CS 接反 → 命令当数据处理,初始化失败
- BL 未供电 → 屏幕黑屏但其实已工作(用手电照能看到 faint 图像)
建议所有控制引脚(CS、DC、RST)加上 10kΩ 上拉电阻,防止干扰导致误触发。
SPI 协议细节:你以为只是发字节?其实有讲究
很多人以为 SPI 就是“发几个字节”,但在 ILI9341 上,必须严格按照 Mode 0:
即 CPOL=0(空闲时 SCK 为低),CPHA=0(上升沿采样)。
如果你用的是 HAL 库或者 CubeMX,配置如下即可:
hspi->Instance = SPI2; hspi->Init.Mode = SPI_MODE_MASTER; hspi->Init.CLKPolarity = SPI_POLARITY_LOW; hspi->Init.CLKPhase = SPI_PHASE_1EDGE; hspi->Init.NSS = SPI_NSS_SOFT; // 使用软件控制 CS另外注意一点:每个操作都要先拉低 CS,结束后拉高,形成独立事务。
为什么不能一直拉低?因为某些命令需要在 CS 抬起后才生效,否则会被当作连续数据吞掉。
最关键一步:初始化序列,顺序不能乱!
这是整个驱动中最容易出问题的部分。
ILI9341 上电后处于未知状态,必须按特定顺序写入一系列寄存器值,才能进入正常工作模式。
这个过程就像给一台旧相机“调焦+上弦”,少一步都不行。
下面这段初始化代码,是我调试过数十块不同厂商模组后提炼出的稳定版本,适用于大多数国产 ILI9341 屏:
void ili9341_init(void) { HAL_Delay(120); // 上电延迟,确保电源稳定 // === 电源控制配置 === lcd_write_command(0xCB); lcd_write_data(0x39); lcd_write_data(0x2C); lcd_write_data(0x00); lcd_write_data(0x34); lcd_write_data(0x02); lcd_write_command(0xCF); lcd_write_data(0x00); lcd_write_data(0xC1); lcd_write_data(0x30); lcd_write_command(0xE8); lcd_write_data(0x85); lcd_write_data(0x00); lcd_write_data(0x78); lcd_write_command(0xEA); lcd_write_data(0x00); lcd_write_data(0x00); lcd_write_command(0xED); lcd_write_data(0x64); lcd_write_data(0x03); lcd_write_data(0x12); lcd_write_data(0x81); lcd_write_command(0xF7); lcd_write_data(0x20); // === 电源参数设置 === lcd_write_command(0xC0); // Power Control 1 lcd_write_data(0x23); lcd_write_command(0xC1); // Power Control 2 lcd_write_data(0x10); lcd_write_command(0xC5); // VCM Control lcd_write_data(0x3E); lcd_write_data(0x28); lcd_write_command(0xC7); // VCM Offset lcd_write_data(0x86); // === 显示方向与格式 === lcd_write_command(0x36); // Memory Access Control lcd_write_data(0x48); // 横屏,RGB顺序,可根据需求修改 lcd_write_command(0x3A); // Pixel Format lcd_write_data(0x55); // 16-bit/pixel, RGB565 format // === 帧率与时序 === lcd_write_command(0xB1); lcd_write_data(0x00); lcd_write_data(0x18); lcd_write_command(0xB6); lcd_write_data(0x08); lcd_write_data(0x82); lcd_write_data(0x27); lcd_write_command(0xF2); lcd_write_data(0x00); lcd_write_command(0x26); lcd_write_data(0x01); // === 开启显示 === lcd_set_address_window(0, 0, 239, 319); // 设置全屏窗口 lcd_write_command(0x11); // Exit Sleep HAL_Delay(120); lcd_write_command(0x29); // Turn On Display }📌重点解释两个寄存器:
0x36寄存器(Memory Access Control):控制屏幕旋转和扫描方向。
常见值:0x48:横向,从左到右、从上到下0x28:竖屏,适合手机样式布局修改它可以实现 UI 自适应旋转
0x3A寄存器(Pixel Format Set):设为0x55表示使用RGB565格式,即每个像素占 2 字节(红5绿6蓝5),总共约 65K 色。这是性能与色彩的平衡之选。
💡 提醒:不要盲目复制别人的初始化代码!不同厂家的模组可能略有差异。如果屏幕花屏或不亮,请优先检查是否匹配你的硬件版本。
如何写入图像?掌握“地址窗口 + RAMWR”机制
想在屏幕上画东西,核心就两步:
- 设定写入区域(地址窗口)
- 发送 RAMWR 命令,连续写入像素数据
设置地址窗口函数示例
void lcd_set_address_window(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) { lcd_write_command(0x2A); // Column Address Set lcd_write_data(x1 >> 8); lcd_write_data(x1 & 0xFF); lcd_write_data(x2 >> 8); lcd_write_data(x2 & 0xFF); lcd_write_command(0x2B); // Page Address Set lcd_write_data(y1 >> 8); lcd_write_data(y1 & 0xFF); lcd_write_data(y2 >> 8); lcd_write_data(y2 & 0xFF); }开始写像素数据
void lcd_draw_pixel(uint16_t x, uint16_t y, uint16_t color) { lcd_set_address_window(x, y, x, y); lcd_write_command(0x2C); // Write RAM lcd_write_data(color >> 8); lcd_write_data(color & 0xFF); }✅
color是 RGB565 格式的颜色值,例如红色为0xF800,绿色为0x07E0,蓝色为0x001F。
批量写更快:
void lcd_fill_area(uint16_t x1, uint16_t y1, uint16_t w, uint16_t h, uint16_t *pixels) { uint32_t len = w * h; lcd_set_address_window(x1, y1, x1+w-1, y1+h-1); lcd_write_command(0x2C); for (uint32_t i = 0; i < len; i++) { lcd_write_data(pixels[i] >> 8); lcd_write_data(pixels[i] & 0xFF); } }如果你的数据量大,强烈建议启用DMA 传输,避免 CPU 被阻塞。
实战避坑指南:那些年我们遇到的“灵异现象”
别笑,以下问题我都亲自踩过:
❌ 屏幕全白 / 全黑?
- 检查 RST 是否有效复位
- 是否漏发
0x11(退出睡眠)或0x29(开启显示) - 背光有没有供电?
❌ 显示错位、偏移、倒置?
- 查看
0x36寄存器设置是否正确 - 地址窗口坐标是否超出实际范围(如写了 320×240 却访问 0~320)
- 某些模组默认是 240×320 竖屏,别被标签骗了!
❌ 颜色发紫、偏绿、像滤镜?
- MCU 输出的是 RGB888?ILI9341 吃的是 RGB565!
- 确保你在转换颜色格式时没搞反高低字节
❌ 刷新慢如幻灯片?
- 当前 SPI 波特率是多少?试试把预分频调小
- 用
HAL_SPI_Transmit逐字节发?换成 DMA 批量传效率提升 3 倍+
工程优化技巧:让你的显示更流畅
掌握了基本操作后,可以进一步提升体验:
✅ 使用双缓冲机制
避免画面撕裂:一帧在后台绘制,完成后整体切换。
✅ 局部刷新代替全屏重绘
UI 变化局部更新,减少 SPI 数据量,显著提升响应速度。
✅ 加入背光 PWM 控制
通过定时器输出 PWM 到 BL 引脚,实现亮度调节,节能又护眼。
✅ 封装绘图库
封装画线、画圆、显示字符等基础函数,为后续引入 GUI 框架铺路。
结语:这不是终点,而是起点
当你第一次看到自己写的代码在彩屏上画出一个红色方块时,那种成就感是无与伦比的。
而 ILI9341 正是这样一个绝佳的跳板——它足够简单,让你快速入门;又足够强大,支撑你完成复杂的图形交互。
下一步,你可以尝试:
- 移植 LVGL,在这上面做个仪表盘界面;
- 接个触摸屏,做成 HMI 控制面板;
- 配合 FATFS 读 SD 卡图片,做个电子相册。
一切可视化的大门,都从这一块小小的屏幕开始。
如果你正在学习嵌入式开发,不妨现在就拿出那块吃灰已久的 TFT 屏,接上线,烧段代码,亲手把它点亮吧!
🔧动手提示:推荐使用 Adafruit_ILI9341 库作为参考,结构清晰,注释完整,非常适合学习移植。
有什么问题欢迎留言交流,我们一起把“看不见”的系统,变成“看得见”的精彩世界。