一.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__ */
串口调试助手演示效果
串口调试助手发送的数据,再通过单片机串口发送回来