资讯动态

STM32F1双SPI互联实验:SPI1主机与SPI2从机收发完整指南

发布时间:2026/10/4 6:15:07 来源:尧图企业网站定制
把SPI模块调通对很多刚接触STM32F1的人来说是一道不大不小的坎尤其是手头只有一块核心板、没有独立从机芯片的时候。我当时就卡在一个很尴尬的位置想写一个SPI收发实验又不想接外挂Flash或者OLED最后干脆把芯片上自带的两个SPI外设对起来做实验SPI1当主机、SPI2当从机不谈协议层面的高深用法就老老实实把它俩之间的收发流程跑通。这个思路看起来简单实际动手后才发现SPI1和SPI2虽然都是STM32F1里的同一种外设但它们的时钟来源、引脚位置、配置方式甚至踩坑的表现形式都不太一样。这篇文章就把我的实验过程、完整代码和排查经验整理出来给准备做STM32F1 SPI收发实验的人一个可以直接照抄的参考。1. 实验目标与接线让SPI1和SPI2在同一颗F103里互相对话1.1 为什么要用SPI1和SPI2做互联很多人做SPI实验会选择买一块W25Q64 Flash小板然后拿STM32的SPI1去读Flash的ID。这种实验当然没问题但它本质上只是“主机读从机”主机发出时钟Flash把数据甩回来整个过程其实是单向观察居多。我想做的“收发实验”更偏向于主从双方同时参与主机能发、能从机也能回还能验证回传数据的正确性。如果手头有两块F103核心板也可以一块当主机、一块当从机用杜邦线把两个板子的SPI引脚连起来。但两板方案涉及供电共地、下载器干扰等问题调试时不容易分清问题到底出在哪边。后来我注意到STM32F103系列基本都有SPI1和SPI2两个外设SPI1挂在APB2总线上SPI2挂在APB1总线上它们可以同时工作。所以最省事的方案就是一颗芯片上同时初始化两个SPI让它们自己跟自己对话。这样既不需要额外买硬件也把“两个SPI外设”共同收发这件事一次验证清楚。1.2 引脚选择与接线表STM32F103的SPI1和SPI2引脚并不都在同一个GPIO端口上连线之前最好把引脚表打出来贴显示器旁边。SPI1固定在PA5、PA6、PA7SPI2固定在PB13、PB14、PB15。虽然F1的引脚可以用重映射功能挪位置但实验阶段用默认引脚最省心重映射容易引入额外的AFIO配置问题。功能SPI1主机引脚SPI2从机引脚连线关系SCKPA5PB13PA5接PB13MISOPA6PB14PA6接PB14MOSIPA7PB15PA7接PB15GND板子GND板子GND共地NSSPA4软件NSS可不接PB12硬件NSS接GNDPB12接GND这里有个关键点主机的NSS引脚和从机的NSS引脚在实际连线时很容易被忽略。如果主机使用软件NSS管理PA4悬空没关系主机内部可以忽略NSS引脚电平只要把SSI位拉高就不会进入模式故障。但从机如果也使用软件NSS就必须把SSI位配好否则从机可能一直认为自己是“未选中”状态SCK来了也不干活。为了避免这个坑我在实验里让从机使用硬件NSS把PB12直接接地这样从机始终处于选中状态行为非常稳定。你不需要纠结从机有没有被“选中”只要主机一发时钟从机必然响应。GPIO模式也要区分清楚SPI的输入输出引脚不能全部照抄成复用推挽。主机侧PA5、PA7是输出配置成复用推挽PA6是输入配置成浮空输入。从机侧PB14是输出配置成复用推挽PB13SCK、PB15MOSI是输入配置成浮空输入PB12作为硬件NSS输入也配置成浮空输入。这个配置不是拍脑袋定的SPI外设的SCK和MOSI线上数据的流入方向完全由主从模式决定主机往从机方向发从机就把这些引脚当输入用。1.3 实验环境和辅助工具我用的是一块常见的F103ZET6板子标准外设库版本为3.5代码用Keil MDK编译下载。串口1用来打印调试信息波特率设置成115200。如果你用的是C8T6最小系统板PA5/PA6/PA7和PB13/PB14/PB15这些引脚也都存在代码可以直接复用只需留意板载LED、按键等占用了哪些引脚避开冲突就行。实验最终要达到的效果很明确主机的SPI1向外发送一个字节从机的SPI2在同一时刻收到这个字节并且从机把接收到的字节取反后回发主机的SPI1再把这个回发数据读取出来。整个流程用串口打印屏幕上能看到每一轮“主机发送的数据”和“主机接收到的数据”。如果从机回传的是主机数据的取反值那就说明主从双方不仅在数据线上通了而且收发方向、时钟极性和相位全部正确。2. 先说通SPI的主从原理谁给时钟谁发数据数据什么时候有效2.1 SPI不是在发数据是在交换移位寄存器我第一次接触SPI时很容易把“发送”和“接收”理解成两个独立动作总觉得发送就是发送、接收就是接收。实际看SPI硬件结构才发现SPI主机和从机内部各有一个8位移位寄存器主机SCLK每翻转一个周期主从两个移位寄存器就同步向左移动一位主机移出一位给从机从机同时移出一位给主机。也就是说每一个时钟周期内主机发出1位、收回1位从机发出1位、收回1位两边真正在做的是“互换”数据。这个原理直接解释了一个很多人困惑的现象主机的“发送函数”执行完之后为什么必须把接收寄存器读出来如果你只是往SPI_DR寄存器里写Byte却从不读SPI_DR接收标志位会一直保持置位甚至影响下一次发送。因为主机每次发送时钟的同时必然有数据从MISO线上回到主机移位寄存器你不把它读走它就堵在那。这个实验正好用两个SPI把这种“同时交换”的节奏展示得特别明显主机每发出一个字节必然也从从机那里拿回一个字节。2.2 CPOL和CPHA必须主从保持一致SPI不像USART那样有起始位和停止位它靠SCK的空闲电平和采样边沿来区分数据。CPOL决定SCK空闲时是低电平还是高电平CPHA决定数据在第1个边沿采样还是第2个边沿采样。主机和从机如果CPOL、CPHA不一致就会出现一种很恶心的情况线路上数据确实在跳变主机也能发送但从机收到的数据全是乱的或者压根进不了接收中断。实验中我使用的是SPI_CPOL_High、SPI_CPHA_2Edge这种常见组合也就是模式3SCK空闲为高数据在第二个边沿被采样。这个组合在Flash、SD卡、各种传感器里都很常见。当然你也可以改成模式0只要主从两边初始化保持一致。需要注意的是主机和从机的初始化结构体里都必须显式写这两个参数别以为从机是“被动接收”时钟极性和相位就无所谓。从机的CPOL、CPHA与主机不一致时从机的移位寄存器会在错误的边沿采样结果不是预期数据。2.3 硬件片选与软件片选实验里该怎么选串口、I2C、SPI之间区别很大串口是全双工点对点靠波特率同步I2C靠SCL同步且带设备地址SPI则依靠NSS片选信号来选中总线上的某个从机设备。SPI协议一开始就是为“一主多从”设计的NSS就是用来区分当前时钟和数据是发给谁的。但STM32给了工程师两种简化方式硬件NSS和软件NSS。硬件NSS模式下NSS引脚由外设自动控制主机可以通过 SSOE 位让SS输出低电平从机必须等NSS被拉低才能工作。软件NSS模式下NSS引脚状态被芯片内部的SSI位替代你不必接片选线只需把SSI位配好。对新手来说软件NSS的概念更容易绕晕所以我建议第一次做这个实验时主机用软件NSS、从机用硬件NSS。主机用软件NSS并强制SSI1保证自己不会出现模式故障从机用硬件NSS并把PB12接地保证从机永远处于选中状态。这相当于把一个复杂问题先降维让你集中精力观察MISO、MOSI和SCK三根线等原理吃透了再玩多从机。3. 初始化代码SPI1挂在APB2SPI2挂在APB1别把时钟开错了3.1 时钟树、GPIO复用和IO模式的分配初始化STM32外设离不开三件事开外设时钟、配GPIO模式、配外设模式。SPI在这三件事上都有一些容易踩的细节。首先SPI1挂在APB2总线上SPI2挂在APB1总线上。开启SPI1时要调用RCC_APB2PeriphClockCmd开启SPI2时要调用RCC_APB1PeriphClockCmd。如果你把SPI2的时钟开到了APB2组里编译不会报错但SPI2永远不能被使能初始化后再怎么配置都没有反应。同理GPIOA、GPIOB和AFIO时钟应该开在APB2组里因为GPIO和AFIO都挂在APB2上。其次STM32F1的GPIO不仅分成输入输出还分成模拟输入、上拉下拉、复用推挽、开漏输出等模式。SPI的SCK和MOSI在主机模式下是输出所以要配成复用推挽MISO是输入配浮空输入。从机模式反过来看SCK和MOSI这两个引脚上是外部输入信号所以要配成输入模式MISO要往外发数据才需要配置成复用推挽。最后AFIO时钟一定记得开。很多人配置了GPIO、配置了SPI但忘了开RCC_APB2Periph_AFIO结果有时候能跑有时候跑不通。对于SPI默认引脚来说AFIO不一定每次都起作用但既然库函数和参考手册都建议开启我建议统一打开避免后续其他外设重映射时产生隐藏问题。3.2 完整SPI1主机初始化代码下面给出我实验中使用的主机初始化函数。这个函数使用SPI1作为全双工主机时钟极性为高、第二边沿采样波特率预分频16分频MSB先行软件NSS管理。void SPI1_Master_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; SPI_InitTypeDef SPI_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO | RCC_APB2Periph_SPI1, ENABLE); 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_IN_FLOATING; GPIO_Init(GPIOA, GPIO_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_High; SPI_InitStructure.SPI_CPHA SPI_CPHA_2Edge; 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); SPI1-CR1 | SPI_CR1_SSI; SPI_Cmd(SPI1, ENABLE); }这里重点是两行容易被忽略的代码。第一SPI1-CR1 | SPI_CR1_SSI;在很多教程里并不显式出现但标准库的SPI_Init在配置SPI_NSS_Soft时并不会自动帮你把SSI位置1。SSI位在主机模式下保持为1可以让芯片知道NSS没有被拉低避免出现模式故障。如果你不写这一行主机可能跑着跑着就进MODF中断SPE位被硬件自动清零SPI直接罢工。第二SPI_CRCPolynomial虽然这次实验用不到CRC功能但库函数的结构体里要求必须显式赋值不赋值有时会使用随机值导致初始化行为不确定。3.3 完整SPI2从机初始化代码从机初始化与主机很像但有几个地方正好相反。SPI_Mode必须写成SPI_Mode_Slave从机的NSS选择了硬件NSSGPIO配置上也有差异。void SPI2_Slave_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; SPI_InitTypeDef SPI_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_14; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_15; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOB, GPIO_InitStructure); SPI_InitStructure.SPI_Direction SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode SPI_Mode_Slave; SPI_InitStructure.SPI_DataSize SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL SPI_CPOL_High; SPI_InitStructure.SPI_CPHA SPI_CPHA_2Edge; SPI_InitStructure.SPI_NSS SPI_NSS_Hard; SPI_InitStructure.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_16; SPI_InitStructure.SPI_FirstBit SPI_FirstBit_MSB; SPI_InitStructure.SPI_CRCPolynomial 7; SPI_Init(SPI2, SPI_InitStructure); SPI_Cmd(SPI2, ENABLE); }从机模式下SPI_BaudRatePrescaler其实不会真正影响通信速率因为SCK是从外部输入的但是从机内部的SCLK频率也不能太低。STM32F1手册里要求从机模式下外部SCK频率必须小于从机内部PCLK频率实际使用中我仍然会设置一个分频值反正不碍事。如果你把从机的波特率预分频写得过高某些边界条件下可能影响移位寄存器的判断所以老老实实和主机用同一个预分频值反而安心。需要额外说明的是PB12作为硬件NSS输入代码里配置成浮空输入接线时要确保它被拉低。如果PB12悬空从机的NSS电平不确定可能在SCK到来时不被选择实验表现为从机完全收不到数据。你既可以用杜邦线把PB12直接连到GND也可以用一块面包板把该引脚和一个普通GPIO输出低电平相连。我个人推荐直接接GND简单粗暴不漏电。3.4 波特率分频先慢后快SPI1挂在APB2上PCLK2为72MHzSPI2挂在APB1上PCLK1为36MHz。这个时钟差异直接决定了SPI1和SPI2的最高分频选择。主机SPI1使用16分频就是72MHz除以16得到4.5MHz的SCK频率。为什么一开始不用最高频率因为实验用杜邦线连接两个SPI杜邦线的分布电容和接触电阻在高频下容易让波形变差一旦出现数据异常很难判断是软件问题还是信号完整性问题。先跑低频等程序逻辑确认无误再逐步降低预分频值才符合排障的思路。4. 收发联动核心代码与运行效果4.1 主从数据交换的节奏感一旦理解了SPI是“换数据”之后收发联动程序就变得好写了。STM32F1的SPI用两个标志判断状态发送时等待TXE标志表示发送数据寄存器已空可以写入下一个数据接收时等待RXNE标志表示接收数据寄存器非空可以读走数据。写入SPI_DR后外设会立即启动SCK生成和数据移位过程读SPI_DR时硬件会清掉RXNE标志。但要注意主机发送一个字节和从机收到这个字节几乎是同一个动作中间并没有延迟。主机的“发送”操作实际把数据写进发送数据寄存器硬件自动把它移到移位寄存器SCK翻转8次8位数据就变成了从机接收的数据。如果从机此刻也往它自己的DR里写了一个字节这个字节会同步通过MISO返回给主机。所以在程序层面主机通常在同一个循环里执行“发送”和“接收”两次操作第一次发送的真正结果在第二次发送时才能被读回来。这个“错一位”的节奏很自然不需要刻意消除只要串口打印时留心就好。4.2 主机轮询收发从机中断响应为了让接收过程更真实我在实验里给SPI2开启了RXNE中断从机一收到数据就进入中断服务函数读走数据并把取反后的结果写入从机发送数据寄存器作为下一轮的回复。主机则使用轮询方式循环发送0x01到0x0A。void SPI2_IRQHandler(void) { uint8_t rx_data; if (SPI_I2S_GetITStatus(SPI2, SPI_I2S_IT_RXNE) ! RESET) { rx_data SPI_I2S_ReceiveData(SPI2); SPI_I2S_SendData(SPI2, (uint8_t)~rx_data); } }中断服务里没有做大量运算只是读数据、写数据这样能保证从机在下一个SCK周期到来前把回复字节准备好。如果你在中断里用printf打印那通信速率会瞬间垮掉因为串口打印一个字符的时间足够主机发好几轮数据。这个实验里处理函数很短即使SPI时钟跑得再高也不容易在中断里丢失数据。主机端的循环可以写成下面这样int main(void) { uint8_t tx_buf[10] {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A}; uint8_t rx_buf[10] {0}; uint8_t i; USART1_Init(115200); SPI1_Master_Init(); SPI2_Slave_Init(); SPI_I2S_ITConfig(SPI2, SPI_I2S_IT_RXNE, ENABLE); NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel SPI2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority 0; NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStructure); SPI_I2S_SendData(SPI2, 0x00); while (1) { for (i 0; i 10; i) { while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) RESET); SPI_I2S_SendData(SPI1, tx_buf[i]); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) RESET); rx_buf[i] SPI_I2S_ReceiveData(SPI1); delay_ms(10); } for (i 0; i 10; i) { printf(tx0x%02X rx0x%02X\n, tx_buf[i], rx_buf[i]); } delay_ms(1000); } }为了让第一轮通信就有一个稳定的回复我在主循环开始前额外向SPI2的DR里写了一个字节0x00。这样当主机第一次发送时钟时从机的MISO线上已经有字节0x00在往外移主机读到的rx_buf[0]会来自这个“预置回复”而不是未知值。4.3 串口输出与关键标志位验证正常运行后串口会打印类似这样的结果tx0x01 rx0xFF tx0x02 rx0xFE tx0x03 rx0xFD tx0x04 rx0xFC第一轮的rx0xFF就是主循环前从机DR里预置的0x00取反值后面的rx则分别是上一轮主机发送数据的取反值。比如主机第二次发送0x02从机在SPI2中断里收到0x02后把0xFD写回DR这个0xFD会在主机第三次发送0x03的时钟周期内经MISO传回给主机。所以第三行打印的rx0xFD实际上对应第二轮发送的0x02取反数据链条是连贯的。如果看到类似的“错一位”现象不要慌这恰恰说明SPI的换数据机制是正常的。真正需要担心的是如果第二行、第三行打印的rx全部固定为0xFF或者全部为0那说明从机没有把正确数据放进DR问题多半出在SPI2初始化或者中断没触发上。这个问题放在下一节讲排查方法。5. 我踩过的坑SPI2收不到数据时到底该检查什么5.1 现象回顾第一次跑这个实验时我的串口输出非常稳定但稳定得让人头疼主机发送的tx数据一直在变主机接收到的rx却永远是0xFF。这看起来像主机完全没有读到从机的任何数据甚至像主机MISO引脚一直是高电平。当时我把代码翻来覆去看了好几遍初始化代码好像没有遗漏GPIO也按表格配的为什么从机就是没反应呢后来用万用表量PB12到GND之间的电压发现竟然不是0V而是1.2V左右的浮动电平。再一看原来杜邦线只插了一半半接触状态导致从机NSS引脚处于不确定电平。把PB12彻底接到GND之后问题立刻消失。5.2 逐层排障路径后来我又故意制造了几个典型错误总结出了一套从软件到硬件的排查顺序。当你发现SPI2收不到数据或者收到全0xFF时不要先怀疑芯片坏了按照下面这个顺序过一遍第一看SPI2的SPE位是否被置1。如果从机初始化后没有调用SPI_Cmd(SPI2, ENABLE)SPI2外设根本没有使能收到SCK也没有反应。这个错误很低级但调试时最容易忽略。第二看PB12是否可靠接地。硬件NSS模式下从机NSS必须为低电平才被选中。如果PB12悬空从机的输入可能是随机值数据自然进不来。第三看GPIO模式是否配反。从机PB13和PB15是输入引脚配成浮空输入PB14是输出引脚配成复用推挽。如果你把PB13、PB15配成了复用推挽虽然多数情况下也能工作但会改变引脚的驱动状态增加信号冲突风险。严格按照输入输出方向配置最能减少干扰。第四看SPI2的中断是否使能。如果从机的数据确实进来了但你没有开SPI2的RXNE中断也没有在中断服务函数里读DR那么数据会被一直堵在接收寄存器里后续数据无法进入。轮询方式的话就要及时读SPI_I2S_ReceiveData。第五用示波器或者逻辑分析仪观察PB13上是否有SCK波形。如果主机这边配置成软件NSS且SSI位为0主机可能进入模式故障SPI1的SCK根本不输出那从机当然什么都收不到。这个Bug很容易被忽视所以我再次强调那句SPI1-CR1 | SPI_CR1_SSI;的重要性。5.3 模式故障与NSS悬空的连锁反应模式故障是STM32F1的SPI特有的一种保护机制主机模式下如果硬件检测到NSS引脚为低电平就会认为总线上有另一主机也要当主机于是触发MODF错误同时把SPE位清零整个SPI停止工作。症状非常像“SPI卡死”。出现这种状况时用调试器看SPI_SR的MODF位会发现它被置1了。避免模式故障最稳妥的手段就是使用软件NSS并把SSI置1。软件NSS模式下NSS引脚被内部逻辑忽略外部不管接不接上拉电阻都不会触发MODF。但要注意主机和从机不能都使用硬件NSS却把NSS引脚接反如果从机的NSS被拉高、主机的NSS被拉低主机反而会先挂掉。这个问题在双机联调时尤其常见。我建议所有做SPI实验的人初始化主机后都主动读一次SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_MODF)如果MODF标志为1说明配置或者接线里存在问题。把这个检查项写进程序里可以节省大量时间。5.4 排障速查表现象最可能原因处理办法主机发数据从机SPI2的RXNE一直不置位从机NSS没有被拉低PB12接GND主机收数据全是0xFF从机DR里没有数据可发或MISO连线断主循环前向SPI2写一次0x00主机一运行就卡死在发送等待SPI1进入MODF模式检查软件NSS配置SPI1-CR1置SSI1从机能收到数据但中断不进入SPI2的NVIC或RXNE中断未使能打开NVIC调用SPI_I2S_ITConfigSPI2外设不工作SPI2时钟开错到了APB2使用RCC_APB1PeriphClockCmd开启SPI2数据大部分正确但偶尔错位使用了杜邦线且SCK频率过高降低波特率预分频检查接线这张表是我这次实验之后整理出来的基本覆盖了新手在SPI主从实验中容易遇到的90%问题。5.5 没有示波器时怎么判断波形很多人手头没有示波器只有一块开发板和一个STM32调试器。这时候可以用一个间接方法验证SPI线路上有没有数据把主机的SCK引脚临时配置成普通GPIO输出程序里翻转该引脚用万用表测量引脚电压变化。虽然这个方法测不了高频波形但至少能确认引脚本身没有损坏。更实用的办法是把SPI1的MOSIPA7用杜邦线直接接到SPI1的MISOPA6做一个主机自发自收回环。如果回环模式下主机能收到自己发送的数据说明SPI1内部收发链路没有问题问题一定出在SPI2这边。这个“分段隔离”技巧在双设备联调时特别好用能迅速缩小故障范围。6. 进阶玩法DMA、高速率、逻辑分析仪与后续想法6.1 为什么DMA一个就够两个会怎样做SPI收发实验时有人会纠结到底配几个DMA通道。其实SPI全双工通信中SCK由主机控制主机的发送和接收是同时进行的。如果只关心“接收大量数据”一个接收DMA就够发送部分可以通过在发送DMA上循环发送0xFF来提供时钟。换句话说主机想要从从机读一长串数据不一定需要配置发送DMA用软件连续写0xFF到SPI_DR也能达到目的。两个DMA都用的场景通常是主机既要连续发送一大块数据又要同时无间断接收一大块数据比如驱动SD卡写入时边读边写。但STMF1的SPI DMA有一个需要注意的地方SPI发送DMA和接收DMA的触发条件是分开的发送DMA由TXE触发接收DMA由RXNE触发。如果只开启接收DMA却没关发送中断当主机发送寄存器空时会产生TXE事件可能导致DMA请求异常。所以实际项目里建议成对使用SPI的TX/RX DMA而实验阶段没有必要轮询已经足够教学了。6.2 把速率拉高之前先看电源和连线实验初期我用了16分频SCK为4.5MHz杜邦线明显可行。后续测试中把预分频改成8分频SCK到9MHz数据依然正常改成4分频SCK到18MHz后短距离杜邦线下偶尔出现数据错位。这不是STM32F1跑不了18MHz而是杜邦线的寄生电感和引脚间的串扰在高频下被放大了。SPI速率与主频的关系很直接主机SPI的SCK频率等于PCLK除以预分频值PCLK2为72MHzPCLK1为36MHz。使用SPI1时最低预分频是2分频即36MHz但这是极限值一般不建议跑满。如果你需要在更高频率下稳定通信至少要把杜邦线换成飞线或者PCB走线同时在SPI的SCK线上串联一个33欧姆左右的电阻抑制振铃。像TF卡这类对信号完整性敏感的存储设备不少电路还会在SPI信号线上加上拉电阻这就是为什么网上总有人讨论“TF卡SPI需要上拉吗”这个问题。其实上拉主要是为了应对主设备引脚漏极开路或空闲电平不确定的情况F103的复用推挽输出本身能驱动高电平上拉不是必须的但有的人加了上拉后稳定性确实更好因为这个上拉能改善信号在空闲状态下的电平。6.3 实验之后我真正想说的三件事第一做STM32F1的SPI收发实验最大的收获不是记住了SPI_InitStructure里的字段而是搞清楚了两个SPI外设挂在不同的总线上、拥有不同引脚、但通信规则完全一致。SPI1和SPI2的寄存器结构几乎一样学会了SPI1SPI2的代码就是复制粘贴改名字的事。第二这次实验表面上是两个SPI在通信本质上是用一个芯片内两个外设做互联遇到问题时比两块板互联更容易控制变量。如果以后接到ESP8266、DHT11这类芯片和模块不要被它们特殊的“类SPI”或者“单总线”时序吓到底层逻辑和这次实验并没有本质区别不过是加个片选时序、加个启动延时、注意数据位宽和采样沿。第三软件NSS和硬件NSS没有谁一定好但第一次做实验时优先选择“主机软件NSS从机硬件NSS接地”是最不容易翻车的组合等主从通信跑通后再改成纯软件NSS或者动态片选也不迟。把这个基础打好后面无论是用SPI驱动Flash、SD卡还是用CubeMX重新配置工程都能快速定位问题。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑