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

STM32 SPI接口详解:从原理到实验,掌握高速通信核心

STM32 SPI接口详解:从原理到实验,掌握高速通信核心 ★ FEATURED ARTICLE
STM32学习心得三十SPI接口原理、配置及实验在嵌入式开发里通信接口跟螺丝刀一样每次选型都顺手但有讲究。我调试过的板子里I2C挂温度传感器UART接调试串口而数据量大、要求速率快的外设十有八九都走SPI。今天这篇就是我自己啃完STM32标准外设库之后对SPI接口的完整复盘从物理层原理讲到寄存器配置再到实际跑通的读写实验一次说透。SPI全称Serial Peripheral Interface同步串行外设接口。它最大的特点是主从架构、全双工、四线搞定速率可以拉到几十兆赫兹非常适合Flash、SD卡、显示屏控制器这类高速外设。如果你正准备用STM32驱动Flash存储日志或者想搞明白为什么SPI的速度能比I2C快这么多这篇内容应该能帮你省不少时间。我用的主控是STM32F103系列外设库版本沿用的V3.5标准库。下面的寄存器细节在不同系列上会有差异但核心原理完全通用后面遇到F4、H7或者G0系列都可以照着这个思路迁移。1. SPI接口的整体认识与设计思路1.1 四线总线的基本架构与连接关系SPI总线最少四根线SCK时钟线、MOSI主出从入、MISO主入从出、CS片选线。很多人一开始被MOSI和MISO绕晕其实记一个原则就行数据方向永远以主机为参照。主机输出就叫Master Output Slave Input从机输出就叫Master Input Slave Output。接线的时候主机的MOSI接从机的MOSI主机的MISO接从机的MISO同名相连不交叉这一点跟UART的TX接RX正好相反。CS片选线名字比较多有的叫NSS有的叫CS有的叫SS。本质都一样低电平有效谁拉低谁就是主机当前通信的对象。所有从机的MISO可以并接在同一根线上但同一时刻只能有一个从机被选中否则MISO上两个设备同时驱动总线轻则数据错误重则烧IO口。片选信号就是这个总线的门禁进门之前必须先确认门牌号。SCK是时钟基准线。主机产生时钟从机跟着这个时钟节奏在上升沿或下降沿采样。因为时钟是主机主动产生并主动控制收发两端对时钟的依赖是同一个源头所以SPI通信天然不需要校验波特率是否对齐。这也是同步通信和异步通信最大的区别UART靠约定波特率两边时钟必须大致相同SPI直接给一根时钟线说什么时候采样就什么时候采样。从物理层再往深看一层SPI的传输是移位寄存器机制。主机和从机内部各有一个8位或16位移位寄存器一个时钟脉冲挪一个bit同时从移位寄存器一端推出去、另一端收进来。一个完整字节传完主机的移位寄存器里其实是“从机移过来的旧数据”从机的移位寄存器里是“主机发过来的数据”。很多初学者不理解为什么读Flash时要先“写一个空字节”基本原理就在这——时钟跑一次数据是双向交换的。你发出去一个字节的同时必然也会收回来一个字节。这个双向性太重要了后面写驱动时会反复用到。记住这句话SPI的每一次发送都伴随一次接收每一次接收也都需要主机先发起时钟。1.2 为什么做SPI通信时都爱用硬件外设刚入门时我有过一段“IO模拟SPI”的时期。GPIO翻转输出时钟手动拉高拉低数据线代码也能跑。但用IO模拟有性能瓶颈一个字节可能要几十条指令跑不到真正的高速率而且时钟和数据引脚之间的相位关系全靠代码卡延时程序一多就被中断打乱波形就歪了。STM32内部有专门的SPI外设硬件自己管理移位寄存器、波特率分频和标志位。你只需要配置好时钟和模式往数据寄存器DR里写一个字节硬件会自动在SCK上产生相应数量的脉冲并把收到的数据填回DR。查询状态寄存器SR里的TXE标志位可以判断发送缓冲区是否空RXNE标志位判断接收缓冲区是否满。整个流程下来CPU只负责搬数据时序由硬件兜底这对调试和稳定性提升是巨大的。当然硬件SPI也不是万能药。如果外设要求的时序特别特殊比如长短不一的延时、非标准的位长或者你只是临时用两个引脚做点单次触发的小逻辑那IO模拟反而简单直接。我的习惯是能上硬件外设就用硬件外设省下的CPU周期留给业务逻辑只有在硬件资源不够或者时序极为特殊时才退回到IO模拟而且模拟时必须用定时器辅助产生精确时钟。1.3 多从机扩展方式与总线的物理负载当总线挂多个从机时有两种典型的连接方式。一种是每个从机独立CS线主机有多个GPIO分别控制SCK、MOSI、MISO共享。这种方式扩展灵活想跟哪个设备说话就拉低哪个CS但占用引脚较多。另一种是每个从机串一条数据线通过菊花链方式连接适合级联移位寄存器一类的场景但通信协议会复杂一些。在STM32调试中我主要接触第一种。这里有一个常见的坑如果某个从机的MISO引脚没有配置为浮空输入或者带上拉在该从机未被选中CS高电平时它的MISO引脚会呈现高阻态主机端的MISO线上可能因为悬空读到杂乱的噪声信号。规范的做法是从机端MISO引脚在不被选中时保持高阻主机端在硬件上给MISO线加一个上拉电阻。若板子上没有上拉电阻则必须在从机初始化时把MISO配置成浮空输入或带上拉的输入模式反正别让这条线悬空晃荡。在STM32中配置这个细节尤其重要。很多人在调试时遇到“每次读回来的数据都是乱的”排查到最后发现是MISO浮空引入的噪声被当作有效数据采进去了。所以给MISO留一个明确的电平状态是SPI高速稳定通信的第一步。2. STM32 SPI模块核心细节与配置要点2.1 CPOL和CPHA决定相位与极性的四种组合SPI通信最容易让人犯迷糊的就是时钟极性CPOL和时钟相位CPHA。这两个参数合在一起决定了数据采样发生在上升沿还是下降沿以及时钟空闲时是高电平还是低电平。CPOL决定时钟空闲电平。CPOL置0时钟空闲时为低SCK空闲电压是0VCPOL置1空置时为高。CPHA决定数据采样时机。CPHA置0在第一个时钟沿采样数据CPHA置1在第二个时钟沿采样数据。这里“第一个沿”是相对片选拉低后的第一个有效跳变而言的。四种模式组合如下表模式CPOLCPHA空闲时钟电平数据采样沿模式000低上升沿模式101低下降沿模式210高下降沿模式311高上升沿绝大多数SPI Flash芯片、SD卡模块都工作在模式0或模式3而这两个模式本质上是“空闲电平高低不同但采样沿都在有效跳变的第一个边沿”所以很多老工程师干脆说模式0和模式3在数据传输上差异不大关键是和从机手册严格对应。我自己踩过的最深的一个坑就是CPHA配反。现象是读Flash的ID偶尔正确偶尔寄回一堆0xFF或乱码用逻辑分析仪看波形数据其实发的没问题可回读就是不对。后来对照手册逐行看时序图才发现从机要求数据在第二个边沿被采样而我配置成了第一个边沿导致数据稳定之后主机才去采采到的有一半是旧bit。从那以后我学到一个检查套路先确认CPOL和CPHA两条曲线和从机手册时序图严格对齐再看波特率最后才怀疑电气连接。2.2 STM32 SPI外设的关键寄存器与配置参数STM32的SPI外设主要通过CR1、CR2、SR、DR这几个寄存器控制。标准外设库封装后在SPI_InitTypeDef结构体里面体现为一组字段但我们要理解它背后的寄存器位。主从模式位MSTR必须置1才让SPI工作在主机模式。波特率分频BR[2:0]位决定SCK频率是APB2总线时钟通常72MHz除以2、4、8、16、32、64、128、256。这个分频系数的选择要同时考虑从机的最大时钟限制和传输速率需求。比如某Flash芯片最高支持36MHz那72MHz二分频得36MHz可用但若PCB走线长、电平质量不好降一档用18MHz更稳。不要极端追求上限频率SPI高速传输时波形质量比标称值更关键。数据帧格式通过DFF位控制清0为8位数据帧置1为16位数据帧。大多数传感器和Flash都按字节收发我一般用8位。但是注意有些器件支持“连续读模式”此时每次读操作只发地址后续时钟连续搬运数据这种场景下如果你用的是8位寄存器顺序读写记得在驱动层处理好地址和数据的区分。MSB优先或LSB优先由LSBFIRST位控制。绝大多数SPI设备默认MSB先发配置时不要乱改。我在调试某数字电位器时手册要求LSB先发改了一个位就正常了所以这一项务必与从机手册核对。还有SSM和SSI位这是软件片选管理的开关。置SSM1会使硬件NSS引脚脱离控制此时SSI位决定内部NSS的电平。置SSM0则是硬件片选模式NSS引脚由外设逻辑自动控制但这个模式需要外部下拉到低电平否则容易误触发。我实际项目里几乎都配置成软件管理NSS用普通GPIO手动控制CS逻辑最清晰。2.3 DMA与中断在王伟配合中的取舍SPI收发数据有两种常见搬运方式轮询、中断、DMA。轮询方式代码最简单读标志位然后读DR或者写DR即可但发送大量数据时会占用CPUCPU要么空转等待TXE要么被其他中断打断整体效率受限。中断方式在每次字节收发完成时触发中断CPU不忙等但频繁进入中断字节少时不错字节多时上下文切换开销也不小。DMA方式最好适合数据量大且地址连续的传输这时CPU只要启动一次DMA传输剩下的事情交给DMA控制器搬运。实际项目中我的选择思路是这样的少量单次读写用轮询代码简单传感器周期性采集用中断Flash或屏幕刷屏用DMA。特别是刷OLED或LCD这类需要持续发送几百字节的场景DMA的效果立竿见影。但DMA也有坑比如传输结束后要记得清除标志位并且SPI的DMA请求需要提前在SPI_CR2里使能TXDMEN/RXDMEN别光配置了DMA通道却忘了开外设端的DMA请求。2.4 SPI与I2C、UART的横向对比很多初学者会问一个问题既然SPI这么好为什么还要用I2C和UART这个问题背后其实是不同场景的需求权衡。I2C只有两根线支持多主机总线仲裁适合挂载大量低速传感器地址机制让你不用操心片选但速率上限通常只有400kbps或1Mbps。UART简单点对点硬件上没有时钟线靠约定波特率适合调试和长距离传输。SPI速度上限最高通常是几十Mbps至上百Mbps但没有总线仲裁机制靠独立的CS线做选择线数也更多。画个简单对照特性SPII2CUART线数4根加CS2根2根时钟同步主机产生同步主机产生异步需约定波特率全双工支持半双工支持速率几十Mbps级别最高几Mbps几Mbps级别多设备靠CS独立选片靠地址总线仲裁点对点为主理解了这些差异选型就不用纠结了。要高速大吞吐选SPI要省引脚挂一堆慢速传感器选I2C要调试和模块间通信选UART。没有好坏只有匹配不匹配。3. 实验流程与关键代码实现3.1 实验目标与硬件准备这次实验我直接用了手头一块STM32F103核心板和一个通用的SPI接口Flash模块市面上很常见的那类容量按Byte算的老牌芯片。实验目标分三阶段。第一阶段读写Flash的JEDEC ID。这一步算是“握手验证”不动任何数据区只发读ID命令能读回厂商ID和芯片型号说明SPI链路基本正常。第二阶段向Flash的某个扇区擦除后写入一长串数据再读出来对比校验。第三阶段连续读取一大块区域确认高速传输时波形稳定性。为了调试我还接了一个8通道逻辑分析仪到SCK、MOSI、MISO、CS四根线上。没有逻辑分析仪的同志也不必慌先用内部回环测试也可以但最后还是强烈建议搞一个几十块钱的入门级逻辑分析仪看时序问题效率翻倍。3.2 引脚复用与时钟使能初始化STM32上GPIO功能默认都是普通IO要对齐到SPI外设需要开启复用功能。STM32F103的SPI1引脚固定为PA5SCK、PA6MISO、PA7MOSI此外PA4可作为硬件NSS但我们已经决定用软件片选所以PA4保留为普通GPIO输出专门控制CS。初始化代码先开时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);这里APB2总线对应PA口和SPI1时钟。GPIO复用引脚要按推挽复用输出配置MISO因为是输入方向要配置为浮空输入或带上拉输入。我习惯对MISO配置为GPIO_Mode_IPU可靠一点。GPIO_InitStructure.GPIO_Pin GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin GPIO_Pin_4; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure);PA4这里注意虽然寄存器上它可以复用成NSS功能但我们不使能该复用只当普通GPIO输出所以不会干扰SPI外设。GPIO_Speed设置50MHz是在输出模式下有用MISO那个结构体配置速度是无效的不过一起写也没问题。3.2 SPI外设参数配置全流程完成引脚后就可以填写SPI_InitStruct了。我配的是模式08位数据为主机模式软件NSS分频系数16。最后SPI_Cmd使能SPI外设。SPI_InitTypeDef SPI_InitStructure; SPI_InitStructure.SPI_Direction SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode SPI_Mode_Master; SPI_InitStructure.SPI_DataSize SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL SPI_CPOL_Low; SPI_InitStructure.SPI_CPHA SPI_CPHA_1Edge; SPI_InitStructure.SPI_NSS SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_16; SPI_InitStructure.SPI_FirstBit SPI_FirstBit_MSB; SPI_InitStructure.SPI_CRCPolynomial 7; SPI_Init(SPI1, SPI_InitStructure); SPI_Cmd(SPI1, ENABLE);这里SPI_CRCPolynomial只在你启用CRC校验时需要设置平时配个默认值就行。分频16意味着在72MHz系统时钟下SCK为4.5MHz这个速率对绝大多数Flash模块足够稳定余量较大适合作为实验起始值。如果想追求高速可以把分频改成4得到18MHz但必须配合短导线和观察波形后再上。硬件片选NSS软件管理之后片选逻辑就完全交给我们自己了。传输前拉低CS传输完成后拉高CS。注意很多Flash命令要求在CS拉高时做状态锁存所以片选不能一直悬低每一个命令周期都要正确闭合。3.3 字节收发底层函数SPI硬件收发核心函数其实只有两个动作发送一个字节同时接收一个字节。我用下面这个send_receive_byte函数统一处理uint8_t SPI1_ReadWriteByte(uint8_t data) { while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) RESET); SPI_I2S_SendData(SPI1, data); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) RESET); return SPI_I2S_ReceiveData(SPI1); }这里第一个while在等发送缓冲区可写发送后第二个while在等接收缓冲区非空。注意SPI是同时收发所以读DR之前一定要确认RXNE置位。有些人图省事发完直接读DR往往读到的是上一次残留数据。模式0下每个字节需要8个SCK时钟脉冲如果发送方持续写DRSCK就会连续跑Flash的一次多字节读操作就是这样连续“dummy写”完成的。比如读取JEDEC ID时主机先发0x9F命令然后连续三次读操作每次写一个0x00占位字节同时收回一个ID字节。3.4 Flash读写实验完整过程读取JEDEC ID的代码非常简单#define FLASH_CS_LOW() GPIO_ResetBits(GPIOA, GPIO_Pin_4) #define FLASH_CS_HIGH() GPIO_SetBits(GPIOA, GPIO_Pin_4) void Flash_ReadID(void) { uint8_t id[3]; FLASH_CS_LOW(); SPI1_ReadWriteByte(0x9F); id[0] SPI1_ReadWriteByte(0x00); id[1] SPI1_ReadWriteByte(0x00); id[2] SPI1_ReadWriteByte(0x00); FLASH_CS_HIGH(); }读回的三字节就是厂商ID、类型ID和容量ID。比如经典的Winbond芯片会返回0xEF 0x40 0x15这个组合一看就知道是哪个型号了。如果读回来全是0xFF或者乱码基本就能断定片选、时钟相位或者接线某一个环节出问题了。接下来是一个更完整的扇区写入和读回校验流程。绝大多数SPI Flash支持页编程一次最多写256字节写之前必须擦除扇区否则没法把1写成0。流程如下uint8_t buf_src[256]; uint8_t buf_dst[256]; for (int i 0; i 256; i) buf_src[i] i; Flash_WriteEnable(); // 发送0x06, 写使能 Flash_WaitBusy(); // 等状态寄存器的WIP位清零 Flash_SectorErase(0x000000); // 擦除第一个扇区 Flash_WaitBusy(); Flash_PageProgram(0x000000, buf_src, 256); // 页编程 Flash_WaitBusy(); Flash_ReadData(0x000000, buf_dst, 256); // 读出校验每个函数里都必须维护CS低电平期间的操作序列。例如页编程命令先拉低CS发送0x02命令、24位地址、256字节数据最后拉高CS。CS拉高的时间点不能过早也不能过晚过晚Flash可能把下次命令吞进去过早则当前命令可能没有被锁存。手册上一般会给出CS高电平最小维持时间tSHSL实际用中加一点余量更稳。3.5 逻辑分析仪波形怎么看实验时逻辑分析仪采样率建议至少是SCK的4倍以上最好8倍以上。接好探头后全速跑一次读写流程观察CS是否在每次命令周期内保持低电平SCK是否有毛刺MISO上的数据是否在SCK有效沿附近保持稳定。如果是模式0CS拉低之后第一个SCK上升沿前后MOSI上的数据应该已经稳定。MISO数据则是从机在SCK上升沿之前驱动总线主机在上升沿采样。你在分析仪界面上看到的应当是SCK上升沿时数据线上电平已经不再跳变。如果上升沿正好撞上数据跳变大概率是CPHA或CPOL配置不对。实际经验告诉我波形分析对于SPI调试是决定性利器。盲调几小时不如拿波形看半分钟。4. 常见问题排查与实操心得4.1 读回数据全为0xFF这是SPI调试中最典型的现象。遇到0xFF第一反应不是检查代码逻辑而是先检查CS有没有真正被拉低。软件NSS模式下若没把CS引脚设为推挽输出并正确拉低从机就永远处于未选中状态MISO自然高阻输出主机读回的就是0xFF。第二反应是检查CPOL和CPHA是否匹配。SPI Flash要求模式0或模式3如果把模式1或模式2配上从机在错误的边沿采样数据对不上。第三反应是测量SCK引脚上是否有脉冲。我用示波器看过好几块“SCK没输出”的板子一查发现RCC时钟没开或者GPIO复用模式配置错误。这三个排查顺序按频率递减几乎能覆盖八成0xFF问题。4.2 MISO数据乱跳偶发正确偶发错误这种故障通常不是模式配置问题而是信号完整性问题。MISO线上若无上拉在从机未选中时会浮空主机采样就会得到随机电平。解决方法是给MISO增加上拉电阻或者配置GPIO为内部上拉输入。另外SCK线走线过长、MOSI和MISO靠得太近还会产生串扰。做实验时可以降低分频系数比如从16降到32或64看问题是否消失。如果频率降下来就好了说明就是信号质量问题而不是配置问题。还有一个大家容易忽略的地方同一组SPI总线上如果挂载了多个从机未选中的从机MISO必须高阻。有些廉价模块没有正确实现三态输出未被选中时MISO仍然强拉电平直接污染总线。这时候要么换模块要么在软件上给每个从机独立的GPIO做隔离。4.3 DMA传输完成后数据最后一个字节丢失遇到DMA发送丢失末字节十有八九是没有等待TXE置位就关闭SPI或拉高CS。DMA控制器把数据搬到SPI的发送缓冲区后SPI硬件还需要时间把缓冲区内容完整移位出去。如果你在DMA传输完成回调里立即拉高CS最后一个字节可能没发完就被截断了。解决方式是在DMA发送完成中断里先等待SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE)为SET再等BSY标志位清零确认移位寄存器完全腾空最后再操作CS和SPI_Cmd关闭。这种“传输完成但硬件还没吐完”的细节不踩一次坑很难记住但记住了以后就再也不会犯。4.4 最大通信速率不要只看芯片标称值从机标称支持36MHz板子设计不当的时候跑36MHz照样出错。SCK上升沿过缓、回波反射、电源噪声都会限制实际速率。从工程稳健角度出发我一般预留50%以上余量能用18MHz就不用36MHz能用8MHz就不硬逼到18MHz。数据吞吐不够时优先检查驱动和传输结构而不是单纯拉高频率。当然有些外设确实需要高吞吐比如持续采集图像数据这时就要从布线长度、阻抗匹配、地平面这些硬件层面去优化了。软件上还能用DMA双缓冲或者乒乓缓冲来剥离CPU负载但频率上限最终一定受硬件制约。这也是为什么嵌入式调试会越到后面越像“测硬件”而不是“写软件”。4.5 SPI多字节传输的连续模式处理不少Flash芯片支持快速读Fast Read和连续读一次拉低CS后可以连续输出一长串数据。这种模式下主机侧要保证在CS为低期间SCK不间断地运行。如果用轮询方式一个个字节发SCK会在每字节间隙停顿如果停顿时间超过从机允许的最长间隔有些芯片会提前把数据线结束驱动。使用DMA可以最大程度保证时钟连续这也是刷屏这种连续数据流应用必须用DMA的根本原因之一。反过来如果是写操作页编程时CS拉低到拉高之间同样要保证时钟连续中间CPU如果去处理别的任务在DMA或中断里拖太久Flash可能会认为命令序列失败。所以SPI驱动写长数据时HAL库里那种“每次操作都等待完成再返回”的流程虽然可靠但效率偏低适合初学者理解实际项目中尽量用任务队列加DMA的方式提速。4.6 实验心得把驱动写成统一收发原语最后分享一个我认为最受用的设计习惯。无论用什么芯片、什么命令集把发送接收都封装成一个SPI_ReadWriteByte基础原语所有上层命令都在这个原语之上排列组合。这样调试时你只需要验证一个函数是否正确其余所有命令都可以靠波形来辅助验证。同时把CS的控制全部收敛到少数几个宏或函数里确保每次拉低CS后无论路径上有多少分支最终都能走到CS高电平。否则某条异常分支CS一直低着后面所有通信都会脏掉。我经历过一次CS没有恢复高电平的故障表现非常隐蔽第一次读写正常第二个命令开始全部失败。排查了半天才发现是前一个函数的错误处理分支直接return了没走到恢复CS那一步。从此读芯片命令的函数里我都用统一的“错误分支也恢复CS”结构这算是一把血泪换来的经验吧。5. 从实验到工程SPI代码结构的演进路径如果只在面包板上点个灯或者读个传感器SPI直接在主循环里运行就够了。但项目一复杂数据吞吐变大一个全局结构体满天飞的裸机工程就会开始失控。我自己的演进路径大致是三层底层SPI驱动、设备驱动、应用任务。底层SPI驱动只负责初始化、收发原语、可能的DMA搬运对上提供带互斥锁的读写入接口。设备驱动比如Flash或LCD在底层原语之上封装命令像Flash_ReadID、Flash_PageProgram、LCD_DrawLine这些每个设备模块内部维护自己的状态机。应用任务则关心业务逻辑比如“每10秒写入一帧日志”“屏幕刷新率30帧”。这种分层的好处是底层驱动调试一次之后所有设备都可以复用换芯片也只要改少量引脚和寄存器配置。在实际工程里我还习惯在底层SPI驱动里加入简单的断言和超时保护。例如等待TXE标志位时加一个超时计数器如果迟迟不置位就返回错误。没有超时保护的SPI驱动会白白卡死整个系统尤其是在高速DMA配合FIFO的场景下一个标志位被卡住整个任务就假死了。这次从原理到代码再到问题排查的完整流程走下来最大的体会是SPI这个接口看起来简单但真正要调稳调快需要同时照顾原理、硬件时序和代码架构。慢慢把每一个细节啃透后面再遇到高速外设心中就有底了。如果大家在配置SPI时遇到什么奇怪现象建议先用逻辑分析仪抓一次时序图很多问题在波形面前都撑不过三秒。
阅读完成 · 觉得有帮助?
咨询建站