当前位置: 首页 > news >正文

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参数,并注意信号完整性和错误处理,确保通信的稳定可靠。

http://www.cnnetsun.cn/news/3768456.html

相关文章:

  • Vue-ECharts 8.0:构建企业级数据可视化应用的终极解决方案
  • 五天变半天!中新赛克以 AI 破解 MEMS 芯片制造工艺漂移溯源难题
  • 浮动利率债券特性与利率风险对冲策略
  • 星火科技助力边远地区防病攻坚
  • Linux系统独立安装Python 2.7完整指南:编译配置与虚拟环境管理
  • 盈兴通:告别产线“隐形杀手”:一张无尘卷轴布,如何为精密制造筑牢品质防线?
  • 告别论文内耗[特殊字符]OKBIYE才是2026真正适配双检的全能学术工具
  • 【AI建筑行业落地实战指南】:20年资深工程师亲授5大不可绕过的应用陷阱与避坑清单
  • 央企市场化转型受阻?北京华恒智信管理案例
  • AI技术如何革新英语学习:从智能纠音到个性化路径
  • XCOM 2模组管理终极指南:用AML启动器告别游戏崩溃烦恼
  • 3分钟搞定Blender四边形重拓扑:QRemeshify新手完全指南
  • 亚马逊账户与规则风险下的渠道韧性比较:BBWEYY独立站客户资产建设,含零代码SAAS、AI编程、源码定制交付
  • 3分钟掌握QGIS地图服务插件:QuickMapServices完全指南 [特殊字符]️
  • 深入解析RDMA:零拷贝、内核旁路与协议卸载三大核心技术
  • 西门子PLC与触摸屏报警系统:从架构设计到工程实现的完整指南
  • 毕业设计博物馆小程序开发实战:从作品上传到检索优化
  • C++项目集成FFmpeg:从环境配置到音视频处理核心流程详解
  • 终极指南:如何用Input Overlay实现直播操作透明化
  • OpCore-Simplify终极指南:3步自动化构建完美Hackintosh EFI配置
  • STM32串口动态调整PWM频率占空比:电机控制与LED调光实战
  • 如何高效使用百度网盘秒传工具:网页版转存生成转换完全指南
  • CoreCycler终极指南:专业级CPU单核稳定性测试与超频调校
  • 零样本与少样本提示:解锁大模型泛化能力的核心技巧
  • Serper与豆包搜索API对比:LLM信息检索Agent技术选型指南
  • pWnOS 2.0靶机渗透测试全攻略笔记
  • MDB Tools深度解析:跨平台Access数据库访问与转换架构剖析
  • 暗黑破坏神2角色编辑器:Diablo Edit2完全指南
  • 芯科 SI4463-C2A-GMR 兼容替代 国产 DP4363
  • 企业级数据治理自动化:OpenMetadata策略引擎终极指南