news 2026/7/31 15:02:59

STM32实战:SPI通信指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32实战:SPI通信指南

一、SPI通信基础

SPI(Serial Peripheral Interface,串行外设接口)是摩托罗拉公司推出的一种高速、全双工、同步的串行通信总线协议,广泛应用于微控制器与各种外设(如Flash存储器、SD卡、LCD显示屏、传感器等)之间的通信。

1.1 SPI通信特点

  • 全双工同步通信:主从设备可以同时发送和接收数据
  • 高速传输:通常可达几MHz到几十MHz的时钟频率
  • 主从架构:一个主设备可控制多个从设备
  • 硬件简单:仅需4根信号线即可实现通信
  • 无寻址机制:通过片选信号选择从设备

1.2 SPI信号线

SPI通信需要4根基本信号线:

  • SCK(Serial Clock):时钟信号,由主设备产生
  • MOSI(Master Out Slave In):主设备输出,从设备输入
  • MISO(Master In Slave Out):主设备输入,从设备输出
  • NSS/CS(Slave Select):片选信号,低电平有效

二、STM32 SPI外设

STM32系列微控制器通常集成多个SPI接口(如SPI1、SPI2、SPI3等),支持主从模式、全双工/半双工通信、多种数据格式和时钟极性/相位配置。

2.1 SPI工作模式

STM32 SPI支持以下工作模式:

  • 全双工模式:同时发送和接收数据(最常用)
  • 半双工模式:同一时刻只能发送或接收
  • 只发送模式:仅发送数据,不接收
  • 只接收模式:仅接收数据,不发送

2.2 时钟配置

SPI时钟配置涉及两个关键参数:

  • CPOL(Clock Polarity):时钟极性
    • CPOL=0:空闲时SCK为低电平
    • CPOL=1:空闲时SCK为高电平
  • CPHA(Clock Phase):时钟相位
    • CPHA=0:数据在第一个时钟边沿采样
    • CPHA=1:数据在第二个时钟边沿采样

CPOL和CPHA的组合形成4种SPI模式(Mode 0-3),通信双方必须配置相同的模式。

三、STM32 SPI编程实战

下面以STM32F103系列为例,演示如何使用HAL库进行SPI通信。

3.1 SPI初始化配置

#include "stm32f1xx_hal.h" SPI_HandleTypeDef hspi1; void SPI1_Init(void) { __HAL_RCC_SPI1_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); // GPIO配置 GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7; // SCK, MISO, MOSI GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); // SPI配置 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 (Mode 0) hspi1.Init.NSS = SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8; // 9MHz (72MHz/8) hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; hspi1.Init.TIMode = SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; hspi1.Init.CRCPolynomial = 10; HAL_SPI_Init(&hspi1); }

3.2 SPI数据收发函数

// 发送一个字节并接收一个字节 uint8_t SPI_TransmitReceive(uint8_t txData) { uint8_t rxData = 0; // 拉低片选(软件控制) HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); // 发送并接收数据 HAL_SPI_TransmitReceive(&hspi1, &txData, &rxData, 1, 1000); // 释放片选 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); return rxData; } // 发送多个字节 void SPI_Transmit(uint8_t *pData, uint16_t Size) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); HAL_SPI_Transmit(&hspi1, pData, Size, 1000); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); } // 接收多个字节 void SPI_Receive(uint8_t *pData, uint16_t Size) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); HAL_SPI_Receive(&hspi1, pData, Size, 1000); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); }

3.3 与W25Qxx Flash通信示例

// W25Qxx指令定义 #define W25X_WriteEnable 0x06 #define W25X_WriteDisable 0x04 #define W25X_ReadStatusReg 0x05 #define W25X_WriteStatusReg 0x01 #define W25X_ReadData 0x03 #define W25X_FastReadData 0x0B #define W25X_PageProgram 0x02 #define W25X_SectorErase 0x20 // 读取Flash ID uint32_t W25Q_ReadID(void) { uint32_t Temp = 0; uint8_t cmd[4] = {0x90, 0x00, 0x00, 0x00}; // 读取ID指令 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); HAL_SPI_Transmit(&hspi1, cmd, 4, 1000); HAL_SPI_Receive(&hspi1, (uint8_t*)&Temp, 3, 1000); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); return Temp; } // 读取数据 void W25Q_Read(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead) { uint8_t cmd[4]; cmd[0] = W25X_ReadData; cmd[1] = (uint8_t)((ReadAddr & 0xFF0000) >> 16); cmd[2] = (uint8_t)((ReadAddr & 0xFF00) >> 8); cmd[3] = (uint8_t)(ReadAddr & 0xFF); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); HAL_SPI_Transmit(&hspi1, cmd, 4, 1000); HAL_SPI_Receive(&hspi1, pBuffer, NumByteToRead, 1000); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); }

四、特殊使用:软件模拟SPI通信

当MCU外设不够时,可以使用GPIO模拟SPI进行通信,对于调试SPI有着极大的灵活性,但会带来更高的 CPU 占用和更复杂的时序控制。

4.1 引脚初始化

配置GPIO引脚为输出模式(SCLK、MOSI、SS)和输入模式(MISO),初始化时钟线和片选线为高电平(根据SPI模式调整)。

// 示例代码(基于STM32 HAL库) void SPI_GPIO_Init() { GPIO_InitTypeDef GPIO_InitStruct = {0}; // 配置SCLK、MOSI、SS为输出 GPIO_InitStruct.Pin = SPI_SCLK_PIN | SPI_MOSI_PIN | SPI_SS_PIN; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(SPI_PORT, &GPIO_InitStruct); // 配置MISO为输入 GPIO_InitStruct.Pin = SPI_MISO_PIN; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; HAL_GPIO_Init(SPI_PORT, &GPIO_InitStruct); // 初始状态:片选高(不选中),时钟高(模式0) HAL_GPIO_WritePin(SPI_PORT, SPI_SS_PIN, GPIO_PIN_SET); HAL_GPIO_WritePin(SPI_PORT, SPI_SCLK_PIN, GPIO_PIN_SET); }

4.2 数据传输函数

根据SPI模式(如模式0:时钟空闲为高,数据在上升沿采样)实现逐位读写:

uint8_t SPI_Sim_TransferByte(uint8_t tx_data) { uint8_t rx_data = 0; for (uint8_t i = 0; i < 8; i++) { if (tx_data & 0x80) { HAL_GPIO_WritePin(SPI_GPIO_PORT, SPI2_MOSI_Pin, GPIO_PIN_SET); } else { HAL_GPIO_WritePin(SPI_GPIO_PORT, SPI2_MOSI_Pin, GPIO_PIN_RESET); } HAL_GPIO_WritePin(SPI_GPIO_PORT, SPI2_CLK_Pin, GPIO_PIN_SET); rx_data <<= 1; if (HAL_GPIO_ReadPin(SPI_GPIO_PORT, SPI2_MISO_Pin) == GPIO_PIN_SET) { rx_data |= 0x01; } HAL_GPIO_WritePin(SPI_GPIO_PORT, SPI2_CLK_Pin, GPIO_PIN_RESET); tx_data <<= 1; } return rx_data; } uint32_t SCL3300_Sim_Transceive(uint32_t cmd) { uint32_t response = 0; uint8_t *cmd_bytes = (uint8_t*)&cmd; uint8_t *resp_bytes = (uint8_t*)&response; HAL_GPIO_WritePin(SPI_GPIO_PORT, SPI2_CS_Pin, GPIO_PIN_RESET); for (int i = 3; i >= 0; i--) { resp_bytes[i] = SPI_Sim_TransferByte(cmd_bytes[i]); } HAL_GPIO_WritePin(SPI_GPIO_PORT, SPI2_CS_Pin, GPIO_PIN_SET); HAL_Delay(1); return response; }

4.3 时序调整

  • 时钟频率:通过调整延时函数(如Delay_us())控制SCLK速度,需匹配从设备要求。
  • 模式支持:修改时钟极性和相位(CPOL/CPHA)以适配不同SPI模式。

4.4 注意事项

  • 信号完整性:软件模拟SPI速率较低(通常<1MHz),高频应用需硬件SPI。
  • 中断处理:避免在传输过程中被中断打断,可临时关闭中断。
  • 多从设备:每个从设备需独立片选引脚,传输前选中对应设备。

4.5 验证方法

通过逻辑分析仪或示波器观察SCLK、MOSI/MISO波形,确保数据对齐时钟边沿,并检查从设备的响应数据。

五、SPI通信注意事项

5.1 时序匹配

  • 主从设备必须配置相同的时钟极性和相位
  • 注意时钟频率不能超过从设备的最大支持频率
  • 考虑信号传输延迟,适当降低时钟频率

5.2 片选信号管理

  • 片选信号必须在数据传输前拉低,传输完成后拉高
  • 多从机系统中,同一时刻只能有一个片选有效
  • 软件片选(NSS Soft)需要手动控制GPIO

5.3 数据对齐

  • STM32支持8位和16位数据格式
  • 注意MSB/LSB(高位在前/低位在前)设置
  • 从设备的数据格式必须与主设备匹配

5.4 错误处理

  • 检查SPI状态标志(BUSY、TXE、RXNE等)
  • 实现超时机制,防止通信卡死
  • 使用DMA传输时注意缓冲区管理

六、SPI与I2C、UART对比

特性SPII2CUART
通信方式同步、全双工同步、半双工异步、全双工
信号线数量4根(3+CS)2根2根(TX、RX)
传输速率高(MHz级)中(KHz-MHz)低(通常<1Mbps)
寻址方式硬件片选软件地址点对点
硬件复杂度简单中等简单
适用场景高速外设多设备低速通信设备间通信

七、常见问题与调试技巧

7.1 通信失败排查步骤

  1. 检查电源和地线连接是否正常
  2. 确认时钟极性和相位配置一致
  3. 使用示波器或逻辑分析仪观察信号波形
  4. 检查片选信号时序是否正确
  5. 验证从设备是否处于就绪状态

7.2 性能优化建议

  • 使用DMA传输减少CPU占用
  • 合理设置SPI时钟分频器
  • 批量传输数据,减少片选切换
  • 使用硬件NSS信号提高稳定性

7.3 多从机系统设计

  • 为每个从设备分配独立的片选引脚
  • 考虑总线负载和信号完整性
  • 实现从设备状态管理机制
  • 注意从设备切换时的时序要求

八、总结

SPI是STM32与外部设备通信的重要接口,具有高速、全双工、硬件简单的优点。掌握SPI通信原理、STM32 SPI外设配置方法以及实际编程技巧,对于嵌入式开发至关重要。在实际项目中,需要根据具体外设的时序要求,合理配置SPI参数,并注意信号完整性和错误处理,确保通信的稳定可靠。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/31 15:02:55

Vue-ECharts 8.0:构建企业级数据可视化应用的终极解决方案

Vue-ECharts 8.0&#xff1a;构建企业级数据可视化应用的终极解决方案 【免费下载链接】vue-echarts Vue.js component for Apache ECharts™. 项目地址: https://gitcode.com/gh_mirrors/vu/vue-echarts 在当今数据驱动的决策环境中&#xff0c;高效的数据可视化已成为…

作者头像 李华
网站建设 2026/7/31 15:02:25

五天变半天!中新赛克以 AI 破解 MEMS 芯片制造工艺漂移溯源难题

一块MEMS芯片从晶圆制造到封装测试&#xff0c;历经数十道工序&#xff0c;每道工序的测试数据以TB级增长。当某批次良率出现异常波动时&#xff0c;质量工程师需要从成百上千个参数维度中逐一排查。一位资深工程师曾耗时五天&#xff0c;翻遍十几个工序的测试日志&#xff0c;…

作者头像 李华
网站建设 2026/7/31 15:02:12

浮动利率债券特性与利率风险对冲策略

1. 浮动利率债券的核心特性解析 浮动利率债券&#xff08;Floating Rate Notes, FRNs&#xff09;作为固定收益证券的特殊品类&#xff0c;其票面利率会定期根据基准利率&#xff08;如LIBOR、SOFR或国债收益率&#xff09;进行调整。这种动态调整机制使其成为利率上行周期中的…

作者头像 李华
网站建设 2026/7/31 15:02:02

星火科技助力边远地区防病攻坚

你好&#xff01;这是一个非常有意义且值得深入探讨的话题。星火科技项目与地方病防治的结合&#xff0c;特别是在少数民族和偏远地区&#xff0c;体现了科技赋能基层健康、精准解决民生痛点的先进理念。下面我将为你详细介绍这一领域的相关情况、核心模式、面临的挑战以及未来…

作者头像 李华
网站建设 2026/7/31 15:01:07

Linux系统独立安装Python 2.7完整指南:编译配置与虚拟环境管理

1. 为什么今天还需要安装Python 2.7&#xff1f; 如果你看到这个标题&#xff0c;第一反应可能是&#xff1a;“都什么年代了&#xff0c;还在折腾Python 2.7&#xff1f;” 确实&#xff0c;Python 2在2020年1月1日就已经正式结束了官方支持&#xff0c;主流生态早已全面转向P…

作者头像 李华