畜牧业网站建设,域名查询138,网络营销案例分析答案,中国建设银行官网站企业银行Stm32F103R6 SPI实战#xff1a;从零配置到双机通信#xff08;附代码#xff09; 对于许多刚接触嵌入式开发的朋友来说#xff0c;SPI通信协议就像一道门槛#xff0c;手册上的框图和数据时序图看久了容易让人眼花缭乱。特别是当你拿到一块像Stm32F103R6这样的“蓝核”开…Stm32F103R6 SPI实战从零配置到双机通信附代码对于许多刚接触嵌入式开发的朋友来说SPI通信协议就像一道门槛手册上的框图和数据时序图看久了容易让人眼花缭乱。特别是当你拿到一块像Stm32F103R6这样的“蓝核”开发板面对密密麻麻的引脚和CubeMX里一堆选项时那种无从下手的感觉我深有体会。这篇文章我想和你分享的不是照本宣科的理论复述而是一次从寄存器底层到HAL库上层的完整SPI实战之旅。我们会一起动手用两块Stm32F103R6开发板搭建一个可靠的双向SPI通信通道过程中你会看到清晰的配置步骤、示波器抓取的真实波形以及能直接烧录运行的完整代码。无论你是想快速实现一个传感器数据读取还是为两个微控制器之间建立高速数据链路这里的内容都能给你一个扎实的起点。1. 理解SPI核心不止是四根线在动手写代码之前我们得先抛开那些复杂的框图用更直观的方式理解SPI到底在干什么。很多人把SPI简单理解为MISO、MOSI、SCK、NSS这四根线这没错但容易忽略其灵活性和配置细节带来的巨大影响。SPI的本质是一种全双工、同步、串行的通信总线。全双工意味着主从设备可以同时发送和接收数据这得益于独立的数据线MOSI和MISO。同步是指通信双方依靠同一个时钟信号SCK来协调数据位的采样与发送时机这是它区别于UART等异步通信的关键。串行则指数据是一位一位地传输的。对于Stm32F103R6其SPI接口的灵活性主要体现在几个关键参数的配置上这些参数直接决定了你的通信能否成功时钟极性CPOL决定了SCK时钟线在空闲状态时的电平。CPOL0SCK空闲时为低电平。CPOL1SCK空闲时为高电平。时钟相位CPHA决定了数据在SCK的哪个边沿被采样。CPHA0数据在SCK的第一个边沿对于CPOL就是上升沿或下降沿被采样。CPHA1数据在SCK的第二个边沿被采样。CPOL和CPHA的组合构成了SPI的四种工作模式Mode 0, 1, 2, 3。主从设备的模式必须完全一致这是通信成功的首要条件。我常用一个简单的记忆方法Mode 0 (CPOL0, CPHA0) 是最常见的数据在SCK上升沿采样下降沿变化。注意与从设备如传感器、存储器通信前第一件事就是查阅其数据手册确认它支持的SPI模式。配置错误是导致“能发不能收”或数据乱码的最常见原因。另一个容易混淆的点是数据帧格式。Stm32F103R6的SPI支持8位或16位数据帧并且可以选择MSB最高位先行或LSB最低位先行。绝大多数外设都采用MSB先行但并非绝对。最后是NSS引脚管理它控制从设备的片选。这里有两种方式硬件NSS将SPI的NSS引脚物理连接到从设备的片选脚。当SPI配置为主模式且SSOE1时NSS引脚会自动输出低电平以选中从设备。这种方式简单但一个SPI主设备通常只能控制一个硬件从设备。软件NSS使用任意一个GPIO引脚来模拟片选信号。在通信开始前手动拉低该引脚通信结束后拉高。这是更常用、更灵活的方式尤其是一个主设备连接多个从设备时。为了更清晰地对比这几种模式我整理了一个表格SPI模式CPOLCPHA时钟空闲状态数据采样边沿数据变化边沿Mode 000低电平SCK上升沿SCK下降沿Mode 101低电平SCK下降沿SCK上升沿Mode 210高电平SCK下降沿SCK上升沿Mode 311高电平SCK上升沿SCK下降沿理解这些基础概念后我们就能胸有成竹地进入配置环节了。2. 硬件连接与CubeMX图形化配置实战的第一步是搭建硬件环境。我们假设你有两块Stm32F103R6核心板或最小系统板。SPI1是功能最全的我们就以它为例。硬件连接非常简单遵循“主出从入主入从出”的原则主机MOSI (PA7)-从机MOSI (PA7)主机MISO (PA6)-从机MISO (PA6)主机SCK (PA5)-从机SCK (PA5)**主机GPIO (例如PA4) ** -从机NSS (PA4)这里我们使用软件NSS用主机的PA4普通IO口控制从机的片选提示如果开发板上的SPI引脚被其他功能占用如烧录口记得在CubeMX中检查并重映射或者换用SPI2PB13, PB14, PB15。接下来是重头戏使用STM32CubeMX进行可视化配置。这个过程能极大减少底层寄存器操作的繁琐。新建工程与芯片选择打开CubeMX选择Stm32F103R6Tx。在Pinout Configuration视图首先在System Core-RCC中将High Speed Clock (HSE)设置为Crystal/Ceramic Resonator为系统提供精确时钟源。配置SPI1为主机在左侧Connectivity中找到SPI1。Mode选择Full-Duplex Master全双工主机。Hardware NSS Signal选择Disable我们使用软件控制。在Configuration选项卡下的Parameter Settings中根据你的从设备需求设置参数。例如我们设置Clock Parameters-Prescaler为8根据系统时钟计算波特率后面会讲。Clock Phase和Clock Polarity设为1 Edge和Low即Mode 0。Data Size选择8 Bits。First Bit选择MSB First。CRC Calculation保持Disable。此时引脚图上的PA5、PA6、PA7会自动被配置为SPI1的SCK、MISO、MOSI。配置软件NSS引脚在引脚图上找到PA4或其他你喜欢的GPIO左键点击选择GPIO_Output。我们可以将其用户标签重命名为SPI1_NSS以便识别。在System Core-GPIO中选中PA4将其默认输出电平设为High空闲时片选无效。配置时钟树点击Clock Configuration选项卡。对于Stm32F103R6外部晶振通常为8MHz。你可以通过配置PLL将系统时钟SYSCLK设置为最高72MHz。APB2总线时钟PCLK2也设为72MHzSPI1挂载在APB2上其时钟源就是PCLK2。SPI的波特率计算公式为BaudRate PCLK2 / Prescaler。例如PCLK272MHz预分频系数设为8则波特率为9MHz。这是一个比较常用的速率。生成代码在Project Manager选项卡设置好工程名、路径、IDE如MDK-ARM V5。在Code Generator中选择Generate peripheral initialization as a pair of ‘.c/.h’ files这样代码结构更清晰。最后点击GENERATE CODE。CubeMX会自动生成完整的初始化代码包括GPIO、SPI、时钟的配置。这比手动写寄存器要高效准确得多。生成的MX_SPI1_Init()函数就包含了我们刚才所有的设置。3. 编写双机通信基础代码代码生成后我们打开工程开始编写应用层逻辑。我们将实现一个简单的场景主机每隔1秒发送一个递增的数据包从机接收后将数据加1再发回给主机主机打印出发送和接收的数据。首先在main.c的/* USER CODE BEGIN PV */区域定义一些变量和缓冲区/* Private variables ---------------------------------------------------------*/ uint8_t txData 0; uint8_t rxData 0; uint8_t slaveRxBuffer 0; uint8_t slaveTxBuffer 0;主机端代码主函数循环内/* USER CODE BEGIN WHILE */ while (1) { // 1. 拉低NSS片选选中从设备 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); // 2. 主机发送一个字节并同时接收从机返回的字节 if (HAL_SPI_TransmitReceive(hspi1, txData, rxData, 1, 1000) HAL_OK) { // 打印发送和接收的数据 printf(Master Sent: %d, Received: %d\r\n, txData, rxData); } else { printf(SPI Communication Error!\r\n); } // 3. 拉高NSS释放从设备 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); // 4. 准备下一个要发送的数据 txData; // 延时1秒 HAL_Delay(1000); /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */从机端代码 从机的配置在CubeMX中略有不同SPI1的Mode应选择Full-Duplex Slave全双工从机其他参数如CPOL、CPHA、数据大小、首位顺序必须与主机完全一致。从机的NSS引脚PA4模式应设置为GPIO_Input用于检测主机的片选信号。从机的主循环不能主动发起传输而应处于等待接收的状态。一种常见的做法是在主循环中轮询接收或者使用中断。这里我们先演示轮询方式/* USER CODE BEGIN WHILE */ while (1) { // 检测NSS引脚是否为低电平是否被主机选中 if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_4) GPIO_PIN_RESET) { // 被选中开始接收数据并准备回复数据 slaveTxBuffer slaveRxBuffer 1; // 简单地将接收到的数据加1后回复 // 从机同时执行接收和发送 if (HAL_SPI_TransmitReceive(hspi1, slaveTxBuffer, slaveRxBuffer, 1, 1000) HAL_OK) { // 可以在这里处理接收到的数据 slaveRxBuffer // 例如点亮一个LED或者通过串口打印 // printf(Slave Received: %d\r\n, slaveRxBuffer); } } /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */注意这个简单的从机轮询代码在实际应用中效率不高因为它持续检测NSS引脚会占用大量CPU资源。更好的方式是使用SPI的中断模式或DMA模式让从机在数据到来时自动响应。将这两段代码分别编译并烧录到两块开发板中连接好硬件打开主机的串口调试助手需要事先配置好串口并重定向printf你应该能看到每秒输出一行数据显示主机发送和接收的值并且接收到的值正好是发送值加1。这就标志着最基础的双机SPI通信成功了4. 进阶优化DMA传输与中断处理当需要传输大量数据如图像、音频帧或者希望CPU从繁琐的字节搬运工作中解放出来时直接使用HAL_SPI_TransmitReceive这种阻塞式函数就不合适了。它会一直占用CPU直到整个数据块传输完成。这时DMA直接存储器访问和中断就该登场了。使用DMA进行SPI传输的核心思想是由DMA控制器在SPI外设和内存之间自动搬运数据CPU只需发起传输命令然后就可以去处理其他任务等DMA传输完成后再通过中断或标志位来通知CPU。在CubeMX中配置SPI DMA非常方便在Connectivity-SPI1的DMA Settings选项卡中点击Add。为SPI1_TX添加一个DMA流如DMA1_Channel3方向为Memory To Peripheral。为SPI1_RX添加一个DMA流如DMA1_Channel2方向为Peripheral To Memory。将两者的模式都设置为Normal单次传输或Circular循环传输。生成代码。配置完成后HAL库会生成DMA和SPI的初始化代码。发送和接收数据可以使用非阻塞函数// 主机端使用DMA发送接收大块数据 #define DATA_SIZE 256 uint8_t masterTxBuffer[DATA_SIZE]; uint8_t masterRxBuffer[DATA_SIZE]; // 填充要发送的数据 for(int i0; iDATA_SIZE; i) { masterTxBuffer[i] i; } // 拉低片选 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); // 启动DMA传输非阻塞 if(HAL_SPI_TransmitReceive_DMA(hspi1, masterTxBuffer, masterRxBuffer, DATA_SIZE) ! HAL_OK) { Error_Handler(); } // 此时CPU可以执行其他任务... // 等待DMA传输完成可以通过标志位、中断回调函数或简单的轮询 while (HAL_SPI_GetState(hspi1) ! HAL_SPI_STATE_READY) {} // 拉高片选 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); // 处理接收到的数据 masterRxBuffer中断处理则提供了更精细的控制。除了DMA传输完成中断SPI本身也有中断例如TXE发送缓冲区空和RXNE接收缓冲区非空中断。在从机端利用中断可以高效地响应主机的呼叫。在CubeMX中使能SPI全局中断后你需要重写对应的中断回调函数// 在 stm32f1xx_it.c 中SPI1_IRQHandler 会自动调用 HAL_SPI_IRQHandler // 我们需要在用户文件中实现回调函数 // 当SPI发送接收完成时对于轮询/DMA/中断模式都适用的回调函数 void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi) { if(hspi-Instance SPI1) { // 传输完成处理数据或通知任务 // 例如置位一个信号量或标志位 spiTransferComplete 1; } } // SPI错误中断回调 void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi) { // 处理通信错误如过载、模式错误等 printf(SPI Error occurred!\r\n); }将DMA和中断结合你可以构建出极其高效且稳定的SPI通信链路。对于从机可以配置为在SPI数据寄存器就绪时触发中断在中断服务例程中快速读取或写入数据从而实现对主机请求的即时响应。5. 调试技巧与波形分析代码写好了但通信没成功别急着怀疑人生硬件工程师最好的朋友——示波器或者逻辑分析仪该出场了。它能让你“看见”电信号是调试数字通信协议不可或缺的工具。连接示波器探头到SCK、MOSI和NSS引脚地线要接好。触发方式可以设置为NSS的下降沿片选开始。然后运行程序你应该能看到类似下图的波形此处应有一张示波器截图显示SCK、MOSI、NSS的时序关系。图中NSS为低电平期间SCK有一串脉冲MOSI上有对应的数据位变化通过分析波形你可以诊断出绝大多数问题没有SCK时钟检查主机SPI是否使能时钟配置是否正确NSS信号是否有效。有SCK但没有MOSI数据检查主机发送函数是否被调用数据缓冲区是否有有效数据MOSI引脚配置是否正确。数据波形不对重点检查SPI模式CPOL/CPHA。对比示波器上数据位变化和采样的边沿与你的配置是否一致。例如你配置为Mode 0 (CPOL0, CPHA0)那么数据应该在SCK上升沿保持稳定被采样在下降沿可以变化。如果你的波形显示数据在上升沿变化那就说明相位配置反了。数据字节顺序错误检查First Bit是MSB还是LSB。用示波器观察第一个时钟周期对应的MOSI电平是最高位还是最低位。除了示波器Stm32F103R6内置的串口打印调试也是快速定位软件问题的好方法。确保printf重定向到串口在关键步骤如进入中断、DMA完成、数据校验错误打印信息。最后分享几个我踩过坑后总结的实战注意事项电平匹配确保通信双方是相同的电压域通常都是3.3V。如果从设备是5V电平需要电平转换电路否则可能损坏STM32的IO口。上拉电阻对于开漏输出的引脚某些情况下MISO可能需要配置为开漏或者线路较长时适当加上拉电阻如4.7kΩ到10kΩ可以增强信号稳定性。时钟速度与布线SPI可以跑很高速度Stm32F103的SPI理论上可达18Mbps 72MHz PCLK。但在面包板或飞线连接时过高的速度会导致信号边沿振铃、失真从而通信失败。如果高速通信不稳定尝试降低波特率增大预分频系数或缩短连接线。多从设备连接当使用软件NSS控制多个从设备时确保在切换设备时有足够的片选建立时间NSS拉低后稍作延时再开始传输并且同一时刻只有一个从设备的MISO引脚被使能输出有效避免总线冲突。从寄存器配置到CubeMX图形化从阻塞传输到DMA中断优化再到最后的调试分析这套组合拳打下来你应该对Stm32F103R6的SPI通信有了从理论到肌肉记忆的理解。我最开始调试SPI读写SD卡时因为相位设错卡了一整天最后用示波器一眼就找到了问题。所以当你遇到问题时别光盯着代码看去听听信号的声音用示波器很多疑惑都会迎刃而解。剩下的就是在你自己的项目里大胆用起来把这条路彻底走通。