您的位置:首页 > 健康 > 养生 > 单片机DMA原理及应用详解(下篇)(附工程源码)

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

2024/10/6 12:25:45 来源:https://blog.csdn.net/m0_73931287/article/details/142034642  浏览:    关键词:单片机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_SizeRCC_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通道1DMA_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_SizeDMA_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_SizeRCC_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通道1DMA_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_SizeDMA_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到255for (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_SIZEHAL_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_Stream0hdma_memtomem_dma2_stream0.Instance = DMA2_Stream0; // 选择DMA2流0hdma_memtomem_dma2_stream0.Init.Channel = DMA_CHANNEL_0; // 选择通道0hdma_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的实战工程,希望我的分享能给你带来不一样的收获!

 

 

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com