简介:本资源是一套基于STM32F10x系列MCU的SPI接口驱动ST7789V彩色TFT液晶屏的完整工程例程,面向嵌入式初学者与中级开发者,解决LCD显示驱动开发中SPI配置、初始化时序、像素数据传输及基础图形绘制等核心问题。压缩包含185个文件,主体为33个C源码、35个头文件(.h)、34个编译中间文件(.o/.d/.crf),辅以Keil工程配置(.uvprojx/.uvoptx)、启动文件(.s)、链接脚本(.sct)及可执行镜像(.hex/.axf),总大小4.91MB,结构完整,可直接编译下载运行。已有1057人学习下载,资源包含标准外设库驱动框架、ST7789V专用LCD驱动层、屏幕初始化序列、RGB565像素写入函数及基础绘图接口,代码注释清晰,结合内容预览可见其覆盖RCC、GPIO、SPI、TIM等关键外设模块,便于理解底层硬件协同逻辑与显示驱动全流程实现。
1. STM32 驱动 ILI7789V 彩色 LCD 不是“接上线就能亮”,而是 SPI 时序、引脚复用与初始化序列的三重校准
很多刚接触 ILI7789V 屏幕的开发者会误以为:只要把 STM32 的 SPI 引脚连到屏的 SCL/SDA/CS/DC,调用 HAL_SPI_Transmit() 发几条命令,屏幕就该亮起彩色图像——结果却是全黑、花屏、闪屏或只显示固定灰阶。根本原因在于:ILI7789V 并非标准 SPI 设备,它要求严格匹配的时钟极性(CPOL)、相位(CPHA)、数据宽度(8-bit)、片选时序(CS active-low 且需保持足够低电平时间),更关键的是其初始化序列包含 20+ 条寄存器写入指令,顺序、延时、参数缺一不可。本例程聚焦 STM32F103/F407 等主流型号,使用 HAL 库 + CubeMX 配置 SPI 外设,不依赖第三方 GUI 库,从裸寄存器操作级还原驱动逻辑。适合需要嵌入式图形界面但受限于 Flash/RAM 资源的工业 HMI、便携设备或毕业设计项目,尤其适用于已掌握基础 GPIO/SPI 操作、正卡在“屏不响应”环节的 1–3 年经验工程师。
2. ILI7789V 与 STM32 的硬件连接必须满足三类电气约束:SPI 模式、DC/CS 电平逻辑与供电稳定性
2.1 ILI7789V 的 SPI 接口特性决定了 STM32 外设配置不能套用通用模板
ILI7789V 数据手册明确标注其 SPI 接口工作在Mode 0(CPOL=0, CPHA=0):空闲时 SCK 为低电平,数据在 SCK 上升沿采样,且必须在 SCK 下降沿前建立稳定。这与多数 Flash 或传感器不同,若 CubeMX 中误设为 Mode 3(CPOL=1, CPHA=1),则通信必然失败,表现为 SPI_TXE 标志永不置位或接收数据全为 0xFF。此外,该芯片仅支持8-bit 数据帧,不兼容 16-bit 传输;其最大 SCK 频率标称为 30 MHz,但实测在 STM32F103 上超过 12 MHz 易出现误码(因 GPIO 切换速度与布线容性影响),推荐初始配置为8 MHz(对应 APB2=72 MHz 时预分频值为 8)。
提示:不要直接复制其他 OLED 或 ST7735 的 SPI 配置。ILI7789V 的 DC(Data/Command)引脚功能等效于“寄存器地址/数据选择信号”,高电平时发送的是显存数据(GRAM),低电平时发送的是控制寄存器地址与参数——这一逻辑必须由软件精确控制,不能交由硬件自动管理。
2.2 典型硬件连接表与关键引脚复用冲突规避
| ILI7789V 引脚 | 功能说明 | 推荐 STM32 引脚(F103C8T6) | 注意事项 |
|---|---|---|---|
| VCC | 3.3V 电源 | 3.3V(LDO 输出) | 必须加 100nF 陶瓷电容紧靠屏端,避免上电瞬间电流冲击导致初始化失败 |
| GND | 地 | GND | 与 MCU 共地,避免地线环路 |
| SCL | SPI 时钟 | PA5(SPI1_SCK) | 若使用 SPI2,需注意 PB13 可能被 JTAG 占用,需禁用 SWD/JTAG 或改用其他引脚 |
| SDA | SPI 数据(MOSI) | PA7(SPI1_MOSI) | 不可接 MISO(PA6),ILI7789V 为单向写入设备,无读回需求 |
| CS | 片选(低有效) | PA4(软件控制) | 必须软件片选:HAL_SPI_Transmit() 不自动拉低 CS,需在每次传输前手动置低 |
| DC | 数据/命令选择 | PA3(GPIO 输出) | 初始化阶段需频繁切换电平,建议用推挽输出模式,避免开漏导致上升沿缓慢 |
| RESET | 复位(低有效) | PA2(GPIO 输出) | 上电后需保持低电平 ≥ 10ms,再拉高;部分模块已内置 RC 复位电路,可悬空 |
| LED | 背光控制 | PB0(PWM 输出) | 建议通过 MOSFET 驱动,避免 MCU IO 直接带载,典型电流达 80–150mA |
注意:PA4(CS)与 PA3(DC)不可复用为 ADC 或其他外设功能。若使用 STM32F407,优先选用 SPI1(APB2 总线),因其时钟频率更高,可支撑更高刷率;若用 SPI2(APB1),需确认预分频后 SCK 实际频率仍满足 8 MHz 要求。
2.3 CubeMX 中 SPI1 的关键参数配置步骤(以 F103 为例)
- 在 Pinout 视图中,将 PA5/PA7/PA4/PA3 分别配置为
SPI1_SCK/SPI1_MOSI/GPIO_Output/GPIO_Output; - 进入 Configuration → Connectivity → SPI1:
- Mode:
Full-Duplex Master - Hardware NSS:
Disabled(强制软件片选) - Frame Format:
Motorola - Data Size:
8 Bits - Clock Polarity (CPOL):
Low - Clock Phase (CPHA):
1st Edge - NSS Signal:
Software - Baud Rate Prescaler:
8(APB2=72MHz → SCK=9MHz,实测稳定) - First Bit:
MSB First
- Mode:
- 生成代码后,在
main.c中确认hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;与hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;已正确赋值。
// 验证 SPI 初始化是否生效(调试时添加) if (HAL_SPI_GetState(&hspi1) != HAL_SPI_STATE_RESET) { // 正常:SPI 外设已初始化 } else { // 错误:检查 CubeMX 是否生成了 MX_SPI1_Init() }该段代码用于快速定位初始化失败点——常见原因是未勾选Generate peripheral initialization as a pair of 'xxx_MspInit/DeInit' functions导致时钟使能代码缺失。
3. ILI7789V 初始化序列必须按精确时序执行:23 条寄存器写入 + 5 类延时策略
3.1 初始化流程图谱:从复位释放到进入 RGB 模式共 7 个阶段
ILI7789V 的初始化不是简单写入几个寄存器,而是一个状态机驱动过程:
①硬复位同步→ ②睡眠退出→ ③像素格式设定→ ④伽马校正加载→ ⑤显示方向配置→ ⑥TE 使能(可选)→ ⑦显示开启。
其中第②、④、⑦步存在最小延时要求(单位:ms),而第③、⑤步需连续写入多寄存器,中间不能插入 CS 抬升。
3.2 关键寄存器写入函数与 DC 电平控制逻辑
所有寄存器操作均通过同一函数完成,区别仅在于is_cmd参数:
// ILI7789V 驱动核心函数(需在 lcd.h 中声明) void ILI7789_WriteCmd(uint8_t cmd) { HAL_GPIO_WritePin(LCD_DC_GPIO_Port, LCD_DC_Pin, GPIO_PIN_RESET); // DC=0: command mode HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_RESET); // CS low HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY); HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_SET); // CS high } void ILI7789_WriteData(uint8_t data) { HAL_GPIO_WritePin(LCD_DC_GPIO_Port, LCD_DC_Pin, GPIO_PIN_SET); // DC=1: data mode HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_RESET); HAL_SPI_Transmit(&hspi1, &data, 1, HAL_MAX_DELAY); HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_SET); } // 批量写入数据(用于GRAM填充或伽马表) void ILI7789_WriteDataBuffer(uint8_t *buf, uint16_t len) { HAL_GPIO_WritePin(LCD_DC_GPIO_Port, LCD_DC_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_RESET); HAL_SPI_Transmit(&hspi1, buf, len, HAL_MAX_DELAY); HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_SET); }逻辑说明:
HAL_SPI_Transmit()是阻塞式发送,HAL_MAX_DELAY表示无限等待 TXE 标志。若实际项目需非阻塞,应改用HAL_SPI_Transmit_IT()并配合回调函数,但初始化阶段无需考虑实时性,阻塞更可靠。LCD_DC_Pin和LCD_CS_Pin需在main.h中定义为对应 GPIO 引脚宏。
3.3 完整初始化序列(含精确延时与寄存器含义注释)
void ILI7789_Init(void) { // 1. 硬件复位(若 RESET 引脚已接 MCU) HAL_GPIO_WritePin(LCD_RST_GPIO_Port, LCD_RST_Pin, GPIO_PIN_RESET); HAL_Delay(20); // ≥10ms HAL_GPIO_WritePin(LCD_RST_GPIO_Port, LCD_RST_Pin, GPIO_PIN_SET); HAL_Delay(150); // 等待内部 PLL 锁定 // 2. 退出睡眠模式 ILI7789_WriteCmd(0x11); // Sleep Out HAL_Delay(120); // 必须 ≥120ms // 3. 设置像素格式:16-bit RGB565 ILI7789_WriteCmd(0x3A); ILI7789_WriteData(0x05); // 16-bit // 4. 设置列地址(X 轴范围:0–239) ILI7789_WriteCmd(0x2A); ILI7789_WriteData(0x00); ILI7789_WriteData(0x00); ILI7789_WriteData(0x00); ILI7789_WriteData(0xEF); // 239 // 5. 设置行地址(Y 轴范围:0–319) ILI7789_WriteCmd(0x2B); ILI7789_WriteData(0x00); ILI7789_WriteData(0x00); ILI7789_WriteData(0x01); ILI7789_WriteData(0x3F); // 319 // 6. 伽马校正(简化版,仅写关键点) ILI7789_WriteCmd(0xE0); // Positive Gamma uint8_t gamma_p[] = {0x0F,0x1F,0x1C,0x0C,0x0F,0x08,0x48,0x90, 0x8F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F}; ILI7789_WriteDataBuffer(gamma_p, 16); ILI7789_WriteCmd(0xE1); // Negative Gamma uint8_t gamma_n[] = {0x0F,0x21,0x1F,0x0F,0x0F,0x0F,0x48,0x90, 0x8F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F}; ILI7789_WriteDataBuffer(gamma_n, 16); // 7. 设置显示方向:竖屏(0x60),横屏(0xA0) ILI7789_WriteCmd(0x36); ILI7789_WriteData(0x60); // MADCTL: MY=0, MX=1, MV=0, ML=0, RGB=1 // 8. 开启显示 ILI7789_WriteCmd(0x29); // Display On }参数说明:
0x36寄存器(MADCTL)控制屏幕旋转与 RGB/BGR 顺序。0x60表示 X/Y 轴翻转组合为竖屏(240×320),若需横屏显示(320×240),应改为0xA0;0x05在0x3A寄存器中指定 16-bit 数据格式,这是 ILI7789V 最常用模式,兼容大多数嵌入式 GUI 库。
3.4 四类延时策略对比与选型建议
| 延时类型 | 适用场景 | 优点 | 缺点 | 本例程采用 |
|---|---|---|---|---|
HAL_Delay() | 初始化阶段(ms 级) | 简单、精准、不占中断资源 | 依赖 SysTick,需配置好时基 | ✅ |
us_delay() | 寄存器间微秒级间隔(如 1μs) | 精度高(基于 DWT 或 NOP) | 占用 CPU,不可在中断中调用 | ❌(未用) |
HAL_TIM_Base_Start_IT() | 动态刷新时帧同步 | 可触发 DMA 传输 | 配置复杂,增加中断负载 | ⚠️(进阶) |
FreeRTOS vTaskDelay() | RTOS 环境下任务调度延时 | 不阻塞其他任务 | 需移植 FreeRTOS | ❌(裸机) |
本例程全程使用HAL_Delay(),因其在初始化阶段唯一且可靠。若后续需实现动画,应迁移到定时器中断 + DMA 方式。
4. 实现 16-bit RGB565 图像填充与矩形绘制:从裸数据到可验证的视觉反馈
4.1 GRAM 写入协议与内存布局映射关系
ILI7789V 的显存(GRAM)是线性排列的 16-bit 像素数组,总容量为240 × 320 × 2 = 153,600 bytes。写入流程为:
① 发送0x2C(Memory Write)命令 → ② 连续发送像素数据(每个像素 2 字节,高位在前)。
关键约束:不能跨行写入。例如设置区域为(0,0)到(239,319)后,必须一次性写满 320 行 × 240 像素,否则屏幕会错位。因此,批量填充函数需严格按行组织数据。
4.2 全屏纯色填充函数(含错误检测与性能提示)
// 填充指定区域为单一颜色(RGB565 格式:R5:G6:B5) void ILI7789_FillRect(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color) { uint32_t width = x2 - x1 + 1; uint32_t height = y2 - y1 + 1; uint32_t total_pixels = width * height; // 1. 设置地址窗口 ILI7789_WriteCmd(0x2A); // Column Address Set ILI7789_WriteData(x1 >> 8); ILI7789_WriteData(x1 & 0xFF); ILI7789_WriteData(x2 >> 8); ILI7789_WriteData(x2 & 0xFF); ILI7789_WriteCmd(0x2B); // Page Address Set ILI7789_WriteData(y1 >> 8); ILI7789_WriteData(y1 & 0xFF); ILI7789_WriteData(y2 >> 8); ILI7789_WriteData(y2 & 0xFF); ILI7789_WriteCmd(0x2C); // Memory Write // 2. 准备颜色数据缓冲区(避免栈溢出) #define MAX_FILL_SIZE 2048 // 一次最多填 1024 像素(2KB) static uint8_t fill_buf[MAX_FILL_SIZE]; uint16_t *p = (uint16_t*)fill_buf; for (uint32_t i = 0; i < total_pixels; i++) { p[i] = color; if ((i + 1) % (MAX_FILL_SIZE/2) == 0 || i == total_pixels - 1) { uint16_t send_len = (i + 1) % (MAX_FILL_SIZE/2) == 0 ? MAX_FILL_SIZE/2 : (i + 1) % (MAX_FILL_SIZE/2); HAL_GPIO_WritePin(LCD_DC_GPIO_Port, LCD_DC_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_RESET); HAL_SPI_Transmit(&hspi1, fill_buf, send_len * 2, HAL_MAX_DELAY); HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_SET); } } }逻辑说明:该函数将
color(如0xF800表示纯红)重复写入指定矩形区域。为防止大区域填充时栈溢出,采用分块发送策略:每 1024 像素(2048 字节)为一批,调用HAL_SPI_Transmit()发送。0x2A/0x2B命令设定地址窗口后,0x2C命令启动连续写入,此时 CS 必须保持低电平直至整批数据发完。
4.3 验证驱动是否成功的三步测试法
- 复位后观察背光:上电后 LED 引脚应输出 PWM 信号,屏背光亮起(若背光不亮,检查 PB0 是否配置为 AF_PP 或 TIM 输出模式);
- 执行
ILI7789_FillRect(0,0,239,319,0x001F):填充纯蓝(B5),若屏幕显示均匀蓝色,证明初始化与 GRAM 写入通路正常; - 叠加红色矩形
ILI7789_FillRect(50,50,150,150,0xF800):若出现清晰红色方块,说明坐标映射与区域设置无误。
注意:若第二步显示灰色或绿色,大概率是
0x3A寄存器未设为0x05(16-bit),或0x36寄存器方向配置错误导致 RGB 位域错位;若第三步方块位置偏移,需检查0x2A/0x2B命令中高低字节顺序是否颠倒。
5. 排查 SPI 通信失败的五大硬核线索:从示波器波形到寄存器快照
5.1 使用逻辑分析仪捕获 SPI 波形的三个必检点
当屏幕无反应时,最高效的方式是用 Saleae 或 PulseView 抓取 SCK/SDA/CS/DC 四路信号:
| 信号 | 正常特征 | 异常表现与根因 |
|---|---|---|
| SCK | 方波,频率 ≈ 8 MHz,占空比 50%,空闲为低电平 | 频率远低于配置值 → APB2 时钟未使能;无波形 → SPI 外设未初始化或引脚复用冲突 |
| SDA | 在 SCK 上升沿变化,数据为 8-bit,首字节应为0x11(Sleep Out) | 数据全为 0x00 → CS 未拉低或 MOSI 引脚虚焊;数据乱码 → CPOL/CPHA 配置错误或线路干扰 |
| CS | 每次命令/数据传输前拉低,传输结束后拉高,低电平持续时间 ≥ 100ns | 始终高电平 →HAL_GPIO_WritePin()调用被注释或引脚宏定义错误;脉冲过窄 → 未加HAL_Delay(1) |
| DC | 发送命令时为低,发送数据时为高,切换时刻严格对齐 CS 下降沿 | DC 电平不变 →LCD_DC_Pin宏指向错误引脚;切换延迟 → GPIO 输出速度未设为GPIO_SPEED_FREQ_HIGH |
提示:在 CubeMX 的 GPIO 配置中,PA4/PA3 必须设为
GPIO_MODE_OUTPUT_PP+GPIO_SPEED_FREQ_HIGH,否则 DC 切换延迟可达数微秒,破坏时序。
5.2 STM32 调试寄存器快照法:绕过 HAL 直查底层状态
若无示波器,可通过调试器读取 SPI 相关寄存器判断硬件状态:
| 寄存器(F103) | 偏移 | 读取值含义 | 故障指示 |
|---|---|---|---|
SPI1->CR1 | 0x00 | bit0:SPI_ENABLE,bit1:SPI_SPE,bit2:SPI_SSI,bit3:SPI_SSM | SPI_ENABLE=0→ 外设未使能;SPI_SPE=0→ 主模式未激活 |
SPI1->SR | 0x0C | bit1:TXE(发送缓冲空),bit2:RXNE(接收非空),bit4:BSY(忙) | TXE=0→ 数据未发出;BSY=1且TXE=0→ 通信卡死,可能 CS 未拉低或从机未响应 |
SPI1->DR | 0x0C | 发送/接收数据寄存器 | 写入后TXE仍为 0 → 时钟未启动或 MOSI 引脚断开 |
在 Keil 或 STM32CubeIDE 调试模式下,打开Peripherals → SPI1 → CR1/SR/DR视图,执行ILI7789_WriteCmd(0x11)后立即暂停,观察SR寄存器中TXE是否由 1 变 0 再变 1——若始终为 1,说明数据未真正发出。
5.3 常见“伪成功”现象与真实原因对照表
| 表象 | 真实原因 | 解决方案 |
|---|---|---|
| 屏幕偶尔闪一下白光,随后黑屏 | 初始化序列中0x29(Display On)前缺少HAL_Delay(120),导致显示控制器未就绪 | 在0x29前添加HAL_Delay(120),参考数据手册 Table 12 “Display ON Timing” |
| 花屏(彩色噪点) | SDA 线过长未加 100Ω 串联电阻,或与 SCK 平行走线 > 5cm 引起串扰 | PCB 布线时 SDA/SCK 间距 ≥ 3 倍线宽,SCK 线加 33Ω 电阻,SDA 线加 100Ω 电阻 |
| 颜色严重偏色(如红变黄) | 0x36(MADCTL)寄存器设置错误,或0x3A(Interface Pixel Format)未设为0x05(16-bit) | 用逻辑分析仪确认0x36命令后紧跟0x60,0x3A后紧跟0x05;检查ILI7789_WriteData()是否传入正确字节 |
| 初始化耗时超 5 秒 | HAL_Delay()依赖的 SysTick 未配置,或SystemCoreClock未正确更新 | 在main()开头添加HAL_Init(); SystemClock_Config();,确保HAL_GetTick()返回值递增 |
| 使用不同批次屏幕效果不一 | ILI7789V 存在 Rev.A/Rev.B 差异,B 版本需额外写入0xB1(Frame Rate Control)寄存器 | 查阅屏厂规格书,若为 B 版本,在0x11后插入:ILI7789_WriteCmd(0xB1); ILI7789_WriteData(0x00); |
最后一行技术内容:若需在 STM32F407 上启用 DMA 加速 GRAM 填充,应配置hdma_spi1_tx通道,将SPI1->DR地址设为外设地址,显存缓冲区地址设为内存地址,并在HAL_SPI_Transmit_DMA()调用前确保hspi1.State为HAL_SPI_STATE_READY。
本文还有配套的精品资源,点击获取