## 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 库。关键在于理解命令/数据时序和窗口设置,并注意硬件细节。希望这篇文章能帮你快速上手,开启嵌入式显示开发之旅。