STM32驱动1.8寸TFT彩屏:从模拟SPI到硬件SPI的实战指南(标准库与HAL库对比)
1. 硬件准备与基础概念1.8寸TFT彩屏是嵌入式开发中常用的显示设备采用SPI接口通信。我刚开始接触这类屏幕时用的也是模拟SPI方式后来发现硬件SPI能大幅提升性能。先说说硬件准备市面上常见的1.8寸TFT屏驱动芯片多为ST7735S或ILI9163引脚定义基本一致VCC3.3V或5V供电GND地线SCL时钟线SDA数据线RES复位线DC数据/命令选择线CS片选线BL背光控制我用的是STM32F103C8T6最小系统板也就是大家常说的蓝板。选择它是因为资源丰富性价比高而且网上资料多。这里有个坑要注意有些屏幕的背光需要单独供电如果接上电源发现屏幕不亮先检查背光是否接好。2. 从模拟SPI到硬件SPI的转变模拟SPI确实容易上手我用GPIO口模拟时序也能让屏幕正常工作。核心代码就是通过循环移位实现数据发送void SPI_WriteData(u8 Data) { for(int i0; i8; i) { if(Data 0x80) SET_SDA(); else CLR_SDA(); CLR_SCL(); delay_us(1); SET_SCL(); Data 1; } }但实测发现刷新率只有12fps左右刷个图片明显卡顿。改用硬件SPI后同样的操作能达到45fps提升近4倍硬件SPI利用了STM32内置的硬件加速器不仅释放了CPU资源还能通过DMA实现无阻塞传输。3. 标准库硬件SPI配置在标准库环境下配置硬件SPI需要先初始化GPIO和SPI外设。我用的是SPI1对应引脚PA5(SCK)、PA6(MISO)、PA7(MOSI)。配置步骤使能GPIO和SPI时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE);配置GPIO为复用推挽输出GPIO_InitStructure.GPIO_Pin GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_Init(GPIOA, 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; SPI_InitStructure.SPI_CPOL SPI_CPOL_Low; SPI_InitStructure.SPI_CPHA SPI_CPHA_1Edge; SPI_InitStructure.SPI_NSS SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_4; SPI_Init(SPI1, SPI_InitStructure); SPI_Cmd(SPI1, ENABLE);这里有个关键点SPI时钟分频系数决定了通信速率。我测试发现分频系数设为4时(主频72MHz下SPI时钟18MHz)屏幕工作最稳定。再提高速率会出现雪花点。4. HAL库硬件SPI实现HAL库的配置更简单用CubeMX工具勾选几下就行。不过有些细节需要注意CubeMX中配置SPI为Transmit Only Master数据宽度8bit时钟极性选择Low相位选择1 Edge片选信号(NSS)选择Software预分频系数建议先设为8后续再调整生成代码后发送数据的函数变为void SPI_WriteData(uint8_t Data) { HAL_SPI_Transmit(hspi1, Data, 1, 100); }HAL库的优势是代码更简洁但实测发现直接调用HAL_SPI_Transmit效率不如标准库。解决方案是启用DMA传输HAL_SPI_Transmit_DMA(hspi1, buffer, length);使用DMA后刷屏速度能达到60fpsCPU占用率几乎为零。不过要注意DMA缓冲区的大小设置太小会导致传输不完整。5. 性能优化实战技巧经过多次测试我总结了几个提升TFT刷新率的技巧批量传输优化不要逐点发送数据而是整行或整块传输。将显示数据先存入数组然后一次性发送。内存布局优化使用__attribute__((aligned(4)))确保DMA缓冲区4字节对齐能提升20%传输速度。时序调整不同屏幕对SPI时序要求不同。我的经验是复位信号(RES)保持低电平至少10ms命令和数据之间延迟至少1us初始化序列结束后延迟120ms再发送图像数据双缓冲技术在内存中维护两个显示缓冲区一个用于绘制一个用于显示通过DMA交替切换实现无闪烁动画。实测数据显示模拟SPI12fps 72MHz标准库硬件SPI45fps 18MHzHAL库DMA60fps 36MHz6. 标准库与HAL库深度对比两种开发方式各有优劣我做了个详细对比特性标准库HAL库初始化复杂度需要手动配置所有寄存器CubeMX可视化配置执行效率高中等但DMA可弥补代码体积小(约5KB)大(约15KB)移植性差需修改硬件相关代码好硬件抽象层隔离开发效率低高中断处理需自行编写中断服务程序提供完整回调机制维护成本高低个人建议如果是学习目的先用标准库理解底层原理实际项目开发推荐HAL库特别是需要快速迭代时。7. 常见问题与解决方案在项目开发中遇到过不少坑这里分享几个典型问题的解决方法问题1屏幕显示花屏检查SPI时钟极性(CPOL)和相位(CPHA)设置确认复位时序是否正确测量电源电压是否稳定建议加100uF电容问题2DMA传输不完整检查缓冲区是否4字节对齐确认DMA通道优先级设置增加DMA完成中断回调进行验证问题3刷新率不达标尝试提高SPI时钟频率改用硬件NSS信号代替软件控制优化显示数据预处理算法问题4功耗过高动态调整SPI时钟速率不刷新时关闭背光使用睡眠指令(0x10)降低屏幕功耗8. 进阶应用GUI实现基础掌握了底层驱动后可以进一步实现简单GUI。我的做法是封装几个基础函数// 绘制矩形 void GUI_DrawRect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color) { Lcd_SetRegion(x, y, xw-1, yh-1); for(int i0; iw*h; i) { LCD_WriteData_16Bit(color); } } // 显示16x16图标 void GUI_DrawIcon(uint16_t x, uint16_t y, const uint16_t *icon) { Lcd_SetRegion(x, y, x15, y15); for(int i0; i256; i) { LCD_WriteData_16Bit(icon[i]); } }更复杂的界面可以考虑移植ucGUI或LVGL等开源库。我在项目中移植过LVGL需要实现以下几个底层接口显示缓冲区管理输入设备读取定时器驱动移植成功后就能用LVGL丰富的控件库开发专业级界面了。