单片机DMA原理及应用详解(下篇)(附工程源码)

news2024/11/17 4:23:25

 

这篇文章详细介绍单片机的DMA原理和应用范例希望我的分享能给你带来不一样的收获!

关于DMA的原理,可以看上一篇文章:

单片机DMA原理及应用详解(上篇)(附工程源码)-CSDN博客

目录

 一、STM32单片机DMA配置及应用

(一)、MyDMA.c

(二)、main.c

二、示例工程

main.c

dma.h

dma.c

三、结语


一、STM32单片机DMA配置及应用

这里以STM32单片机“DMA数据转运”为例来给大家介绍。

(一)、MyDMA.c

#include "stm32f10x.h"                  // 引入STM32F10x系列的头文件,包含设备相关的定义和声明

uint16_t MyDMA_Size;                    // 定义一个全局变量,用于存储DMA传输的数据大小

// 初始化DMA
void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size)
{
	MyDMA_Size = Size;                  // 将传入的Size参数保存到全局变量MyDMA_Size
	
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // 使能DMA1时钟
	
	DMA_InitTypeDef DMA_InitStructure;  // 定义DMA初始化结构体
	
	// 配置DMA初始化结构体的各个参数
	DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA; // 外设基地址
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // 外设数据大小为字节
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable; // 使能外设地址递增
	DMA_InitStructure.DMA_MemoryBaseAddr = AddrB; // 内存基地址
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // 内存数据大小为字节
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 使能内存地址递增
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; // 数据传输方向:从外设到内存
	DMA_InitStructure.DMA_BufferSize = Size; // DMA缓冲区大小
	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; // DMA工作模式:正常模式
	DMA_InitStructure.DMA_M2M = DMA_M2M_Enable; // 使能内存到内存的传输(虽然在这里是从外设到内存,但结构体需要设置)
	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; // DMA优先级设置为中等
	
	DMA_Init(DMA1_Channel1, &DMA_InitStructure); // 初始化DMA1通道1
	
	DMA_Cmd(DMA1_Channel1, DISABLE); // 禁用DMA1通道1,以进行配置
}

// 启动DMA传输
void MyDMA_Transfer(void)
{
	DMA_Cmd(DMA1_Channel1, DISABLE); // 禁用DMA1通道1,确保传输参数更新
	
	DMA_SetCurrDataCounter(DMA1_Channel1, MyDMA_Size); // 设置DMA传输的数据量为MyDMA_Size
	
	DMA_Cmd(DMA1_Channel1, ENABLE); // 启用DMA1通道1
	
	// 等待DMA传输完成
	while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET); // 检查传输完成标志位是否置位
	
	DMA_ClearFlag(DMA1_FLAG_TC1); // 清除传输完成标志位
}

代码分析 

1、头文件

#include "stm32f10x.h"                  // 引入STM32F10x系列的头文件,包含设备相关的定义和声明
  • #include "stm32f10x.h":这行代码包含了STM32F10x系列微控制器的头文件,提供了对微控制器的寄存器、外设和配置选项的访问。

2、全局变量

uint16_t MyDMA_Size;                    // 定义一个全局变量,用于存储DMA传输的数据大小
  • MyDMA_Size:这是一个全局变量,用于存储DMA传输的数据大小,以便在传输函数中使用。

3、DMA初始化函数

void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size)
{
    MyDMA_Size = Size;                  // 将传入的Size参数保存到全局变量MyDMA_Size
    
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // 使能DMA1时钟
    
    DMA_InitTypeDef DMA_InitStructure;  // 定义DMA初始化结构体
    
    // 配置DMA初始化结构体的各个参数
    DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA; // 外设基地址
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // 外设数据大小为字节
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable; // 使能外设地址递增
    DMA_InitStructure.DMA_MemoryBaseAddr = AddrB; // 内存基地址
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // 内存数据大小为字节
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 使能内存地址递增
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; // 数据传输方向:从外设到内存
    DMA_InitStructure.DMA_BufferSize = Size; // DMA缓冲区大小
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; // DMA工作模式:正常模式
    DMA_InitStructure.DMA_M2M = DMA_M2M_Enable; // 使能内存到内存的传输(虽然在这里是从外设到内存,但结构体需要设置)
    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; // DMA优先级设置为中等
    
    DMA_Init(DMA1_Channel1, &DMA_InitStructure); // 初始化DMA1通道1
    
    DMA_Cmd(DMA1_Channel1, DISABLE); // 禁用DMA1通道1,以进行配置
}
  • MyDMA_Init 函数用于配置DMA。它接收三个参数:外设地址 (AddrA)、内存地址 (AddrB) 和传输大小 (Size)。
  1. 保存大小MyDMA_Size 被赋值为 Size。这允许在DMA传输函数中使用传输的大小。

  2. 使能DMA1时钟RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE) 使能DMA1的时钟。DMA需要时钟才能工作,因此必须先使能其时钟。

  3. 配置DMA

    • DMA_InitTypeDef DMA_InitStructure:声明一个DMA初始化结构体实例。
    • 通过配置 DMA_InitStructure 的各个字段,设置DMA的工作参数。
      • DMA_PeripheralBaseAddr:设置DMA外设的基地址。
      • DMA_PeripheralDataSize:设置外设的数据大小,通常为字节。
      • DMA_PeripheralInc:设置是否使能外设地址递增。
      • DMA_MemoryBaseAddr:设置DMA内存的基地址。
      • DMA_MemoryDataSize:设置内存的数据大小,通常为字节。
      • DMA_MemoryInc:设置是否使能内存地址递增。
      • DMA_DIR:设置数据传输方向。在这里是从外设到内存。
      • DMA_BufferSize:设置DMA缓冲区的大小。
      • DMA_Mode:设置DMA工作模式。正常模式表示一次性传输。
      • DMA_M2M:设置内存到内存的传输使能标志。虽然在此例中使用的是从外设到内存,但结构体的配置还是需要设置这个标志。
      • DMA_Priority:设置DMA优先级。
  4. 初始化DMA通道DMA_Init(DMA1_Channel1, &DMA_InitStructure) 使用配置好的结构体初始化DMA1的通道1。

  5. 禁用DMA通道DMA_Cmd(DMA1_Channel1, DISABLE) 禁用DMA通道1。配置完成后,必须先禁用DMA通道,然后再启用,以确保配置生效。

4、DMA传输函数

void MyDMA_Transfer(void)
{
    DMA_Cmd(DMA1_Channel1, DISABLE); // 禁用DMA1通道1,确保传输参数更新
    
    DMA_SetCurrDataCounter(DMA1_Channel1, MyDMA_Size); // 设置DMA传输的数据量为MyDMA_Size
    
    DMA_Cmd(DMA1_Channel1, ENABLE); // 启用DMA1通道1
    
    // 等待DMA传输完成
    while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET); // 检查传输完成标志位是否置位
    
    DMA_ClearFlag(DMA1_FLAG_TC1); // 清除传输完成标志位
}
  • MyDMA_Transfer 函数启动DMA传输并等待传输完成。
  1. 禁用DMA通道DMA_Cmd(DMA1_Channel1, DISABLE) 确保在更新传输参数之前禁用DMA通道1。

  2. 设置数据计数器DMA_SetCurrDataCounter(DMA1_Channel1, MyDMA_Size) 设置DMA传输的数据量。这个值应该与初始化时设置的一致。

  3. 启用DMA通道DMA_Cmd(DMA1_Channel1, ENABLE) 启用DMA通道1,开始数据传输。

  4. 等待传输完成

    • while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET):使用 DMA_GetFlagStatus 检查传输完成标志位(TC1),直到传输完成。
    • DMA_ClearFlag(DMA1_FLAG_TC1):清除传输完成标志位,以准备下一次传输。

(二)、main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyDMA.h"

uint8_t DataA[] = {0x01, 0x02, 0x03, 0x04};
uint8_t DataB[] = {0, 0, 0, 0};

int main(void)
{
	OLED_Init();
	
	MyDMA_Init((uint32_t)DataA, (uint32_t)DataB, 4);
	
	OLED_ShowString(1, 1, "DataA");
	OLED_ShowString(3, 1, "DataB");
	OLED_ShowHexNum(1, 8, (uint32_t)DataA, 8);
	OLED_ShowHexNum(3, 8, (uint32_t)DataB, 8);
		
	while (1)
	{
		DataA[0] ++;
		DataA[1] ++;
		DataA[2] ++;
		DataA[3] ++;
		
		OLED_ShowHexNum(2, 1, DataA[0], 2);
		OLED_ShowHexNum(2, 4, DataA[1], 2);
		OLED_ShowHexNum(2, 7, DataA[2], 2);
		OLED_ShowHexNum(2, 10, DataA[3], 2);
		OLED_ShowHexNum(4, 1, DataB[0], 2);
		OLED_ShowHexNum(4, 4, DataB[1], 2);
		OLED_ShowHexNum(4, 7, DataB[2], 2);
		OLED_ShowHexNum(4, 10, DataB[3], 2);
		
		Delay_ms(1000);
		
		MyDMA_Transfer();
		
		OLED_ShowHexNum(2, 1, DataA[0], 2);
		OLED_ShowHexNum(2, 4, DataA[1], 2);
		OLED_ShowHexNum(2, 7, DataA[2], 2);
		OLED_ShowHexNum(2, 10, DataA[3], 2);
		OLED_ShowHexNum(4, 1, DataB[0], 2);
		OLED_ShowHexNum(4, 4, DataB[1], 2);
		OLED_ShowHexNum(4, 7, DataB[2], 2);
		OLED_ShowHexNum(4, 10, DataB[3], 2);

		Delay_ms(1000);
	}
}

二、示例工程

main.c

#include "stm32f4xx.h"  // 引入STM32F4系列的头文件,根据实际情况选择合适的头文件
#include "dma.h"        // 引入DMA配置的头文件

#define BUFFER_SIZE 256 // 定义缓冲区大小为256字节

// 源数据和目标数据缓冲区
uint8_t srcBuffer[BUFFER_SIZE];
uint8_t dstBuffer[BUFFER_SIZE];

int main(void) {
    // 初始化HAL库
    HAL_Init();
    // 初始化系统时钟(用户自定义的函数,根据具体时钟配置要求实现)
    SystemClock_Config();
    // 初始化DMA相关配置
    MX_DMA_Init();

    // 填充源数据缓冲区,数据从0到255
    for (int i = 0; i < BUFFER_SIZE; i++) {
        srcBuffer[i] = i;
    }

    // 配置DMA进行内存到内存的数据传输
    DMA_Config((uint32_t)srcBuffer, (uint32_t)dstBuffer, BUFFER_SIZE);

    // 启动DMA传输,从srcBuffer到dstBuffer,传输大小为BUFFER_SIZE
    HAL_DMA_Start(&hdma_memtomem_dma2_stream0, (uint32_t)srcBuffer, (uint32_t)dstBuffer, BUFFER_SIZE);
    
    // 等待DMA传输完成
    while (!__HAL_DMA_GET_FLAG(&hdma_memtomem_dma2_stream0, DMA_FLAG_TCIF0_4)) {
    }

    // 清除DMA传输完成标志
    __HAL_DMA_CLEAR_FLAG(&hdma_memtomem_dma2_stream0, DMA_FLAG_TCIF0_4);

    // 在这里可以检查dstBuffer的内容是否与srcBuffer匹配

    // 主循环保持程序运行
    while (1) {
    }
}

dma.h

#ifndef __DMA_H
#define __DMA_H

#include "stm32f4xx_hal.h" // 引入HAL库头文件

// DMA句柄的声明
extern DMA_HandleTypeDef hdma_memtomem_dma2_stream0;

// 函数声明
void MX_DMA_Init(void);          // 初始化DMA
void DMA_Config(uint32_t srcAddress, uint32_t dstAddress, uint32_t size); // 配置DMA传输

#endif /* __DMA_H */

dma.c

#include "dma.h"

// DMA句柄定义
DMA_HandleTypeDef hdma_memtomem_dma2_stream0;

void MX_DMA_Init(void) {
    // 启用DMA2时钟
    __HAL_RCC_DMA2_CLK_ENABLE();

    // 配置DMA2_Stream0
    hdma_memtomem_dma2_stream0.Instance = DMA2_Stream0; // 选择DMA2流0
    hdma_memtomem_dma2_stream0.Init.Channel = DMA_CHANNEL_0; // 选择通道0
    hdma_memtomem_dma2_stream0.Init.Direction = DMA_MEMORY_TO_MEMORY; // 设置传输方向:内存到内存
    hdma_memtomem_dma2_stream0.Init.PeriphInc = DMA_PINC_ENABLE; // 外设地址增量使能
    hdma_memtomem_dma2_stream0.Init.MemInc = DMA_MINC_ENABLE; // 内存地址增量使能
    hdma_memtomem_dma2_stream0.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; // 外设数据对齐:字节
    hdma_memtomem_dma2_stream0.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; // 内存数据对齐:字节
    hdma_memtomem_dma2_stream0.Init.Mode = DMA_NORMAL; // 设置DMA模式:普通模式
    hdma_memtomem_dma2_stream0.Init.Priority = DMA_PRIORITY_LOW; // 设置DMA优先级:低
    hdma_memtomem_dma2_stream0.Init.FIFOMode = DMA_FIFOMODE_DISABLE; // FIFO模式禁用
    HAL_DMA_Init(&hdma_memtomem_dma2_stream0); // 初始化DMA

    // 配置DMA中断(可选)
    __HAL_DMA_ENABLE_IT(&hdma_memtomem_dma2_stream0, DMA_IT_TC); // 使能传输完成中断
}

void DMA_Config(uint32_t srcAddress, uint32_t dstAddress, uint32_t size) {
    // 配置DMA传输的源地址、目标地址和数据大小
    hdma_memtomem_dma2_stream0.Init.Mem0BaseAddr = srcAddress; // 设置源地址
    hdma_memtomem_dma2_stream0.Init.Mem1BaseAddr = dstAddress; // 设置目标地址
    hdma_memtomem_dma2_stream0.Init.NbData = size; // 设置传输数据的字节数
}

三、结语

以上就是关于DAM的实战工程,希望我的分享能给你带来不一样的收获!

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2116757.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

黑龙江IBM Storwize v3700存储故障维修

中国黑龙江省佳木斯IBM Storwize v3700存储控制器故障维修案例&#xff1a; 报修单位&#xff1a;黑龙江佳木斯某某医院 存储型号&#xff1a;联想或IBM v3700 存储故障&#xff1a;无法正常访问存储磁盘&#xff0c;无法进存储web管理界面&#xff0c;可以进存储服务助手界…

《C++ 中的左值与右值》

在 C 的世界里&#xff0c;左值和右值是两个重要的概念&#xff0c;理解它们对于掌握 C 语言的精髓至关重要。 首先&#xff0c;我们来认识左值。左值&#xff0c;即 “locator value”&#xff0c;可以理解为有明确存储位置且可以被取地址的表达式。简单来说&#xff0c;一个可…

Linux之MySQL主从复制

概述 MySQL的主从复制&#xff08;Master-Slave Replication&#xff09;是一种数据复制解决方案&#xff0c;将主数据库的DDL&#xff08;数据定义语言&#xff09;和DML&#xff08;数据操纵语言&#xff09;操作通过二进制日志传到从库服务器中&#xff0c;然后在从库上对这…

自动驾驶ADAS算法--使用MATLBA和UE4生成测试视频

原文参考&#xff1a;金书世界 环境搭建参考&#xff1a;用MATLAB2020b和虚拟引擎&#xff08;Unreal Engine&#xff09;联合仿真输出AVM全景测试视频----Matlab环境搭建 matlab参考&#xff1a; https://ww2.mathworks.cn/help/driving/ug/simulate-a-simple-driving-sce…

Vue2电商项目(二) Home模块的开发;(还需要补充js节流和防抖的回顾链接)

文章目录 一、Home模块拆分1. 三级联动组件TypeNav2. 其余组件 二、发送请求的准备工作1. axios的二次封装2. 统一管理接口API----跨域3. nprogress进度条 三、 vuex模块开发四、TypeNav三级联动组件开发1. 动态展示三级联动数据2. 三级联动 动态背景(1)、方式一&#xff1a;CS…

CAS概念、性质、优缺点 | 乐观锁、悲观锁是什么?

前言&#xff1a; 今天在深入了解HashMap时&#xff0c;看到这句话&#xff1a;“concurrentHashMap&#xff0c;在 JDK 1.7 中采用 分段锁的方式&#xff1b;JDK 1.8 中直接采用了CAS&#xff08;无锁算法&#xff09; synchronized。” 哦~~这个CAS好像之前接触过&#xff0c…

VSCode拉取远程项目

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

2024年测评7款最佳AI论文修改润色平台

在2024年&#xff0c;AI论文修改润色平台的测评和推荐成为学术界和研究者们关注的热点。本文将详细评测并推荐7款最佳AI论文修改润色平台&#xff0c;包括千笔-AIPassPaper&#xff0c;并结合我搜索到的资料进行分析。 一、千笔-AIPassPaper 千笔-AIPassPaper是一款集论文大纲…

芯旺微,车规级32位MCU KF32A芯片简介

文章目录 1. 产品功能特点2. 行业应用3. 开发环境(IDE)4. 开发资源5. KungFu 内核参考1. 产品功能特点 2. 行业应用 汽车照明汽车车窗控制汽车空调面板汽车控制器3. 开发环境(IDE)

营养方案调整执行流程 第十篇

追踪调整方案 疾病诊断≠营养诊断 出健康管理建议应考虑地域习惯等特点&#xff0c;因人而异

HarmonyOS应用开发( Beta5.0)一杯冰美式的时间“拿捏Grid组件”

常见情形 在很多手机商城的页面中会出现类似网格状一样的情况&#xff0c;例如&#xff1a; 京东 ​这里呢是采用Grid组件中的控制滚动,里面的rowsTemplate属性为一行且不对列的行数属性进行操作&#xff0c;这样的话就可以控制水平滑动了。 2.淘宝 ​ 这里就是极其简单的2*…

Google Play结算防掉单方案

我们公司的产品主要是出海产品,使用的是Google Play支付,但是在上线以后,经常有客诉,说支付以后,权益没有到账,于是对整个Google支付体系做了研究了一下。 我们的整个支付流程图大概如下: 其中后端参考的文档地址为: https://developers.google.com/android-publishe…

Springboot中基于X509完成SSL检验的原理与实践

前言 各位对HTTPS不陌生吧&#xff1f;几乎涉及安全的领域&#xff0c;均要求通过HTTPS协议进行数据传输。而在传输过程中&#xff0c;又涉及到了SSL证书的使用。既然提到了SSL证书&#xff0c;那咱们先了解了解什么是SSL证书&#xff1a; SSL证书通过在客户端浏览器和Web服务…

生信代码入门:从零开始掌握生物信息学编程技能

少走弯路,高效分析;了解生信云,访问 【生信圆桌x生信专用云服务器】 : www.tebteb.cc 介绍 生物信息学是一个高度跨学科的领域&#xff0c;结合了生物学、计算机科学和统计学。随着高通量测序技术的发展&#xff0c;海量的生物数据需要通过编程来进行处理和分析。因此&#x…

如何使用云服务器AutoDL进行炼丹

如何使用云服务器进行炼丹 文章目录 如何使用云服务器进行炼丹1、选择平台2、学生认证3、租用新实例1&#xff09;创建实例2&#xff09;选择镜像3&#xff09;注意事项 4、文件传输1&#xff09;下载XFTP&#xff0c;用来传输文件2&#xff09;XFTP连接 5、pycharm远程连接1&a…

【Github项目推荐】DataLoom

项目推荐 - DataLoom 项目背景 在数据驱动的时代&#xff0c;越来越多的企业和个人用户需要从复杂的数据中提取出高价值的信息。然而&#xff0c;传统的数据处理和分析流程复杂且耗时&#xff0c;需要技术人员的深度参与。那么&#xff0c;有没有一种工具能够简化这一过程&am…

Linux:归档及压缩

tar命令 • tar 集成备份工具 – -c&#xff1a;创建归档 – -x&#xff1a;释放归档 – -f&#xff1a;指定归档文件名称,必须在所有选项的最后 – -z、-j、-J&#xff1a;调用 .gz、.bz2、.xz 格式工具进行处理 – -t&#xff1a;显示归档中的文件清单 – -C&#xff1a;指定…

十年多空局

我收到的有关大洋彼岸的推送&#xff1a; 微信和头条上的长者们&#xff0c; 讲着他们小时候没有的GPU和生物技术&#xff0c; 看多 B站上的年轻人&#xff0c; 谈着他们一知半解的制度设计和宏观经济&#xff0c; 看空 十年尺度的看空与看多

githup怎么上传自己的项目

&#xff08;1&#xff09;首先进入自己要上传项目的文件夹中&#xff0c;然后点击Open git Bash here. &#xff08;2&#xff09;然后进入下面界面&#xff0c;输入git init &#xff08;3&#xff09;然后就会生成一个.git的文件夹 &#xff08;4&#xff09;输入git add *…

如何处理软件卸载不干净的情况?

如何处理软件卸载不干净的情况&#xff1f; 一、清理注册表 下载CCleaner&#xff0c;下载之后&#xff0c;点击settings&#xff0c;将语言改为中文。 点击注册表&#xff0c;点击扫描问题-修复选定的问题&#xff0c;最好将之前的注册表完整备份。 反复这一过程&#xff0c…