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 我的推荐策略

对于大多数嵌入式系统,我推荐使用「动态切换 + 轮询调度」的组合。具体做法是:

  1. 建立一个从机列表,每个从机包含 CS 引脚、SPI 速率、传输超时时间
  2. 主循环中按顺序轮询每个从机
  3. 如果某个从机长时间无响应,标记为异常并跳过
  4. 高优先级事件通过中断触发,打断当前轮询

避坑指南:我曾经在一个项目里用 DMA + 中断的方式同时操作两个 SPI 从机。结果 DMA 传输完成中断和 CS 切换中断发生了竞争,导致 CS 时序错乱。后来我改用「一个传输完成后再启动下一个」的串行方式,问题就解决了。记住:SPI 多从机操作,宁慢勿乱。

4.4 知识结构图

下面这张图总结了 SPI 多从机拓扑的核心知识点和决策路径。

SPI多从机拓扑知识结构图 SPI 多从机拓扑知识结构 SPI 多从机拓扑 拓扑类型 总线冲突避免 从机选择策略 独立片选 菊花链 硬件防护 软件防护 静态选择 动态切换 每从机独立 CS MISO 三态控制 数据逐级传递 时钟频率受限 MISO 限流电阻 三态缓冲器 CS 切换死区 总线空闲检测 固定分配 实时性好 轮询/优先级 灵活但复杂 核心原则:CS 互斥 + 总线释放 + 时序隔离 宁慢勿乱,先测后信

4.5 实战建议总结

  • 选型阶段:优先选支持三态 MISO 的从机,省去外部缓冲器
  • 原理图阶段:每个从机 MISO 串 100Ω 电阻,CS 加 10kΩ 上拉
  • 驱动开发阶段:CS 切换必须加死区,不要相信数据手册上的「典型值」
  • 调试阶段:用逻辑分析仪抓 CS、SCK、MISO 三根线,确认时序无误
  • 压力测试:连续切换 10000 次从机,看有没有偶发冲突

最后说一句:SPI 多从机其实不难,难的是把每个细节都做到位。我见过太多「看起来能跑,一上生产就出问题」的案例。记住:硬件留余量,软件做保护,测试全覆盖。做到这三点,你的 SPI 多从机系统就能稳定运行。