第2章:显示接口协议基础

做嵌入式显示驱动,说白了就是跟各种接口协议打交道。我刚开始接触这块时,总觉得协议就是一堆时序图,看着就头疼。后来踩的坑多了,才明白——搞懂协议,就是搞懂怎么跟外设「说话」。

这一章,咱们重点聊三个最常见的显示接口:SPI、I2C、LVDS。嗯,一个一个来。

2.1 SPI协议详解

SPI,全称Serial Peripheral Interface。我习惯叫它「四线制」协议。为什么?因为它至少需要四根线:

  • SCLK:时钟线,由主机控制
  • MOSI:主机输出,从机输入
  • MISO:主机输入,从机输出
  • CS:片选线,低电平有效

你想想看,四根线就能搞定全双工通信,效率确实高。我在项目中遇到过用SPI驱动128×64的OLED屏,刷新率能做到30fps以上,完全够用。

2.1.1 SPI的四种模式

这里有个坑,我刚开始做时经常搞混——SPI有四种工作模式。区别在于时钟极性(CPOL)和时钟相位(CPHA)的组合:

模式 CPOL CPHA 特点
模式0 0 0 空闲时低电平,上升沿采样
模式1 0 1 空闲时低电平,下降沿采样
模式2 1 0 空闲时高电平,下降沿采样
模式3 1 1 空闲时高电平,上升沿采样

我个人习惯的做法是:先看从机数据手册,找到它支持的SPI模式。如果手册没写,就用模式0试,因为大部分器件默认就是模式0。

注意:我曾经因为SPI模式不匹配,折腾了整整一个下午。现象是数据能发出去,但读回来的全是0xFF。后来用逻辑分析仪一看,才发现采样沿搞反了。

2.1.2 SPI驱动OLED的代码示例

以SSD1306 OLED驱动为例,初始化时序是这样的:

// SPI初始化
void SPI_Init(void) {
    // 设置SCLK、MOSI为推挽输出
    // 设置CS为推挽输出
    // 设置MISO为浮空输入
    // 时钟频率设为4MHz(OLED一般支持到10MHz)
}

// 写一个字节
void SPI_WriteByte(uint8_t data) {
    for(uint8_t i = 0; i < 8; i++) {
        SCLK_LOW();
        if(data & 0x80) MOSI_HIGH();
        else MOSI_LOW();
        data <<= 1;
        SCLK_HIGH();  // 上升沿采样
    }
}

// 写命令
void OLED_WriteCmd(uint8_t cmd) {
    DC_LOW();        // 命令模式
    CS_LOW();
    SPI_WriteByte(cmd);
    CS_HIGH();
}

// 写数据
void OLED_WriteData(uint8_t data) {
    DC_HIGH();       // 数据模式
    CS_LOW();
    SPI_WriteByte(data);
    CS_HIGH();
}
小技巧:实际项目中,我建议用硬件SPI而不是软件模拟。硬件SPI有FIFO和DMA支持,CPU占用率能降低80%以上。特别是刷全屏时,差距非常明显。

2.2 I2C协议详解

I2C跟SPI最大的区别是什么?它只需要两根线:SDA(数据线)和SCL(时钟线)。但代价是速度慢一些,而且协议更复杂。

我刚开始用I2C时,最头疼的就是那个「起始条件」和「停止条件」。说白了就是:

  • 起始条件:SCL高电平时,SDA从高变低
  • 停止条件:SCL高电平时,SDA从低变高

嗯,记住这个口诀就行:「高高低低是起始,低低高高是停止」。

2.2.1 I2C的地址和读写

每个I2C从设备都有一个7位或10位地址。以SSD1306为例,它的7位地址是0x3C(如果DC引脚接地)。读写操作是这样的:

操作 发送数据 说明
写命令 0x00 + 命令字节 控制字节最低位为0
写数据 0x40 + 数据字节 控制字节最低位为1
读数据 先写地址,再读数据 需要重新发送起始条件
核心要点:I2C的每个字节后面都要跟一个ACK应答位。如果从机没应答,说明设备没在线或者地址错了。我调试时经常用这个来快速判断硬件连接是否正常。

2.2.2 I2C的速率选择

I2C有三种标准速率:

  • 标准模式:100kHz,最常用,兼容性最好
  • 快速模式:400kHz,大多数器件支持
  • 高速模式:3.4MHz,需要特殊硬件支持

我个人习惯:调试阶段先用100kHz,等通信稳定了再往上提。为什么?因为速率越高,对PCB走线和上拉电阻的要求越严格。我曾经在快速模式下遇到数据错位,最后发现是上拉电阻从4.7kΩ换到2.2kΩ就解决了。

避坑指南:I2C总线上的电容不能太大。每增加1米线长,大约增加100pF电容。超过400pF时,信号上升沿会变缓,导致通信失败。我建议总线长度控制在30cm以内。

2.3 LVDS协议简介

LVDS,Low-Voltage Differential Signaling。说白了就是「低压差分信号」。跟SPI、I2C的单端信号不同,LVDS用两根线传一个信号,一根正、一根负。

为什么这么做?抗干扰能力强。你想想看,外部噪声同时耦合到两根线上,接收端一相减,噪声就抵消了。这就是差分信号的优势。

2.3.1 LVDS在显示中的应用

LVDS主要用于大尺寸、高分辨率的显示屏。比如7寸以上的TFT屏,分辨率1024×600或更高。我做过一个项目,用LVDS驱动10.1寸的屏,分辨率1280×800,刷新率60Hz,效果非常流畅。

LVDS的典型信号组包括:

  • 4对数据线:传输RGB数据和控制信号
  • 1对时钟线:提供像素时钟
  • 可选的第5对数据线:用于更高色深(如8位色)

2.3.2 LVDS的电气特性

参数 典型值 说明
差分电压 350mV 正负线之间的电压差
共模电压 1.2V 两根线对地的平均电压
传输速率 可达1Gbps 每对数据线
功耗 约100mW 每对线,远低于TTL电平
经验之谈:LVDS的PCB走线要特别注意。我建议:差分对内的两根线等长,误差控制在5mil以内;差分对之间间距保持3倍线宽以上;尽量少打过孔,如果必须过孔,要成对打。

2.3.3 什么时候选LVDS?

我一般这样判断:

  • 分辨率超过800×480,用LVDS
  • 传输距离超过30cm,用LVDS
  • 对EMI有严格要求,用LVDS
  • 否则,用SPI或RGB接口更简单

嗯,说白了就是:小屏用SPI,中屏用RGB,大屏用LVDS。这个经验法则在我做过的十几个项目中都适用。

总结一下:SPI适合小尺寸、低分辨率的屏,驱动简单,速率适中;I2C适合对引脚数有严格限制的场景,但速度慢;LVDS适合大尺寸、高分辨率的屏,抗干扰能力强,但硬件设计更复杂。选哪个,看你的项目需求。

下一章,咱们聊聊RGB接口和MIPI DSI。这两个在嵌入式显示驱动里也经常用到,特别是MIPI DSI,现在手机和平板上几乎都是它了。