STM32 LCD 显示驱动:ILI9341 SPI 屏幕入门实战指南
👁 1 阅读 · 2026-08-17 · STM32 显示
本文面向有一定嵌入式基础的开发者,深入讲解如何用STM32通过SPI接口驱动ILI9341液晶屏。内容涵盖ILI9341核心原理、SPI通信配置、常用初始化序列、像素写入与画点函数实现,并附完整可运行的代码示例。通过本文,你将掌握从零开始点亮屏幕、显示颜色和简单图形的方法,为后续GUI开发打下坚实基础。
## 1. ILI9341 与 SPI 显示基础
ILI9341 是一款常见的 TFT-LCD 驱动控制器,支持 SPI、并行等接口,其中 SPI 模式因引脚少、速度快而广泛用于嵌入式设备。SPI 接口下,ILI9341 作为从设备,STM32 作为主机,通过 MOSI、SCK、CS、DC(数据/命令选择)、RST 等引脚进行通信。
**关键点:**
- ILI9341 内部有显存(GRAM),大小为 240x320 像素,每像素 16 位(RGB565)。
- 写命令时 DC 引脚拉低,写数据时 DC 拉高。
- SPI 模式支持 Mode 0(CPOL=0, CPHA=0)和 Mode 3,通常使用 Mode 0。
- 最大 SPI 时钟可达 10MHz 以上,但需根据布线调整。
## 2. 硬件连接与 SPI 配置
### 2.1 引脚连接(以 STM32F103 为例)
| STM32 引脚 | LCD 引脚 | 说明 |
|------------|----------|------|
| PA5 | SCK | SPI 时钟 |
| PA7 | MOSI | 主出从入 |
| PA4 | CS | 片选(低有效) |
| PA6 | DC | 数据/命令选择 |
| PA3 | RST | 复位(低有效) |
| 3.3V | VCC | 电源 |
| GND | GND | 地 |
注意:DC 和 RST 可用普通 GPIO 控制,不占用 SPI 外设。
### 2.2 SPI 初始化(HAL 库)
使用 STM32CubeMX 配置 SPI1,模式为 Full-Duplex Master,数据大小 8 位,时钟极性 Low,相位 1Edge,预分频 16(即 8MHz)。生成代码后,在 main.c 中补充 DC 和 RST 的 GPIO 初始化。
```c
// SPI 句柄声明(CubeMX 已生成)
extern SPI_HandleTypeDef hspi1;
// DC 和 RST 引脚定义(根据实际接线)
#define LCD_DC_PIN GPIO_PIN_6
#define LCD_DC_PORT GPIOA
#define LCD_RST_PIN GPIO_PIN_3
#define LCD_RST_PORT GPIOA
// 简单宏定义命令/数据切换
#define LCD_DC_LOW() HAL_GPIO_WritePin(LCD_DC_PORT, LCD_DC_PIN, GPIO_PIN_RESET)
#define LCD_DC_HIGH() HAL_GPIO_WritePin(LCD_DC_PORT, LCD_DC_PIN, GPIO_PIN_SET)
```
## 3. ILI9341 初始化序列
ILI9341 上电后需发送一系列初始化命令,设置显示参数。以下为常用序列(摘自官方数据手册):
```c
void LCD_Init(void) {
// 硬件复位
HAL_GPIO_WritePin(LCD_RST_PORT, LCD_RST_PIN, GPIO_PIN_RESET);
HAL_Delay(20);
HAL_GPIO_WritePin(LCD_RST_PORT, LCD_RST_PIN, GPIO_PIN_SET);
HAL_Delay(120);
// 发送初始化命令(部分)
LCD_WriteCmd(0xCF); LCD_WriteData(0x00); LCD_WriteData(0xC1); LCD_WriteData(0x30);
LCD_WriteCmd(0xED); LCD_WriteData(0x64); LCD_WriteData(0x03); LCD_WriteData(0x12); LCD_WriteData(0x81);
LCD_WriteCmd(0xE8); LCD_WriteData(0x85); LCD_WriteData(0x10); LCD_WriteData(0x7A);
LCD_WriteCmd(0xCB); LCD_WriteData(0x39); LCD_WriteData(0x2C); LCD_WriteData(0x00); LCD_WriteData(0x34); LCD_WriteData(0x02);
LCD_WriteCmd(0xF7); LCD_WriteData(0x20);
LCD_WriteCmd(0xEA); LCD_WriteData(0x00); LCD_WriteData(0x00);
LCD_WriteCmd(0xC0); LCD_WriteData(0x1B);
LCD_WriteCmd(0xC1); LCD_WriteData(0x12);
LCD_WriteCmd(0xC5); LCD_WriteData(0x32); LCD_WriteData(0x3C);
LCD_WriteCmd(0xC7); LCD_WriteData(0x91);
LCD_WriteCmd(0x36); LCD_WriteData(0x48); // 内存访问控制,设置扫描方向
LCD_WriteCmd(0x3A); LCD_WriteData(0x55); // 像素格式为 RGB565
LCD_WriteCmd(0xB1); LCD_WriteData(0x00); LCD_WriteData(0x1B);
LCD_WriteCmd(0xB6); LCD_WriteData(0x0A); LCD_WriteData(0xA2);
LCD_WriteCmd(0xF2); LCD_WriteData(0x00);
LCD_WriteCmd(0x26); LCD_WriteData(0x01);
LCD_WriteCmd(0xE0); // 设置 Gamma 曲线(略)
// ... 更多 Gamma 参数
LCD_WriteCmd(0x11); // 退出睡眠
HAL_Delay(120);
LCD_WriteCmd(0x29); // 开启显示
}
```
**说明:** 实际初始化序列需根据屏幕厂商提供的数据手册调整,但核心命令如 0x3A(像素格式)、0x36(扫描方向)、0x11、0x29 必须正确。
## 4. 底层写命令与写数据函数
```c
// 发送命令
void LCD_WriteCmd(uint8_t cmd) {
LCD_DC_LOW();
HAL_SPI_Transmit(&hspi1, &cmd, 1, 100);
}
// 发送数据
void LCD_WriteData(uint8_t data) {
LCD_DC_HIGH();
HAL_SPI_Transmit(&hspi1, &data, 1, 100);
}
// 连续发送数据(用于填充像素)
void LCD_WriteDataBuffer(uint8_t *buf, uint16_t len) {
LCD_DC_HIGH();
HAL_SPI_Transmit(&hspi1, buf, len, 1000);
}
```
## 5. 设置显示窗口与画点
ILI9341 支持设置矩形窗口,之后写入像素数据即可填充该区域。
```c
// 设置窗口(x0,y0 左上角,x1,y1 右下角)
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); // 开始写入显存
}
// 画一个像素(颜色为 RGB565)
void LCD_DrawPixel(uint16_t x, uint16_t y, uint16_t color) {
if (x >= 240 || y >= 320) return;
LCD_SetWindow(x, y, x, y);
uint8_t data[2] = { color >> 8, color & 0xFF };
LCD_WriteDataBuffer(data, 2);
}
// 填充整个屏幕
void LCD_FillScreen(uint16_t color) {
LCD_SetWindow(0, 0, 239, 319);
uint32_t pixels = 240 * 320;
uint8_t *buf = (uint8_t*)malloc(pixels * 2);
if (buf) {
for (uint32_t i = 0; i < pixels; i++) {
buf[i*2] = color >> 8;
buf[i*2+1] = color & 0xFF;
}
LCD_WriteDataBuffer(buf, pixels * 2);
free(buf);
} else {
// 内存不足时逐像素发送(较慢)
for (uint32_t i = 0; i < pixels; i++) {
uint8_t d[2] = { color >> 8, color & 0xFF };
LCD_WriteDataBuffer(d, 2);
}
}
}
```
## 6. 完整示例:点亮屏幕并显示彩色条纹
```c
#include "main.h"
#include "spi.h"
#include "gpio.h"
// 函数声明
void LCD_Init(void);
void LCD_WriteCmd(uint8_t cmd);
void LCD_WriteData(uint8_t data);
void LCD_WriteDataBuffer(uint8_t *buf, uint16_t len);
void LCD_SetWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1);
void LCD_DrawPixel(uint16_t x, uint16_t y, uint16_t color);
void LCD_FillScreen(uint16_t color);
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_SPI1_Init();
LCD_Init();
LCD_FillScreen(0x001F); // 蓝色
HAL_Delay(1000);
LCD_FillScreen(0x07E0); // 绿色
HAL_Delay(1000);
LCD_FillScreen(0xF800); // 红色
HAL_Delay(1000);
// 画一条对角线
for (int i = 0; i < 240; i++) {
LCD_DrawPixel(i, i, 0xFFFF); // 白色
}
while (1) {
// 循环
}
}
```
## 7. 性能优化与注意事项
- **SPI 时钟频率**:不要盲目提高,若屏幕出现花屏,降低分频系数。
- **DMA 传输**:对于大量像素填充,建议使用 SPI DMA,可大幅提升刷新率。
- **内存管理**:填充全屏时避免一次性分配大缓冲区,可分段发送。
- **电源稳定性**:LCD 背光电流较大,确保电源足够,否则可能闪烁。
- **扫描方向**:通过 0x36 命令的 MADCTL 位控制,不同屏幕安装方向需调整。
- **初始化序列**:不同厂商的 ILI9341 模块可能有细微差异,务必参考模块资料。
## 8. 总结
本文从原理到实践,详细介绍了 STM32 通过 SPI 驱动 ILI9341 屏幕的方法。掌握这些基础后,你可以进一步实现字符显示、图片显示,甚至移植 LVGL 等 GUI 库。关键在于理解命令/数据时序和窗口设置,并注意硬件细节。希望这篇文章能帮你快速上手,开启嵌入式显示开发之旅。