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 个字节的数据:

  1. 主机拉低 CS,开始发送 24 个时钟周期(3 字节 × 8 位)。
  2. 第一个字节进入从机1,从机1 内部处理完后,把旧数据从 MISO 推给从机2。
  3. 第二个字节进入从机1,同时从机1 的第一个字节被推到从机2。
  4. 依此类推,24 个时钟后,每个从机都拿到了属于自己的数据。
  5. 主机拉高 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 多从机架构的核心逻辑,我画出来方便你理解:

SPI 多从机架构核心知识体系 SPI 多从机架构 独立片选 菊花链拓扑 多主仲裁 每从机独立 CS 线 MISO 需三态输出 切换需加延时 共用一根 CS 线 数据串行传递 时钟频率需降低 需额外握手线 主从角色可切换 不推荐,稳定性差 选型原则:引脚够用选独立片选,引脚紧张选菊花链,多主通信换 I2C

嗯,这张图把三种架构的要点都列出来了。你实际做项目时,可以根据引脚数量、从机类型、通信频率来选。我个人 90% 的项目都用独立片选,简单可靠,调试也快。菊花链只在 LED 驱动项目里用过两次。多主仲裁?说实话,我尽量避开。

最后一个小技巧:不管用哪种架构,调试时先用示波器抓 CS 和 SCK 的波形。看看 CS 拉低后 SCK 是否稳定,CS 拉高时 MISO 是否回到高阻。这两个波形对了,通信就成功了一半。


公众号:蓝海资料掘金营,微信deep3321