2、SPI接口基础:四线制、时钟极性与相位、多线模式
SPI接口,说白了就是显示驱动芯片最常用的“门铃”。
我刚开始做驱动芯片开发那会儿,总觉得SPI太简单,不就是四根线嘛。结果第一次调屏,画面死活刷不出来,折腾了两天才发现是时钟相位配错了。嗯,从那以后我再也不敢小看这个接口了。
2.1 四线制:SCLK、MOSI、MISO、CS
标准的SPI有四根信号线,每一根都有自己的活要干:
- SCLK(串行时钟):由主设备产生,决定数据传输的节奏。我习惯叫它“心跳线”。
- MOSI(主出从入):主设备发送数据给从设备。在显示驱动芯片里,大部分指令和数据都走这根线。
- MISO(主入从出):从设备回传数据给主设备。读寄存器状态、读帧缓冲都用它。
- CS(片选):低电平有效。拉低时,从设备才“听”得见时钟和数据。
关键点:显示驱动芯片通常只写不读,所以很多方案直接把MISO省掉了。但如果你要做参数回读或者调试,MISO就是救命线。
我曾经在一个项目里,为了省一根IO,把MISO砍了。结果量产时发现有一批屏的gamma值偏了,没法回读校准,只能拆机换屏。教训啊。
2.2 时钟极性与相位(CPOL、CPHA)
这两个参数,是SPI最容易踩坑的地方。
- CPOL(时钟极性):决定空闲时SCLK是高电平还是低电平。
- CPHA(时钟相位):决定数据是在时钟的上升沿还是下降沿被采样。
组合起来有四种模式:
| 模式 | CPOL | CPHA | 采样沿 | 空闲电平 |
|---|---|---|---|---|
| 模式0 | 0 | 0 | 上升沿 | 低 |
| 模式1 | 0 | 1 | 下降沿 | 低 |
| 模式2 | 1 | 0 | 上升沿 | 高 |
| 模式3 | 1 | 1 | 下降沿 | 高 |
注意:显示驱动芯片最常用的是模式0和模式3。我建议你拿到芯片手册后,第一件事就是确认CPOL和CPHA。别问我怎么知道的——我烧过三块屏才记住这个教训。
为什么会这样?因为很多国产屏厂的手册写得含糊,只写“SPI接口”,不写具体模式。你只能看时序图,数数采样沿在哪。
2.3 单线/双线/四线模式
标准SPI是单线模式,也就是MOSI发、MISO收。但显示驱动芯片为了刷屏速度,搞出了多线模式。
- 单线模式(Standard SPI):一根数据线发,一根数据线收。速度一般,但兼容性最好。
- 双线模式(Dual SPI):把MOSI和MISO都变成双向数据线。一次传2个bit,速度翻倍。
- 四线模式(Quad SPI):再加两根数据线(通常叫IO2、IO3)。一次传4个bit,刷屏速度起飞。
我做过一个手表项目,用单线SPI刷128x128的屏,帧率只有15fps,拖影严重。换成四线模式后,直接干到60fps。你想想看,同样的晶振频率,数据吞吐量差了4倍。
实战技巧:多线模式通常需要先发一个命令字节,告诉芯片“我要切到双线/四线模式了”。这个命令只能用单线模式发。我习惯在初始化代码里先配好模式,后面全速跑数据。
2.4 知识体系总览
下面这张图,是我自己画的一个SPI接口知识结构。你可以把它当成一个快速索引:
2.5 代码示例:初始化SPI为模式0
下面是我在一个LCD驱动项目里实际用过的初始化代码。注意看CPOL和CPHA的配置:
// SPI初始化 - 模式0 (CPOL=0, CPHA=0)
void spi_init_mode0(void) {
// 配置GPIO
GPIO_InitTypeDef gpio = {0};
gpio.Pin = SCLK_PIN | MOSI_PIN | CS_PIN;
gpio.Mode = GPIO_MODE_AF_PP;
gpio.Pull = GPIO_PULLUP;
gpio.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &gpio);
// 配置SPI外设
SPI_HandleTypeDef hspi = {0};
hspi.Instance = SPI1;
hspi.Init.Mode = SPI_MODE_MASTER;
hspi.Init.Direction = SPI_DIRECTION_2LINES;
hspi.Init.DataSize = SPI_DATASIZE_8BIT;
hspi.Init.CLKPolarity = SPI_POLARITY_LOW; // CPOL = 0
hspi.Init.CLKPhase = SPI_PHASE_1EDGE; // CPHA = 0
hspi.Init.NSS = SPI_NSS_SOFT;
hspi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;
hspi.Init.FirstBit = SPI_FIRSTBIT_MSB;
HAL_SPI_Init(&hspi);
// 拉高CS,进入空闲状态
HAL_GPIO_WritePin(CS_PORT, CS_PIN, GPIO_PIN_SET);
}
个人习惯:我一般把CPOL和CPHA的配置单独写成宏定义,方便不同屏之间切换。比如:
#define LCD_SPI_CPOL 0
#define LCD_SPI_CPHA 0
换屏时只改这两个宏,不用动初始化函数。
2.6 多线模式切换示例
四线模式刷屏时,命令和数据的发送方式不一样。看这段代码:
// 发送命令(必须用单线模式)
void lcd_send_cmd(uint8_t cmd) {
HAL_GPIO_WritePin(DC_PORT, DC_PIN, GPIO_PIN_RESET); // 命令模式
HAL_GPIO_WritePin(CS_PORT, CS_PIN, GPIO_PIN_RESET); // 拉低片选
HAL_SPI_Transmit(&hspi, &cmd, 1, 100);
HAL_GPIO_WritePin(CS_PORT, CS_PIN, GPIO_PIN_SET); // 拉高片选
}
// 发送像素数据(四线模式)
void lcd_send_pixels_quad(uint8_t *data, uint32_t len) {
HAL_GPIO_WritePin(DC_PORT, DC_PIN, GPIO_PIN_SET); // 数据模式
HAL_GPIO_WritePin(CS_PORT, CS_PIN, GPIO_PIN_RESET);
// 切换到四线模式(具体命令看芯片手册)
lcd_send_cmd(0x3A); // 四线模式使能命令
// 用四线方式发送数据
HAL_SPI_Transmit(&hspi, data, len, 1000);
HAL_GPIO_WritePin(CS_PORT, CS_PIN, GPIO_PIN_SET);
}
我曾经踩过的坑:四线模式下,数据线的方向会变化。如果你用的是GPIO模拟SPI,记得在发送和接收之间切换IO方向。硬件SPI外设会自动处理,但软件模拟的话,这一步很容易忘。
好了,SPI接口的基础就这些。说白了就是四根线、四种模式、三种速度。实际项目中,你只要把CPOL和CPHA配对了,剩下的就是调时钟频率和看波形了。
公众号:蓝海资料掘金营,微信deep3321