4. SPI多从机架构:片选信号(CS)管理、菊花链拓扑、多从机总线仲裁
SPI 总线最基础的用法是一主一从,一对一通信,简单直接。但实际项目中,一个主控芯片往往要挂多个外设——比如同时接一个温度传感器、一个加速度计、一个 LCD 屏幕。这时候就涉及到多从机架构了。
我个人习惯把多从机架构分成三种典型场景:独立片选、菊花链、多主仲裁。每种场景的硬件连接和软件处理都不一样,踩过的坑也各不相同。今天咱们一个一个聊透。
4.1 独立片选:最常用的多从机方案
这是最直观的做法。每个从机独占一根 CS 线,主机有多少个从机,就分配多少个 GPIO 做片选。
核心要点:同一时刻,只有一根 CS 线被拉低。其他从机的 CS 保持高电平,它们的数据引脚(MISO)必须处于高阻态,不能干扰总线。
硬件连接示意图如下:
主机 (Master)
SCK → 所有从机的 SCK
MOSI → 所有从机的 MOSI
MISO ← 所有从机的 MISO (需三态输出)
CS1 → 从机1 的 CS
CS2 → 从机2 的 CS
CS3 → 从机3 的 CS
这里有个关键点:从机的 MISO 引脚必须支持三态输出。当 CS 为高时,MISO 输出高阻,不参与总线驱动。如果从机不支持三态,那你就得在外部加三态缓冲器,比如 74HC125。我在项目中遇到过一款老旧的 SPI 温度芯片,它的 MISO 在 CS 拉高后仍然输出低电平,结果两个从机同时驱动 MISO,直接把主机 IO 口烧了。嗯,从那以后我选型 SPI 器件,第一件事就是看 datasheet 里有没有「CS disables output」的描述。
软件处理流程
// 伪代码:选择从机1,发送数据
GPIO_WriteLow(CS1_PIN); // 拉低 CS1
SPI_Transmit(data); // 发送数据
GPIO_WriteHigh(CS1_PIN); // 拉高 CS1,释放总线
// 选择从机2,发送数据
GPIO_WriteLow(CS2_PIN);
SPI_Transmit(data);
GPIO_WriteHigh(CS2_PIN);
看起来很简单对吧?但实际调试时有个容易忽略的细节:两次片选切换之间,必须留一点延时。有些从机在 CS 拉高后,内部状态机需要几个时钟周期才能复位。我建议至少延时 1us,或者等一个完整的 SPI 时钟周期。
避坑指南:我曾经在切换从机时没加延时,结果第二个从机收到的第一个字节总是错的。后来用示波器一看,CS 拉高后 MISO 上还有残留电平,延时 2us 后问题消失。
4.2 菊花链拓扑:少引脚、高效率
独立片选虽然简单,但每增加一个从机就要多占用一个 GPIO。如果主控引脚紧张,或者从机数量很多(比如 LED 驱动阵列),菊花链就派上用场了。
菊花链的硬件连接很特别:
主机 (Master)
SCK → 所有从机的 SCK
MOSI → 从机1 的 MOSI
从机1 的 MISO → 从机2 的 MOSI
从机2 的 MISO → 从机3 的 MOSI
...
最后一个从机的 MISO → 主机的 MISO
CS → 所有从机的 CS (共用一根)
说白了,就是把从机串成一串,数据像流水一样从第一个传到最后一个。所有从机共用一根 CS 线,主机只需要一个 GPIO。
但菊花链有个硬性要求:所有从机必须支持菊花链模式。不是所有 SPI 器件都支持,你得看 datasheet 里有没有「Daisy-chain capable」或者「Data pass-through」的描述。
数据传输原理
假设你有 3 个从机,每个从机的寄存器是 8 位。主机要发送 3 个字节的数据:
- 主机拉低 CS,开始发送 24 个时钟周期(3 字节 × 8 位)。
- 第一个字节进入从机1,从机1 内部处理完后,把旧数据从 MISO 推给从机2。
- 第二个字节进入从机1,同时从机1 的第一个字节被推到从机2。
- 依此类推,24 个时钟后,每个从机都拿到了属于自己的数据。
- 主机拉高 CS,所有从机同时锁存数据。
你看,菊花链的通信效率其实不高——你要给所有从机发完数据才能拉高 CS。但它的优势是节省引脚,特别适合那种「一次性更新所有从机」的场景,比如 LED 屏幕刷新。
注意:菊花链的时钟频率不能太高。因为数据要经过多个从机内部的移位寄存器,每个从机都会引入一点延迟。我曾经在 20MHz 时钟下跑 8 级菊花链,结果数据完全错位。降到 5MHz 就正常了。
4.3 多主总线仲裁:SPI 的短板
SPI 总线天生是主从架构,不支持多主通信。但有些场景下,多个设备都需要主动发起通信——比如两个 MCU 之间互相传数据。这时候怎么办?
说实话,SPI 的多主仲裁非常麻烦,远不如 I2C 方便。我一般不推荐用 SPI 做多主通信,但如果你非要做,有几种变通方案:
方案一:软件仲裁 + 外部握手
用额外的 GPIO 做总线请求和应答。比如:
设备A 拉低 REQ1 线 → 请求总线
设备B 检测到 REQ1 为低 → 释放总线(拉高自己的 CS,MISO 置高阻)
设备A 检测到总线空闲 → 开始通信
设备A 通信结束 → 拉高 REQ1
这个方案需要你自定义握手协议,而且实时性不好。我在一个双 MCU 的项目里用过,两个 MCU 之间传传感器数据,频率不高,勉强能用。
方案二:主从角色切换
两个设备都支持 SPI 主从模式切换。平时设备A 做主,设备B 做从。当设备B 需要发送数据时,通过一个 GPIO 通知设备A,设备A 切换到从模式,设备B 切到主模式。
这个方案需要硬件支持主从切换,而且切换过程有延迟。我记得有一次切换时没处理好时钟相位,导致数据错位,排查了两天才找到原因。
我的建议:如果真需要多主通信,老老实实用 I2C 或者 CAN 总线。SPI 的多主仲裁是「硬凑」出来的,稳定性、效率、复杂度都不理想。
4.4 三种架构对比
| 特性 | 独立片选 | 菊花链 | 多主仲裁 |
|---|---|---|---|
| 引脚占用 | 高(每从机一根 CS) | 低(共用一根 CS) | 高(需额外握手线) |
| 通信效率 | 高(可单独操作) | 低(需传完所有数据) | 低(需仲裁等待) |
| 硬件要求 | 从机需三态输出 | 从机需支持菊花链 | 需自定义握手协议 |
| 适用场景 | 通用,最常用 | LED 阵列、移位寄存器 | 不推荐,除非别无选择 |
| 调试难度 | 低 | 中(需注意时序) | 高 |
4.5 核心知识体系
下面这张图总结了 SPI 多从机架构的核心逻辑,我画出来方便你理解:
嗯,这张图把三种架构的要点都列出来了。你实际做项目时,可以根据引脚数量、从机类型、通信频率来选。我个人 90% 的项目都用独立片选,简单可靠,调试也快。菊花链只在 LED 驱动项目里用过两次。多主仲裁?说实话,我尽量避开。
最后一个小技巧:不管用哪种架构,调试时先用示波器抓 CS 和 SCK 的波形。看看 CS 拉低后 SCK 是否稳定,CS 拉高时 MISO 是否回到高阻。这两个波形对了,通信就成功了一半。
公众号:蓝海资料掘金营,微信deep3321