news 2026/9/7 11:58:47

SPI通信协议全解析:从硬件时序到STM32实战应用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SPI通信协议全解析:从硬件时序到STM32实战应用

1. 从“一根线”到“四根线”:为什么SPI总能在嵌入式江湖里站稳脚跟?

如果你玩过单片机,或者捣鼓过各种传感器、屏幕、存储芯片,那你一定绕不开SPI这个名字。它不像UART那样,一根线发一根线收,简单直接;也不像I2C那样,用两根线就能挂一堆设备,听起来很省事。SPI偏偏要用三根、四根甚至更多线,看起来有点“铺张浪费”。但奇怪的是,从8位单片机到32位ARM,再到FPGA,几乎所有的硬件平台都把它当作标配,无数的Flash芯片、OLED屏幕、以太网PHY、ADC/DAC都首选SPI作为通信接口。这背后到底有什么魔力?

简单来说,SPI(Serial Peripheral Interface,串行外设接口)是一种全双工、同步、高速的串行通信总线。它的核心魅力在于“简单粗暴”和“速度至上”。说它简单,是因为它的协议层几乎没有“规矩”——没有复杂的地址帧、没有应答机制、没有总线仲裁,主设备说发就发,从设备说收就收,一切时序都由主设备的时钟(SCLK)严格掌控。这种主从之间“一对一”的紧密耦合,虽然牺牲了总线扩展的便利性,却换来了极高的通信效率和极简的软件驱动实现。说它高速,是因为它没有像I2C那样的开漏输出和上拉电阻带来的速度瓶颈,在短距离、板级通信的场景下,轻松跑到几十甚至上百MHz是家常便饭。

所以,当你需要一个快速、可靠、实时性高的方式,在板子上跟某个特定的芯片“说悄悄话”时,SPI往往是那个最不会让你失望的选择。无论是初始化一块OLED屏幕,还是从Flash芯片里快速读取固件,或是高速采集传感器的数据,SPI都能提供一种近乎“直连”的畅快体验。接下来,我们就抛开那些枯燥的教科书定义,从一个硬件工程师和嵌入式软件工程师的实操视角,彻底拆解SPI的里里外外。

2. SPI的“四梁八柱”:硬件连线与核心信号深度解析

很多人一提到SPI,就脱口而出“四根线”:SCLK、MOSI、MISO、CS。这个说法对,但不全对。这四根线构成了SPI通信最经典、最基础的物理骨架,但实际应用中,变体非常多。理解每一根线的职责和它们之间的配合,是玩转SPI的第一步。

2.1 核心四线制:标准配置下的分工协作

我们来逐一拆解这四根核心信号线:

  1. SCLK (Serial Clock,串行时钟):这是SPI总线的“心跳”,由主设备(Master)产生并完全控制。所有数据的发送和接收都严格对齐这个时钟的边沿。SCLK的频率直接决定了通信速率。这里有一个关键点:SCLK在空闲时(即没有数据传输时)的电平状态,以及数据在时钟的哪个边沿被采样,是由SPI的“模式”决定的,我们后面会详细讲。

  2. MOSI (Master Output Slave Input,主出从入):数据从主设备流向从设备的通道。主设备通过这根线,一位一位地把命令或数据“推”给从设备。

  3. MISO (Master Input Slave Output,主入从出):数据从从设备流向主设备的通道。从设备通过这根线,一位一位地把响应或数据“送”回给主设备。注意,MOSI和MISO是同时工作的,这使得SPI实现了真正的全双工通信。主设备在发送一个字节的同时,也能接收到一个字节。这个特性非常有用,例如在读取Flash芯片时,主设备先发送一个读命令(比如0x03),紧接着发送地址,与此同时,从设备就会通过MISO线开始回送数据。

  4. CS/SS (Chip Select / Slave Select,片选):这是SPI总线架构的灵魂所在。它通常是一个低电平有效的信号(即CS为低电平时,从设备被选中)。每个从设备都需要独占一根来自主设备的CS线。主设备通过拉低某根特定的CS线,来“点名”要与哪个从设备通信。当CS为高时,对应的从设备必须将其MISO引脚置于高阻态,从而与总线隔离。这就是为什么SPI理论上可以挂很多设备,但需要大量GPIO口来做片选的原因。

注意:CS信号的控制是SPI通信可靠性的基石。一个常见的低级错误是CS信号时序混乱,比如在时钟还没稳定时就拉低CS,或者在数据传输未完成时就拉高CS,这极易导致从设备锁死或数据错位。稳妥的做法是,在SCLK处于空闲电平(根据模式确定)时,先拉低CS,等待至少一个时钟周期后再开始产生SCLK;通信结束后,先确保最后一个SCLK边沿完成,再拉高CS。

2.2 硬件片选 vs. 软件片选:成本与灵活性的权衡

这是新手和老手的一个分水岭。所谓“硬件片选”,就是使用主控芯片硬件SPI外设自带的、专用于片选的GPIO引脚。很多MCU的SPI模块会提供1到2个专用的NSS(Negtive Slave Select)引脚。使用硬件片选时,SPI控制器硬件会自动在数据传输前后控制NSS引脚的电平,软件无需干预,不仅省事,而且时序精准。

而“软件片选”,则是用任意一个普通的GPIO口来模拟CS信号的功能。你需要手动在代码里控制这个GPIO的电平。这是最常用的方式,因为硬件片选引脚数量有限。当你需要连接多个SPI从设备时,几乎必然要使用多个GPIO来做软件片选。

两者的核心区别与选择建议:

  • 硬件片选:时序精准,不占用CPU资源,适合对时序要求极其严格或主设备非常繁忙的场景。缺点是引脚固定,数量有限。
  • 软件片选:灵活,不受引脚限制,想接多少设备(理论上)就分配多少GPIO。缺点是控制时序需要软件介入,会引入微小的延迟,并且在多任务或中断环境中,如果CS控制不当,容易引发冲突。

对于绝大多数应用,软件片选完全足够。你需要做的,就是在驱动代码里,把CS引脚的控制封装成独立的函数,确保在任何SPI数据传输函数中,都成对地调用CS_Low()CS_High()

2.3 超越四线:HOLD、WP与多设备拓扑

在实际芯片中,你经常会看到除了四根基础线之外,还有别的引脚,比如Flash芯片上的HOLDWP(Write Protect)。

  • HOLD引脚:当这个引脚被拉低时,它会暂停当前正在进行的SPI通信(就像按了暂停键),但不会终止通信。主设备可以在忙时先Hold住从设备,去处理更高优先级的任务,然后再恢复通信。这在早期的SPI Flash中用于实现简单的多主机共享,但现在更高速的器件通常不建议使用,因为会打断连续的数据流。现代设计中,这个引脚通常直接上拉到VCC(使其无效),除非你有非常特殊的流控需求。
  • WP引脚:写保护。拉低此引脚可以禁止对存储器的写和擦除操作,防止误操作。在产品化代码中,通常会在初始化后将其拉高以允许写入。

关于拓扑,SPI本质上是一种点对点一主多从(独立片选)的总线。它不支持像I2C那样的多主或多从(共用片选)仲裁。有一种“菊花链”(Daisy-Chain)模式,可以将多个从设备的MISO和MOSI串接起来,数据像移位寄存器一样在所有设备间传递。但这种模式需要从设备硬件支持,并不通用,常见于一些特定的数字电位器或LED驱动芯片中。对于大多数通用器件,老老实实用“一主多从+独立片选”是最稳妥的方案。

3. 时序是灵魂:深入理解SPI的四种模式与波形

如果说硬件连线是SPI的躯体,那时序就是它的灵魂。SPI通信的可靠性,几乎完全取决于对时序的精确理解和控制。而时序的核心,就体现在CPOL和CPHA这两个参数上,它们共同定义了SPI的四种工作模式。

3.1 CPOL与CPHA:定义时钟的“性格”

  • CPOL (Clock Polarity,时钟极性):定义SCLK线在空闲时的电平。
    • CPOL=0:SCLK空闲时为低电平。
    • CPOL=1:SCLK空闲时为高电平。
  • CPHA (Clock Phase,时钟相位):定义数据在SCLK的第几个边沿被采样(捕获)。
    • CPHA=0:数据在SCLK的第一个边沿(即从空闲状态跳变到相反状态的第一个边沿)被采样。对于CPOL=0,第一个边沿是上升沿;对于CPOL=1,第一个边沿是下降沿。
    • CPHA=1:数据在SCLK的第二个边沿(即第一个边沿之后的下一个边沿)被采样。

组合起来,就得到了SPI的四种模式:Mode 0 (CPOL=0, CPHA=0), Mode 1 (CPOL=0, CPHA=1), Mode 2 (CPOL=1, CPHA=0), Mode 3 (CPOL=1, CPHA=1)

3.2 模式0与模式3:为什么它们最常见?

让我们以Mode 0 (CPOL=0, CPHA=0)为例,画出它的波形,并理解其背后的硬件行为:

  1. 空闲时,SCLK为低(CPOL=0),CS为高(未选中)。
  2. 当CS被拉低,选中从设备。
  3. 在第一个SCLK边沿(上升沿)到来之前,主设备必须将要发送的第一位数据(通常是最高位MSB)放到MOSI线上并保持稳定。同时,从设备也会将其要发送的第一位数据放到MISO线上。
  4. 第一个SCLK上升沿到来,此时主设备和从设备同时采样对方的输入线。主设备在上升沿采样MISO得到第一位数据,从设备在上升沿采样MOSI得到第一位数据。这就是“数据在第一个边沿被采样”(CPHA=0)
  5. 在第一个上升沿之后,主从双方根据要发送的下一位数据,更新各自的输出线(MOSI/MISO)。
  6. 第一个SCLK的下降沿到来,但这个边沿在Mode 0下仅用于数据线的更新和稳定,不进行采样。
  7. 第二个SCLK上升沿到来,再次采样,如此循环8次,完成一个字节的传输。

Mode 3 (CPOL=1, CPHA=1)是另一个极端,但逻辑对称:空闲时SCLK为高,数据在SCLK的第二个边沿(对于CPOL=1,第一个边沿是下降沿,第二个边沿是上升沿)被采样。你可以发现,Mode 0和Mode 3有一个共同点:数据采样的边沿(对于主从设备都是同一个边沿)发生在SCLK脉冲的中间位置,这给了数据线充足的时间(半个时钟周期)来达到稳定状态,抗干扰能力最强。因此,绝大多数SPI器件(如Flash、EEPROM、传感器)都支持Mode 0或Mode 3,并且常将Mode 0作为默认模式。

Mode 1和Mode 2则相对少见,它们的数据采样边沿更靠近数据更新的边沿,稳定时间窗口更小,对硬件时序要求更苛刻。

实操心得:如何确定从设备的SPI模式?永远不要猜!第一选择是仔细阅读数据手册(Datasheet)的时序图章节。如果手册找不到,一个实用的方法是:如果器件是常见的存储器或传感器,先尝试Mode 0。如果通信失败(读回的数据全是0xFF或0x00),再尝试Mode 3。可以在代码中灵活配置主设备的SPI模式进行测试。用逻辑分析仪抓取波形是最直接的调试方法,可以清晰地看到时钟和数据的关系。

3.3 用逻辑分析仪“看”到时序:以读取Flash ID为例

理论说再多,不如看一次真实的波形。假设我们要与一个SPI Flash芯片(支持Mode 0)通信,读取它的制造商和设备ID。标准命令是0x9F

  1. 我们设置主设备为Mode 0,时钟频率设为1MHz(初期调试建议用较低频率)。
  2. 拉低CS。
  3. 主设备通过MOSI发送命令字节0x9F(二进制1001 1111,MSB先发)。
  4. 与此同时,从设备在MISO上回送数据。发送0x9F的同时,主设备收到的第一个字节可能是无效的(因为从设备还没准备好),但紧接着,从设备就会开始回送ID。通常连续读回3个字节:制造商ID(如0xEF)、存储器类型(如0x40)、容量ID(如0x17)。
  5. 拉高CS。

用逻辑分析仪抓取这段通信,你会看到:CS拉低后,SCLK开始产生脉冲。在每一个SCLK上升沿,MOSI和MISO线上的数据电平被捕获。将MOSI线上的电平序列翻译出来,就是0x9F;将MISO线上的电平序列翻译出来,就是0xEF0x400x17。这个可视化的过程,能让你对“全双工”、“边沿采样”有最直观的认识。

4. 从寄存器到代码:STM32 HAL库驱动SPI Flash实战

理解了硬件和时序,我们进入实战环节。以STM32F103系列MCU和一块W25Q16JV SPI Flash芯片为例,使用STM32CubeMX和HAL库,完成从初始化到读写擦除的全流程代码解析。

4.1 硬件连接与CubeMX配置

假设连接如下:

  • STM32 SPI1 (主设备)
    • PA5 -> SPI1_SCK -> Flash CLK
    • PA6 -> SPI1_MISO -> Flash DO (Data Output)
    • PA7 -> SPI1_MOSI -> Flash DI (Data Input)
  • STM32 GPIO
    • PA4 (任意GPIO) -> Flash CS
    • (可选) PB0 -> Flash HOLD (上拉)
    • (可选) PB1 -> Flash WP (上拉)

在CubeMX中:

  1. 启用SPI1,模式选择“Full-Duplex Master”。
  2. 配置参数:
    • Prescaler (时钟分频): 先设置为/256,得到低速(约280kHz),便于调试,稳定后可提高。
    • CPOL: Low (对应Mode 0/1)
    • CPHA: 1 Edge (对应Mode 0/2)。这里“1 Edge”即第一个边沿,与CPHA=0对应。这是CubeMX里最容易搞混的地方!它的“1 Edge”指的是数据在第一个边沿捕获,即CPHA=0。如果要配置Mode 3,需要CPOL=High, CPHA=2 Edge(第二个边沿)。
    • Data Size: 8 bits
    • First Bit: MSB First (绝大多数器件都是MSB先传)
    • NSS: Disabled (因为我们使用软件片选GPIO)
  3. 配置PA4为GPIO_Output,并设置初始输出电平为高(CS无效)。
  4. 生成代码。

4.2 软件驱动层封装:核心函数实现

生成的代码提供了HAL_SPI_TransmitReceive等基础函数,但我们需要封装更适合Flash操作的函数。

// spi_flash.c #include "spi_flash.h" // 定义CS引脚操作宏 #define FLASH_CS_LOW() HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_Pin, GPIO_PIN_RESET) #define FLASH_CS_HIGH() HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_Pin, GPIO_PIN_SET) // 1. 基础字节读写函数 static uint8_t SPI_Flash_SendByte(uint8_t byte) { uint8_t rx_data; // HAL_SPI_TransmitReceive是阻塞式全双工函数,同时完成发送和接收 HAL_SPI_TransmitReceive(&hspi1, &byte, &rx_data, 1, HAL_MAX_DELAY); return rx_data; } // 2. 读取Flash状态寄存器(重要!) uint8_t SPI_Flash_ReadStatusReg(void) { uint8_t cmd = 0x05; // 读状态寄存器1命令 uint8_t status; FLASH_CS_LOW(); SPI_Flash_SendByte(cmd); status = SPI_Flash_SendByte(0xFF); // 发送哑元数据以产生时钟来接收 FLASH_CS_HIGH(); return status; } // 3. 等待Flash空闲(检查BUSY位) void SPI_Flash_WaitForReady(void) { while ((SPI_Flash_ReadStatusReg() & 0x01) == 0x01) // 检查状态寄存器第0位(BUSY) { // 可以加入超时机制,防止死等 } } // 4. 写使能 void SPI_Flash_WriteEnable(void) { uint8_t cmd = 0x06; FLASH_CS_LOW(); SPI_Flash_SendByte(cmd); FLASH_CS_HIGH(); } // 5. 读取数据(标准读) void SPI_Flash_ReadData(uint32_t addr, uint8_t *pBuffer, uint32_t size) { uint8_t cmd = 0x03; // 标准读命令 FLASH_CS_LOW(); SPI_Flash_SendByte(cmd); SPI_Flash_SendByte((addr >> 16) & 0xFF); // 发送24位地址,高位在前 SPI_Flash_SendByte((addr >> 8) & 0xFF); SPI_Flash_SendByte(addr & 0xFF); for(uint32_t i=0; i<size; i++) { pBuffer[i] = SPI_Flash_SendByte(0xFF); // 连续读取 } FLASH_CS_HIGH(); } // 6. 页编程(写数据,一次最多256字节) void SPI_Flash_PageProgram(uint32_t addr, uint8_t *pData, uint32_t size) { uint8_t cmd = 0x02; // 页编程命令 if(size > 256) size = 256; // 页编程不能跨页 SPI_Flash_WaitForReady(); // 等待上次操作完成 SPI_Flash_WriteEnable(); // 写使能 FLASH_CS_LOW(); SPI_Flash_SendByte(cmd); SPI_Flash_SendByte((addr >> 16) & 0xFF); SPI_Flash_SendByte((addr >> 8) & 0xFF); SPI_Flash_SendByte(addr & 0xFF); for(uint32_t i=0; i<size; i++) { SPI_Flash_SendByte(pData[i]); } FLASH_CS_HIGH(); SPI_Flash_WaitForReady(); // 等待编程完成 } // 7. 扇区擦除(4KB) void SPI_Flash_SectorErase(uint32_t addr) { uint8_t cmd = 0x20; // 扇区擦除命令 SPI_Flash_WaitForReady(); SPI_Flash_WriteEnable(); FLASH_CS_LOW(); SPI_Flash_SendByte(cmd); SPI_Flash_SendByte((addr >> 16) & 0xFF); SPI_Flash_SendByte((addr >> 8) & 0xFF); SPI_Flash_SendByte(addr & 0xFF); FLASH_CS_HIGH(); SPI_Flash_WaitForReady(); // 擦除耗时较长,需等待 }

4.3 避坑指南:HAL库SPI驱动中的那些“坑”

  1. 时钟分频与极性问题:CubeMX里SPI的时钟分频是基于APB总线时钟的。务必核对最终生成的时钟频率是否在你的从设备支持范围内。极性和相位(CPOL/CPHA)必须与从设备严格匹配,这是通信失败的首要排查点。
  2. 阻塞式HAL_SPI_TransmitReceive的超时:这个函数的最后一个参数是超时时间(毫秒)。如果SPI通信因硬件问题卡住,这个超时会触发错误回调。务必在代码中实现HAL_SPI_ErrorCallback函数,至少打印个错误信息,否则程序会死等。
  3. DMA与中断模式:对于高速、大数据量传输(如读写大量Flash数据),强烈建议使用DMA或中断模式,避免阻塞主循环。HAL库提供了HAL_SPI_TransmitReceive_DMAHAL_SPI_TransmitReceive_IT函数。使用DMA时,需要注意内存对齐和缓存一致性问题(如果开了D-Cache)。
  4. CS信号的手动控制:当使用软件CS时,HAL库函数不会控制CS引脚。你必须像上面代码那样,在调用传输函数前后手动拉低和拉高CS。确保在每次传输前后都有完整的CS控制序列,这是很多“时灵时不灵”问题的根源。
  5. Flash的写保护与状态寄存器:在写或擦除Flash前,必须先发送写使能命令(0x06),且该命令在一次写/擦除操作后会自动清除。每次操作后必须等待BUSY位清零,才能进行下一步操作。忽略这个等待,直接发起下一次操作,是导致数据写入失败的最常见原因。

5. 当硬件SPI不够用:软件模拟SPI的灵活性与代价

不是所有MCU都有足够的硬件SPI外设,或者你的硬件SPI引脚可能被其他功能占用。这时,“软件模拟SPI”(Bit-Banging)就成了救命稻草。其核心思想很简单:用普通的GPIO口,通过代码精确控制电平翻转的时序,来模拟出SCLK、MOSI、MISO和CS的行为。

5.1 软件SPI的实现骨架

以STC15系列单片机(51内核)为例,模拟Mode 0的SPI:

// 定义GPIO引脚 sbit SOFT_SPI_SCLK = P1^0; sbit SOFT_SPI_MOSI = P1^1; sbit SOFT_SPI_MISO = P1^2; sbit SOFT_SPI_CS = P1^3; // 初始化,将所有引脚设为准双向口,CS置高 void Soft_SPI_Init(void) { SOFT_SPI_CS = 1; SOFT_SPI_SCLK = 0; // Mode 0, 空闲低电平 } // 模拟发送/接收一个字节 (MSB first) uint8_t Soft_SPI_ReadWriteByte(uint8_t txData) { uint8_t i, rxData = 0; SOFT_SPI_CS = 0; // 拉低CS // 此处可加微小延时,等待从设备准备,特别是高速时 for(i=0; i<8; i++) { SOFT_SPI_SCLK = 0; // 确保从空闲状态开始 // 准备数据位 if(txData & 0x80) // 先发送最高位 SOFT_SPI_MOSI = 1; else SOFT_SPI_MOSI = 0; txData <<= 1; // 短暂延时,建立时间(Tsu) _nop_(); _nop_(); // 产生上升沿,从设备在此刻采样MOSI,主设备也应在此刻采样MISO SOFT_SPI_SCLK = 1; rxData <<= 1; // 左移,为接收下一位做准备 if(SOFT_SPI_MISO) rxData |= 0x01; // 短暂延时,保持时间(Th) _nop_(); _nop_(); SOFT_SPI_SCLK = 0; // 下降沿,数据线可以变化 } SOFT_SPI_CS = 1; return rxData; }

5.2 软件SPI的致命弱点与适用场景

软件SPI的最大问题在于速度慢且不稳定。它的速度受限于CPU执行指令的速度和延时函数的精度。即使使用_nop_()(空操作)来延时,在中断频繁或任务调度复杂的系统中,时序也极易被打乱,导致通信错误。

适用场景

  • 对速度要求极低(几十KHz以下)的场合。
  • 调试初期,快速验证硬件连接和从设备基本功能。
  • MCU硬件SPI引脚被占用,且通信速率要求不高的补充方案。

绝对避免的场景

  • 需要高速通信(>1MHz)。
  • 需要长时间、大数据量稳定传输。
  • 系统实时性要求高,中断频繁。

经验之谈:软件SPI的延时_nop_()个数需要根据主频精确计算和测试。一个粗略的方法是,用逻辑分析仪抓取波形,调整_nop_()数量,使得SCLK周期、数据建立和保持时间满足从设备数据手册的要求。这是一个非常耗时且不精确的过程,再次印证了硬件SPI的优越性。

6. 进阶与对比:SPI在FPGA、ESP32与高速系统中的变体

当项目需求走向高端,SPI也在不断进化,衍生出更复杂、更强大的变体。

6.1 FPGA中的SPI:从状态机到AXI接口

在FPGA(如Xilinx Zynq)中实现SPI,给了你最大的灵活性。你可以用Verilog/VHDL编写一个精确符合你需求的SPI控制器。

  • SPI Master状态机设计:一个典型的SPI Master核心是一个状态机,包含IDLE、LOAD、SHIFT、DONE等状态。在SHIFT状态,根据CPOL和CPHA,在正确的时钟边沿对移位寄存器进行移位操作,并控制MOSI输出和采样MISO输入。
  • 与处理器交互:在Zynq这样的SoC FPGA中,你通常通过AXI总线将自定义的SPI IP核挂载到PS(处理器系统)上。PS端的Linux驱动或裸机程序通过读写IP核的寄存器(如控制寄存器、数据寄存器、状态寄存器)来发起SPI传输。Vivado的IP Integrator可以帮你快速搭建这样的系统,甚至可以直接使用Xilinx提供的AXI Quad SPI IP核。
  • 速度优势:FPGA可以实现极高速度的SPI(数百MHz),因为时钟和数据生成都由硬件并行完成,不受软件指令流限制。这对于需要超高速数据流(如图像传感器接口)的应用至关重要。

6.2 ESP32-S3的SPI外设:灵活性与性能的平衡

ESP32-S3的SPI外设非常强大,它支持:

  • 多种工作模式:除了标准的SPI模式,还支持Dual SPI和Quad SPI(QSPI)。在QSPI模式下,数据线从1根(MOSI/MISO)增加到4根(IO0-IO3),可以同时传输4位数据,理论带宽提升4倍。这正是很多高速SPI Flash(如ESP32用于存储程序和数据的片外Flash)所采用的模式。
  • 内存映射模式:对于QSPI Flash,ESP32-S3可以将其映射到处理器的内存地址空间。这意味着你可以像读取内部RAM一样,直接使用指针访问Flash中的内容,无需调用复杂的读函数,极大提升了执行效率。
  • DMA支持:内置的GDMA(通用DMA)可以接管SPI的数据搬运,让CPU在SPI传输大量数据时得以解脱,处理其他任务。

6.3 SPI vs. I2C vs. UART:如何选择?

这是嵌入式通信协议选择的经典问题。我们来做一个快速对比:

特性SPII2CUART
线数3-4+ (SCLK, MOSI, MISO, CS*N)2 (SDA, SCL)2 (TX, RX) 或 1
速度(MHz级别,甚至百MHz)中 (标准模式100kbps,快速模式400kbps,高速模式3.4Mbps)低到中 (常见115200bps,最高可达数Mbps)
通信方式全双工,同步半双工,同步全双工,异步
拓扑一主多从(独立片选),点对点多主多从,总线式点对点,多设备需软件协议
协议复杂度极简(几乎无协议)中等 (地址、ACK/NACK、仲裁)简单 (起始位、数据位、停止位)
硬件开销主设备IO口需求多主从设备IO口需求少主从设备IO口需求少
典型应用Flash, 显示屏, ADC/DAC, 高速传感器温度传感器, EEPROM, IO扩展芯片, 低速设备调试串口, 蓝牙/Wi-Fi模块, GPS模块

选择建议:

  • 追求速度、实时性、点对点通信:选SPI。比如驱动TFT屏、读写大容量Flash。
  • 设备多、引脚资源紧张、速度要求不高:选I2C。比如连接一堆传感器到MCU。
  • 简单、通用、距离稍远、无需时钟线:选UART。比如和电脑通信、连接无线模组。

最后,关于网络热词中提到的“SPI中ACK是什么角色”,这里需要澄清:标准的SPI协议中没有ACK(应答)机制。这是SPI与I2C的一个根本区别。SPI主设备发出时钟和数据,从设备被动接收和发送,主设备默认从设备总是“在线”且“准备好”的。通信的可靠性依赖于严格的时序和硬件连接。一些特定的SPI器件可能会在数据帧内定义自己的软件ACK机制,但这属于器件自定义行为,而非SPI协议本身的规定。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/31 12:47:28

马尔可夫链:从状态转移矩阵到稳态分布,构建可解释的序列预测模型

1. 项目概述&#xff1a;从随机游走到状态转移 如果你在数据分析、金融预测或者算法策略的领域里摸爬滚打过一阵子&#xff0c;大概率会碰到一种让人又爱又恨的场景&#xff1a;系统的未来状态&#xff0c;似乎只和现在有关&#xff0c;跟过去漫长的历史没什么直接关系。比如&a…

作者头像 李华
网站建设 2026/8/30 19:14:41

蓝桥杯国赛经典题解:四阶幻方搜索剪枝与算法优化实战

1. 项目概述&#xff1a;从一道经典国赛题看算法竞赛的思维深度提起“蓝桥杯”国赛&#xff0c;尤其是大学A组的题目&#xff0c;很多参加过竞赛的朋友都会心头一紧。这个级别的题目&#xff0c;早已不是考察简单的语法或基础算法&#xff0c;而是对选手数学思维、编程技巧和耐…

作者头像 李华
网站建设 2026/8/30 19:15:25

Qseven遇上i.MX8M Plus:老载板升级边缘AI的实战指南

我最近在做一个工业设备升级项目&#xff0c;客户手里有一条老产线&#xff0c;载板还是五年前按Qseven规格设计的&#xff0c;主控模块用的是老一代低功耗平台。客户提的需求很直接&#xff1a;要在不重画载板、不动结构件的前提下&#xff0c;给设备加上AI视觉缺陷检测能力。…

作者头像 李华
网站建设 2026/8/31 4:29:17

AI 小说漫改视频零基础入门:口型同步和字幕匹配怎么调?

很多新手在尝试把小说做成漫剧或短剧时&#xff0c;最崩溃的往往不是画面生成得不够精美&#xff0c;而是辛苦做出来的视频&#xff0c;角色一开口说话&#xff0c;嘴形和台词完全对不上&#xff0c;字幕也像是硬贴上去的&#xff0c;怎么看怎么出戏。这个问题不解决&#xff0…

作者头像 李华