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

DMA数据转运

基本原理

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

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

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

接线图

image

代码实现

标准库实现

已开源到: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的设置:
image

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 */

实现效果

image

posted @ 2025-08-18 19:04  秦瑞迁  阅读(42)  评论(0)    收藏  举报