STM32F103ZET6 SPI驱动ILI9341 TFT屏实战全解析
第一次拿到2.4寸TFT屏时,看着五颜六色的排线,我完全不知道从何下手。屏幕背面印着"ILI9341"的型号标识,网上搜到的资料又零散不全。经过三天调试,当屏幕终于显示出清晰的图像时,那种成就感至今难忘。本文将分享从硬件连接到软件调试的全过程经验,特别是那些容易踩坑的细节。
1. 硬件连接与SPI配置
1.1 引脚定义与物理连接
ILI9341驱动的2.4寸TFT屏通常采用20pin排线接口,关键信号线包括:
| 屏引脚 | STM32F103ZET6连接 | 备注 |
|---|---|---|
| VCC | 3.3V | 电源正极 |
| GND | GND | 电源地 |
| CS | PB11 | 片选信号 |
| RESET | PB12 | 复位信号 |
| DC/RS | PB10 | 数据/命令选择 |
| SDA | PB15(MOSI) | SPI数据线 |
| SCL | PB13(SCK) | SPI时钟线 |
| LED | PB9 | 背光控制 |
常见接线错误:
- 将MOSI和MISO接反(TFT屏通常不需要MISO)
- 忘记连接复位线导致屏幕无法初始化
- 背光LED直接接3.3V导致无法关闭背光
1.2 SPI外设初始化
STM32F103的SPI2外设位于PB13~PB15引脚,初始化配置如下:
void SPI2_Init(void) { SPI_InitTypeDef SPI_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; // 使能时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE); // 配置SPI引脚 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_15; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); // SPI参数配置 SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Tx; // 单线发送模式 SPI_InitStructure.SPI_Mode = SPI_Mode_Master; // 主机模式 SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; // 8位数据 SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; // 时钟极性 SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; // 时钟相位 SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; // 软件控制NSS SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4; // 18MHz SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;// 高位在前 SPI_Init(SPI2, &SPI_InitStructure); SPI_Cmd(SPI2, ENABLE); }注意:SPI时钟不宜过高,初期调试建议设置为系统时钟的1/4(18MHz),稳定后可逐步提高。
2. ILI9341初始化序列详解
2.1 关键寄存器配置
ILI9341有数十个配置寄存器,以下几个最为关键:
0x36 - 内存访问控制:
- 控制显示方向、颜色顺序等
- 典型值:0x48(竖屏,BGR顺序)
0x3A - 像素格式:
- 设置16位(0x55)或18位(0x66)颜色模式
- TFT屏通常使用16位RGB565格式
0xB1 - 帧率控制:
- 设置刷新率,影响显示流畅度
- 典型值:0x00, 0x1B(约70Hz)
0xC0/C1 - 电源控制:
- 配置内部电压调节器
- 需要严格按照数据手册顺序设置
2.2 初始化代码优化
原始初始化序列往往包含大量冗余设置,经测试可简化为:
void LCD_Init(void) { LCD_Reset(); // 硬件复位 // 精简后的初始化序列 LCD_WR_REG(0xCF); LCD_WR_DATA(0x00); LCD_WR_DATA(0xC1); LCD_WR_DATA(0X30); LCD_WR_REG(0xED); LCD_WR_DATA(0x64); LCD_WR_DATA(0x03); LCD_WR_DATA(0X12); LCD_WR_DATA(0X81); LCD_WR_REG(0xE8); LCD_WR_DATA(0x85); LCD_WR_DATA(0x00); LCD_WR_DATA(0x78); LCD_WR_REG(0xCB); LCD_WR_DATA(0x39); LCD_WR_DATA(0x2C); LCD_WR_DATA(0x00); LCD_WR_DATA(0x34); LCD_WR_DATA(0x02); LCD_WR_REG(0xF7); LCD_WR_DATA(0x20); LCD_WR_REG(0xEA); LCD_WR_DATA(0x00); LCD_WR_DATA(0x00); // 电源控制 LCD_WR_REG(0xC0); LCD_WR_DATA(0x1B); // VRH LCD_WR_REG(0xC1); LCD_WR_DATA(0x12); // SAP LCD_WR_REG(0xC5); LCD_WR_DATA(0x32); // VCM控制 LCD_WR_REG(0xC7); LCD_WR_DATA(0XC0); // 对比度 // 内存访问控制 LCD_WR_REG(0x36); LCD_WR_DATA(0x48); // 竖屏模式 LCD_WR_REG(0x3A); LCD_WR_DATA(0x55); // 16位像素 // 显示控制 LCD_WR_REG(0xB1); LCD_WR_DATA(0x00); LCD_WR_DATA(0x1B); // 帧率 LCD_WR_REG(0xB6); LCD_WR_DATA(0x0A); LCD_WR_DATA(0xA2); // 显示功能 // Gamma校正 static const u8 Gamma_Sequence[] = { /* 省略具体数值 */ }; LCD_WR_REG(0xE0); for(u8 i=0; i<15; i++) LCD_WR_DATA(Gamma_Sequence[i]); LCD_WR_REG(0xE1); for(u8 i=0; i<15; i++) LCD_WR_DATA(Gamma_Sequence[i+15]); LCD_WR_REG(0x11); // 退出睡眠 delay_ms(120); LCD_WR_REG(0x29); // 开启显示 }调试技巧:如果屏幕出现花屏,可尝试调整0x36寄存器的值,特别是MY/MX/MV这三个方向控制位。
3. 显示优化技巧
3.1 双缓冲机制实现
直接刷屏会导致明显的闪烁,采用双缓冲可大幅改善:
// 定义显存 u16 frame_buffer[2][320*240]; u8 current_buffer = 0; // 切换显示缓冲区 void LCD_SwitchBuffer(void) { LCD_SetWindows(0, 0, 239, 319); LCD_CS_CLR; LCD_RS_SET; for(u32 i=0; i<320*240; i++) { SPI_WriteByte(SPI2, frame_buffer[current_buffer][i]>>8); SPI_WriteByte(SPI2, frame_buffer[current_buffer][i]&0xFF); } LCD_CS_SET; current_buffer ^= 1; // 切换缓冲区 }3.2 局部刷新优化
全屏刷新耗时长,针对动态区域可局部更新:
void LCD_UpdateRegion(u16 x1, u16 y1, u16 x2, u16 y2) { LCD_SetWindows(x1, y1, x2, y2); LCD_CS_CLR; LCD_RS_SET; u16 width = x2 - x1 + 1; u16 height = y2 - y1 + 1; for(u16 y=0; y<height; y++) { for(u16 x=0; x<width; x++) { u16 color = frame_buffer[current_buffer][(y1+y)*320 + (x1+x)]; SPI_WriteByte(SPI2, color>>8); SPI_WriteByte(SPI2, color&0xFF); } } LCD_CS_SET; }3.3 字体显示优化
嵌入式系统常用点阵字体,存储和渲染需特别处理:
// 字体结构定义 typedef struct { u8 width; // 字体宽度 u8 height; // 字体高度 u8 first_char;// 起始ASCII码 u8 char_num; // 字符数量 const u16 *data; // 字体数据 } FontType; // 显示一个字符 void LCD_ShowChar(u16 x, u16 y, u8 chr, FontType font, u16 color, u16 bgcolor) { u8 i,j; u8 temp; u16 x0 = x; chr -= font.first_char; const u16 *p = &font.data[chr * font.height]; for(i=0; i<font.height; i++) { temp = *(p+i); for(j=0; j<font.width; j++) { if(temp & (1<<(font.width-1-j))) { LCD_DrawPoint(x+j, y+i, color); } else if(bgcolor != color) { LCD_DrawPoint(x+j, y+i, bgcolor); } } } }4. 常见问题排查指南
4.1 白屏问题分析
当屏幕只亮背光无显示时,按以下步骤排查:
检查电源:
- 测量VCC电压是否为3.3V
- 确认GND连接可靠
验证复位时序:
- 复位信号需保持低电平至少10ms
- 复位后需延迟100ms再初始化
SPI信号测试:
- 用逻辑分析仪抓取SPI波形
- 确认CS、DC信号变化正常
初始化序列:
- 确保发送了0x11(退出睡眠)和0x29(开启显示)
- 检查0x36寄存器设置是否正确
4.2 颜色异常处理
颜色显示不正常时,重点关注:
像素格式设置:
- 确认0x3A寄存器设置为0x55(16位接口)
- 检查SPI数据是否为RGB565顺序
内存访问控制:
- 0x36寄存器的BGR位决定颜色顺序
- 典型设置:0x48(竖屏+BGR)
Gamma校正:
- 不正确的Gamma值会导致颜色失真
- 建议使用厂商提供的标准Gamma序列
4.3 性能优化建议
SPI时钟优化:
- 初期使用较低时钟(如18MHz)
- 稳定后可逐步提高至36MHz
DMA传输:
- 使用DMA可释放CPU资源
- 配置示例:
void SPI2_DMA_Init(void) { DMA_InitTypeDef DMA_InitStructure; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); DMA_DeInit(DMA1_Channel5); DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&SPI2->DR; DMA_InitStructure.DMA_MemoryBaseAddr = (u32)frame_buffer; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; DMA_InitStructure.DMA_BufferSize = 320*240; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA1_Channel5, &DMA_InitStructure); SPI_I2S_DMACmd(SPI2, SPI_I2S_DMAReq_Tx, ENABLE); }
屏幕旋转优化:
- 修改0x36寄存器实现硬件旋转
- 避免软件旋转带来的性能损耗
调试ILI9341最耗时的部分是反复修改初始化参数。建议将初始化代码模块化,通过宏定义快速切换不同配置。实际项目中,我会先确保基本显示正常,再逐步添加高级功能。