深入解析SPI协议:从四线制到工程实践,掌握高速串行通信核心
发布时间:2026/8/27 3:29:47 作者:尧图编辑部 阅读量:1,286

1. 项目概述从“协议”到“对话”“SPI协议理解”这个标题听起来像是一份技术文档的开篇但在我看来它更像是一场工程师之间的对话邀请。SPI全称Serial Peripheral Interface中文常译为串行外设接口。它不像UART那样需要事先约定好波特率也不像I2C那样需要复杂的地址寻址和应答机制。SPI的本质是一种高速、全双工、同步的串行通信总线其核心思想极其简洁用四根线实现一个主设备与一个或多个从设备之间稳定、高效的数据交换。我接触SPI超过十年了从最早用51单片机的IO口模拟时序驱动SD卡到后来在STM32、ESP32上使用硬件SPI外设驱动各种屏幕、Flash、传感器再到用FPGA编写SPI主从机逻辑。我发现很多初学者甚至一些有经验的工程师对SPI的理解容易停留在“四根线主从通信”的层面而忽略了其协议细节、模式选择、硬件与软件协同背后的精妙设计。这直接导致了在实际项目中SPI通信不稳定、速率上不去、多从机切换混乱等问题。这篇文章我就以一个老工程师的视角掰开揉碎地聊聊SPI目标不仅是让你“知道”SPI是什么更是让你“理解”它为什么这么设计以及在实际项目中如何“用好”它。2. SPI协议的核心思想与硬件基础2.1 四线制与角色定义一场精心安排的对话SPI通信至少需要四根线这是它的物理基础也决定了它的工作模式SCLK (Serial Clock串行时钟)由主设备产生并输出给所有从设备。这是整个通信的“节拍器”所有数据的发送和接收都严格跟随这个时钟的边沿进行。SCLK的存在是SPI被称为“同步”通信的根本原因。主设备完全掌控通信的节奏。MOSI (Master Out Slave In主出从入)主设备的数据输出线从设备的数据输入线。主设备通过这根线向从设备发送指令或数据。MISO (Master In Slave Out主入从出)主设备的数据输入线从设备的数据输出线。从设备通过这根线向主设备回传数据。CS/SS (Chip Select / Slave Select片选)由主设备控制用于选择要与哪个从设备进行通信。通常为低电平有效。当某个从设备的CS线被主设备拉低时该从设备被“唤醒”并准备响应通信其他CS线为高电平的从设备则忽略SCLK、MOSI上的信号并将自己的MISO置于高阻态以避免总线冲突。注意CS线是SPI实现一主多从的关键。每个从设备都需要一根独立的CS线连接到主设备。这也是SPI相比I2C共用数据线靠地址寻址的一个特点硬件连接更简单直观但需要占用更多主设备GPIO。这四根线构成了一场清晰的对话主设备Master是发起者和控制者它通过拉低某个从设备Slave的CS线说“你准备好我要跟你说话了。”然后主设备产生SCLK时钟同时在MOSI上逐位说出数据被选中的从设备则在每个时钟节拍下一边从MOSI读取主设备说的话一边在MISO上逐位回应自己的话。由于MOSI和MISO是独立的所以“说”和“听”可以同时进行这就是“全双工”。2.2 深入时序时钟极性(CPOL)与时钟相位(CPHA)这是SPI理解中最容易混淆也最核心的部分。它定义了数据在时钟信号的哪个边沿被采样捕获这直接关系到通信能否成功。SPI有四种模式由时钟极性CPOL和时钟相位CPHA两个参数组合而成。CPOL (Clock Polarity时钟极性)CPOL0时钟空闲状态即CS有效后数据传输开始前为低电平。CPOL1时钟空闲状态为高电平。CPHA (Clock Phase时钟相位)CPHA0数据在时钟的第一个边沿如果CPOL0则是上升沿如果CPOL1则是下降沿被采样在下一个边沿切换。CPHA1数据在时钟的第二个边沿被采样在第一个边沿切换。为了更直观我们通常结合波形来记忆这四种模式Mode 0, 1, 2, 3模式CPOLCPHA时钟空闲电平数据采样边沿数据切换边沿常见应用Mode 000低电平第一个上升沿下降沿最常用多数SPI Flash、传感器Mode 101低电平第二个下降沿第一个上升沿部分ADC、DAC芯片Mode 210高电平第一个下降沿上升沿相对少见Mode 311高电平第二个上升沿第一个下降沿部分RFID芯片实操心得绝大多数SPI从设备的数据手册都会明确要求通信的SPI模式。主从设备的SPI模式必须严格一致否则无法正确通信。一个快速判断模式的方法是看数据手册的时序图找到数据采样Data Sampled对应的时钟边沿。如果是在时钟从低变高上升沿时采样且时钟空闲为低那就是Mode 0如果是在时钟从高变低下降沿时采样且时钟空闲为低那就是Mode 1依此类推。2.3 硬件片选 vs. 软件片选这是一个工程实践中的关键选择。硬件片选Hardware CS/NSS主设备的SPI外设模块通常有一个专用的NSSNegative Slave Select引脚。当配置为硬件主模式时这个引脚会在SPI通信开始时自动拉低通信结束后自动拉高。优点是省心时序精准由硬件保证。缺点是通常一个SPI外设只有一个硬件NSS引脚难以直接驱动多个从设备除非外加译码器。软件片选Software CS使用主设备的一个普通GPIO引脚来模拟CS信号。在通信前手动将该GPIO拉低通信结束后再手动拉高。优点是极其灵活一个SPI外设可以配合任意多个GPIO来控制多个从设备是最常见的多从机连接方式。缺点是需要程序员手动控制时序要特别注意在产生SCLK之前拉低CS在SCLK停止之后再拉高CS。避坑指南对于大多数应用我强烈推荐使用软件片选。灵活性压倒一切。但在使用软件片选时有两点必须注意使能SPI外设前先配置好CS引脚为输出模式并置高空闲状态。避免SPI使能瞬间产生错误的片选信号。通信间隔CS拉高时间很多从设备尤其是SPI Flash要求两次通信之间CS必须有一个最小的高电平时间tCSH。如果主设备切换从设备的速度太快没有满足这个时间可能导致从设备内部状态机紊乱。在快速连续操作不同从设备时需要在软件中插入微小延时或检查器件手册。3. SPI协议的工作机制与数据交换3.1 数据帧格式与移位过程SPI协议本身没有预定义的数据帧格式如起始位、停止位、校验位。它传输的是原始的二进制位流。常见的帧长度是8位一个字节或16位但这完全由主从设备双方约定俗成SPI硬件只负责按位移动。数据传输的核心是一个名为“移位寄存器”的硬件单元。主从设备内部各有一个移位寄存器并且通过MOSI和MISO线首尾相连形成一个巨大的“循环移位寄存器”。工作过程以8位数据Mode 0为例主设备拉低CS启动通信。主设备将待发送的8位数据例如0xA5二进制10100101写入自己的发送数据寄存器TXDR该数据会自动加载到主设备的移位寄存器中。主设备产生第一个SCLK脉冲上升沿。在上升沿时刻从设备采样MOSI线上的电平即主设备移位寄存器的最高位MSB并将其移入自己移位寄存器的最低位LSB。同时在SCLK的下降沿Mode 0的数据切换边沿主设备移位寄存器的内容会左移一位其MSB通过MOSI线输出到下一位从设备移位寄存器的内容也会左移一位其MSB通过MISO线输出给主设备主设备在下一个上升沿采样它。重复第3步8次。8个时钟周期后主设备最初写入的0xA5被完全移出到了从设备而从设备移位寄存器中原来的内容可能是之前收到的数据或是它要发送的数据则被完全移入到了主设备。主设备拉高CS结束通信。关键理解SPI的每次通信一定是双向同时的。主设备在发送一个字节的同时也必然会收到一个字节。这个收到的字节可能是从设备有意义的响应数据也可能是无意义的“哑元”Dummy Byte通常为0xFF或0x00。因此在编写SPI读数据函数时通常的操作是主设备先发送一个命令或地址需要的数据然后继续发送若干个哑元在这些哑元被发送出去的同时从设备返回的数据才会被主设备接收进来。3.2 多从机连接拓扑SPI支持一主多从主要有两种连接方式独立片选Standard SPI这是最标准、最可靠的方式。每个从设备独占MOSI、MISO、SCLK但CS线独立连接主设备的不同GPIO。主设备通过控制不同的CS线来选择通信对象。优点是逻辑清晰各从设备互不干扰可以工作在不同的SPI模式。缺点是占用主设备GPIO资源多。菊花链Daisy Chain所有从设备的MOSI、MISO首尾相连形成一个环。只有第一个从设备的MOSI接主设备MOSI最后一个从设备的MISO接主设备MISO所有设备共用SCLK和一个CS。主设备发送一个很长的数据流数据像接力棒一样从一个设备传到下一个。优点是节省GPIO只需一个CS。缺点是所有从设备必须支持菊花链模式且通信效率低要操作最后一个设备必须把前面所有设备的数据都移位一遍软件复杂。常见于某些特定的数字电位器、LED驱动芯片阵列。工程选择除非器件手册明确推荐或要求否则一律使用独立片选方式。它的简单性和可靠性在工程中价值更高。4. SPI的工程实现从软件模拟到硬件外设4.1 软件模拟SPIBit-Banging在没有硬件SPI外设或硬件SPI引脚被占用时可以用普通GPIO口模拟SPI时序。这给了我们极大的灵活性也是理解SPI时序的最佳实践。下面以STM32标准库风格模拟Mode 0时序为例展示一个字节的收发函数// 假设引脚定义 #define SIM_SPI_SCLK_PIN GPIO_Pin_6 #define SIM_SPI_MOSI_PIN GPIO_Pin_7 #define SIM_SPI_MISO_PIN GPIO_Pin_8 #define SIM_SPI_CS_PIN GPIO_Pin_9 #define SIM_SPI_PORT GPIOB // 初始化函数 (略) void SIM_SPI_Init(void); // 软件模拟SPI发送接收一个字节 uint8_t SIM_SPI_TransmitReceiveByte(uint8_t txData) { uint8_t rxData 0; uint8_t i; // 循环8次处理每个位 for (i 0; i 8; i) { // 1. 先设置MOSI输出当前位的值 (MSB First) if (txData 0x80) { // 检查最高位 GPIO_SetBits(SIM_SPI_PORT, SIM_SPI_MOSI_PIN); } else { GPIO_ResetBits(SIM_SPI_PORT, SIM_SPI_MOSI_PIN); } txData 1; // 左移准备下一个位 // 2. 制造一个下降沿 (Mode 0数据切换边沿) GPIO_SetBits(SIM_SPI_PORT, SIM_SPI_SCLK_PIN); Delay_us(1); // 短暂延时维持高电平 GPIO_ResetBits(SIM_SPI_PORT, SIM_SPI_SCLK_PIN); // 3. 制造一个上升沿 (Mode 0数据采样边沿) GPIO_SetBits(SIM_SPI_PORT, SIM_SPI_SCLK_PIN); // 在上升沿期间读取MISO if (GPIO_ReadInputDataBit(SIM_SPI_PORT, SIM_SPI_MISO_PIN)) { rxData (rxData 1) | 0x01; // 左移并入1 } else { rxData (rxData 1); // 左移并入0 } // 注意这里读取的是上一个时钟周期从设备放到MISO上的位 Delay_us(1); GPIO_ResetBits(SIM_SPI_PORT, SIM_SPI_SCLK_PIN); // 回到空闲低电平 } return rxData; }注意事项Delay_us(1)的延时时间决定了模拟SPI的速率。这个时间必须大于从设备要求的最小SCLK高/低电平时间。位顺序Bit Order代码示例是MSB最高位先发送。有些器件要求LSB最低位先发送需要修改循环内的位处理逻辑。软件SPI会大量占用CPU时间速率有限通常在几百Kbps到1Mbps且时序容易受中断干扰。仅适用于低速或临时调试场景。4.2 硬件SPI外设使用以STM32 HAL库为例现代MCU如STM32 ESP32都集成了硬件SPI外设通过DMA和中断机制可以高效不占CPU地完成通信。初始化配置关键点SPI_HandleTypeDef hspi1; void SPI1_Init(void) { hspi1.Instance SPI1; hspi1.Init.Mode SPI_MODE_MASTER; // 主模式 hspi1.Init.Direction SPI_DIRECTION_2LINES; // 全双工 hspi1.Init.DataSize SPI_DATASIZE_8BIT; // 8位数据帧 hspi1.Init.CLKPolarity SPI_POLARITY_LOW; // CPOL0 hspi1.Init.CLKPhase SPI_PHASE_1EDGE; // CPHA0 (对应Mode 0) hspi1.Init.NSS SPI_NSS_SOFT; // **软件片选管理** hspi1.Init.BaudRatePrescaler SPI_BAUDRATEPRESCALER_8; // 波特率预分频决定SCLK频率 hspi1.Init.FirstBit SPI_FIRSTBIT_MSB; // MSB先行 hspi1.Init.TIMode SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation SPI_CRCCALCULATION_DISABLE; hspi1.Init.CRCPolynomial 10; if (HAL_SPI_Init(hspi1) ! HAL_OK) { Error_Handler(); } }阻塞式传输函数// 拉低CS HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); uint8_t txData[2] {0x03, 0x00}; // 读命令和地址 uint8_t rxData[2] {0}; // HAL_SPI_TransmitReceive 是阻塞式全双工函数 // 它发送txData的同时将接收到的数据存入rxData HAL_SPI_TransmitReceive(hspi1, txData, rxData, 2, 1000); // 拉高CS HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);中断与DMA方式对于大数据量传输如读写SPI Flash、刷新屏幕务必使用DMA。可以调用HAL_SPI_TransmitReceive_DMA函数并配合回调函数HAL_SPI_TxRxCpltCallback来处理传输完成事件实现非阻塞通信极大解放CPU。速率计算SPI时钟频率 APBx总线时钟 / BaudRatePrescaler。例如APB2时钟为72MHz预分频设为8则SPI时钟为9MHz。需确保此频率不超过从设备支持的最大SCLK频率。5. SPI高级话题与常见器件驱动要点5.1 SPI Flash存储器驱动解析SPI Flash如W25Q64, GD25Q127C是SPI最典型的应用之一。驱动它需要理解其命令集和特殊引脚。HOLD#引脚当此引脚被拉低时Flash会暂停当前正在进行的任何操作如编程、擦除并将所有IO置于高阻态但内部状态机保持。主设备可以在此期间去操作其他共享SPI总线的设备。操作完成后再将HOLD#拉高Flash从中断处继续执行。如果不用此功能通常建议将HOLD#引脚上拉至VCC防止意外干扰。WP#引脚写保护引脚。拉低时禁止对状态寄存器进行写操作防止意外擦写。对于需要频繁擦写的应用通常也将其上拉至VCC禁用保护。关键操作流程读ID发送0x9F命令后续会收到制造商ID、存储器类型、容量ID。用于检测器件是否连接正常。写使能在执行编程或擦除前必须先发送0x06Write Enable命令。页编程发送0x02命令24位地址数据。注意一次页编程不能跨页通常一页256字节。写入前目标扇区必须是已擦除状态值为0xFF。扇区擦除发送0x20命令24位地址擦除一个4KB扇区。擦除时间较长几十ms期间读取状态寄存器0x05的BUSY位等待其为0。读数据发送0x03标准读或0x0B快速读命令24位地址然后持续产生时钟Flash会从该地址开始连续输出数据。快速读命令后有一个Dummy Cycle哑元周期能提供更高的读取速率。避坑指南SPI Flash的编程和擦除操作耗时很长绝不能在等待期间用查询方式阻塞CPU。正确做法是启动操作后延时或切换到其他任务定期查询状态寄存器。更好的方法是利用Flash的自动轮询BUSY位结束的特性某些读命令在BUSY期间会输出0或者配合RTOS的任务调度。5.2 多SPI外设与通道管理以ESP32-S3为例像ESP32-S3这类高性能MCU可能包含多个SPI外设如SPI2, SPI3且功能强大。SPI主机模式通常SPI2 (HSPI) 和 SPI3 (VSPI) 被用作通用主机。它们可以配置到不同的GPIO引脚有引脚映射表非常灵活。SPI从机模式某些SPI外设可以配置为从机用于与其他主MCU通信。SPI Job Channel在一些复杂的驱动框架如ESP-IDF的SPI Master驱动中引入了“Job”和“Channel”的概念。Channel代表一条到特定从设备的物理通信链路包含了该从设备的配置模式、速率、CS引脚等。初始化时创建。Job代表一次具体的传输任务包含要发送的数据缓冲区、接收数据缓冲区、长度等。通过将Job提交Queue到对应的Channel来执行传输。 这种抽象将设备配置和传输任务解耦特别适合在RTOS环境中多个任务需要操作同一个SPI设备或者需要管理多个SPI从设备的场景能有效避免资源冲突。5.3 SPI与其他串行协议的对比理解SPI常常需要将其与UART、I2C放在一起比较。特性SPII2CUART全称Serial Peripheral InterfaceInter-Integrated CircuitUniversal Asynchronous Receiver/Transmitter通信类型同步有时钟线同步有时钟线异步无时钟线靠波特率数据线全双工MOSI, MISO半双工SDA全双工TX, RX控制线CS (每从机一根)SCL (时钟)无寻址方式硬件片选线选软件地址7/10位无点对点最高速率高 (50Mbps)标准/快速/高速模式 (100k/400k/3.4Mbps)中等 (通常10Mbps)优点速率高协议简单时序易控引脚少支持多主多从有应答机制点对点简单距离可较长缺点引脚占用多无硬件应答无流控速率较低协议较复杂需上拉电阻速率精度要求高无多设备协议选择建议追求高速、点对点或一主几从选SPI。器件多、引脚紧张、速率要求不高选I2C。与PC通信、调试打印、长距离选UART。6. 实战问题排查与调试技巧6.1 通信失败的排查步骤当SPI通信没有反应时可以按照以下步骤排查检查物理连接确保VCC、GND连接正确牢固。这是最常见的问题。确认电源与电平测量从设备VCC电压是否达标。确认主从设备逻辑电平是否匹配如3.3V与5V器件互连需电平转换。验证片选信号用示波器或逻辑分析仪观察CS引脚。确保在通信期间CS被稳定拉低且通信间隔满足器件要求的最小CS高电平时间。抓取时序波形这是最有效的调试手段。使用示波器或逻辑分析仪同时捕捉SCLK、MOSI、MISO、CS四路信号。看模式对照数据手册检查SCLK空闲电平CPOL和数据采样边沿CPHA是否正确。看数据检查MOSI上发送的命令/地址是否正确MISO上是否有数据返回。看时序参数测量SCLK频率是否超限数据建立Setup和保持Hold时间是否满足从设备要求。核对软件配置SPI模式CPOL/CPHA是否与从设备一致数据位顺序MSB/LSB First是否正确时钟预分频是否合适速率是否太快如果是硬件NSS配置是否正确如果是软件CSGPIO初始化及控制时序是否正确检查从设备状态某些器件如Flash在执行内部编程或擦除时会拒绝新命令。尝试发送“读状态寄存器”等简单命令确认器件是否就绪。6.2 逻辑分析仪的使用对于数字通信调试一个支持协议分析功能的逻辑分析仪如Saleae比示波器更方便。它能长时间录制多路信号并自动解析出SPI数据包直观地显示为十六进制字节极大提升调试效率。连接与设置将分析仪的通道分别连接到SCLK、MOSI、MISO、CS。在软件中设置正确的阈值电压如3.3V并添加SPI协议分析器指定各通道角色和SPI模式。6.3 提高通信稳定性的技巧上拉电阻对于MOSI、MISO、SCLK通常不需要上拉。但对于CS引脚特别是软件控制且走线较长时可以考虑加上拉电阻如10kΩ确保空闲时为确定的高电平防止干扰。电源去耦在每个SPI器件的VCC和GND之间靠近引脚处放置一个0.1uF的陶瓷电容用于滤除高频噪声。阻抗匹配与布线对于高速SPI10MHz需考虑信号完整性。尽量缩短走线避免过孔MOSI/MISO/SCLK走线等长必要时串联小电阻22-33Ω进行阻抗匹配减少振铃。软件去抖与重试在初始化阶段或关键操作前可以增加读写测试如读ID如果失败则加入短暂延时后重试几次。DMA与中断对于持续的数据流务必使用DMA。并合理设置DMA和SPI中断的优先级避免因其他高优先级中断导致SPI数据流中断或溢出。SPI协议的精髓在于其简单和直接。它把复杂的通信同步问题通过一根时钟线清晰地定义下来。掌握它不仅仅是记住四根线和四种模式更是要理解数据在时钟边沿如何舞蹈理解主设备如何通过CS线指挥整个乐队理解在具体的芯片数据手册中那些时序参数如何转化为代码中的延时与配置。从软件模拟的逐位操控到硬件外设的DMA流传输SPI贯穿了嵌入式开发的各个层次。希望这篇长文能帮你建立起对SPI立体而深入的理解下次再遇到SPI相关的问题时你能胸有成竹地拿出示波器或者自信地修改代码让数据再次流畅地奔跑起来。