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

 

这篇文章详细介绍单片机的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的实战工程,希望我的分享能给你带来不一样的收获!

 

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小小_扫地僧

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值