STM32驱动1.8寸TFT彩屏:从模拟SPI到硬件SPI的实战指南(标准库与HAL库对比)

1. 硬件准备与基础概念

1.8寸TFT彩屏是嵌入式开发中常用的显示设备,采用SPI接口通信。我刚开始接触这类屏幕时,用的也是模拟SPI方式,后来发现硬件SPI能大幅提升性能。先说说硬件准备,市面上常见的1.8寸TFT屏驱动芯片多为ST7735S或ILI9163,引脚定义基本一致:

  • VCC:3.3V或5V供电
  • GND:地线
  • SCL:时钟线
  • SDA:数据线
  • RES:复位线
  • DC:数据/命令选择线
  • CS:片选线
  • BL:背光控制

我用的是STM32F103C8T6最小系统板,也就是大家常说的"蓝板"。选择它是因为资源丰富,性价比高,而且网上资料多。这里有个坑要注意:有些屏幕的背光需要单独供电,如果接上电源发现屏幕不亮,先检查背光是否接好。

2. 从模拟SPI到硬件SPI的转变

模拟SPI确实容易上手,我用GPIO口模拟时序也能让屏幕正常工作。核心代码就是通过循环移位实现数据发送:

void SPI_WriteData(u8 Data) { for(int i=0; i<8; 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)。配置步骤:

  1. 使能GPIO和SPI时钟:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE);
  1. 配置GPIO为复用推挽输出:
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOA, &GPIO_InitStructure);
  1. 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工具勾选几下就行。不过有些细节需要注意:

  1. CubeMX中配置SPI为"Transmit Only Master",数据宽度8bit
  2. 时钟极性选择Low,相位选择1 Edge
  3. 片选信号(NSS)选择Software
  4. 预分频系数建议先设为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后,刷屏速度能达到60fps,CPU占用率几乎为零。不过要注意DMA缓冲区的大小设置,太小会导致传输不完整。

5. 性能优化实战技巧

经过多次测试,我总结了几个提升TFT刷新率的技巧:

  1. 批量传输优化:不要逐点发送数据,而是整行或整块传输。将显示数据先存入数组,然后一次性发送。

  2. 内存布局优化:使用__attribute__((aligned(4)))确保DMA缓冲区4字节对齐,能提升20%传输速度。

  3. 时序调整:不同屏幕对SPI时序要求不同。我的经验是:

    • 复位信号(RES)保持低电平至少10ms
    • 命令和数据之间延迟至少1us
    • 初始化序列结束后延迟120ms再发送图像数据
  4. 双缓冲技术:在内存中维护两个显示缓冲区,一个用于绘制,一个用于显示,通过DMA交替切换,实现无闪烁动画。

实测数据显示:

  • 模拟SPI:12fps @ 72MHz
  • 标准库硬件SPI:45fps @ 18MHz
  • HAL库+DMA:60fps @ 36MHz

6. 标准库与HAL库深度对比

两种开发方式各有优劣,我做了个详细对比:

特性标准库HAL库
初始化复杂度需要手动配置所有寄存器CubeMX可视化配置
执行效率中等,但DMA可弥补
代码体积小(约5KB)大(约15KB)
移植性差,需修改硬件相关代码好,硬件抽象层隔离
开发效率
中断处理需自行编写中断服务程序提供完整回调机制
维护成本

个人建议:如果是学习目的,先用标准库理解底层原理;实际项目开发推荐HAL库,特别是需要快速迭代时。

7. 常见问题与解决方案

在项目开发中遇到过不少坑,这里分享几个典型问题的解决方法:

问题1:屏幕显示花屏

  • 检查SPI时钟极性(CPOL)和相位(CPHA)设置
  • 确认复位时序是否正确
  • 测量电源电压是否稳定(建议加100uF电容)

问题2:DMA传输不完整

  • 检查缓冲区是否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, x+w-1, y+h-1); for(int i=0; i<w*h; i++) { LCD_WriteData_16Bit(color); } } // 显示16x16图标 void GUI_DrawIcon(uint16_t x, uint16_t y, const uint16_t *icon) { Lcd_SetRegion(x, y, x+15, y+15); for(int i=0; i<256; i++) { LCD_WriteData_16Bit(icon[i]); } }

更复杂的界面可以考虑移植ucGUI或LVGL等开源库。我在项目中移植过LVGL,需要实现以下几个底层接口:

  • 显示缓冲区管理
  • 输入设备读取
  • 定时器驱动

移植成功后,就能用LVGL丰富的控件库开发专业级界面了。