【自学嵌入式:stm32单片机】DMA数据转运
DMA数据转运
基本原理

上图是将SRAM里的DataA数组转移到SRAM里的DataB数组中,在这个任务里,那在这个任务里,外设地址显然立该填DataA数组的首地址,存储器地址,给DataB数组的首地址,然后数据宽度,两个数组的类型都是uint8_t,所以数据宽度都是按8位的字节传输,之后地址是否自增,在中间可以看到,我们想要的效果是DataA[0]转到DataB[0],两个数组的位置一一对应,所以转运完DataA[0]和DataB[0]之后,两个站点的地址都立该自增,都移动到下一个数据的位置,继续转运DataA[1]和DataB[1]。这样来进行,如果左侧不自增,右侧自增,相当于把左侧DataA[0]复制给了B数组中,如下:

转运完成后,DataB的所有数据都会等于DataA[0],如果左边自增,右边不自增,那效果如下:

转运完成后,DataB[0]等于DataA的最后一个数,DataB其他的数不变

如上图,如果左右都不自增,那就一直是DataA[0]转到DataB[0],其他的数据不变,这就是地址是否自增的效果

如上图,方向参数显然是外设站点转移到存储器站点,如果想把DataB的数据转移到DataA,可以把方向换过来,这样就是反向转运了,然后是传输计数器和是否要自动重装,在这里,显然要转运7次,所以传输计数器给7,自动重装暂时不需要,之后触发选择部分,这里,我们要使用软件触发,因为这是存储器到存储器的数据转运,是不需要等待硬件时机的,尽快转运完成就行了,那最后,调用DMA_Cmd,给DMA使能,这样数据就会从DataA转运到DataB了,转运7次之后,传输计数器自减到0,DMA停止,转运完成,这里的数据转运是一种复制转运,转运完成后DataA的数据并不会消失,这个过程相当于是把DataA的数据复制到了DataB的位置
接线图

代码实现
标准库实现
已开源到:https://gitee.com/qin-ruiqian/jiangkeda-stm32
MyDMA.h
#ifndef __MYDMA_H
#define __MYDMA_H
void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size); //初始化DMA
void MyDMA_Transfer(void); //转运数据
#endif
MyDMA.c
#include "stm32f10x.h" // Device header
uint16_t MyDMA_Size; //传输计数器的值
//初始化DMA
//AddrA,源数组首地址
//AddrB,目的数组首地址
//Size,数组长度,也算传输计数器的值
void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size)
{
MyDMA_Size = Size;
//DMA是AHB的设备
//RCC_AHBPeriph_DMA1对应stm32f103系列
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //开启DMA的时钟
DMA_InitTypeDef DMA_InitStructure; //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_DIR_PeripheralDST外设站点作为目的地
//DMA_DIR_PeripheralSRC外设站点作为源端
//把DataA放到外设站点,DataB放到存储器站点
//传输方向就是外设站点到存储器站点
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //传输方向,指定外设站点是源端还是目的地
DMA_InitStructure.DMA_BufferSize = Size; //缓存区大小,其实就是传输计数器
//自动重装不能应用在存储器到存储器的情况下
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //传输模式,是否使用自动重装
DMA_InitStructure.DMA_M2M = DMA_M2M_Enable; //选择是否是存储器到存储器,其实就是选择硬件触发还是软件触发
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //优先级,只有一个DMA通道,选个中等由新阿基
//DMA1_Channel1 - DMA1通道1
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
//如果初始化立刻工作就写ENABLE参数
//这里不让DMA初始化之后立刻进行转运
DMA_Cmd(DMA1_Channel1, DISABLE);
}
//转运数据
//调用一次这个函数,就再次启动一次DMA转运
void MyDMA_Transfer(void)
{
//重新给传输计数器赋值,赋值前必须先给DMA失能再赋值,最后使能
DMA_Cmd(DMA1_Channel1, DISABLE);
DMA_SetCurrDataCounter(DMA1_Channel1, MyDMA_Size);
DMA_Cmd(DMA1_Channel1, ENABLE);
while(DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET); //没完成就一直循环等待
DMA_ClearFlag(DMA1_FLAG_TC1); //清除标志位
}
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "MYOLED.h"
#include "MyDMA.h"
uint8_t DataA[] = {0x01, 0x02, 0x03, 0x04}; //源端数组
uint8_t DataB[] = {0, 0, 0, 0}; //目的数组
int main(void)
{
MYOLED_Init();
MyDMA_Init((uint32_t)DataA, (uint32_t)DataB, 4);
MYOLED_ShowString(0,0,"DataA");
MYOLED_ShowString(0,2,"DataB");
MYOLED_ShowHexNum(7,0,(uint32_t)DataA,8);
MYOLED_ShowHexNum(7,2,(uint32_t)DataB,8);
MYOLED_ShowHexNum(0,1,DataA[0],2);
MYOLED_ShowHexNum(3,1,DataA[1],2);
MYOLED_ShowHexNum(6,1,DataA[2],2);
MYOLED_ShowHexNum(9,1,DataA[3],2);
MYOLED_ShowHexNum(0,3,DataB[0],2);
MYOLED_ShowHexNum(3,3,DataB[1],2);
MYOLED_ShowHexNum(6,3,DataB[2],2);
MYOLED_ShowHexNum(9,3,DataB[3],2);
while(1)
{
DataA[0]++;DataA[1]++;DataA[2]++;DataA[3]++;
MYOLED_ShowHexNum(0,1,DataA[0],2);
MYOLED_ShowHexNum(3,1,DataA[1],2);
MYOLED_ShowHexNum(6,1,DataA[2],2);
MYOLED_ShowHexNum(9,1,DataA[3],2);
MYOLED_ShowHexNum(0,3,DataB[0],2);
MYOLED_ShowHexNum(3,3,DataB[1],2);
MYOLED_ShowHexNum(6,3,DataB[2],2);
MYOLED_ShowHexNum(9,3,DataB[3],2);
Delay_ms(1000); //延迟1秒再转运
MyDMA_Transfer();
//转运后的DataA和DataB
MYOLED_ShowHexNum(0,1,DataA[0],2);
MYOLED_ShowHexNum(3,1,DataA[1],2);
MYOLED_ShowHexNum(6,1,DataA[2],2);
MYOLED_ShowHexNum(9,1,DataA[3],2);
MYOLED_ShowHexNum(0,3,DataB[0],2);
MYOLED_ShowHexNum(3,3,DataB[1],2);
MYOLED_ShowHexNum(6,3,DataB[2],2);
MYOLED_ShowHexNum(9,3,DataB[3],2);
Delay_ms(1000);
}
}
HAL库实现
已开源到:https://gitee.com/qin-ruiqian/jiangkeda-stm32-hal
关于DMA的设置:

MyDMA.h
/*
* MyDMA.h
*
* Created on: Aug 18, 2025
* Author: Administrator
*/
#ifndef SYSTEM_MYDMA_H_
#define SYSTEM_MYDMA_H_
typedef struct MyDMA
{
DMA_HandleTypeDef dhtd; //启动哪个DMA及其通道
uint32_t AddrA;
uint32_t AddrB;
uint16_t Size;
}MyDMA;
void MyDMA_Init(MyDMA* mydma, DMA_HandleTypeDef dhtd, uint32_t AddrA, uint32_t AddrB, uint16_t Size);
void MyDMA_Transfer(MyDMA* mydma);
#endif /* SYSTEM_MYDMA_H_ */
MyDMA.c
/*
* MyDMA.c
*
* Created on: Aug 18, 2025
* Author: Administrator
*/
#include "stm32f1xx_hal.h"
#include "MyDMA.h"
//初始化MyDMA
void MyDMA_Init(MyDMA* mydma, DMA_HandleTypeDef dhtd, uint32_t AddrA, uint32_t AddrB, uint16_t Size)
{
mydma->dhtd = dhtd;
mydma->AddrA = AddrA;
mydma->AddrB = AddrB;
mydma->Size = Size;
//设置非自动重装
mydma->dhtd.Init.Mode = DMA_NORMAL;
// 禁用 DMA(初始化后不立即启动)
HAL_DMA_Abort (&(mydma->dhtd));
}
//转运数据
//调用一次这个函数,就再次启动一次DMA转运
void MyDMA_Transfer(MyDMA* mydma)
{
HAL_DMA_Abort(&(mydma->dhtd)); //先关闭
HAL_DMA_Start(&(mydma->dhtd),mydma->AddrA,mydma->AddrB,mydma->Size);
}
main.c
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "MYOLED.h"
#include "MyDMA.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
DMA_HandleTypeDef hdma_memtomem_dma1_channel1;
/* USER CODE BEGIN PV */
uint8_t DataA[] = {0x01, 0x02, 0x03, 0x04}; //源端数组
uint8_t DataB[] = {0, 0, 0, 0}; //目的数组
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
/* USER CODE BEGIN 2 */
MYOLED_Init();
MyDMA mydma;
MyDMA_Init(&mydma, hdma_memtomem_dma1_channel1, (uint32_t)DataA, (uint32_t)DataB, 4);
MYOLED_ShowString(0,0,"DataA");
MYOLED_ShowString(0,2,"DataB");
MYOLED_ShowHexNum(7,0,(uint32_t)DataA,8);
MYOLED_ShowHexNum(7,2,(uint32_t)DataB,8);
MYOLED_ShowHexNum(0,1,DataA[0],2);
MYOLED_ShowHexNum(3,1,DataA[1],2);
MYOLED_ShowHexNum(6,1,DataA[2],2);
MYOLED_ShowHexNum(9,1,DataA[3],2);
MYOLED_ShowHexNum(0,3,DataB[0],2);
MYOLED_ShowHexNum(3,3,DataB[1],2);
MYOLED_ShowHexNum(6,3,DataB[2],2);
MYOLED_ShowHexNum(9,3,DataB[3],2);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
DataA[0]++;DataA[1]++;DataA[2]++;DataA[3]++;
MYOLED_ShowHexNum(0,1,DataA[0],2);
MYOLED_ShowHexNum(3,1,DataA[1],2);
MYOLED_ShowHexNum(6,1,DataA[2],2);
MYOLED_ShowHexNum(9,1,DataA[3],2);
MYOLED_ShowHexNum(0,3,DataB[0],2);
MYOLED_ShowHexNum(3,3,DataB[1],2);
MYOLED_ShowHexNum(6,3,DataB[2],2);
MYOLED_ShowHexNum(9,3,DataB[3],2);
HAL_Delay(1000);
MyDMA_Transfer(&mydma);
//转运后的DataA和DataB
MYOLED_ShowHexNum(0,1,DataA[0],2);
MYOLED_ShowHexNum(3,1,DataA[1],2);
MYOLED_ShowHexNum(6,1,DataA[2],2);
MYOLED_ShowHexNum(9,1,DataA[3],2);
MYOLED_ShowHexNum(0,3,DataB[0],2);
MYOLED_ShowHexNum(3,3,DataB[1],2);
MYOLED_ShowHexNum(6,3,DataB[2],2);
MYOLED_ShowHexNum(9,3,DataB[3],2);
HAL_Delay(1000);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
/**
* Enable DMA controller clock
* Configure DMA for memory to memory transfers
* hdma_memtomem_dma1_channel1
*/
static void MX_DMA_Init(void)
{
/* DMA controller clock enable */
__HAL_RCC_DMA1_CLK_ENABLE();
/* Configure DMA request hdma_memtomem_dma1_channel1 on DMA1_Channel1 */
hdma_memtomem_dma1_channel1.Instance = DMA1_Channel1;
hdma_memtomem_dma1_channel1.Init.Direction = DMA_MEMORY_TO_MEMORY;
hdma_memtomem_dma1_channel1.Init.PeriphInc = DMA_PINC_ENABLE;
hdma_memtomem_dma1_channel1.Init.MemInc = DMA_MINC_ENABLE;
hdma_memtomem_dma1_channel1.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_memtomem_dma1_channel1.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_memtomem_dma1_channel1.Init.Mode = DMA_NORMAL;
hdma_memtomem_dma1_channel1.Init.Priority = DMA_PRIORITY_MEDIUM;
if (HAL_DMA_Init(&hdma_memtomem_dma1_channel1) != HAL_OK)
{
Error_Handler( );
}
}
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8|GPIO_PIN_9, GPIO_PIN_SET);
/*Configure GPIO pins : PB8 PB9 */
GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* USER CODE BEGIN MX_GPIO_Init_2 */
/* USER CODE END MX_GPIO_Init_2 */
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
实现效果

浙公网安备 33010602011771号