这篇文章详细介绍单片机的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
)。
保存大小:
MyDMA_Size
被赋值为Size
。这允许在DMA传输函数中使用传输的大小。使能DMA1时钟:
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE)
使能DMA1的时钟。DMA需要时钟才能工作,因此必须先使能其时钟。配置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优先级。初始化DMA通道:
DMA_Init(DMA1_Channel1, &DMA_InitStructure)
使用配置好的结构体初始化DMA1的通道1。禁用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传输并等待传输完成。
禁用DMA通道:
DMA_Cmd(DMA1_Channel1, DISABLE)
确保在更新传输参数之前禁用DMA通道1。设置数据计数器:
DMA_SetCurrDataCounter(DMA1_Channel1, MyDMA_Size)
设置DMA传输的数据量。这个值应该与初始化时设置的一致。启用DMA通道:
DMA_Cmd(DMA1_Channel1, ENABLE)
启用DMA通道1,开始数据传输。等待传输完成:
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的实战工程,希望我的分享能给你带来不一样的收获!