3、SPI多从机配置:菊花链拓扑与独立片选拓扑
多从机连接,是SPI实战中绕不开的坎。很多初学者觉得SPI就是四根线,接上就能用。但真到了项目里,一个主控要挂三四个传感器、两个ADC、一个LCD,怎么连?连完怎么保证不打架?
我个人习惯把多从机拓扑分成两大类:独立片选和菊花链。这两种方式,从硬件到软件,思路完全不同。今天咱们就把它掰开揉碎了讲清楚。
3.1 独立片选拓扑:最常用的方案
独立片选,说白了就是每个从机独占一条CS线。主控有多少个从机,就得有多少个GPIO口当片选用。
核心特点: 每个从机独立CS,共享SCK、MOSI、MISO。
我最早做的一个数据采集项目,就是用的这种方案。主控是STM32F103,挂了三个传感器:温度、湿度、气压。每个传感器一根CS线,共用了SCK和MOSI。当时觉得挺简单,但调试时发现一个问题——CS线的时序必须严格对齐。
为什么会这样?因为如果CS拉低后SCK还没稳定,从机可能误判起始条件。嗯,这里要注意:CS拉低后,至少要等半个SCK周期再发时钟。
3.1.1 硬件连接要点
- CS线独立:每个从机一根,主控GPIO直接控制
- MISO线共享:从机输出必须三态,未选中时输出高阻
- SCK/MOSI共享:所有从机同时接收,但只有被选中的才响应
我曾经踩过的坑: 某次项目中用了两个不同厂家的SPI Flash,一个CS低有效,一个CS高有效。结果初始化时忘了配置极性,两个芯片同时响应,MISO上数据打架,总线直接崩溃。后来花了半天才定位到问题。所以,务必确认每个从机的CS极性,别想当然。
3.1.2 软件配置流程
- 初始化所有CS引脚为推挽输出,默认置高(失能)
- 配置SPI外设:模式、极性、相位、速率
- 通信前,拉低目标从机的CS
- 发送/接收数据
- 通信结束,拉高CS
- 切换下一个从机,重复步骤3-5
// 独立片选示例:切换从机
void SPI_SelectSlave(uint8_t slave_id) {
// 先全部失能
HAL_GPIO_WritePin(CS1_GPIO_Port, CS1_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(CS2_GPIO_Port, CS2_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(CS3_GPIO_Port, CS3_Pin, GPIO_PIN_SET);
// 使能目标从机
switch(slave_id) {
case 0: HAL_GPIO_WritePin(CS1_GPIO_Port, CS1_Pin, GPIO_PIN_RESET); break;
case 1: HAL_GPIO_WritePin(CS2_GPIO_Port, CS2_Pin, GPIO_PIN_RESET); break;
case 2: HAL_GPIO_WritePin(CS3_GPIO_Port, CS3_Pin, GPIO_PIN_RESET); break;
default: break;
}
// 等待CS稳定,至少延时1us
delay_us(1);
}
3.2 菊花链拓扑:少引脚,高要求
独立片选虽然简单,但GPIO不够用怎么办?比如一个主控只有3个空闲IO,却要控制8个从机。这时候,菊花链就派上用场了。
菊花链的玩法是:所有从机共用一根CS线,数据像链条一样逐级传递。主控发数据给从机1,从机1处理完再转发给从机2,以此类推。
我个人的经验: 菊花链适合写多读少的场景。比如LED驱动、DAC配置这类只需要下发数据的应用。如果是传感器读取,菊花链的延迟会让人抓狂。
3.2.1 硬件连接方式
| 信号 | 连接方式 | 说明 |
|---|---|---|
| CS | 所有从机共用 | 同时使能/失能所有从机 |
| SCK | 所有从机共用 | 时钟同步 |
| MOSI | 主控→从机1→从机2→... | 数据逐级传递 |
| MISO | 最后一个从机→主控 | 最终数据返回 |
你想想看,这种结构下,主控发一个字节,要经过N个从机才能到达目标。每个从机都会延迟一个字节的时间。所以,菊花链的总延迟 = 从机数量 × 字节传输时间。
3.2.2 数据帧结构
菊花链模式下,数据帧必须包含地址信息。每个从机收到数据后,先检查地址是否匹配自己,匹配则处理,不匹配则原样转发。
// 菊花链数据帧格式
// | 地址(1字节) | 命令(1字节) | 数据(N字节) | 校验(1字节) |
#define CHAIN_ADDR_SENSOR1 0x01
#define CHAIN_ADDR_SENSOR2 0x02
#define CHAIN_ADDR_SENSOR3 0x03
uint8_t chain_frame[] = {
CHAIN_ADDR_SENSOR2, // 目标地址
0x10, // 读命令
0x00, 0x00, // 寄存器地址
0x00 // 校验和(简化)
};
3.3 多从机地址分配与仲裁机制
不管是独立片选还是菊花链,只要总线上有多个从机,就绕不开地址分配和冲突仲裁。
3.3.1 地址分配策略
我见过三种常见的分配方式:
- 硬件引脚配置:从机通过外部引脚设置地址(如ADDR0、ADDR1)。优点是上电即用,缺点是占用引脚。
- 软件初始化分配:主控上电后,逐个使能从机,下发唯一地址。优点是灵活,缺点是需要从机支持。
- 固定地址:从机出厂地址固定,不可更改。最简单,但容易冲突。
我建议: 如果项目允许,优先用硬件引脚配置。我在一个工业控制项目中用过软件分配,结果某次掉电重启后,从机地址丢失,整个系统瘫痪。从那以后,我对软件分配就多留了个心眼。
3.3.2 总线仲裁机制
SPI本身没有像I2C那样的多主仲裁机制。但多从机场景下,MISO线上的冲突仍然可能发生。常见仲裁方式有两种:
| 仲裁方式 | 原理 | 适用场景 |
|---|---|---|
| CS互斥 | 同一时刻只使能一个从机 | 独立片选拓扑 |
| 三态输出 | 未选中从机MISO输出高阻 | 所有拓扑 |
| 地址过滤 | 从机只响应匹配地址的帧 | 菊花链拓扑 |
嗯,这里要特别强调:三态输出是SPI多从机的基础。如果某个从机的MISO不是三态,而是推挽输出,那它没被选中时也会拉低或拉高总线,直接导致其他从机通信失败。
我曾经遇到过一个案例: 某款国产SPI ADC,数据手册上写着MISO是三态输出。结果实际测试发现,失能后MISO仍然输出低电平。后来加了外部三态缓冲器才解决。所以,关键芯片一定要实测验证,别全信数据手册。
3.4 两种拓扑的对比与选型建议
说了这么多,到底该用哪种?我整理了一个对比表,方便你决策:
| 对比项 | 独立片选 | 菊花链 |
|---|---|---|
| GPIO占用 | 多(每个从机1根) | 少(所有从机共用1根) |
| 通信速率 | 高(无额外延迟) | 低(逐级延迟) |
| 软件复杂度 | 低 | 高(需地址管理) |
| 可靠性 | 高(故障隔离) | 低(单点故障影响全链) |
| 适用场景 | 传感器、ADC、Flash | LED驱动、DAC、移位寄存器 |
我个人习惯是:GPIO够用就选独立片选。简单、可靠、好调试。只有GPIO实在不够,或者从机数量超过10个,我才会考虑菊花链。
3.5 本章知识结构图
下面这张图,把两种拓扑的核心差异和选型逻辑串起来了。你可以保存下来,做项目时对照着看。
3.6 避坑总结
最后,把我这些年踩过的坑汇总一下,你遇到类似问题可以直接对照排查:
- CS时序:拉低CS后必须等待至少半个SCK周期再发时钟
- MISO三态:务必确认从机失能后MISO是高阻,不是推挽
- 地址冲突:硬件引脚配置最可靠,软件分配要做好掉电保护
- 菊花链延迟:总延迟 = 从机数 × 字节时间,实时性要求高的别用
- CS极性:不同从机可能高低有效不同,初始化时逐一确认
嗯,多从机配置这块,说白了就是硬件拓扑决定上限,软件设计决定下限。选对拓扑,配好地址,仲裁机制到位,SPI多从机系统就能稳定跑起来。