ARM处理器外设驱动SPI与I2C传输驱动开发实战 - 第1节

1.1 SPI协议概述:四线制、主从模式、全双工

SPI,全称Serial Peripheral Interface,是Motorola公司在上世纪80年代推出的一种同步串行通信协议。说实话,这玩意儿虽然年纪不小了,但在嵌入式领域至今仍是主力。我最早接触SPI是在做一款工业数据采集卡的时候,当时需要在主控和ADC之间高速搬运数据,试了一圈,最后还是选了SPI。

1.1.1 四线制信号

SPI用四条线完成通信,每条线各司其职:

  • SCLK(Serial Clock):串行时钟,由主设备产生。时钟频率决定了通信速率。
  • MOSI(Master Out Slave In):主设备输出,从设备输入。数据从主设备流向从设备。
  • MISO(Master In Slave Out):主设备输入,从设备输出。数据从从设备流向主设备。
  • SS(Slave Select):从设备选择线,低电平有效。主设备通过拉低某个从设备的SS线来选中它。

你想想看,四条线就能实现全双工通信,这在硬件设计上是非常经济的。我在项目中见过有人把SS线叫做CS(Chip Select),一回事,别搞混就行。

1.1.2 主从模式

SPI总线上的设备分为主设备和从设备。主设备负责产生时钟信号,并控制所有通信的发起和结束。从设备只能被动响应,不能主动发起通信。

一个主设备可以挂多个从设备,每个从设备独占一条SS线。主设备想跟谁说话,就把谁的SS线拉低,其他从设备的SS线保持高电平,它们就会乖乖待在总线上不动。

我的经验:多从设备场景下,SS线的GPIO资源消耗是个坑。如果你挂8个从设备,就得占用8个GPIO做片选。我建议优先考虑带硬件片选输出的SPI控制器,或者用GPIO扩展芯片来缓解引脚压力。

1.1.3 全双工通信

SPI最让我欣赏的一点就是全双工。什么意思?主设备在发送数据的同时,从设备也在发送数据。一个时钟周期内,MOSI和MISO上各有一位数据在传输,互不干扰。

底层实现其实很简单:主设备和从设备各有一个移位寄存器,时钟的上升沿和下降沿分别负责数据的采样和移位。主设备把数据从MOSI推出去的同时,从设备把数据从MISO推回来。所以SPI的每一次传输,主设备都会收到从设备返回的数据——哪怕你不需要,也得读出来。

避坑指南:我曾经在一个项目中忽略了SPI的接收缓冲区,主设备发完数据后没有及时读取MISO上的数据,结果导致后续数据覆盖了前面的内容,整个采集序列全乱了。记住:SPI是全双工的,发多少就得收多少,别偷懒。

1.2 I2C协议概述:两线制、多主多从、半双工

I2C,Inter-Integrated Circuit,是Philips公司(现在的NXP)开发的。它的设计理念跟SPI完全不同——用最少的线完成最灵活的通信。我刚开始做驱动时,总觉得I2C比SPI复杂,后来用熟了才发现,它的优雅之处恰恰在于那套协议层的设计。

1.2.1 两线制信号

I2C只需要两条线:

  • SCL(Serial Clock):串行时钟线,由主设备控制。
  • SDA(Serial Data):串行数据线,双向传输数据。

这两条线都是开漏输出,外部需要上拉电阻。开漏的好处是多个设备可以共用总线,谁想拉低就拉低,不会发生短路。坏处嘛,就是速度上不去,因为上拉电阻的充放电时间限制了信号跳变的速度。

1.2.2 多主多从与仲裁

I2C支持多个主设备挂在同一条总线上。这听起来很美好,但实际用起来需要小心。多个主设备同时发起通信怎么办?I2C的解决方案是仲裁——谁先拉低SDA,谁就赢得总线控制权。

仲裁机制是这样的:每个主设备在发送数据的同时,会监测SDA上的电平。如果它想发送高电平,但SDA上实际是低电平,说明有其他主设备在拉低总线,它就自动退出,让出控制权。整个过程对上层软件是透明的。

关键点:多主仲裁只发生在起始条件和数据阶段。地址阶段如果出现冲突,仲裁失败的设备必须立即释放总线。我建议新手先别碰多主模式,单主多从已经能覆盖90%的应用场景。

1.2.3 半双工与地址寻址

I2C是半双工的,同一时刻只能有一个方向的数据传输。主设备发送时,从设备只能接收;主设备接收时,从设备才能发送。切换方向需要重新发送起始条件和从设备地址。

每个I2C从设备都有一个7位或10位的地址。主设备在通信开始时,先发送从设备地址和读写位(0表示写,1表示读),然后等待从设备的应答信号(ACK)。如果从设备应答了,说明它在线并且准备好了,后续就可以传输数据。

7位地址最多支持112个设备(保留地址除外),10位地址可以扩展到1024个。实际项目中,7位地址基本够用。

我的习惯:调试I2C设备时,我第一步总是用逻辑分析仪抓起始条件、地址和ACK。如果地址发出去没有ACK回来,90%的情况是地址写错了,或者从设备没上电。别急着怀疑驱动代码,先看波形。

1.3 SPI与I2C对比分析

做项目选型时,SPI和I2C怎么选?我见过不少工程师在这个问题上纠结。下面这张表是我多年经验的总结,你直接拿去用。

对比维度 SPI I2C
引脚数量 4线(SCLK, MOSI, MISO, SS) 2线(SCL, SDA)
通信模式 全双工 半双工
主从关系 单主多从 多主多从
速度范围 典型10MHz~50MHz,最高可达100MHz+ 标准模式100kHz,快速模式400kHz,高速模式3.4MHz
从设备选择 硬件片选线(每设备一根) 软件地址寻址(7位或10位)
协议复杂度 低,无应答机制 高,有起始/停止条件、应答、仲裁
硬件开销 引脚多,但逻辑简单 引脚少,但需要上拉电阻
典型应用 ADC/DAC、LCD显示屏、SD卡、Flash存储器 温度传感器、EEPROM、RTC、IO扩展芯片

1.3.1 速度与引脚:鱼和熊掌

SPI的优势在速度。50MHz的SPI总线,每秒能传50Mbit的数据,换算成字节就是6.25MB/s。I2C的快速模式才400kHz,差了100多倍。所以需要高速数据搬运的场景,比如摄像头图像数据、音频流、Flash读写,SPI是唯一选择。

但SPI的代价是引脚多。每增加一个从设备,就要多一根SS线。如果你的主控GPIO本来就紧张,挂4个SPI从设备就得占用7个引脚(SCLK+MOSI+MISO+4×SS),而I2C只需要2个引脚。

我记得有一次做智能家居网关,主控引脚只剩3个了,但需要挂6个传感器。SPI肯定没戏,最后全部走I2C,用地址区分设备,2根线搞定6个传感器,省下来的引脚接了按键和LED。

1.3.2 复杂度:简单粗暴 vs 协议严谨

SPI的协议层几乎为零。主设备只管发时钟、推数据、收数据,没有应答、没有地址、没有起始停止条件。驱动写起来非常直接——配置好时钟极性和相位,往数据寄存器里写值,读回来就行。

I2C就复杂多了。你得处理起始条件、从设备地址、读写位、ACK/NACK、停止条件,还要考虑时钟拉伸(Clock Stretching)和仲裁。驱动代码量至少是SPI的两倍。

但复杂度换来了灵活性。I2C的地址机制让总线扩展变得非常容易,想加设备就加,只要地址不冲突就行。SPI加设备就得加SS线,硬件改动大。

我曾经踩过的坑:有一次用I2C驱动一个温湿度传感器,从设备手册上说支持400kHz快速模式,结果实际跑起来经常丢数据。用示波器一看,从设备在时钟拉伸时拉得太久,主控的超时时间设得太短,直接报错了。后来把I2C时钟降到100kHz,问题解决。所以别太相信手册上的理论值,实际调试时降速试试。

1.3.3 应用场景:各有所长

选型时我一般遵循这个原则:

  • 选SPI的场景:数据量大、速度要求高、引脚不紧张。比如SD卡读写、LCD刷屏、音频编解码器、高速ADC。
  • 选I2C的场景:引脚受限、设备数量多、数据量小。比如温度传感器、光照传感器、EEPROM配置存储、RTC时钟芯片。
  • 两者都行:有些设备同时支持SPI和I2C接口,比如某些加速度计和陀螺仪。这时候看你的主控资源,哪个方便用哪个。

另外,SPI没有标准的总线枚举机制,你没法像I2C那样扫描总线发现设备。所以SPI设备通常都是固定的,上电就知道挂在哪里。I2C支持热插拔和动态发现,适合需要灵活配置的系统。

1.4 知识体系总览

下面这张图把SPI和I2C的核心知识点串起来了。你可以把它当作本章的思维导图,后续章节会逐一展开每个模块。

SPI与I2C协议知识体系 SPI协议 四线制 主从模式 全双工通信 I2C协议 两线制 多主多从 半双工通信 SPI vs I2C 对比分析 速度差异 引脚数量 协议复杂度 应用场景 选型原则:高速大数据量选SPI,引脚少设备多选I2C