DMA在SPI和I2C通信中的应用详解

嵌入式开发从第一性原理拆解SPI、I2C与DMA机制 同步串行通信依赖时钟线实现数据收发,SPI与I2C在电气特性和协议设计上差异显著,DMA则通过硬件搬运释放CPU负载。理解这些底层机制,能帮助工程师在STM32等平台上高效配置外设、排查时序故障,并应对多设备通信与高速数据流场景。从芯片手册到工程落地,掌握SPI、I2C和DMA的配合方式,是嵌入式系统设计师提升调试能力的关键。 阅读详情

DMA在SPI和I2C通信中的应用详解

目录

1. DMA基本原理

1.1 什么是DMA

DMA(Direct Memory Access,直接内存访问)是一种允许外围设备(如SPI、I2C控制器)直接访问系统内存而无需CPU干预的数据传输技术。在传统的数据传输中,CPU需要执行一系列指令来移动数据,这不仅占用宝贵的处理时间,还限制了数据传输速率。

DMA控制器是一个专门的硬件模块,能够独立于CPU执行内存操作,允许数据传输在后台进行,同时CPU可以执行其他任务。当传输完成时,DMA控制器会通知CPU(通常通过中断)。

1.2 DMA工作模式

在嵌入式微控制器中,DMA通常支持以下几种工作模式:

1. 存储器到存储器模式:

  • 在内存区域之间复制数据
  • 不涉及外设,纯粹的内存操作
  • 典型应用:大块数据的快速复制

2. 存储器到外设模式:

  • 数据从内存传输到外设
  • 典型应用:SPI/I2C发送操作

3. 外设到存储器模式:

  • 数据从外设传输到内存
  • 典型应用:SPI/I2C接收操作

4. 外设到外设模式:

  • 在两个外设之间直接传输数据
  • 较少使用,但在特定场景效率很高

5. 循环模式:

  • DMA在完成传输后自动重新加载初始配置
  • 适用于持续性数据流,如ADC采样

1.3 DMA在嵌入式系统中的优势

  1. 降低CPU负载:CPU不必参与数据传输过程,可专注于其他任务。

  2. 提高传输效率:DMA控制器优化设计用于数据移动,比CPU执行循环复制更高效。

  3. 确定性时序:DMA传输通常具有可预测的时序,有助于实时系统的设计。

  4. 减少中断频率:无需每字节/字产生中断,只在传输完成时通知CPU。

  5. 降低功耗:CPU可在DMA传输过程中进入低功耗模式,节省能源。

2. DMA+SPI通信实现

2.1 SPI+DMA工作原理

SPI(Serial Peripheral Interface)是一种全双工同步串行通信接口,常用于与传感器、存储器和其他外设通信。传统的SPI实现中,CPU需要不断写入和读取SPI数据寄存器(DR),这在大数据量传输时效率低下。

当将DMA与SPI结合使用时:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 发送流程:

    • CPU配置SPI寄存器和DMA参数
    • DMA控制器在SPI发送缓冲区准备好时,自动将下一个数据从内存传输到SPI数据寄存器
    • 此过程循环直至所有数据发送完成
    • DMA发出传输完成中断通知CPU
  2. 接收流程:

    • CPU配置SPI寄存器和DMA参数
    • 当SPI接收缓冲区有数据时,DMA自动将数据从SPI数据寄存器传输到内存
    • 此过程循环直至接收完所有数据
    • DMA发出传输完成中断通知CPU
  3. 全双工操作:

    • 大多数SPI控制器支持同时使用两个DMA通道
    • 一个通道负责TX(发送),另一个负责RX(接收)
    • 两个DMA通道协同工作实现全双工数据传输

2.2 配置步骤

以STM32微控制器为例,配置SPI+DMA的基本步骤如下:

  1. 配置GPIO引脚:

    • 设置SPI相关引脚(SCK、MISO、MOSI、CS)
    • 配置引脚的复用功能、速度和上拉/下拉状态
  2. 配置SPI外设:

    • 设置SPI模式、时钟分频、数据格式等
    • 启用SPI的DMA请求功能
  3. 配置DMA:

    • 设置DMA通道和流
    • 配置源地址和目标地址
    • 设置传输方向、大小和模式
    • 配置优先级和中断
  4. 启动传输:

    • 启用DMA
    • 启用SPI
    • 开始传输过程
  5. 处理完成中断:

    • 接收DMA传输完成中断
    • 执行必要的后续处理

2.3 代码实现示例

以下是STM32平台上实现SPI+DMA传输的简化示例:

// SPI+DMA初始化 (基于STM32 HAL库)
void SPI_DMA_Init(void) {
   
   
    // GPIO配置
    GPIO_InitTypeDef GPIO_InitStruct = {
   
   0};
    
    // 使能时钟
    __HAL_RCC_SPI1_CLK_ENABLE();
    __HAL_RCC_DMA2_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();
    
    // 配置SPI引脚
    GPIO_InitStruct.Pin = GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
    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;
    hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
    hspi1.Init.NSS = SPI_NSS_SOFT;
    hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16;
    hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
    hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
    hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
    HAL_SPI_Init(&hspi1);
    
    // 配置DMA - TX
    hdma_spi1_tx.Instance = DMA2_Stream3;
    hdma_spi1_tx.Init.Channel = DMA_CHANNEL_3;
    hdma_spi1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
    hdma_spi1_tx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_spi1_tx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_spi1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_spi1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    hdma_spi1_tx.Init.Mode = DMA_NORMAL;
    hdma_spi1_tx.Init.Priority = DMA_PRIORITY_MEDIUM;
    hdma_spi1_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
    HAL_DMA_Init(&hdma_spi1_tx);
    
    // 关联DMA和SPI
    __HAL_LINKDMA(&hspi1, hdmatx, hdma_spi1_tx);
    
    // 配置DMA - RX (类似TX配置)
    hdma_spi1_rx.Instance = DMA2_Stream0;
    // ... 其他RX配置类似 ...
    hdma_spi1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
    // ... 其他设置相同 ...
    HAL_DMA_Init(&hdma_spi1_rx);
    
    __HAL_LINKDMA(&hspi1, hdmarx, hdma_spi1_rx);
    
    // 配置DMA中断
    HAL_NVIC_SetPriority(DMA2_Stream3_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(DMA2_Stream3_IRQn);
    HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn);
}

// 使用DMA发送SPI数据
void SPI_DMA_Transmit(uint8_t *data, uint16_t size) {
   
   
    // 选中从设备 (拉低CS引脚)
    HAL_GPIO_WritePin(SPI_CS_GPIO_Port, SPI_CS_Pin, GPIO_PIN_RESET);
    
    // 启动DMA传输
    HAL_SPI_Transmit_DMA(&hspi1, data, size);
    
    // 注意:CS引脚会在DMA完成中断处理函数中拉高
}

// DMA完成中断处理函数
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi) {
   
   
    // 传输完成,释放从设备 (拉高CS引脚)
    HAL_GPIO_WritePin(SPI_CS_GPIO_Port, SPI_CS_Pin, GPIO_PIN_SET);
    
    // 通知应用层传输完成
    SPI_TransferComplete_Callback();
}

// 使用DMA接收SPI数据
void SPI_DMA_Receive(uint8_t *buffer, uint16_t size) {
   
   
    // 选中从设备
    HAL_GPIO_WritePin(SPI_CS_GPIO_Port, SPI_CS_Pin, GPIO_PIN_RESET);
    
    // 启动DMA接收
    HAL_SPI_Receive_DMA(&hspi1, buffer, size);
    
    // CS引脚会在接收完成回调中拉高
}

// 接收完成回调
void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi) {
   
   
    HAL_GPIO_WritePin(SPI_CS_GPIO_Port, SPI_CS_Pin, GPIO_PIN_SET);
    
    // 通知应用层接收完成
    SPI_ReceiveComplete_Callback();
}

// 全双工传输
void SPI_DMA_TransmitReceive(uint8_t *txData, uint8_t *rxData, uint16_t size) {
   
   
    HAL_GPIO_WritePin(SPI_CS_GPIO_Port, SPI_CS_Pin, GPIO_PIN_RESET);
    
    // 启动全双工DMA传输
    HAL_SPI_TransmitReceive_DMA(&hspi1, txData, rxData, size);
}

// 全双工传输完成回调
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi) {
   
   
    HAL_GPIO_WritePin(SPI_CS_GPIO_Port, SPI_CS_Pin, GPIO_PIN_SET);
    
    // 通知应用层传输完成
    SPI_TransferComplete_Callback();
}

2.4 高级应用场景

1. 大容量存储设备访问

与Flash存储器(如SPI NOR Flash或SD卡)交互时,DMA+SPI组合能显著提高性能:

// 从Flash中读取大量数据的示例
void ReadFlashBlock(uint32_t address, uint8_t *buffer, uint32_t size) {
   
   
    uint8_t cmd[5] = {
   
   0};
    
    // 准备读取命令
    cmd[0] = FLASH_READ_CMD;       // 读取命令 (通常为0x03)
    cmd[1] = (address >> 24) & 0xFF; // 地址字节3
    cmd[2] = (address >> 16) & 0xFF; // 地址字节2
    cmd[3] = (address >> 8) & 0xFF;  // 地址字节1
    cmd[4] = address & 0xFF;         // 地址字节0
    
    // 选中Flash芯片
    FLASH_CS_LOW();
    
    // 发送命令和地址
    HAL_SPI_Transmit(&hspi1, cmd, 5, HAL_MAX_DELAY);
    
    // 使用DMA读取数据
    HAL_SPI_Receive_DMA(&hspi1, buffer, size);
    
    // CS信号会在DMA完成回调中释放
}

// 写入大量数据到Flash
void WriteFlashPage(uint32_t address, uint8_t *data, uint16_t size) {
   
   
    // 略去了擦除和写使能操作,实际应用中需要这些步骤
    
    uint8_t cmd[5] = {
   
   0};
    cmd[0] = FLASH_PAGE_PROGRAM;   // 页编程命令 (通常为0x02)
    cmd[1] = (address >> 24) & 0xFF;
    cmd[2] = (address >> 16) & 0xFF;
    cmd[3] = (address >> 8) & 0xFF;
    cmd[4] = address & 0xFF;
    
    FLASH_CS_LOW();
    
    // 发送命令和地址
    HAL_SPI_Transmit(&hspi1, cmd, 5, HAL_MAX_DELAY);
    
    // 使用DMA发送数据</
SPI+DMA传输性能比较 第一次我是以SPI+DMA驱动LCD显示240X82的图片,可以看出显示该图片在有DMA(加DMA中断)的加持下,耗时156us。第二次是我单独用SPI驱动LCD显示240X82的图片,耗时8685us,所以区别还是蛮大的。本文章仅仅简单记录32单片机的SPI+DMA驱动显示屏的性能测试,这里不花费时间介绍SPI和DMA。这里我定时器计数频率分频到1Mhz,最大计数50000次,也是就计数频率1us,周期50ms。(2)SPI的读写接口(非DMA情况下需要使用到)(3)SPI的DMA配置初始化。 阅读详情

相关推荐

STM32使用SPI+DMA(标准库)

因为DMA_Mode_Normal模式使用一次后DMA_BufferSize会清零,如下代码用于重置DMA_BufferSize。注意:DMA_PeripheralBaseAddr不是SPI外设的地址,而是SPI1->DR即SPI数据寄存器的地址。既然使用的是SPI+DMA,必定要使能SPI,详细请参考。配置完SPI后,配置DMA,如下。

m0_67530207的博客 2万+

AiCube图形化程序自动生成【SPI,SPI-DMA,I2C,I2C-DMA】代码,驱动OLED-12864

当需要使用I2C接口时,R2、R3、R9不焊,R1、R4焊4.7K电阻,R8焊0欧电阻,如下图。AiCube 图形化程序自动生成【SPI,SPI-DMA,I2C,I2C-DMA】代码,2.4、代码完成代码编写后,将编译生成的HEX文件下载到试验箱,同样也可看到如1楼的效果。2.4、代码完成代码编写后,将编译生成的HEX文件下载到试验箱,同样也可看到的效果。当需要使用SPI接口时,R1、R2、R8不焊,R3、R4焊4.7K电阻,如下图。准双向口 准双向口。开漏+上拉 开漏+上拉。

USB_CAN_8051的博客 1414

嵌入式开发必看:UART、I2C、SPI三种通信协议实战对比(附STM32代码示例)

本文从嵌入式开发者实战角度,深度对比UART、I2C、SPI三种核心串行通信协议。内容涵盖硬件接线差异、STM32 CubeMX配置要点、HAL库代码避坑指南,并结合OLED驱动等典型场景,提供清晰的协议选型逻辑与决策树,帮助开发者根据速度、引脚和可靠性需求做出最优选择。

n7o8p的博客 649

DMA技术在STM32中优化UART、SPI和I2C通信性能的研究与实现

通过上述代码示例,可以实现UART、SPI和I2C的DMA传输。在实际应用中,需要根据具体需求和外设功能进行配置,以实现最佳的性能和稳定性。DMA可以实现数据的高速传输,减轻CPU的负担,提高系统性能。在本篇文章中,我将探讨DMA技术在STM32中优化这些通信协议的研究和实现。DMA控制器位于片内,独立于CPU,可以直接访问片外存储器,以及与UART、SPI和I2C等外设进行数据交换。1. CPU配置DMA的控制寄存器,包括源地址、目的地址、传输长度和传输模式等。代码获取、问题探讨及文章转载可私信。

weixin_66608063的博客 1390

MSP430硬件串行通信实战:USI/USCI模块的SPI与I2C低功耗应用

在嵌入式系统开发中,串行通信是实现微控制器与外围设备数据交换的基础技术。其核心原理是通过特定的时序协议,在单根或少数几根数据线上逐位传输数据,从而简化硬件连接。从技术价值看,硬件实现的串行通信外设相较于软件模拟,能显著提升系统可靠性、降低CPU开销并优化功耗,这对于电池供电的物联网节点、便携式医疗设备等场景至关重要。常见的应用场景包括传感器数据采集、存储器扩展、显示器驱动及多设备组网。本文聚焦于德州仪器MSP430系列微控制器的**USI**和**USCI**硬件通信模块,通过**SPI**和**I2C**

weixin_28718793的博客 242

SPI/I2C接口在Arduino ESP32中的硬件实现

深入解析Arduino ESP32如何通过硬件实现SPI与I2C通信协议,涵盖引脚配置、工作模式及实际应用中的关键细节,帮助开发者高效利用arduino esp32的多任务处理能力提升项目稳定性。

weixin_35749796的博客 967

【STM32 CubeMX】I2C中断方式与DMA方式

在STM32 CubeMX环境中,I2C(Inter-Integrated Circuit)通信协议的实现可以采用中断方式或DMA(Direct Memory Access)方式。中断方式通过触发中断来处理数据传输,而DMA方式则利用直接内存访问技术来实现数据传输,减轻了CPU的负担。在选择I2C通信方式时,开发者需要根据具体的应用需求和性能要求来选择合适的方式。STM32 CubeMX提供了图形化配置工具,使得配置I2C通信方式变得更加便捷。

m0_62599305的博客 7738

单片机串行通信技术教程

UART是一种异步串行通信协议,用于在两个设备之间传输数据。它通常包含一个数据位、一个奇偶校验位和一个停止位。UART通信的关键是波特率,即每秒传输的位数,必须在发送端和接收端之间匹配。

kkchenjj的博客 1772

从‘最长白列’到‘八邻域’:智能车图像巡线算法怎么选?我的踩坑经验分享

本文深入探讨了全国大学生智能汽车竞赛中图像巡线算法的选型与优化,对比了最长白列法、八邻域算法等五种主流方法的性能与适用场景。通过实战数据和代码示例,分析了各算法在直道、弯道及特殊赛道元素中的表现,并提供了内存优化、参数调整等实用技巧,帮助参赛团队提升智能车的巡线精度与效率。

weixin_30374009的博客 626

redhawk-sc(seascape)学习日志2

design view是主view,是所有seascape view中最root的部分,一个design view可以驱动多种类型分析比如静态IR drop,动态IR drop以及EM.这些分析可以在不同的PVT和翻转率下.Design view是一个必须的view对于redhawk中的任何类型分析来说。seascape view相当于一个容器,用于保存各种和设计相关的数据,类似于逻辑信息、物理信息等等,每个view都有其对应的输入件和依赖,这些view都是redhawk用于各种类型的分析所需。

qq_39493558的博客 3479

【SABRE 3D, SABRE 3DxT,LAM设备】2025年9月13日-安全配置-准备工作

请遵循本规程将SABRE 3D/3DxT/3D Scimitar系统设置为安全配置,以便能够在电镀单元、APT模块、XMM(或SRD)模块、工厂接口(FI)外壳或中央通道内部及周边区域执行作业p104-p117。在生产过程中暂停调度程序存在一定风险:若待维护模块未离线且在其他模块完成处理前重启调度程序,可能对生产晶圆造成影响。维护人员需特别注意电镀模块内的晶圆状态,并尽可能快速地恢复调度程序。确认待维护模块内无晶圆滞留暂停调度程序并将相应模块设置为离线状态打开模块检修门并关闭安全盖板(槽位)

weixin_46735260的博客 1351

保姆级教程:从Salmon输出到DESeq2,搞定RNA-seq表达矩阵标准化与下游分析准备

本文提供从Salmon输出到DESeq2标准化的RNA-seq数据分析全流程指南,详细解析表达矩阵构建、标准化方法选择及下游分析准备。重点介绍如何将Salmon定量结果转换为DESeq2兼容格式,包括TPM与原始计数的处理技巧,以及常见问题排查方法,帮助研究人员高效完成RNA-seq数据分析。

weixin_42699679的博客 297

从IPv6到Tomcat:彻底解决127.0.0.1拒绝连接的完整指南

本文提供了从IPv6到Tomcat的全面指南,帮助开发者彻底解决127.0.0.1拒绝连接的问题。涵盖网络协议栈、服务配置、端口管理等多个层面的排查方法,特别针对Tomcat服务提供了专项解决方案,确保开发者能够快速定位并修复连接问题。

weixin_29207439的博客 463

S7-1500——(一)西门子PLC编程从入门到精通4、SCL间接寻址

学习目标是在掌握博途平台SCL编程语言的基本使用基础之上,进一步了解SCL语言环境下的间接寻址过程,初步具备编写复杂控制程序的能力;在编写plc程序过程中, 需要对plc中的数据进行访问,这就是寻址;直接寻址间接寻址;直接寻址直接寻址就是直接对数据的存储单元进行访问;根据表达方式的不同,可分为绝对寻址和符号寻址;对于plc中的标准数据存储区包括IQMTCL以及标准数据块,既可以使用绝对寻址,也可以使用符号寻址;绝对寻址在使用绝对寻址时,可以寻址的宽度包括位,字,节字,双字;

大小曲奇的博客 2111

【Agent实战】Anthropic Skills、MCP与LangGraph的工程实践

随着大语言模型(LLM)应用从简单的Chatbot向自主智能体(Autonomous Agents)演进,如何管理复杂的任务上下文、标准化的工具调用以及确定性的业务流程,成为了系统设计的核心挑战。Anthropic 推出的 Skills 规范,结合 Model Context Protocol (MCP) 与 Function Calling,为构建模块化、可扩展的 Agent 提供了全新的范式。

kakaZhui的博客 578

TenserRt平台SMOKE 3D目标检测部署完整教程:从理论到实践

本教程详细介绍了在RK3588平台上部署SMOKE 3D目标检测模型的完整流程。SMOKE是一种创新的单阶段单目3D检测方法,通过关键点估计直接预测3D目标信息,有效解决了深度信息缺失等挑战。文章从理论基础出发,解析了SMOKE的核心算法和网络架构,包括DLA-34主干网络和关键点中心化策略。同时提供了详细的Docker环境配置、模型导出到ONNX格式的实践指南,以及TensorRT引擎构建和性能优化的关键技术要点。该教程为开发者在嵌入式平台上实现高效3D目标检测提供了完整的技术参考。

LXY_LOVE36的博客 366

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

上一篇: I²C总线高级特性与故障处理分析
下一篇: 嵌入式开发中栈溢出的处理方法
夜月yeyue
博客等级 码龄7年 1428粉丝 126原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值