SPI通信协议详解:从原理到实战,嵌入式开发必懂
1. 项目概述从“线”到“话”的同步对话搞嵌入式开发尤其是和各类传感器、存储器、显示屏打交道SPISerial Peripheral Interface这个名字你绝对绕不开。它和I²C、UART并称为嵌入式界的“三巨头”但脾气却大不相同。如果说UART是两个人隔着老远喊话异步I²C是几个人在一条小路上轮流发言半双工、地址寻址那SPI就是主从双方拉了几条专线面对面、高效率地同步聊天全双工、点对点。我最初接触SPI是为了驱动一块小小的SPI Flash芯片来存储数据。当时觉得不就是几根线嘛时钟、数据进、数据出再给个片选能有多复杂结果一上手就踩了坑时钟极性、相位设错读回来的全是乱码片选时序没处理好通信时灵时不灵。这些教训让我明白SPI协议看似简单直接但细节决定成败。它不像I²C有标准的协议帧更像是一种基于硬件接口的“君子协定”时序全靠主从设备双方约定好。理解透这些约定你才能让它稳定可靠地工作无论是读取IMU传感器ICM20948的数据还是点亮一块ST7735S的TFT屏幕亦或是与FPGA进行高速数据交换。这篇文章我就结合自己这些年调试各种SPI设备的经验掰开揉碎了讲讲SPI通信的核心原理、四种工作模式、硬件电路设计要点以及如何用软件模拟模拟SPI和硬件控制器如STM32的硬件SPI去驾驭它。目标是让你看完后不仅能看懂时序图更能独立设计、调试一个SPI通信系统避开我当年踩过的那些坑。2. SPI通信的核心原理与架构拆解SPI的本质是一种同步、全双工、主从式的串行通信总线。它的设计哲学是“简单粗暴高效”用最少的信号线实现高速数据交换特别适合板级器件之间的短距离通信。2.1 四线制基础与角色定义一个最基础的SPI连接需要四根信号线SCLK (Serial Clock) 串行时钟线。由主机产生并完全控制用于同步数据位传输的节奏。所有数据位的采样和输出都严格跟随这个时钟的边沿。这是SPI“同步”特性的核心。MOSI (Master Out Slave In) 主机输出从机输入线。数据从主机流向从机。MISO (Master In Slave Out) 主机输入从机输出线。数据从从机流回主机。注意MOSI和MISO是同时工作的这意味着主机在发送一个比特的同时也能接收一个比特实现了真正的全双工通信。SS/CS (Slave Select / Chip Select) 从机选择线。由主机控制低电平有效。当主设备需要和某个从设备通信时就将对应从设备的SS线拉低选中它。这是SPI“点对点”或“一点对多点”管理的基础。一个主机可以连接多个从机通过多根SS线分别控制。这里有一个关键点常被忽略MISO线的逻辑状态。当从设备未被选中其SS线为高电平时它必须将其MISO引脚置于高阻态High-Impedance。这是为了防止多个从设备的MISO线同时连接到主机MISO引脚时发生总线冲突。因此在实际电路中即使只有一个从设备也建议在主机MISO线上配置一个上拉电阻以确保在从机释放总线时主机能读取到一个确定的高电平避免悬空引脚引入噪声。2.2 时钟极性(CPOL)与相位(CPHA)通信的“暗号”这是SPI最核心也最容易出错的概念。SPI协议没有规定数据必须在时钟的上升沿还是下降沿采样而是把这个选择权留给了设备厂商。主从设备必须使用相同的时钟配置才能正确通信这就是CPOL和CPHA。CPOL (Clock Polarity) 时钟极性。定义SCLK线在空闲状态即两次传输之间SS为高时的电平。CPOL0 SCLK空闲时为低电平。CPOL1 SCLK空闲时为高电平。CPHA (Clock Phase) 时钟相位。定义数据采样的边沿。CPHA0 数据在第一个时钟边沿即SCLK从空闲状态跳变到有效状态的边沿被采样。对于CPOL0第一个边沿是上升沿对于CPOL1第一个边沿是下降沿。CPHA1 数据在第二个时钟边沿即SCLK跳变回空闲状态的边沿被采样。对于CPOL0第二个边沿是下降沿对于CPOL1第二个边沿是上升沿。这两者组合起来就构成了SPI的四种工作模式Mode 0, 1, 2, 3。通常设备的数据手册会明确说明它支持哪种模式。模式0 (CPOL0, CPHA0) 最常用的模式。空闲时钟为低数据在上升沿采样下降沿变化。很多SPI Flash如W25Q系列、传感器都默认用此模式。模式1 (CPOL0, CPHA1) 空闲时钟为低数据在下降沿采样上升沿变化。模式2 (CPOL1, CPHA0) 空闲时钟为高数据在下降沿采样上升沿变化。模式3 (CPOL1, CPHA1) 空闲时钟为高数据在上升沿采样下降沿变化。实操心得拿到一个新的SPI设备第一件事就是翻数据手册找到“SPI timing characteristics”或“interface”章节确认其支持的CPOL和CPHA。如果手册没写有些国产模块确实如此可以先用模式0尝试因为它是事实上的“默认模式”。如果通信失败再依次尝试其他三种模式。调试时用逻辑分析仪抓取SCLK、MOSI、MISO、CS四根线的波形对照时序图分析是定位模式错误最快的方法。2.3 数据帧格式与传输过程SPI的数据传输以“帧”为单位一帧通常是一个字节8位但也可能是其他长度如12位、16位这取决于设备。传输过程如下片选使能主机将目标从机的SS线拉低表示通信开始。时钟驱动与数据同步主机开始产生SCLK时钟脉冲。在每一个时钟周期内同时发生两件事主机通过MOSI线移出一位数据从最高位MSB或最低位LSB开始需统一。从机通过MISO线移出一位数据。数据采样根据约定的CPHA主从双方在对应的时钟边沿采样对方的数据线将数据位读入各自的移位寄存器。传输完成一帧数据的所有位传输完毕后主机可以拉高SS线结束本次通信也可以保持SS为低继续发送下一帧数据很多SPI Flash的读写命令就是多帧连续传输。关于数据位顺序SPI协议本身没有规定先传最高位(MSB)还是最低位(LSB)。绝大多数设备采用MSB First但总有例外比如某些音频编解码器。这同样需要在设备数据手册中确认。在配置MCU的SPI外设或编写模拟SPI代码时这是一个必须设置的参数。3. SPI的硬件设计与电路要点理解了原理我们来看看如何把它落实到电路板上。良好的硬件设计是稳定通信的基石。3.1 单主多从的拓扑结构SPI支持三种常见的连接方式标准单从机一个主机一个从机四根线直连。最简单直接。独立片选多从机一个主机多个从机。所有从机的SCLK、MOSI、MISO并联到主机对应引脚但每个从机有自己独立的SS线。主机通过拉低不同的SS线来选择通信对象。这是最推荐、最稳定的多从机连接方式。缺点是占用主机IO口较多n个从机需要n个SS引脚。菊花链(Daisy Chain)所有从机的SPI接口串接起来。主机只连接第一个从机第一个从机的输出连接到第二个从机的输入依此类推。数据像接力一样传递。这种方式节省SS线但所有从机收到的是相同的数据流需要设计特定的协议来区分命令和目标数据且通信速度受链长影响。一些具有菊花链功能的ADC、DAC芯片会用到此模式。3.2 上拉电阻与电平匹配SS片选线通常建议在主机端加上拉电阻如4.7kΩ或10kΩ到VCC。这确保了在主机初始化或复位期间SS线处于确定的高电平未选中状态防止从机误激活。MISO线如前所述强烈建议在主机MISO引脚处加上拉电阻。当总线上所有从机都不被选中时它们的MISO都是高阻上拉电阻保证了主机读取到稳定的高电平避免浮空输入引入的随机噪声和额外功耗。电平匹配确保主从设备的工作电压VDD和IO电平兼容。如果主机是3.3V从机是5V直接连接可能导致主机引脚过压损坏或逻辑电平识别错误。此时需要使用电平转换芯片如TXS0108E或电阻分压网络。3.3 信号完整性与布线考虑对于低速SPI如几MHz以下布线要求相对宽松。但当速度提升到几十MHz如读写QSPI Flash就必须考虑信号完整性等长要求SPI的几根线需要等长吗通常不需要严格等长。因为SPI是源同步时钟系统数据随时钟一起传输接收端用同一个时钟采样。只要数据相对时钟的偏移Skew在允许的建立时间Setup Time和保持时间Hold Time窗口内即可。这个窗口通常比较宽裕。但是对于非常高速50MHz或走线很长10cm的情况建议将SCLK、MOSI、MISO作为一组进行粗略的等长控制误差在几百mil内并让它们远离高速噪声源。阻抗与端接板内短距离通信一般不需要端接电阻。长距离传输时可在驱动端串联一个小电阻如22Ω-33Ω来抑制过冲和振铃。回路面积走线尽量短而直减小信号环路面积降低电磁干扰EMI。4. 软件实现从模拟SPI到硬件SPI在微控制器上我们可以通过两种方式实现SPI通信软件模拟Bit-Banging和使用硬件SPI外设。4.1 模拟SPI极致的灵活与控制模拟SPI就是用普通的GPIO引脚通过程序代码精确控制其高低电平变化和时序来模拟出SCLK、MOSI、SS线的行为并读取MISO线。它的最大优点是灵活和可移植性。一个典型的模拟SPI发送字节函数模式0 MSB First的C代码思路/** * brief 模拟SPI发送一个字节 (模式0) * param data: 要发送的数据 * retval 无 */ void Simulate_SPI_SendByte(uint8_t data) { uint8_t i; // 确保初始状态SCLK低 (CPOL0) SS低选中 SCLK_LOW(); CS_LOW(); for (i 0; i 8; i) { // 先设置MOSI数据位在时钟边沿到来前稳定 if (data 0x80) { // 判断最高位(MSB) MOSI_HIGH(); } else { MOSI_LOW(); } data 1; // 左移准备下一次发送 // 产生一个时钟脉冲上升沿从机采样 delay_us(1); // 短暂延时保证数据建立时间 SCLK_HIGH(); delay_us(1); // 保持高电平 // 下降沿数据变化准备下一位 SCLK_LOW(); delay_us(1); } // 一个字节发送完毕可以拉高CS也可以保持为低继续发 // CS_HIGH(); }模拟SPI接收一个字节同时也会发送一个哑元数据如0xFF/** * brief 模拟SPI接收一个字节 (模式0) * param 无 * retval 接收到的数据 */ uint8_t Simulate_SPI_ReceiveByte(void) { uint8_t i, received_data 0; // 通常接收时也需要产生时钟主机需要发送一些数据可以是哑元0xFF来驱动时钟 uint8_t dummy_tx 0xFF; SCLK_LOW(); // CS假设已经为低 for (i 0; i 8; i) { received_data 1; // 先左移为接收新位腾出空间MSB First // 设置主机发送位哑元 if (dummy_tx 0x80) { MOSI_HIGH(); } else { MOSI_LOW(); } dummy_tx 1; // 时钟上升沿从机输出数据到MISO主机在此后读取 delay_us(1); SCLK_HIGH(); delay_us(1); // 主机在时钟高电平期间或根据CPHA调整读取MISO if (READ_MISO_PIN()) { received_data | 0x01; // 将最低位置1 } // 时钟下降沿准备下一位 SCLK_LOW(); delay_us(1); } return received_data; }注意事项与心得延时是关键delay_us(1)是示例实际延时需要根据从设备要求的最小时钟周期和建立/保持时间来调整。太快了设备跟不上太慢了影响速度。最好用示波器或逻辑分析仪校准。CPHA的处理以上代码是CPHA0在第一个边沿采样。对于CPHA1读取MISO的操作需要移到时钟下降沿之后。MISO引脚配置主机MISO引脚必须配置为输入模式并启用内部上拉如果硬件支持且电路未外加。适用场景模拟SPI适合IO口紧张、对速度要求不高通常1MHz、或需要驱动不常见SPI时序的设备。它的缺点是CPU占用率高速度慢且不稳定受中断影响。4.2 硬件SPI解放CPU追求高速现代MCU如STM32、GD32、ESP32等都集成了硬件SPI外设。你只需要配置好几个寄存器数据搬运就由DMA和硬件移位寄存器自动完成CPU几乎不用干预效率极高。以STM32 HAL库为例配置硬件SPI的步骤引脚配置将SCK、MOSI、MISO、CS可任选一个GPIO配置为复用功能。SPI外设初始化设置模式CPOL, CPHA、数据大小8位或16位、数据顺序MSB/LSB、波特率预分频、主从模式等。编写收发函数使用阻塞式、中断式或DMA式传输。// STM32 CubeMX生成的SPI初始化代码片段主模式模式0 8位数据 hspi1.Instance SPI1; hspi1.Init.Mode SPI_MODE_MASTER; hspi1.Init.Direction SPI_DIRECTION_2LINES; // 全双工 hspi1.Init.DataSize SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity SPI_POLARITY_LOW; // CPOL 0 hspi1.Init.CLKPhase SPI_PHASE_1EDGE; // CPHA 0 hspi1.Init.NSS SPI_NSS_SOFT; // 软件控制NSS即我们用自己的GPIO当CS hspi1.Init.BaudRatePrescaler SPI_BAUDRATEPRESCALER_64; // 波特率 系统时钟/64 hspi1.Init.FirstBit SPI_FIRSTBIT_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(); } // 使用阻塞方式发送并接收一个字节 uint8_t tx_data 0xAA; uint8_t rx_data; HAL_SPI_TransmitReceive(hspi1, tx_data, rx_data, 1, HAL_MAX_DELAY);硬件SPI的优势高速时钟频率可达几十甚至上百MHz。低CPU占用尤其是配合DMA可实现大数据块如图像数据刷屏的无CPU干预传输。时序精确由硬件保证不受其他中断干扰。硬件SPI的注意事项NSS引脚很多情况下硬件SPI的NSS硬件片选引脚并不好用因为它可能被SPI外设自动控制时序不符合某些从设备要求。因此我强烈建议使用普通GPIO来模拟CS片选信号即SPI_NSS_SOFT模式这样你可以完全控制CS的拉低和拉高时机灵活性最高。时钟极性/相位务必与从设备匹配。波特率设置不能超过从设备支持的最大SCLK频率。5. 典型应用场景与实战解析让我们通过几个具体的热门应用看看SPI是如何大显身手的。5.1 驱动SPI Flash存储器如W25Q64SPI Flash是SPI最经典的应用之一常用于存储固件、配置参数、日志数据。通信特点通常使用模式0(CPOL0, CPHA0)。命令-地址-数据帧结构。例如读取数据命令0x03后跟24位地址然后开始连续读取数据。支持高速模式如Dual SPI, Quad SPI利用多根数据线并行传输提升速度。操作流程示例读取数据拉低CS。发送读命令0x03。发送24位地址例如0x000000。连续读取N个字节的数据从机会在主机提供时钟的同时从指定地址开始依次输出数据。拉高CS。避坑技巧SPI Flash通常有写保护机制和状态寄存器。在写操作Program或Erase前必须先发送Write Enable命令(0x06)。写操作完成后需要轮询状态寄存器等待BUSY位清零才能进行下一步操作。忽略这个等待过程是导致写数据失败的常见原因。5.2 驱动TFT显示屏如ST7735S像1.8寸的ST7735S屏常用SPI接口驱动以节省IO口。通信特点区分命令和数据通常通过一根额外的DC数据/命令选择引脚来控制。DC0表示接下来发送的是命令如复位、设置扫描方向DC1表示发送的是显示数据RGB像素值。可能只使用单向传输有些屏只接受主机数据不需要回读因此MISO线可能不需要连接。对速度有要求刷屏时需要连续发送大量像素数据如1601282字节使用硬件SPIDMA是理想选择。初始化序列通常是一长串固定的命令和参数需要严格按照数据手册中的顺序和延时要求发送。5.3 连接高精度传感器如ICM-20948ICM-20948是一款9轴MEMS运动传感器支持SPI和I²C。SPI模式下的特点支持标准SPI模式模式3。寄存器地址访问读写操作首先发送寄存器地址并且地址的最高位表示读/写1为读0为写。支持突发读写设置地址后可以连续读写多个寄存器地址自动递增这能有效提高数据读取效率。与I²C的对比选择速度SPI通常远快于I²C标准模式100kHz快速模式400kHz。ICM-20948的SPI时钟可达7MHz。引脚SPI需要4根线I²C只需要2根SDA, SCL。多设备I²C通过地址寻址总线可挂多个设备SPI需要独立的CS线。实时性SPI是全双工流控更直接I²C是半双工协议更复杂。选择建议如果项目对数据速率要求高如高频采样IMU或者MCU的I²C总线已经占用优先选SPI。如果设备很多且对速度不敏感或者IO口非常紧张可以考虑I²C。6. 高级话题与深度优化6.1 QSPI与Dual SPI突破速度瓶颈标准SPI只有一根数据输出线MOSI和一根输入线MISO。为了进一步提升速度出现了扩展模式Dual SPI将MOSI和MISO都变成双向IO。在发送命令和地址阶段它们按标准SPI工作在数据传输阶段两根线都用于传输数据输出或输入理论带宽翻倍。许多SPI Flash支持此模式。Quad SPI (QSPI)使用了4根数据线IO0-IO3进行数据传输理论带宽是标准SPI的4倍。这是目前高性能SPI Flash如用于存储代码的XIP Flash的主流接口。MCU需要有专门的QSPI外设控制器来支持。6.2 SPI与DMA的联姻当需要传输大量数据时如从SPI Flash读取固件、向显示屏发送帧缓冲区使用CPU一个个字节搬运会严重浪费资源。此时直接内存访问DMA就是救星。配置SPI DMA的流程初始化SPI为发送或接收模式。配置DMA通道将源地址设为内存缓冲区目标地址设为SPI数据寄存器对于发送或反之对于接收。设置传输数据量。启动DMA传输。SPI外设会在每个数据字节移出或移入后自动触发DMA请求DMA控制器则在后台完成内存与外设间的数据搬运。传输完成后产生DMA中断通知CPU。好处CPU被完全解放可以处理其他任务系统整体吞吐量和响应性大幅提升。6.3 软件片选与硬件片选的博弈软件片选Software NSS用普通GPIO控制CS线。优点是完全可控可以在传输前后插入任意延时适配各种奇葩时序要求的设备。缺点是占用CPU一点时间。硬件片选Hardware NSS使用SPI外设专用的NSS引脚。它可能由硬件自动管理在多主机模式下但很多时候其自动拉低/拉高的时机不符合从设备要求特别是在多字节连续传输时它可能在字节间也保持低电平而有些设备要求每字节传输后CS要有短暂拉高。个人强烈建议除非从设备数据手册明确说明并完美匹配硬件NSS的时序否则一律使用软件片选。多用一个GPIO换来的是巨大的灵活性和稳定性这笔买卖非常划算。7. 调试技巧与常见问题排查SPI调试逻辑分析仪是“终极武器”。没有它就像在黑暗中修车。调试步骤连接将逻辑分析仪的通道分别连接到SCLK、MOSI、MISO、CS以及DC等控制线。抓取波形触发一次SPI通信。解码使用分析仪的SPI解码功能设置正确的CPOL、CPHA、位顺序。软件会自动将高低电平解析成十六进制或二进制数据。对照分析将解码出的命令、地址、数据与你的程序代码、设备数据手册的时序图进行逐位对比。常见问题速查表现象可能原因排查思路完全无数据1. 电源或地未接好2. CS片选信号未有效拉低3. 时钟频率过高1. 检查硬件连接2. 用逻辑分析仪看CS波形3. 降低SPI波特率再试数据错位如0x55读成0xAA数据位顺序MSB/LSB设置错误检查并修改主机SPI配置的“FirstBit”参数与从机保持一致数据全是0xFF或0x001. MISO线连接错误或损坏2. 从机未上电或损坏3. 从机模式CPOL/CPHA不匹配1. 检查MISO线路2. 测量从机电源3. 用逻辑分析仪看MISO是否有波形并核对模式偶尔通信成功大部分失败1. 时序不满足建立/保持时间2. 电源噪声大3. 中断干扰了模拟SPI时序1. 用逻辑分析仪测量时钟边沿与数据稳定的时间差2. 检查电源滤波电容3. 在模拟SPI关键循环中禁用中断多字节传输时从第二字节开始出错1. 从机要求每字节传输后CS要有跳变2. 主机SPI FIFO或DMA配置错误1. 尝试在每字节间用软件控制CS产生一个短暂高脉冲2. 检查多字节传输的API调用是否正确最后的心得SPI是一个“配置敏感”的接口。开始任何新设备的SPI驱动开发时养成条件反射一查手册看模式二查手册看时序三用逻辑分析仪验证。把这三个步骤走扎实了绝大部分SPI问题都能迎刃而解。它不像I²C那样有复杂的协议状态机一旦配置正确其稳定性和速度表现都非常令人满意是嵌入式工程师手中一把可靠而高效的利器。