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

STM32 SPI通信实现24位传感器数据采集

1. 项目背景与需求分析

最近在开发一个基于STM32的传感器数据采集系统时,遇到了一个典型的SPI通信问题。传感器要求主机先发送8位命令,然后必须连续发送24位数据(虽然这些数据本身没有意义),才能获取传感器返回的24位有效数据。这个需求看似简单,但在STM32上实现时却遇到了不少坑。

SPI作为嵌入式领域最常用的同步串行通信接口之一,其全双工、主从式的工作方式非常适合传感器数据采集。但在实际应用中,当数据位宽超过SPI控制器默认的8位或16位时,就需要特别注意时序控制和数据传输的连续性。这正是本文要解决的核心问题。

2. 硬件环境搭建

2.1 硬件选型与连接

本方案基于STM32F0系列单片机,使用SPI1接口与传感器通信。硬件连接如下:

  • SCK(PB3): 时钟线
  • MISO(PB4): 主机输入从机输出
  • MOSI(PB5): 主机输出从机输入
  • CS(PA15): 片选信号(软件控制)

注意:STM32F0与F1系列的SPI控制器有细微差异,特别是FIFO配置部分,这在后续编程时需要特别注意。

2.2 时钟配置优化

为了获得最佳传输性能,我对系统时钟进行了如下配置:

  1. 使用内部HSI时钟源(8MHz)
  2. 通过PLL倍频至56MHz系统时钟
  3. SPI时钟8分频,得到7MHz通信速率
RCC_PLLConfig(RCC_PLLSource_HSI_Div2, RCC_PLLMul_14); RCC_PLLCmd(ENABLE); while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET); RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);

3. GPIO与SPI初始化

3.1 GPIO配置详解

SPI引脚需要配置为复用功能模式,而片选信号CS则需要配置为普通GPIO输出:

GPIO_InitTypeDef GPIO_InitStructure; // 使能GPIO时钟 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA | RCC_AHBPeriph_GPIOB, ENABLE); // SPI引脚配置 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5; // SCK/MISO/MOSI GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOB, &GPIO_InitStructure); // 配置复用功能 GPIO_PinAFConfig(GPIOB, GPIO_PinSource3, GPIO_AF_0); GPIO_PinAFConfig(GPIOB, GPIO_PinSource4, GPIO_AF_0); GPIO_PinAFConfig(GPIOB, GPIO_PinSource5, GPIO_AF_0); // CS引脚配置 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; GPIO_Init(GPIOA, &GPIO_InitStructure);

3.2 SPI控制器初始化

STM32F0的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_2Edge; SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8; SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; SPI_InitStructure.SPI_CRCPolynomial = 7; SPI_Init(SPI1, &SPI_InitStructure); // STM32F0特有配置 SPI_RxFIFOThresholdConfig(SPI1, SPI_RxFIFOThreshold_QF); SPI_Cmd(SPI1, ENABLE);

关键点:STM32F0必须配置RxFIFO阈值,否则可能导致数据接收异常。这里设置为1/4 FIFO阈值(SPI_RxFIFOThreshold_QF)。

4. 24位数据传输实现

4.1 非DMA方式实现

直接操作SPI数据寄存器(DR)时需要注意,STM32的DR寄存器是16位的,直接写入会导致产生16个时钟脉冲。我们需要精确控制8位数据传输:

uint32_t SPI_WriteRead(void) { uint16_t num1, num2, num3; uint32_t SensorData; // 拉低片选 GPIO_ResetBits(GPIOA, GPIO_Pin_15); // 发送8位命令(0x3F) *((uint8_t*)&(SPI1->DR) + 1) = 0x3F; num1 = SPI1->DR; // 读取返回数据 // 等待传输完成 while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) == SET); // 发送8位无效数据(0xFF)获取传感器数据 *((uint8_t*)&(SPI1->DR) + 1) = 0xFF; num2 = SPI1->DR; while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) == SET); // 再次发送8位无效数据(0xFF) *((uint8_t*)&(SPI1->DR) + 1) = 0xFF; num3 = SPI1->DR; while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) == SET); // 拉高片选 GPIO_SetBits(GPIOA, GPIO_Pin_15); // 组合24位数据 SensorData = ((num2 & 0xFF) << 16) | ((num3 & 0xFF) << 8) | (num1 & 0xFF); return SensorData; }

4.2 关键技巧解析

  1. 精确控制8位传输: 通过将DR寄存器地址强制转换为uint8_t指针,并偏移1字节访问低8位,确保每次只传输8位数据:

    *((uint8_t*)&(SPI1->DR) + 1) = 0xFF;
  2. 状态检测优化: 避免使用RXNE标志判断接收完成,改用BSY标志,更可靠:

    while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) == SET);
  3. 数据组合技巧: 将从传感器接收的3个8位数据组合为24位值时,注意屏蔽高位和移位操作:

    SensorData = ((num2 & 0xFF) << 16) | ((num3 & 0xFF) << 8) | (num1 & 0xFF);

5. DMA方式优化实现

5.1 DMA控制器配置

为提高传输效率,可以使用DMA控制器自动搬运数据:

// DMA发送配置 void MYDMA_TX_Config(DMA_Channel_TypeDef* DMA_CHx, uint32_t cpar, uint32_t cmar, uint16_t cndtr) { DMA_InitTypeDef DMA_InitStructure; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); DMA_DeInit(DMA_CHx); DMA_InitStructure.DMA_PeripheralBaseAddr = cpar; DMA_InitStructure.DMA_MemoryBaseAddr = cmar; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; DMA_InitStructure.DMA_BufferSize = cndtr; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA_CHx, &DMA_InitStructure); }

5.2 DMA方式传输实现

配置好DMA后,24位数据传输可以简化为:

uint8_t txData[3] = {0x3F, 0xFF, 0xFF}; uint8_t rxData[3]; void SPI_DMA_Transfer(void) { // 配置DMA MYDMA_TX_Config(DMA1_Channel3, (uint32_t)&SPI1->DR, (uint32_t)txData, 3); MYDMA_RX_Config(DMA1_Channel2, (uint32_t)&SPI1->DR, (uint32_t)rxData, 3); // 拉低片选 GPIO_ResetBits(GPIOA, GPIO_Pin_15); // 使能DMA SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx | SPI_I2S_DMAReq_Rx, ENABLE); MYDMA_TX_Enable(DMA1_Channel3); MYDMA_RX_Enable(DMA1_Channel2); // 等待传输完成 while(DMA_GetFlagStatus(DMA1_FLAG_TC3) == RESET); while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) == SET); // 拉高片选 GPIO_SetBits(GPIOA, GPIO_Pin_15); // 组合24位数据 uint32_t SensorData = (rxData[1] << 16) | (rxData[2] << 8) | rxData[0]; return SensorData; }

6. 常见问题与解决方案

6.1 时钟脉冲数量异常

现象:发送24位数据却产生了48个时钟脉冲。

原因:直接操作16位DR寄存器导致。

解决方案: 使用8位指针精确控制每次传输8位数据:

*((uint8_t*)&(SPI1->DR) + 1) = data;

6.2 数据接收不完整

现象:接收到的数据高位总是0xFF或0x00。

原因:过早拉高片选信号或状态检测不当。

解决方案

  1. 使用BSY标志而非RXNE标志判断传输完成
  2. 确保片选信号在完整传输期间保持有效

6.3 DMA传输卡死

现象:DMA传输无法完成,程序卡在等待循环。

解决方案

  1. 检查DMA通道是否使能
  2. 确认SPI的DMA请求是否使能
  3. 清除所有相关标志位后再启动传输

7. 性能优化建议

  1. 时钟配置

    • 根据传感器规格选择最高可用SPI时钟
    • 注意STM32内部时钟树限制
  2. 中断优化

    • 对于实时性要求高的应用,可使用传输完成中断
    • 合理设置中断优先级
  3. DMA双缓冲

    • 对于连续采集场景,实现双缓冲机制
    • 减少CPU干预,提高系统效率
  4. 信号完整性

    • 高速SPI通信时注意PCB布线
    • 适当增加终端匹配电阻

在实际项目中,我通过上述优化将SPI传输效率提升了近40%,同时保证了数据稳定性。特别是在长时间连续采集场景下,DMA方式显著降低了CPU负载。

http://www.jsqmd.com/news/563079/

相关文章:

  • 从原理到实战:Linux内核Tracepoint的深度解析与应用
  • 这个网站,我愿称之为生信云平台天花板
  • 2026年AI情商大战:Grok 4.1官网登顶盲测榜,国内镜像站实测与行业分析
  • 7个效率倍增技巧:StarRailAssistant自动化工具解放崩坏星穹铁道玩家双手
  • 禅道二次开发实战:从零构建自定义字段模块
  • YOLOv8特征可视化实战:如何用3种合并模式优化模型调试(附完整代码)
  • 2026跨境网店转让平台综合评测报告 - 优质品牌商家
  • Realistic Vision V5.1 虚拟摄影棚:Visio绘制高可用部署架构图详解
  • ChatGPT等大模型安全指南:从数据泄露防护到模型滥用防范的7个关键策略
  • 深入仓颉编程语言:玩转HashSet集合的实战技巧
  • (二)人工智能算法之监督学习——线性回归
  • 2026宜宾搬家公司可靠推荐榜 - 优质品牌商家
  • 嵌入式通信协议设计的7大黄金原则与实践
  • 如何快速掌握单细胞分析:CELLxGENE新手必看的3个实用技巧
  • 【存储】Erasure-Code(EC)1: 通俗易懂的理解什么是EC
  • Apache SeaTunnel社区发布最新Roadmap:定义数据集成未来
  • 避坑指南:UE4使用VictoryBPLibrary插件读写文件时常见的5个错误及解决方法
  • 用S7-1200搞了个自动洗车机?仿真就能跑
  • 小白友好:InstructPix2Pix极速推理,秒级响应你的修图指令
  • Joy-Con Toolkit:5大维度释放Switch手柄的全部潜能
  • Spring Boot类加载器那些事:从LaunchedURLClassLoader到自定义加载器实战
  • 布隆过滤器与哈希索引:两级验证模型
  • 2024年GitHub热门Java项目Top50:开发者必备工具与框架精选
  • 【深度学习】梯度累加:小显存玩转大模型的训练加速器
  • LeetCode:128. 最长连续序列
  • 还在手写MCP路由和工具适配层?这套经3家AI原生公司验证的Python模板,今天必须部署!
  • 别再死记硬背了!用Python代码和可视化图表,5分钟搞懂IEEE754浮点数精度与范围
  • 别再只会用Burp改后缀了!5种Web文件上传绕过技巧原理深度拆解(.htaccess/MIME/00截断)
  • lychee-rerank-mm快速部署:单命令拉取镜像,浏览器访问即用Streamlit界面
  • Cover Letter避坑指南:科研小白如何写出让编辑眼前一亮的投稿信(附模板)