4. SPI 通信协议详解
SPI(Serial Peripheral Interface,串行外设接口)是一种高速、全双工、同步的串行通信总线。在 LCD TFT 驱动中,SPI 常用于传输初始化命令、配置寄存器以及低速图像数据(如 ILI9341/ST7789 等驱动 IC 的初始化序列)。本章将深入剖析 SPI 的四线模式、时钟极性与相位、主从模式以及 STM32 的配置方法。
4.1 SPI 四线模式
标准 SPI 总线使用四根信号线实现全双工通信,各信号线定义如下:
| 信号线 | 全称 | 方向(主→从) | 功能描述 |
|---|---|---|---|
| SCLK | Serial Clock | 主→从 | 由主机产生的时钟信号,同步数据传输 |
| MOSI | Master Out Slave In | 主→从 | 主机输出数据线,从机接收 |
| MISO | Master In Slave Out | 从→主 | 从机输出数据线,主机接收 |
| CS/SS | Chip Select / Slave Select | 主→从 | 片选信号,低电平有效,选中特定从机 |
LCD 驱动中的 SPI 变体: 许多 LCD 驱动 IC(如 ILI9341)支持 3 线 SPI 模式(无 MISO),或 4 线 SPI + D/CX 模式。D/CX 线用于区分传输的是命令(Command)还是数据(Data),这是 LCD 驱动中非常关键的扩展。
4.2 时钟极性 (CPOL) 与相位 (CPHA)
SPI 协议通过配置时钟极性(CPOL)和时钟相位(CPHA)来定义数据采样与切换的时序关系,共有四种工作模式。
- CPOL:定义 SCLK 空闲状态的电平。
- CPOL = 0:空闲时 SCLK 为低电平。
- CPOL = 1:空闲时 SCLK 为高电平。
- CPHA:定义数据采样发生在 SCLK 的哪个边沿。
- CPHA = 0:在第一个边沿(上升沿或下降沿,取决于 CPOL)采样数据。
- CPHA = 1:在第二个边沿采样数据。
四种模式速查表:
| 模式 | CPOL | CPHA | 空闲时钟电平 | 数据采样边沿 | 数据切换边沿 |
|---|---|---|---|---|---|
| 模式 0 | 0 | 0 | 低 | 上升沿 | 下降沿 |
| 模式 1 | 0 | 1 | 低 | 下降沿 | 上升沿 |
| 模式 2 | 1 | 0 | 高 | 下降沿 | 上升沿 |
| 模式 3 | 1 | 1 | 高 | 上升沿 | 下降沿 |
常见误区: 多数 LCD 驱动 IC(如 ILI9341、ST7789)默认使用 SPI 模式 0(CPOL=0, CPHA=0)。但务必查阅具体芯片的数据手册,错误配置 CPOL/CPHA 会导致通信数据完全错位。
4.3 SPI 主从模式
SPI 总线采用主从(Master-Slave)架构,通信始终由主机发起和控制。
- 主机 (Master):产生 SCLK 时钟信号,控制 CS 片选,决定通信时序。通常 MCU(如 STM32)作为主机。
- 从机 (Slave):被动接收 SCLK 和 CS 信号,在主机控制下发送或接收数据。LCD 驱动 IC 通常作为从机。
多从机连接时,每个从机独占一根 CS 线,主机通过拉低对应 CS 线来选择通信目标。MOSI 和 MISO 线可以共享。
4.4 STM32 SPI 配置
以 STM32F4 系列为例,使用 HAL 库配置 SPI 外设的典型步骤如下:
- 使能 SPI 及 GPIO 时钟:通过 RCC 开启 SPI 外设时钟和对应引脚 GPIO 时钟。
- 配置 GPIO 复用功能:将 SCLK、MOSI、MISO、CS 引脚设置为复用推挽输出/输入模式。
- 初始化 SPI 句柄:设置 SPI 模式(主/从)、数据大小(8/16位)、CPOL/CPHA、波特率预分频、MSB/LSB 优先等参数。
- 使能 SPI 外设:调用 HAL_SPI_Init() 完成初始化。
- 收发数据:使用 HAL_SPI_Transmit()、HAL_SPI_Receive() 或 HAL_SPI_TransmitReceive() 进行全双工通信。
STM32 HAL 库 SPI 配置示例代码:
// SPI 句柄定义
SPI_HandleTypeDef hspi1;
// SPI 初始化函数
void MX_SPI1_Init(void)
{
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER; // 主机模式
hspi1.Init.Direction = SPI_DIRECTION_2LINES; // 全双工
hspi1.Init.DataSize = SPI_DATASIZE_8BIT; // 8 位数据
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; // CPOL = 0
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; // CPHA = 0 (模式 0)
hspi1.Init.NSS = SPI_NSS_SOFT; // 软件管理 CS
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16; // 分频
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; // MSB 先行
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi1.Init.CRCPolynomial = 10;
if (HAL_SPI_Init(&hspi1) != HAL_OK)
{
Error_Handler(); // 初始化失败处理
}
}
// GPIO 配置(以 PB3=SCLK, PB4=MISO, PB5=MOSI, PA4=CS 为例)
void HAL_SPI_MspInit(SPI_HandleTypeDef* hspi)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if (hspi->Instance == SPI1)
{
__HAL_RCC_SPI1_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
// SCLK, MOSI, MISO 复用推挽输出
GPIO_InitStruct.Pin = GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
// CS 引脚(软件控制,推挽输出)
GPIO_InitStruct.Pin = GPIO_PIN_4;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); // CS 默认高电平
}
}
// 发送数据示例
void SPI_WriteByte(uint8_t data)
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); // CS 拉低
HAL_SPI_Transmit(&hspi1, &data, 1, HAL_MAX_DELAY);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); // CS 拉高
}
配置避坑指南:
- 波特率:SPI 时钟频率不能超过从机支持的最大频率(LCD 驱动 IC 通常为 10~40 MHz)。
- CS 控制:使用软件 NSS 时,务必在每次传输前后手动拉低/拉高 CS 引脚。
- 数据大小:LCD 驱动 IC 通常使用 8 位 SPI 传输,但部分命令/参数需组合成 16 位。
- 全双工 vs 半双工:若 LCD 只写不读(如仅发送命令和像素数据),可配置为 2 线只发模式以节省引脚。