从零驱动1.3寸TFT:基于STM32的SPI屏显实战笔记
1. 硬件准备与连接第一次接触1.3寸TFT屏时我被它小巧的尺寸和丰富的显示效果惊艳到了。这块240x240分辨率的屏幕虽然比不上手机屏幕细腻但对于嵌入式项目来说已经足够强大。我手头的这块屏采用ST7789V驱动芯片通过精简版SPI接口通信只需要7根线就能搞定。屏幕的引脚排列非常标准GND接地VCC3.3V供电SCL时钟线SDA数据线RES复位线DC数据/命令选择线BLK背光控制我用的是STM32F103C6T6这款性价比超高的单片机具体连接方式如下#define LCD_BLK_PIN GPIO_Pin_5 // PB5 #define LCD_DC_PIN GPIO_Pin_6 // PB6 #define LCD_RST_PIN GPIO_Pin_7 // PB7 #define LCD_SDA_PIN GPIO_Pin_8 // PB8 #define LCD_SCL_PIN GPIO_Pin_9 // PB9这里有个小技巧所有GPIO都应配置为推挽输出模式但初始电平有讲究。SCL和SDA需要初始化为高电平因为SPI协议规定时钟线在空闲状态要保持高电平。而RES、DC和BLK则初始化为低电平避免屏幕在上电时出现异常状态。2. GPIO模拟SPI的实现由于STM32F103C6T6的硬件SPI可能被其他外设占用我选择用GPIO模拟SPI协议。这种方式虽然速度稍慢但胜在灵活可控。ST7789V的SPI时序有个特点在时钟上升沿采样数据所以我们的代码要确保数据在时钟线从低变高时保持稳定。这是我最开始写的传输函数void LCD_WriteByte(uint8_t data) { for(uint8_t i0; i8; i) { LCD_SCL_Low(); if(data 0x80) LCD_SDA_High(); else LCD_SDA_Low(); LCD_SCL_High(); data 1; } }实际测试时发现屏幕偶尔会出现花屏后来才明白是时序问题。ST7789V对时序要求比较严格在两个字节传输之间需要加入微小延时。修改后的版本增加了延时void LCD_WriteByte(uint8_t data) { for(uint8_t i0; i8; i) { LCD_SCL_Low(); Delay_us(1); // 关键延时 if(data 0x80) LCD_SDA_High(); else LCD_SDA_Low(); LCD_SCL_High(); Delay_us(1); // 关键延时 data 1; } }3. 屏幕初始化详解屏幕初始化是个精细活ST7789V有几十个寄存器需要配置。我花了整整一天时间才调通所有参数。初始化流程大致分为以下几个步骤硬件复位拉低RES引脚至少10ms退出睡眠模式设置像素格式我选择16位RGB565配置伽马曲线开启显示最关键的像素格式设置命令是0x3A参数0x55表示16位色0x66表示18位色。我推荐使用16位色因为18位色会显著增加传输数据量但视觉效果提升不明显。void LCD_Init(void) { // 硬件复位 LCD_RST_Low(); Delay_ms(20); LCD_RST_High(); Delay_ms(20); // 背光开启 LCD_BLK_High(); // 设置像素格式 LCD_WriteCmd(0x3A); LCD_WriteData(0x55); // 16位RGB // 更多初始化命令... LCD_WriteCmd(0x11); // 退出睡眠 Delay_ms(120); LCD_WriteCmd(0x29); // 开启显示 }有个坑我踩过初始化后必须等待120ms以上才能发送其他命令否则屏幕可能无法正常响应。这个延时在数据手册里写得很小但实际需要更长。4. 显示区域设置与绘图要在屏幕上显示内容首先需要设置操作区域。ST7789V使用4个命令来定义矩形区域0x2A设置列地址X坐标0x2B设置行地址Y坐标0x2C开始写入显存我封装了一个区域设置函数void LCD_SetWindow(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) { LCD_WriteCmd(0x2A); LCD_WriteData(x1 8); LCD_WriteData(x1 0xFF); LCD_WriteData(x2 8); LCD_WriteData(x2 0xFF); LCD_WriteCmd(0x2B); LCD_WriteData(y1 8); LCD_WriteData(y1 0xFF); LCD_WriteData(y2 8); LCD_WriteData(y2 0xFF); LCD_WriteCmd(0x2C); // 准备写入数据 }清屏函数就是设置全屏区域后填充颜色void LCD_Clear(uint16_t color) { LCD_SetWindow(0, 0, LCD_WIDTH-1, LCD_HEIGHT-1); for(uint32_t i0; iLCD_WIDTH*LCD_HEIGHT; i) { LCD_WriteData(color 8); LCD_WriteData(color 0xFF); } }画线函数稍微复杂些需要考虑水平线、垂直线和斜线的情况。以水平线为例void LCD_DrawHLine(uint16_t x, uint16_t y, uint16_t len, uint16_t color) { LCD_SetWindow(x, y, xlen-1, y); for(uint16_t i0; ilen; i) { LCD_WriteData(color 8); LCD_WriteData(color 0xFF); } }5. 字符与图形显示实战显示字符需要先准备好字模库。我使用8x16的点阵字库每个字符占用16字节。比如显示字符Aconst uint8_t font8x16[] { 0x00,0x00,0x18,0x3C,0x66,0x66,0x7E,0x66, 0x66,0x66,0x66,0x66,0x00,0x00,0x00,0x00 // A }; void LCD_DrawChar(uint16_t x, uint16_t y, char c, uint16_t color) { uint8_t i,j; uint8_t pixel; c - 32; // ASCII码偏移 LCD_SetWindow(x, y, x7, y15); for(i0; i16; i) { pixel font8x16[c*16 i]; for(j0; j8; j) { if(pixel (1(7-j))) { LCD_WriteData(color 8); LCD_WriteData(color 0xFF); } else { LCD_WriteData(0x00); LCD_WriteData(0x00); } } } }显示字符串就是逐个显示字符void LCD_Print(uint16_t x, uint16_t y, char *str, uint16_t color) { while(*str) { LCD_DrawChar(x, y, *str, color); x 8; if(x LCD_WIDTH-8) { x 0; y 16; } } }显示数字需要先转换成字符串void LCD_PrintNum(uint16_t x, uint16_t y, uint32_t num, uint16_t color) { char buf[10]; sprintf(buf, %lu, num); LCD_Print(x, y, buf, color); }6. 性能优化技巧经过一段时间的使用我总结出几个提升显示性能的技巧批量写入设置好区域后连续写入多个像素数据减少命令开销双缓冲在内存中维护一个屏幕缓冲区修改后再整体刷新局部刷新只更新屏幕上变化的部分而不是全屏刷新DMA传输如果使用硬件SPI可以启用DMA减少CPU占用比如改进后的清屏函数void LCD_FastClear(uint16_t color) { LCD_SetWindow(0, 0, LCD_WIDTH-1, LCD_HEIGHT-1); uint8_t hi color 8; uint8_t lo color 0xFF; for(uint32_t i0; iLCD_WIDTH*LCD_HEIGHT; i) { LCD_SDA hi; LCD_SCL_High(); LCD_SCL_Low(); LCD_SDA lo; LCD_SCL_High(); LCD_SCL_Low(); } }这个版本比原始版本快约30%因为它减少了函数调用次数直接操作GPIO寄存器。7. 常见问题排查在调试过程中我遇到过各种奇怪的问题这里分享几个典型案例问题1屏幕全白或有条纹检查电源是否稳定3.3V供电不足会导致异常确认复位时序正确RES引脚要有足够的低电平时间检查SPI时序特别是时钟极性是否符合ST7789V要求问题2显示内容错位确认屏幕分辨率设置正确240x240检查区域设置命令的参数顺序确保像素格式与代码设置一致问题3显示颜色异常检查RGB格式设置5-6-5或6-6-6确认伽马校正参数是否正确测试基础颜色红、绿、蓝是否正常显示问题4屏幕闪烁增加电源滤波电容检查背光电路是否稳定降低SPI时钟频率试试记得每次修改只调整一个参数这样才能准确定位问题根源。调试时可以用逻辑分析仪抓取SPI波形这是最直接的诊断方法。

相关新闻