DMA&DMA数据转运&串口空闲中断+DMA转运

一.DMA简介及原理

1.简介

DMA,直接存储寄存器

DMA可提供外设和存储器或者存储器和存储器之间的高速数据传输,无需CPU干预节省CPU资源

12个独立可配置的通道,DMA1 (七个通道),DMA2 (五个通道)

每个通道都支持软件触发和特定的硬件触发;

软件触发:当把Flash的数据全部转运到SRAM中,使用软件触发,把数据全部转运;

硬件触发:当外设到存储器的运转,需要使用硬件触发,因为外设的数据有一定时机;

2.存储器映像

ROM:只读存储器,非易失性,掉电不丢失存储器;

RAM:随机存储器,易失行,掉电丢失存储器;

3.DMA框图

下图中的总线矩阵左边是主动单元,总线矩阵右边是被动单元;

右边的被动单元存储器只能被左边的主动单元进行读写;

DMA 具有12 个独立可编程的通道,其中DMA1 有7 个通道,DMA2 有5 个通道,每个通道对应不同的外设的DMA 请求。虽然每个通道可以接收多个外设的请求,但是同一时间只能接收一个,不能同时接收多个。

4.DMA基本结构

传输计数器(自减计数器):指定需要运转几次数据;运转一次,次数减一;

自动重装器:当传输计数器的值减到0,是否将传输计数器的值恢复最初的值;如果不使用自动重装器,假如传输计数器的值由5减到0,那么DMA转运结束;使用自动重装器,计数器的值减到0后,又回到5的值;

不自动重装,就是单次模式,自动重装就是循环模式;

如果转运一个数组,就是单次模式;

触发方式:

软件触发:以最快的速度,连续不断触发DMA;软件触发和循环模式不能同时使用,因为软件触发就是要以最快的速度把传输计数器清零,自动重装模式,清零后,再次恢复传输计数器;

硬件触发:触发源可以选择ADC,定时器,USART...

使用硬件触发的运转,大多是与硬件有关的转运;

转运需要一定是时机,比如ADC转换完成,串口收到数据,定时器计时到;

5.DMA请求通道

不同外设请求DMA对应的通道不同

即特定的硬件触发

6.软件触发  数据转运+DMA

下图所示,此例程中,Data[A]为外设地址,Data[B]为存储器地址,

两个数组都是uint8_t,数据宽度相同;

两个站点地址都应进行自增;

传输计数器运转7次即可,不需要自动重装;

二.代码演示

1.DMA 数据转运(软件触发)   标准库

MyDMA_Init()   初始化DMA函数

#include "stm32f10x.h"                  // Device header

uint16_t MyDMA_Size;

void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size)   //函数体传参
{
	MyDMA_Size = Size;
	
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);    //开启时钟,DMA是AHB总线上;
	
	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_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_Init(DMA1_Channel1, &DMA_InitStructure);  //初始化结构体,由于是软件触发,DMA通道可以任意选择
	
	DMA_Cmd(DMA1_Channel1, DISABLE);   //初始化先不开启DMA  
}

void MyDMA_Transfer(void)     //调用一次这个函数,启动一次DMA
{
	DMA_Cmd(DMA1_Channel1, DISABLE);  //先关闭DMA才能给重装计数器赋值
	DMA_SetCurrDataCounter(DMA1_Channel1, MyDMA_Size);  //给重装计数区赋值
	DMA_Cmd(DMA1_Channel1, ENABLE);   //开启DMA
	
	while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET); //等待运转完成表示位
	DMA_ClearFlag(DMA1_FLAG_TC1);    //标志位完成后,在手动清除标志位
}

MyDMA_Init() 头文件声明

#ifndef __MYDMA_H
#define __MYDMA_H

void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size);
void MyDMA_Transfer(void);

#endif

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);
	}
}

2.DMA 数据转运(软件触发)   HAL库

1.配置RCC为外部晶振模式;

2.配置SYS debug为Seril Wire ;

3.配置时钟树最大频率

4.配置工程的输出设置;

串口DMA配置

第一步:设置串口模式为异步通讯模式,设置波特率,校验位,停止位等

2.开启串口中断

3.打开DMA转运   (此处将串口DMA一同配置,后面将直接生成代码,不再演示)

点击DMA Setting;
然后点击 Add,添加USART1_RX 和USART1_TX;

传输方向:
外设到内存 Peripheral To Memory
内存到外设 Memory To Peripheral
内存到内存 Memory To Memory
外设到外设 Peripheral To Peripheral

Priority: 传输速度
最高优先级 Very Hight
高优先级 Hight
中等优先级 Medium
低优先级 Low


配置模式Mode:
Normal:正常模式,只能启用一次DMA数据传输;
Circular:循环模式,传输一次以后还可以传输下一次,一直循环;

Increment Address:地址指针递增

Peripheral:外设地址寄存器
功能:设置传输数据的时候外设地址是否递增。如果设置为递增,那么下一次传输的时候地址加 Data Width个字节,

Memory :内存地址寄存器
功能:设置传输数据时候内存地址是否递增。如果设置为递增,那么下一次传输的时候地址加 Data Width个字节,

MemToMem: DMA通道的操作也可以在没有外设请求的情况下进行,这种操作就是存储器到存储器模式。如果要使用DMA的存储器到存储器模式,在该菜单下进行配置

生成代码部分

CubeMX自动生成M2M的代码部分

dma.c

生成代码可按照标准库参照,此处不再进行解释;

实现效果也可根据标准库代码进行参考,此处不再演示;

初始化配置DMA

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file    dma.c
  * @brief   This file provides code for the configuration
  *          of all the requested memory to memory DMA transfers.
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2023 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 "dma.h"

/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/*----------------------------------------------------------------------------*/
/* Configure DMA                                                              */
/*----------------------------------------------------------------------------*/

/* USER CODE BEGIN 1 */

/* USER CODE END 1 */
DMA_HandleTypeDef hdma_memtomem_dma1_channel1;

/**
  * Enable DMA controller clock
  * Configure DMA for memory to memory transfers
  *   hdma_memtomem_dma1_channel1
  */
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_LOW;
  if (HAL_DMA_Init(&hdma_memtomem_dma1_channel1) != HAL_OK)
  {
    Error_Handler();
  }

}

/* USER CODE BEGIN 2 */

/* USER CODE END 2 */

启动DMA

uint8_t DataA[] = {0x01,0x02,0x03,0x04};
uint8_t DataB[] = {0};
uint8_t MyDMA_Size;
HAL_DMA_Start (&hdma_memtomem_dma1_channel1 ,(uint32_t)DataA, (uint32_t)DataB, MyDMA_Size);

3.串口空闲中断+DMA 数据转运

空闲中断:当串口没有数据,串口会识别到一个空闲状态, 触发空闲中断,

串口配置上面已经演示过,此处直接展示代码部分

stm32f1xx_it.c

uint8_t RX_Buff[BUFF_SIZE];
uint8_t RX_LEN;
uint8_t RX_Flag;


void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
	uint32_t temp;
	if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) == SET)     //判断空闲中断是否触发开启
	{
		__HAL_UART_CLEAR_IDLEFLAG(&huart1);     		//清除中断
		HAL_UART_DMAStop(&huart1);             	 		//关闭DMA
		temp = __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);  //没有被接受数据的个数
		RX_LEN = BUFF_SIZE - temp;						//接收到的数据
		RX_Flag = 1;                                    //标志位置一
	}
  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */

  /* USER CODE END USART1_IRQn 1 */
}

main.c

while (1)
  {
	  if(RX_Flag == 1)             //接收完成标志位
	  {
		  HAL_UART_Transmit_DMA(&huart1,RX_Buff,RX_LEN);    //将数据发送到串口
		  RX_Flag = 0;
		  RX_LEN = 0;
		  HAL_UART_Receive_DMA(&huart1,RX_Buff,BUFF_SIZE);  //再次开启DMA接收
	  }
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

定义声明变量

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file    usart.h
  * @brief   This file contains all the function prototypes for
  *          the usart.c file
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2023 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 */
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __USART_H__
#define __USART_H__

#ifdef __cplusplus
extern "C" {
#endif

/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

extern UART_HandleTypeDef huart1;

/* USER CODE BEGIN Private defines */
#define BUFF_SIZE     200
extern uint8_t RX_Buff[BUFF_SIZE];
extern uint8_t RX_LEN;
extern uint8_t RX_Flag;
/* USER CODE END Private defines */

void MX_USART1_UART_Init(void);

/* USER CODE BEGIN Prototypes */

/* USER CODE END Prototypes */

#ifdef __cplusplus
}
#endif

#endif /* __USART_H__ */

串口调试助手演示效果

串口调试助手发送的数据,再通过单片机串口发送回来

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

胸有猛虎丶细嗅蔷薇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值