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