4、SPI多从机拓扑:独立片选与菊花链连接、总线冲突避免、从机选择策略
SPI 多从机拓扑,说白了就是怎么把好几个从机挂到同一条 SPI 总线上。我见过不少新手工程师,一上来就按单从机的思路写驱动,结果接上第二个设备就出各种怪问题。嗯,今天咱们就把这块彻底讲透。
4.1 两种主流拓扑:独立片选 vs 菊花链
SPI 多从机有两种经典接法。我个人习惯把它们叫做「各自为王」和「串成一串」。
| 对比项 | 独立片选(Parallel CS) | 菊花链(Daisy Chain) |
|---|---|---|
| 片选信号 | 每个从机独立 CS 引脚 | 所有从机共享 CS |
| 数据线 | MISO/MOSI 共享 | MOSI 串联,MISO 串联 |
| 从机数量 | 受限于 MCU GPIO 数量 | 理论上无限 |
| 通信效率 | 高,直接寻址 | 低,数据需逐级传递 |
| 驱动复杂度 | 简单 | 中等 |
| 典型应用 | 传感器、ADC、LCD | 移位寄存器、LED 驱动 |
4.1.1 独立片选拓扑
这是最常见的接法。每个从机独占一根 CS 线,主机通过拉低对应 CS 来选择目标设备。其他从机的 CS 保持高电平,它们的 MISO 输出会处于高阻态,不会干扰总线。
我在项目中遇到过一个问题:某款传感器在 CS 拉高后,MISO 并没有立即进入高阻态,而是延迟了约 200ns。这就导致下一个从机被选中时,总线上还残留着上一个设备的数据。解决办法是在 CS 切换之间插入一段「死区时间」。
关键配置要点:
- 每个从机独立 GPIO 做 CS,推挽输出
- CS 拉低到第一个 SCK 之间留足建立时间(通常 ≥ 100ns)
- 最后一个 SCK 到 CS 拉高之间留足保持时间
- CS 拉高后,等待 MISO 释放总线再切换下一个从机
4.1.2 菊花链拓扑
菊花链的玩法不一样。所有从机共享 CS 和 SCK,但 MOSI 从第一个从机的 SI 进,从它的 SO 出,再接到第二个从机的 SI,以此类推。MISO 也是串联的。主机发一串数据,每个从机只取自己需要的那一段。
说白了,菊花链就是让数据像火车一样,一节一节传过去。你想想看,如果链上有 4 个从机,主机要发 32 字节,每个从机取 8 字节,那主机就得一次性发完 32 字节,然后同时拉高 CS,所有从机同时锁存数据。
我的经验:菊花链的时钟频率不能太高。因为数据每经过一级从机,都会引入一个传播延迟。我曾经在 20MHz 下挂 8 级菊花链,结果最后一级的数据完全错位。后来降到 5MHz 才稳定。建议每增加一级,时钟频率降低 20%~30%。
4.2 总线冲突避免
总线冲突是 SPI 多从机最头疼的问题。MISO 是共享线,如果两个从机同时往 MISO 上拉数据,轻则数据错误,重则烧毁引脚。
4.2.1 冲突的根源
- CS 逻辑错误:两个从机的 CS 同时被拉低
- 从机输出延迟:CS 拉高后,从机 MISO 未及时释放
- 三态控制失效:从机内部 MISO 驱动未正确进入高阻态
- 电平不匹配:不同从机供电电压不同,导致 MISO 电平冲突
4.2.2 硬件层面的防护
我建议在所有从机的 MISO 上串联一个 100Ω~1kΩ 的限流电阻。这样即使发生冲突,电流也被限制在安全范围内。另外,如果从机不支持三态输出,可以在 MISO 上加一个 74HC125 三态缓冲器,由 CS 信号控制。
注意:千万不要在软件层面依赖「从机应该会释放 MISO」这种假设。我吃过这个亏——某款国产 Flash 芯片的数据手册说支持三态,实际测试发现 CS 拉高后 MISO 还保持低电平 500ns。从那以后,我所有多从机项目都在 MISO 上加了下拉电阻(10kΩ),确保总线空闲时处于已知状态。
4.2.3 软件层面的防护
// 安全的多从机切换流程(伪代码)
void spi_select_slave(uint8_t slave_id) {
// 1. 先取消选中所有从机
for (int i = 0; i < SLAVE_COUNT; i++) {
GPIO_WriteLow(CS_PINS[i]);
}
delay_ns(500); // 等待所有从机释放 MISO
// 2. 确认 MISO 总线为空闲状态(高电平或高阻)
if (GPIO_Read(MISO_PIN) == 0) {
// 总线被拉低,可能有冲突
return SPI_ERROR_BUS_BUSY;
}
// 3. 选中目标从机
GPIO_WriteHigh(CS_PINS[slave_id]);
delay_ns(200); // 等待从机准备好
// 4. 开始 SPI 传输
spi_transfer_start();
}
4.3 从机选择策略
从机选择不只是拉个 CS 那么简单。不同的应用场景,策略完全不同。
4.3.1 静态选择 vs 动态切换
| 策略 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 静态选择 | 系统初始化时固定分配 | 无切换开销,实时性好 | 灵活性差 |
| 动态切换 | 多个从机轮流通信 | 灵活,节省硬件资源 | 有切换延迟 |
| 优先级抢占 | 实时性要求高的场景 | 高优先级任务及时响应 | 实现复杂 |
| 轮询调度 | 周期性采集数据 | 实现简单,可预测 | 效率低 |
4.3.2 我的推荐策略
对于大多数嵌入式系统,我推荐使用「动态切换 + 轮询调度」的组合。具体做法是:
- 建立一个从机列表,每个从机包含 CS 引脚、SPI 速率、传输超时时间
- 主循环中按顺序轮询每个从机
- 如果某个从机长时间无响应,标记为异常并跳过
- 高优先级事件通过中断触发,打断当前轮询
避坑指南:我曾经在一个项目里用 DMA + 中断的方式同时操作两个 SPI 从机。结果 DMA 传输完成中断和 CS 切换中断发生了竞争,导致 CS 时序错乱。后来我改用「一个传输完成后再启动下一个」的串行方式,问题就解决了。记住:SPI 多从机操作,宁慢勿乱。
4.4 知识结构图
下面这张图总结了 SPI 多从机拓扑的核心知识点和决策路径。
4.5 实战建议总结
- 选型阶段:优先选支持三态 MISO 的从机,省去外部缓冲器
- 原理图阶段:每个从机 MISO 串 100Ω 电阻,CS 加 10kΩ 上拉
- 驱动开发阶段:CS 切换必须加死区,不要相信数据手册上的「典型值」
- 调试阶段:用逻辑分析仪抓 CS、SCK、MISO 三根线,确认时序无误
- 压力测试:连续切换 10000 次从机,看有没有偶发冲突
最后说一句:SPI 多从机其实不难,难的是把每个细节都做到位。我见过太多「看起来能跑,一上生产就出问题」的案例。记住:硬件留余量,软件做保护,测试全覆盖。做到这三点,你的 SPI 多从机系统就能稳定运行。