本节摘要:总线是外设与主控之间的"交通法规"。本节从两根线的 I2C、四根线的 SPI 讲到可枚举的 PCIe,对比三者的通信模型、时钟角色、设备寻址与软件接口差异,为第 4 章的总线驱动模型打好物理层底子。
芯片圈的行话把这三条总线分成"两根线""四根线"和"差分对",一句话就点出了各自的复杂度梯度:I2C 用两根线(时钟加数据)拼出多设备共享总线;SPI 用四根线(时钟、两根数据、片选)换更高的速率与全双工;PCIe 用高速差分对加交换结构,撑起显卡与固态盘的吞吐。驱动写给哪条总线上的设备,就得懂哪条总线的规矩——总线决定了你怎样"够到"寄存器。
I2C 总线只有两根线:串行时钟线与串行数据线,所有设备并联其上,靠七位地址区分彼此。通信永远是主设备发起:先在总线上喊出目标地址,应答的设备继续对话。数据线是开漏输出配上拉电阻——任何设备都可以把线拉低,这种电气设计让多设备共存成为可能,也带来了 I2C 的速度上限:标准模式一百千赫兹、快速模式四百千赫兹,再往上就要更严格的电气条件。
一次"读传感器温度寄存器"的完整对话,在逻辑分析仪上是这个样子:
START | 设备地址 0x48 写 | ACK | 寄存器号 0x00 | ACK | RESTART | 设备地址 0x48 读 | ACK | 数据 0x1C | NACK | STOP 主机指定要读哪个寄存器,随后转为读方向取回数据
两个 I2C 特有的现象值得记下。时钟拉伸:慢速从设备可以拉住时钟线,让主设备等它准备好——这是从设备的"喘气权"。总线死锁:从设备在主机复位中途卡在"以为自己在发数据"的状态、一直拉着数据线,总线就瘫了;老练的驱动会用九个时钟脉冲加停止条件这类手法解救。这些电气层的脾气,到了第 4 章会被 I2C 核心层封装掉,但出问题时你还得下到这一层看波形。
SPI 加了两根数据线(主出从入、主入从出)与片选线,换来三个质变:全双工(收发同时进行)、更高速率(几十兆赫兹起步)、无地址协议(片选拉低谁,谁就是对话对象)。没有地址、没有应答机制,一切靠双方预先约定的"模式"对齐——时钟极性与相位组合出四种模式,主从双方必须一致,模式配错的典型症状是"数据错位一个比特"。
SPI 传输的字节序对话(模式0,先发高位): 主机: 0x03 0x00 0x2A 读命令 + 寄存器地址 + 填充字节 从机: 0x00 0x00 0x1C 填充 + 填充 + 目标寄存器数据 本质是移位寄存器对射:主机发 N 字节,必然同时收回 N 字节
Flash 芯片、显示屏、高速 ADC 是 SPI 的常客。它的驱动模型也因"无地址"而直白:内核 SPI 核心层把"片选、模式、速率"做成设备参数,传输用消息结构描述,第 4 章的 spi_driver 一节会实际写一遍。
前两条总线都解决不了"设备自报家门"的问题——挂了什么设备,软件只能靠板级描述。PCIe 从物理层就换了个世界:高速差分对、包交换协议、树状拓扑(根复合体挂交换器,交换器挂端点)。最关键的是配置空间:每台设备加电后都自带一份标准化的身份与能力清单——厂商识别、设备识别、内存空间需求(BAR,基址寄存器)。
软件枚举就是沿着这棵树读配置空间:发现设备、读出它需要多大的地址窗口、分配物理地址、把窗口基址写回 BAR。枚举完成后,设备的寄存器区就被映射进统一地址空间,访问方式回到了 3.1 节的 ioremap 那一套。驱动无需知道设备插在哪个槽——总线号、设备号、功能号三层编号唯一定位,写在设备描述里递给驱动。

三条总线对驱动写作的影响可以压缩成一张表:
| 维度 | I2C | SPI | PCIe |
|---|---|---|---|
| 设备如何被发现 | 板级描述(设备树) | 板级描述(设备树) | 总线枚举自报家门 |
| 寄存器访问方式 | 总线传输报文 | 总线传输报文 | 映射后直接读写 |
| 一台设备的信息来源 | 设备树节点 | 设备树节点 | 配置空间加设备树 |
| 典型驱动入口 | i2c 驱动的 probe | spi 驱动的 probe | pci 驱动的 probe |
| 传输对象 | 消息(地址加数据段) | 消息(片选控制加字节流) | DMA 为主 |
看出规律了吗?发现机制与访问方式组合出三类驱动形态,而"probe"是它们共同的入口仪式。这正是下一章的主角:内核把"设备怎么来、驱动怎么认"抽象成了统一的总线模型——平台总线、I2C 总线、SPI 总线、PCI 总线在软件层共享同一套匹配与探查框架。
⚠️ 常见坑:把 I2C 设备当内存映射设备访问。I2C 设备的"寄存器"是总线报文里的字节偏移,不存在物理地址,
ioremap对它毫无意义——总线上没有"地址窗口"这回事。
认识总线不必等到写驱动那天,现成系统就是观察场。嵌入式开发板上,设备树的运行时视图(系统设备树目录)把 I2C 与 SPI 控制器连同挂载的子设备一览无余;桌面系统上,总线枚举工具列出每台 PCIe 设备的厂商识别与设备识别——PCIe 的"自报家门"在这里直接可见:
$ lspci -nn | grep -i net 03:00.0 Ethernet controller [0200]: 厂商识别:设备识别 ↑ 总线03 设备00 功能0 ← 三层编号唯一定位 $ ls /proc/device-tree/ 2>/dev/null ... i2c@ff020000 spi@ff1f0000 ...
第一份输出里的方括号编码就是配置空间里读出的身份对——第 4 章讲匹配表时你会认出:PCI 驱动的匹配表登记的正是这些号码。第二份输出是设备树在运行系统里的投影,控制器节点与子节点的关系清晰可见。工具先行是硬件学习的好习惯:先看见总线上的居民,再研究怎么跟他们说话。
嵌入式调试常配一件入门硬件——逻辑分析仪:几根探针夹在总线上,软件把电平变化翻译成协议报文。一次 I2C 读取在屏幕上呈现为"起始位、地址、应答、数据、停止位"的完整序列,与 3.4 节的文字描述逐字对应。这种"波形与代码互相印证"的体验,是建立硬件直觉最快的方式——第 7 章的排错工具箱里,它也是诊断总线死锁、时序违例的终极手段。
给一块新板子排外设时,总线选型有三个常用判断维度:速率——低速配置类外设(每秒几个读数)I2C 足矣,中高速数据流(传感器原始波形、显示屏缓冲)SPI 更稳,吞吐大类(存储、网卡)才轮到内存映射或 PCIe;布线成本——I2C 两根线挂一串设备最省引脚,SPI 的片选线随设备数量增长,PCIe 的差分对则是高速专属的布线功课;软件生态——I2C 与 SPI 设备的驱动骨架高度模板化,接错线的排错成本主要在硬件侧,PCIe 设备则享受自动枚举的便利但要求设备实现完整的配置空间。三维度摆在一起你会发现:总线的"脾气"决定了驱动代码的形状——这正是下一章要把它们统一进总线模型的原因:形状不同,撮合的流程相同。
物理层的课补完了。第 4 章回到软件世界,看内核如何用统一的总线模型把"设备描述"与"驱动代码"撮合到一起——那个从第 2 章就写死的 GPIO 地址,终于要有个体面的归宿。