1. 三线SPI驱动ST7789V的硬件挑战
第一次拿到三线SPI接口的ST7789V屏幕时,我整个人都是懵的。和常见的四线SPI不同,这个屏幕只有SDA、CLK和CS三根线,缺少了最关键的数据/命令选择线(DC)。这意味着我们需要在硬件层面解决一个棘手问题:如何用标准SPI协议传输9位数据(1位命令/数据标识+8位有效数据)。
标准SPI协议通常只支持8位或16位数据传输,而ST7789V要求的第一位标识位(0表示命令,1表示数据)直接打破了常规。我在Nordic芯片上实测发现,直接发送9位数据会导致屏幕无法识别。后来翻阅手册才发现,ST7789V的SPI模式其实是通过在8位数据前插入标识位实现的特殊协议。
2. 硬件连接方案设计
2.1 引脚定义与连接
典型的ST7789V三线SPI接口包含以下关键引脚:
- SDA:双向数据线(MOSI)
- SCL:时钟线(SCK)
- CS:片选信号(低电平有效)
在STM32上的推荐连接方式:
| 屏幕引脚 | STM32引脚 | 备注 |
|---|---|---|
| SDA | PA7 | SPI1_MOSI |
| SCL | PA5 | SPI1_SCK |
| CS | PA4 | 软件控制NSS |
| RESET | PB1 | 硬件复位 |
| BLK | PB0 | 背光控制(可选) |
2.2 硬件SPI配置要点
在CubeMX中配置SPI时需要注意:
- 选择全双工主模式
- 时钟极性(CPOL)设为低电平
- 时钟相位(CPHA)设为1边沿采样
- 数据宽度设置为8位
- NSS信号模式选择软件控制
// STM32 HAL库SPI初始化示例 hspi1.Instance = SPI1; hspi1.Init.Mode = SPI_MODE_MASTER; hspi1.Init.Direction = SPI_DIRECTION_2LINES; hspi1.Init.DataSize = SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; hspi1.Init.NSS = SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4; HAL_SPI_Init(&hspi1);3. 9位SPI协议的特殊处理
3.1 数据打包方案
经过多次实验,我总结出两种可行的9位数据传输方案:
方案A:双字节传输法
- 将9位数据拆分为两个字节发送
- 第一个字节包含标识位+高7位数据
- 第二个字节包含剩余1位数据+7位填充0
void SPI_Send9Bit(uint8_t is_data, uint8_t value) { uint16_t packet = (is_data ? 0x0100 : 0x0000) | value; uint8_t buf[2] = { (packet >> 7) & 0xFF, // 发送标识位+高7位 (packet << 1) & 0xFF // 发送剩余1位+7个0 }; HAL_SPI_Transmit(&hspi1, buf, 2, HAL_MAX_DELAY); }方案B:16位模式利用
- 配置SPI为16位模式
- 将9位数据左移7位形成16位数据包
- 额外补7个0作为填充
void SPI_Send9Bit_16Mode(uint8_t is_data, uint8_t value) { uint16_t packet = ((is_data ? 0x0100 : 0x0000) | value) << 7; HAL_SPI_Transmit(&hspi1, (uint8_t*)&packet, 2, HAL_MAX_DELAY); }3.2 性能对比测试
在STM32F407上实测不同方案的传输效率:
| 方案 | 传输速率 | CPU占用率 | 适用场景 |
|---|---|---|---|
| 8位双字节 | 1.2Mbps | 中等 | 通用场景 |
| 16位模式 | 2.4Mbps | 低 | 高性能需求 |
| DMA+16位模式 | 4.8Mbps | 极低 | 视频流等实时应用 |
4. 颜色数据传输优化
4.1 RGB565格式处理
ST7789V支持16位RGB565颜色格式,需要将颜色数据拆分为三个8位段发送:
void LCD_SendColor(uint16_t color) { uint8_t buf[3]; // 第一字节:标识位1 + R高5位 + G高2位 buf[0] = 0x80 | (color >> 10); // 第二字节:G中间3位 + B高5位 buf[1] = ((color >> 5) & 0x07) | ((color << 3) & 0xE0); // 第三字节:标识位1 + B低3位 + 填充 buf[2] = 0x80 | (color & 0x1F); HAL_SPI_Transmit(&hspi1, buf, 3, HAL_MAX_DELAY); }4.2 DMA加速方案
对于需要高速刷新的场景,建议使用DMA传输:
// DMA配置 hdma_spi1_tx.Instance = DMA2_Stream3; hdma_spi1_tx.Init.Channel = DMA_CHANNEL_3; hdma_spi1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH; HAL_DMA_Init(&hdma_spi1_tx); __HAL_LINKDMA(&hspi1, hdmatx, hdma_spi1_tx); // DMA传输函数 void LCD_FillDMA(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color) { LCD_SetWindow(x1, y1, x2, y2); uint8_t buf[3]; // 同上颜色打包逻辑 HAL_SPI_Transmit_DMA(&hspi1, buf, 3); }5. 实际项目调优经验
5.1 时序优化技巧
- 时钟分频选择:SPI时钟建议设置在10-20MHz之间,过高会导致信号失真
- CS信号延迟:每次传输后保持CS高电平至少50ns
- 复位时序:硬件复位后需要延迟120ms再初始化
void LCD_Reset() { HAL_GPIO_WritePin(LCD_RST_GPIO_Port, LCD_RST_Pin, GPIO_PIN_RESET); HAL_Delay(120); HAL_GPIO_WritePin(LCD_RST_GPIO_Port, LCD_RST_Pin, GPIO_PIN_SET); HAL_Delay(120); }5.2 常见问题排查
花屏问题:
- 检查SPI时钟相位设置
- 确认电源电压稳定(3.3V±5%)
- 检查PCB走线长度(建议<10cm)
数据传输错误:
- 用逻辑分析仪捕获SPI波形
- 检查接地是否良好
- 尝试降低SPI时钟频率
显示偏移:
- 重新校准显示区域参数
- 检查初始化代码中的扫描方向设置
在最近的一个智能家居项目中,我们通过优化SPI时序将240x240屏幕的刷新率从15fps提升到了28fps,关键是把16位DMA传输与双缓冲机制结合使用。具体做法是开辟两个显示缓冲区,当一个缓冲区通过DMA传输时,CPU可以准备下一帧数据,这种乒乓操作显著提升了显示流畅度。