首页 / 资讯中心 / 文章详情

SPI通信协议从入门到精通:原理、配置与实战避坑指南

SPI通信协议从入门到精通:原理、配置与实战避坑指南 ★ FEATURED ARTICLE
1. 为什么SPI值得花时间彻底搞懂嵌入式开发里通信协议是绕不开的基本功。UART、I2C、SPI这三兄弟几乎每个做硬件驱动的人都要打交道。但如果你问我哪个最值得优先吃透我会毫不犹豫地说SPI。原因很简单——它够快、够简单、够灵活在实际项目中的出场率高得惊人。从Flash存储芯片、传感器、显示屏到ADC采样、无线模块配置SPI的身影无处不在。很多人对SPI的第一印象是“四根线接上就能用”。这话对也不对。说它对是因为SPI的硬件连接确实直观SCLK、MOSI、MISO、CS四根线各司其职不像I2C那样需要考虑上拉电阻、总线仲裁、时钟拉伸这些烦心事。说它不对是因为“接上就能用”的前提是你把CPOL、CPHA、时钟频率、数据位宽、CS控制方式这些参数全部配对。我见过太多人在这上面翻车——代码写得没问题逻辑分析仪一抓波形数据全是乱的最后发现是模式选错了。这篇文章的目标很明确把SPI从物理层到协议层从寄存器配置到代码实现从单机通信到多从机管理全部讲透。不管你是刚接触嵌入式的新手还是已经用过SPI但总觉得“知其然不知其所以然”的老手看完之后应该都能有新的收获。我会尽量用生活化的类比来解释概念用实际代码来展示操作用踩坑经验来提醒你避开那些教科书上不会写的雷区。先说说SPI到底解决了什么问题。想象一下你有一个主控芯片和一个外设芯片两者之间需要交换数据。如果没有任何通信协议你就得自己定义什么时候发数据、什么时候收数据、数据从哪根线走、时钟谁来控制。SPI就是把这个过程标准化了主设备产生时钟数据在时钟的节拍下一位一位地传输从设备被动响应。这种“主从架构同步时钟”的设计让SPI的速率可以轻松跑到几十MHz甚至上百MHz远超I2C的几百kHz到几MHz。适合阅读这篇文章的人包括正在学习嵌入式驱动的学生、需要调试SPI外设的工程师、想从软件层面理解硬件通信的开发者以及任何对底层协议感兴趣的技术爱好者。我会尽量不假设你有深厚的硬件背景但基本的数字电路概念比如高低电平、上升沿下降沿还是需要了解的。2. SPI协议的核心机制拆解2.1 四根线各干什么活SPI总线的物理层由四根信号线组成每根线都有明确的职责。理解这四根线是理解整个协议的基础。SCLKSerial Clock时钟线由主设备产生。你可以把它想象成乐队的指挥所有数据的收发都跟着它的节拍走。时钟频率决定了通信速率频率越高单位时间内传输的数据越多。但频率不是想设多高就设多高的受限于从设备的规格、PCB走线质量、主控的SPI控制器能力。MOSIMaster Out Slave In主设备输出、从设备输入。数据从主设备流向从设备。在Full-Duplex模式下这根线在每个时钟周期都会输出一位数据。MISOMaster In Slave Out主设备输入、从设备输出。数据从从设备流向主设备。注意如果从设备没有被选中CS无效它的MISO引脚应该处于高阻态不能干扰总线上的其他设备。CSChip Select片选线也叫SSSlave Select。低电平有效是常见约定但具体要看从设备的数据手册。主设备通过拉低某个从设备的CS来“点名”告诉它“现在轮到你了”。一根CS对应一个从设备多个从设备就需要多根CS线。这里有个容易混淆的点MOSI和MISO的方向是固定的不会因为通信方向而改变。也就是说即使你只想从从设备读数据主设备仍然会在MOSI上发送一些内容通常是0xFF或0x00因为SPI是全双工的时钟一旦启动两个方向的数据线都会同时工作。2.2 时钟极性和相位的四种组合CPOL和CPHA是SPI里最容易让人头晕的两个参数但也是必须搞懂的核心概念。它们决定了数据在时钟的哪个边沿被采样、哪个边沿被切换。CPOLClock Polarity时钟极性。CPOL0表示空闲时SCLK为低电平CPOL1表示空闲时SCLK为高电平。CPHAClock Phase时钟相位。CPHA0表示数据在第一个边沿即空闲状态之后的第一个跳变沿被采样CPHA1表示数据在第二个边沿被采样。组合起来就是四种模式模式CPOLCPHA空闲电平采样边沿切换边沿Mode 000低上升沿下降沿Mode 101低下降沿上升沿Mode 210高下降沿上升沿Mode 311高上升沿下降沿Mode 0和Mode 3是最常用的两种。很多Flash芯片支持Mode 0和Mode 3传感器则可能只支持其中一种。选错模式的后果是你发出去的数据从设备收不对从设备回的数据你也采不对整个通信全是乱码。我个人的经验是拿到一个新的SPI从设备第一件事就是翻数据手册找时序图确认它支持哪种模式。如果手册写的是“支持Mode 0和Mode 3”那就优先用Mode 0因为这是最直观的模式——空闲低电平上升沿采样下降沿切换逻辑清晰。2.3 全双工与半双工的取舍SPI天然支持全双工通信MOSI和MISO可以同时工作。这意味着在一个时钟周期内主设备发送一位数据的同时也能接收一位数据。这种特性在某些场景下非常有用比如读取Flash的状态寄存器时你可以在发送读命令的同时就开始接收状态数据。但全双工也带来了一些“麻烦”。比如你只想读一个字节但SPI的机制要求你必须同时发一个字节通常是dummy byte。这个dummy byte的内容不重要但它占用的时钟周期是实打实的。如果你对时序要求很严格这个额外的时钟周期需要计算在内。半双工模式则是在某些特定场景下使用的变体。比如三线SPI只有SCLK、CS和一根双向数据线或者某些显示驱动芯片使用的“单线模式”。半双工的好处是省引脚坏处是收发不能同时进行控制逻辑稍微复杂一些。2.4 多从机管理的两种方案当你的系统里有多个SPI从设备时有两种管理方式独立CS和菊花链。独立CS是最常见的方式。每个从设备分配一根独立的CS线主设备通过拉低对应的CS来选中目标设备。这种方式逻辑简单软件上只需要控制不同的GPIO即可。缺点是占用引脚多如果有8个从设备就需要8根CS线。菊花链则是把所有从设备串起来主设备的MOSI接到第一个从设备的输入第一个从设备的输出接到第二个从设备的输入以此类推最后一个从设备的输出接回主设备的MISO。CS同时接到所有从设备。数据从主设备发出后经过第一个从设备、第二个从设备……最后从最后一个从设备返回。这种方式节省引脚但软件上需要把数据按顺序排列好而且所有从设备必须支持菊花链模式。在实际项目中独立CS是绝对主流。菊花链我只在某些LED驱动芯片和移位寄存器上见过。所以如果你刚开始学SPI先把独立CS玩明白就够了。3. 从寄存器到代码SPI的实操配置3.1 主控SPI控制器的关键寄存器不同厂商的MCUSPI控制器的寄存器命名和位定义可能不同但核心功能是相似的。以常见的嵌入式主控为例SPI控制器通常包含以下几个关键寄存器控制寄存器CR配置SPI使能、主从模式、时钟极性、时钟相位、数据位宽、软件CS管理等。这是最核心的寄存器基本上所有通信参数都在这里设置。状态寄存器SR反映当前SPI的工作状态比如发送缓冲区是否为空、接收缓冲区是否有数据、是否发生溢出错误等。轮询方式发送数据时需要不断检查这个寄存器。数据寄存器DR写入要发送的数据读取接收到的数据。有些控制器把发送和接收分成两个寄存器有些则共用一个。波特率寄存器BR设置时钟分频系数。最终时钟频率 总线时钟 / 分频系数。分频系数通常是2的幂次比如2、4、8、16、32、64、128、256。片选寄存器CSR如果使用硬件CS管理需要在这里配置CS的极性、时序等参数。配置SPI的基本流程是先使能SPI时钟然后配置GPIO复用功能接着设置控制寄存器的各项参数最后使能SPI。这个顺序很重要如果先使能SPI再配置GPIO可能会出现短暂的错误信号。3.2 时钟频率的计算与选择时钟频率的选择是一个权衡过程。频率越高数据传输越快但信号完整性越难保证。计算方式通常是SPI时钟频率 外设总线时钟 / 分频系数假设总线时钟是72MHz你需要的SPI时钟是9MHz那么分频系数就是8。如果需要的频率不能整除就选最接近且不超过目标频率的分频值。选择时钟频率时需要考虑以下因素从设备的最高时钟频率这是硬性上限绝对不能超过。比如某Flash芯片手册写最高支持50MHz你就不能设到60MHz。PCB走线质量走线越长、越细、越靠近干扰源能稳定工作的频率就越低。我见过在面包板上跑10MHz就出错的也见过在精心设计的四层板上跑80MHz稳如泰山的。主控SPI控制器的能力有些主控的SPI控制器在高频下会有额外的时序要求比如需要插入等待周期。通信距离板内通信和板间通信能支持的频率差别很大。板间通信通常需要降低频率或者加缓冲芯片。我的建议是先从较低频率比如1MHz开始调试确认通信正常后再逐步提高频率直到找到稳定工作的上限然后留出20%到30%的余量。3.3 代码实现轮询方式发送接收轮询方式是最简单的SPI实现方式适合对实时性要求不高的场景。下面是一个典型的轮询发送接收函数uint8_t spi_transfer(uint8_t data) { // 等待发送缓冲区为空 while (!(SPI-SR SPI_SR_TXE)); // 写入要发送的数据 SPI-DR data; // 等待接收缓冲区非空 while (!(SPI-SR SPI_SR_RXNE)); // 读取接收到的数据 return SPI-DR; }这个函数每调用一次就完成一个字节的全双工交换。发送和接收是同时发生的你发出去一个字节同时也收回来一个字节。如果要发送多个字节可以循环调用这个函数。但要注意在循环中如果CS一直保持有效从设备会连续接收多个字节。有些从设备要求每个字节之间CS拉高再拉低有些则允许CS一直保持低电平连续传输。这个要看从设备的数据手册。3.4 代码实现中断与DMA方式轮询方式虽然简单但在高速通信或大数据量传输时CPU会被大量占用。这时候就需要中断或DMA方式。中断方式每发送完一个字节或接收完一个字节SPI控制器产生中断CPU在中断服务函数中处理数据。这种方式比轮询效率高但频繁的中断仍然会消耗CPU资源。DMA方式配置DMA通道让DMA控制器自动把数据从内存搬到SPI数据寄存器或者从SPI数据寄存器搬到内存。CPU只需要在DMA传输完成后处理一次即可。这是效率最高的方式适合大批量数据传输比如读写Flash、刷新显示屏。DMA方式的配置稍微复杂一些需要设置DMA源地址、目标地址、传输长度、传输方向、优先级等参数。而且要注意DMA传输完成中断的处理以及SPI和DMA的启动顺序。一般来说先配置好DMA再使能SPI的DMA请求最后启动DMA传输。4. 实战中那些教科书不会告诉你的坑4.1 CS时序被忽视的关键细节CS的时序是SPI调试中最容易出问题的地方之一。很多人以为CS就是简单的“拉低开始、拉高结束”但实际上CS的建立时间、保持时间、以及和SCLK的相位关系都有讲究。CS建立时间从CS拉低到第一个SCLK边沿之间的时间。如果这个时间太短从设备可能还没准备好接收数据导致第一个字节出错。大多数从设备要求CS建立时间在几十纳秒到几百纳秒之间。CS保持时间从最后一个SCLK边沿到CS拉高之间的时间。如果这个时间太短从设备可能还没处理完最后一个字节导致数据丢失或状态错误。CS与SCLK的相位关系在某些从设备中CS的跳变和SCLK的跳变不能同时发生否则会被误判为时钟边沿。这种情况下需要在CS操作和SCLK操作之间插入微小的延时。我在调试某款ADC芯片时就遇到过因为CS保持时间不足导致转换结果偶尔出错的问题。后来在CS拉高之前加了1微秒的延时问题就消失了。这个延时在数据手册上写的是“最小10纳秒”但实际测试下来1微秒才能保证100%稳定。所以数据手册的参数是理论值实际使用时要留足余量。4.2 数据位宽与字节序的陷阱SPI的数据位宽可以是4位、8位、16位甚至32位具体取决于从设备的要求。大多数从设备使用8位数据位宽但有些ADC或DAC使用16位甚至24位。当数据位宽大于8位时就涉及到字节序的问题。比如一个16位的数据是先发高8位还是先发低8位这个要看从设备的数据手册。有些设备是MSB first高位在前有些是LSB first低位在前。我遇到过一款压力传感器它的数据是24位的但数据手册只写了“MSB first”没有明确说明是每个字节内部MSB first还是整个24位数据MSB first。后来用逻辑分析仪抓波形才发现它是每个字节内部MSB first但字节之间是低字节在前。这种细节如果不注意读出来的数据就会完全错误。4.3 多从机场景下的总线冲突在多从机系统中如果CS控制不当可能会出现两个从设备同时被选中的情况导致总线冲突。这种冲突轻则数据出错重则损坏芯片。避免总线冲突的关键是任何时候只能有一个CS处于有效状态。在切换从设备时要确保先拉高当前从设备的CS再拉低目标从设备的CS中间要有足够的延时。另外如果某个从设备的MISO引脚在CS无效时不是高阻态而是推挽输出那么它就会干扰总线上的其他设备。这种情况下需要在硬件上加缓冲器或者用模拟开关来隔离。4.4 长走线带来的信号完整性问题SPI时钟频率较高时PCB走线就变成了传输线需要考虑信号完整性问题。常见的问题包括过冲和振铃走线太长或阻抗不匹配时信号边沿会出现过冲和振铃可能导致从设备误判。串扰SCLK和MOSI/MISO走线靠得太近时时钟信号会耦合到数据线上造成数据错误。反射走线末端没有正确端接时信号会反射回来和原始信号叠加造成波形畸变。解决这些问题的方法包括缩短走线长度、增加地平面、使用差分信号如果支持、在走线末端加端接电阻、降低时钟频率等。在实际项目中我通常会在SPI走线旁边铺地线并且尽量让SCLK和MOSI/MISO保持足够的间距。4.5 常见问题速查表现象可能原因排查方法解决方案完全无数据CS未拉低、SPI未使能、GPIO复用未配置用逻辑分析仪抓CS和SCLK检查CS控制代码、SPI使能位、GPIO配置数据全为0xFFMISO未连接、从设备未供电、模式错误检查硬件连接、测量从设备供电重新焊接、确认供电、尝试其他模式数据全为0x00MOSI未连接、从设备未响应检查MOSI连接、用示波器看波形重新焊接、确认从设备工作状态数据偶尔出错时钟频率过高、CS时序不足、干扰降低频率测试、增加CS延时降低频率、调整CS时序、加屏蔽只能读不能写写保护引脚未处理、命令错误检查写保护引脚电平、核对命令拉高写保护引脚、修正命令多从机时数据混乱CS冲突、MISO干扰逐个测试从设备、检查CS逻辑确保CS互斥、加缓冲器5. 进阶话题SPI的变体与扩展5.1 三线SPI与四线SPI的区别标准的四线SPI有独立的MOSI和MISO支持全双工。三线SPI则把MOSI和MISO合并成一根双向数据线半双工工作。三线SPI的优点是省一根引脚缺点是收发不能同时进行而且方向切换需要额外的控制逻辑。三线SPI常见于一些引脚资源紧张的场合比如小封装的传感器或存储器。使用三线SPI时主设备需要在发送数据时把数据线配置为输出接收数据时配置为输入。这个切换时机很关键如果切换太早或太晚都会导致数据错误。5.2 双SPI与四SPI更快的读写速度对于大容量Flash存储器标准的单线SPI速率可能不够用。于是就有了双SPIDual SPI和四SPIQuad SPI。双SPI使用两根数据线四SPI使用四根数据线理论上速率可以提升到单线SPI的2倍和4倍。双SPI和四SPI的协议是在标准SPI的基础上扩展的。发送命令时仍然使用单线模式发送地址和数据时切换到多线模式。这种模式切换需要从设备支持并且需要按照特定的命令序列来操作。四SPI在嵌入式系统中非常常见很多MCU都内置了Quad SPI控制器可以直接执行XIPExecute In Place也就是直接从Flash中运行代码不需要先把代码搬到RAM里。这对于资源受限的嵌入式系统来说非常实用。5.3 SPI与I2C、UART的对比选型特性SPII2CUART线数4根CSSCLKMOSIMISO2根SCLSDA2根TXRX速率高几十MHz中几百kHz到几MHz中通常几百kHz到几MHz多设备独立CS占用引脚多总线寻址节省引脚点对点不支持多设备全双工支持不支持支持时钟同步同步异步复杂度简单中等简单典型应用Flash、传感器、显示屏传感器、EEPROM调试、模块通信选型时的考虑因素如果需要高速传输选SPI如果引脚资源紧张且速率要求不高选I2C如果是点对点通信且不需要时钟同步选UART。当然实际项目中经常是多种协议混用比如用SPI接显示屏用I2C接传感器用UART做调试输出。5.4 SPI在RTOS下的驱动设计在RTOS环境下使用SPI需要考虑任务调度和资源互斥的问题。多个任务可能同时需要访问SPI总线如果不加保护就会出现数据混乱。常见的做法是为SPI总线创建一个互斥锁Mutex任务在访问SPI之前先获取锁访问完成后释放锁。这样可以保证同一时间只有一个任务在使用SPI总线。另外如果使用DMA方式传输还需要考虑DMA传输完成后的回调处理。通常的做法是使用信号量Semaphore来同步任务发起DMA传输后阻塞在信号量上DMA传输完成中断释放信号量任务被唤醒继续执行。还有一个细节是CS的控制。在RTOS下CS的拉低和拉高应该和SPI传输作为一个原子操作不能被打断。否则可能出现CS已经拉低但任务被切换出去另一个任务又拉低了另一个CS导致总线冲突。6. 调试工具与实战技巧6.1 逻辑分析仪SPI调试的利器逻辑分析仪是调试SPI最实用的工具。它可以同时抓取CS、SCLK、MOSI、MISO四根线的波形并且大多数逻辑分析仪软件都内置了SPI协议解码功能可以直接把波形翻译成十六进制数据。使用逻辑分析仪时采样率要足够高。根据奈奎斯特采样定理采样率至少是信号频率的2倍但实际使用中建议至少是信号频率的5到10倍。比如SPI时钟是10MHz采样率建议设置在100MHz以上。抓取波形时触发条件设置很关键。通常把CS的下降沿设置为触发条件这样可以抓到完整的传输过程。如果数据量很大可以设置更复杂的触发条件比如CS下降沿且MOSI为特定值。6.2 示波器看信号质量逻辑分析仪看的是逻辑电平示波器看的是模拟波形。当SPI通信不稳定时用示波器观察信号质量往往能找到根本原因。用示波器重点看几个方面信号幅度是否足够通常要求3.3V系统的高电平在3.0V以上低电平在0.3V以下、上升沿和下降沿是否陡峭上升时间过长会导致采样错误、是否有过冲和振铃、时钟和数据线的相位关系是否正确。我习惯用示波器的双通道模式一个通道接SCLK一个通道接MOSI或MISO这样可以直观地看到数据和时钟的对应关系。如果示波器有协议解码功能那就更方便了。6.3 软件模拟SPI没有硬件SPI时的备选方案有些低成本MCU没有硬件SPI控制器或者硬件SPI引脚被其他功能占用了。这时候可以用GPIO模拟SPI也就是所谓的“软件SPI”或“Bit-Banging”。软件SPI的优点是灵活任何GPIO都可以用作SPI引脚而且可以方便地调整时序。缺点是速率慢因为每个时钟边沿都需要CPU干预通常只能跑到几百kHz到1MHz左右。软件SPI的实现要点是严格按照从设备的时序要求来控制GPIO的翻转。特别是CPOL和CPHA在软件SPI中需要手动实现。下面是一个Mode 0的软件SPI发送函数示例void soft_spi_send_byte(uint8_t data) { for (int i 7; i 0; i--) { // 设置MOSI if (data (1 i)) MOSI_HIGH(); else MOSI_LOW(); // 延时让数据稳定 delay_ns(100); // 上升沿从设备采样 SCLK_HIGH(); delay_ns(100); // 下降沿 SCLK_LOW(); delay_ns(100); } }软件SPI的延时需要根据从设备的时序要求来调整。延时太短从设备来不及采样延时太长通信速率上不去。通常需要结合示波器或逻辑分析仪来调试。6.4 实操心得从零调试SPI的步骤如果你拿到一块新板子需要从零开始调试SPI我建议按照以下步骤来确认硬件连接用万用表检查CS、SCLK、MOSI、MISO四根线是否连通有没有虚焊或短路。确认供电测量从设备的供电电压是否正常电流是否在合理范围内。配置GPIO把SPI引脚配置为正确的复用功能确认没有其他外设占用这些引脚。配置SPI控制器设置模式、频率、数据位宽等参数。频率先设低一些比如1MHz。发送简单命令比如读取从设备的ID寄存器。大多数从设备都有一个ID寄存器读出来的值应该是固定的。用逻辑分析仪抓波形确认CS、SCLK、MOSI、MISO的波形是否符合预期。逐步提高频率确认低速下通信正常后逐步提高频率直到找到稳定工作的上限。测试边界条件比如连续读写、多从机切换、异常情况处理等。这个流程看起来简单但每一步都可能遇到问题。我的经验是不要跳过任何一步特别是用逻辑分析仪抓波形这一步。很多时候看一眼波形就能定位问题比盲目改代码高效得多。6.5 常见误区与纠正误区一SPI速率越高越好。实际上速率越高信号完整性越难保证出错概率越大。应该根据实际需求选择速率而不是盲目追求高速。误区二CS随便接一个GPIO就行。虽然CS可以用普通GPIO控制但要注意GPIO的驱动能力和翻转速度。如果CS翻转太慢会影响通信时序。误区三SPI模式可以随便选。从设备支持哪种模式是固定的选错了就是通不了。必须查数据手册确认。误区四MISO不用时可以悬空。如果从设备的MISO在CS无效时不是高阻态悬空会导致总线电平不确定可能干扰其他设备。应该加上拉或下拉电阻。误区五SPI不需要考虑电磁兼容。高速SPI信号是电磁干扰的来源之一在电磁兼容要求严格的场合需要采取屏蔽、滤波等措施。7. 一个完整的SPI驱动实例7.1 场景描述与需求分析假设我们需要驱动一款SPI接口的Flash存储芯片要求实现以下功能读取芯片ID、擦除扇区、写入数据、读取数据。Flash芯片支持Mode 0和Mode 3最高时钟频率50MHz数据位宽8位MSB first。这个场景很典型基本上涵盖了SPI驱动的核心操作。下面我会给出关键代码和配置说明。7.2 硬件初始化与SPI配置void spi_flash_init(void) { // 1. 使能SPI时钟和GPIO时钟 RCC-APB2ENR | RCC_APB2ENR_SPI1EN; RCC-APB2ENR | RCC_APB2ENR_IOPAEN; // 2. 配置SPI引脚PA5SCLK, PA6MISO, PA7MOSI // 复用推挽输出50MHz GPIOA-CRL ~(0xFFF 20); GPIOA-CRL | (0xBBB 20); // 3. 配置CS引脚PA4普通推挽输出 GPIOA-CRL ~(0xF 16); GPIOA-CRL | (0x3 16); GPIO_CS_HIGH(); // CS默认拉高 // 4. 配置SPI控制器 SPI1-CR1 0; SPI1-CR1 | SPI_CR1_MSTR; // 主模式 SPI1-CR1 | SPI_CR1_SSM | SPI_CR1_SSI; // 软件CS管理 SPI1-CR1 | SPI_CR1_BR_1; // 分频系数8假设总线72MHzSPI时钟9MHz SPI1-CR1 | SPI_CR1_CPOL | SPI_CR1_CPHA; // Mode 3 SPI1-CR1 | SPI_CR1_DFF_8BIT; // 8位数据 SPI1-CR1 | SPI_CR1_SPE; // 使能SPI }这段代码里分频系数设为8如果总线时钟是72MHzSPI时钟就是9MHz。这个频率对于大多数Flash芯片来说是安全的。Mode 3的配置是CPOL1、CPHA1对应SPI_CR1_CPOL和SPI_CR1_CPHA都置位。7.3 读写操作的完整实现// 发送一个字节并接收一个字节 uint8_t spi_flash_transfer(uint8_t data) { while (!(SPI1-SR SPI_SR_TXE)); SPI1-DR data; while (!(SPI1-SR SPI_SR_RXNE)); return SPI1-DR; } // 读取Flash ID uint32_t spi_flash_read_id(void) { uint32_t id 0; GPIO_CS_LOW(); spi_flash_transfer(0x9F); // 读ID命令 id | spi_flash_transfer(0xFF) 16; id | spi_flash_transfer(0xFF) 8; id | spi_flash_transfer(0xFF); GPIO_CS_HIGH(); return id; } // 写使能 void spi_flash_write_enable(void) { GPIO_CS_LOW(); spi_flash_transfer(0x06); // 写使能命令 GPIO_CS_HIGH(); } // 等待忙状态结束 void spi_flash_wait_busy(void) { GPIO_CS_LOW(); spi_flash_transfer(0x05); // 读状态寄存器命令 while (spi_flash_transfer(0xFF) 0x01); // 等待BUSY位清零 GPIO_CS_HIGH(); } // 扇区擦除4KB扇区 void spi_flash_erase_sector(uint32_t addr) { spi_flash_write_enable(); GPIO_CS_LOW(); spi_flash_transfer(0x20); // 扇区擦除命令 spi_flash_transfer((addr 16) 0xFF); spi_flash_transfer((addr 8) 0xFF); spi_flash_transfer(addr 0xFF); GPIO_CS_HIGH(); spi_flash_wait_busy(); } // 页写入最多256字节 void spi_flash_page_write(uint32_t addr, uint8_t *data, uint16_t len) { spi_flash_write_enable(); GPIO_CS_LOW(); spi_flash_transfer(0x02); // 页写入命令 spi_flash_transfer((addr 16) 0xFF); spi_flash_transfer((addr 8) 0xFF); spi_flash_transfer(addr 0xFF); for (uint16_t i 0; i len; i) { spi_flash_transfer(data[i]); } GPIO_CS_HIGH(); spi_flash_wait_busy(); } // 读取数据 void spi_flash_read(uint32_t addr, uint8_t *buf, uint16_t len) { GPIO_CS_LOW(); spi_flash_transfer(0x03); // 读命令 spi_flash_transfer((addr 16) 0xFF); spi_flash_transfer((addr 8) 0xFF); spi_flash_transfer(addr 0xFF); for (uint16_t i 0; i len; i) { buf[i] spi_flash_transfer(0xFF); } GPIO_CS_HIGH(); }这段代码涵盖了Flash操作的核心流程读ID、写使能、等待忙、擦除、写入、读取。每个操作都遵循“拉低CS→发送命令和地址→传输数据→拉高CS”的模式。7.4 性能优化与注意事项上面的代码是轮询方式适合小数据量传输。如果要传输大量数据比如几KB的固件轮询方式会占用大量CPU时间。这时候可以改用DMA方式。DMA方式的配置要点把SPI的发送和接收都配置为DMA请求DMA控制器自动搬运数据。CPU只需要在DMA传输完成后处理一次。这样可以释放CPU去处理其他任务。另外Flash的写入操作需要先擦除再写入而且擦除是以扇区为单位的。如果要在已有数据的扇区写入新数据需要先把整个扇区的数据读到RAM里修改后再擦除扇区最后写回去。这个“读-改-写”的流程在实际项目中很常见需要特别注意。还有一个细节是Flash的页写入限制。大多数Flash芯片的页大小是256字节一次写入不能跨页。如果要写入的数据超过256字节需要分成多次写入每次写入前都要重新发送写使能命令。8. 写在最后的一些个人体会SPI这个协议入门容易精通难。说它容易是因为四根线的逻辑很直观配置几个寄存器就能跑起来。说它难是因为实际项目中会遇到各种各样的问题时序不匹配、信号完整性、多设备冲突、性能瓶颈……每一个问题都需要结合硬件和软件来分析和解决。我个人的经验是调试SPI时逻辑分析仪是必备工具。很多时候看一眼波形就能定位问题比盲目改代码高效得多。另外数据手册一定要仔细看特别是时序图和参数表。数据手册上写的“典型值”和“最大值”往往有较大差距实际使用时要留足余量。还有一点是不要害怕降低频率。很多通信问题都是因为频率太高导致的。先把频率降下来确认通信正常再逐步提高这样更容易定位问题。最后SPI的变体很多三线、双线、四线、菊花链……但核心原理是一样的。把标准四线SPI搞明白其他变体都是在这个基础上的扩展。所以先把基础打牢再去看那些高级特性会轻松很多。
阅读完成 · 觉得有帮助?
咨询建站