DMA(Direct Memory Access) 是直接存储器访问的缩写,它是一种允许外设直接与内存进行数据传输而不需要CPU参与的技术。就是数据搬运工!
目录
[1. DMA内存到内存数据搬运](#1. DMA内存到内存数据搬运)
[2. DMA内存到外设数据搬运](#2. DMA内存到外设数据搬运)
一.DMA的意义
代替CPU搬运数据,为CPU减负
1.因为数据搬运的工作比较耗时间
-
数据搬运工要求时效高,一有数据来就要被搬走
-
因为数据搬运没啥技术含量(CPU空出来可以做其他工作)
二.搬运数据类型
存储器、外设
这里的外设是指spi,usart, IIC, adc等基于APB1、APB2或AHB时钟的外设,而这里的存储器包括自身的闪存(flash)或者内存(SRAM)以及外设的存储设备都可以作为访问地源或者目的。
三种搬运方式
- 存储器->存储器
2.存储器->外设 eg:将某数据buf写入串口TDR寄存器
- 外设->存储器 eg:将串口RDR寄存器写入某数据buf

比如外设->存储器,DMA方式可以及时的将串口接收到的数据发送给内存缓冲区,不然下一次数据来的时候没及时搬运将会覆盖掉之前的数据,造成数据丢失。
使用DMA搬运,先走红的搬运到DMA之后DMA再给SRAM

三.数据传输方式+DMA优先级管理
首先,外设向DMA发送请求后,DMA接收到请求要进行处理,然后再进行我们刚刚讲的数据传输。
因为接收到的数据多,所以数据必然有优先级,就涉及到管理
优先级管理分为软件+硬件
软件:每个通道的优先级可以在DMA------CCRx寄存器中设置,有4个等级:最高级>高级>中级>低级
硬件:如果有两个请求,软件优先级相同的情况下,则较低编号的通道比较高编号的通道有较高的优先权
eg:如果软件优先级相同,通道2优先于通道4

四.DMA传输方式
1. DMA_Mode_Normal(正常模式)
一次DMA数据传输完后,停止DMA传送,也就是传输一次
2. DMA_Mode_Circular(循环传输模式)
当传输结束后,硬件自动会将传输数据量寄存器进行重装,进行下一轮的数据传输。也就是多次传输。
五.指针递增
源和数据都可以递增,如下图

六.常用函数

-
打开DMA时钟
-
DMA进行链接
5/6. DMA的发送与接收
- DMA标志位状态--------判断是否传输完成
七.传输流程+三种方式代码

1. DMA内存到内存数据搬运
dma.h
objectivec
#ifndef __DMA_H__
#define __DMA_H__
#include "sys.h"
void dma_init(void);
void dma_transmit(void);
#endif
dma.c
objectivec
#include "dma.h"
#include "stdio.h"
#define BUF_SIZE 16
uint32_t src_buf[BUF_SIZE] =
{
0x00000000,0x11111111,0x22222222,0x33333333,
0x44444444,0x55555555,0x66666666,0x77777777,
0x88888888,0x99999999,0xAAAAAAAA,0xBBBBBBBB,
0xCCCCCCCC,0xDDDDDDDD,0xEEEEEEEE,0xFFFFFFFF
}; //发送数据数组
uint32_t dst_buf[BUF_SIZE] = {0}; //接收数据数组
DMA_HandleTypeDef dma_handle = {0}; //这是一个dma结构体
void dma_init(void)
{
__HAL_RCC_DMA1_CLK_ENABLE();
dma_handle.Instance = DMA1_Channel1;
dma_handle.Init.Direction = DMA_MEMORY_TO_MEMORY;
//内存相关配置
dma_handle.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; //数据对齐方式
dma_handle.Init.MemInc = DMA_MINC_ENABLE; //数据增长方式
//外设相关配置
dma_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; //外设对齐方式
dma_handle.Init.PeriphInc = DMA_PINC_ENABLE; //外设增长方式
dma_handle.Init.Priority = DMA_PRIORITY_MEDIUM;
dma_handle.Init.Mode = DMA_NORMAL;
HAL_DMA_Init(&dma_handle);
}
void dma_transmit(void)
{
HAL_DMA_Start(&dma_handle,(uint32_t)src_buf,(uint32_t)dst_buf,sizeof(uint32_t)* BUF_SIZE); //HAL_StatusTypeDef HAL_DMA_Start(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength)
//这里传递的是数组
while(__HAL_DMA_GET_FLAG(&dma_handle,DMA_FLAG_GL1) == RESET); //判断是否传输完成 __HAL_DMA_GET_FLAG(&dma_handle,DMA的第几个通道)
int i = 0;
for(i = 0;i < BUF_SIZE;i++)
printf("buf[%d] = %x\r\n",i,dst_buf[i]);
}
main.c
objectivec
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "uart1.h"
#include "dma.h"
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
led_init(); /* 初始化LED灯 */
uart1_init(115200);
dma_init();
dma_transmit();
while(1)
{
}
}
2. DMA内存到外设数据搬运
代码实现目标:使用DMA将一个大数组的数据通过串口1发送
通道一定要去手册看一下外设具体是在哪个通道,内存-内存的就无所谓,哪个通道都可以

我们要的是串口1的TX,在通道4

uart1.h
objectivec
#ifndef __USART_H__
#define __USART_H__
#include "sys.h"
#define UART1_RX_BUF_SIZE 128
#define UART1_TX_BUF_SIZE 64
#define UART_EOK 0
#define UART_ERROR 1
#define UART_ETIMEOUT 2
#define UART_EINVAL 3
void uart1_init(uint32_t baudrate);
void uart1_receiv_test(void);
void HAL_UART_MspInit(UART_HandleTypeDef *huart);
void uart1_rx_clear(void);
void USART1_IRQHandler(void);
#endif
uart1.c
objectivec
#include "uart1.h"
#include "stdio.h"
#include "string.h"
uint8_t uart1_rx_buf[UART1_RX_BUF_SIZE];
uint16_t uart1_rx_len = 0;
UART_HandleTypeDef uart1_handle = {0};
void uart1_init(uint32_t baudrate)
{
uart1_handle.Instance = USART1;
uart1_handle.Init.BaudRate = baudrate;
uart1_handle.Init.WordLength = UART_WORDLENGTH_8B;
uart1_handle.Init.StopBits = UART_STOPBITS_1;
uart1_handle.Init.Parity = UART_PARITY_NONE;
uart1_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
uart1_handle.Init.Mode = UART_MODE_TX_RX;
HAL_UART_Init(&uart1_handle);
}
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1)
{
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitTypeDef gpio_initstruct;
//调用GPIO初始化函数
gpio_initstruct.Pin = GPIO_PIN_9;
gpio_initstruct.Mode = GPIO_MODE_AF_PP;
gpio_initstruct.Pull = GPIO_PULLUP; // 上拉
gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH; // 高速
HAL_GPIO_Init(GPIOA, &gpio_initstruct);
gpio_initstruct.Pin = GPIO_PIN_10;
gpio_initstruct.Mode = GPIO_MODE_AF_INPUT;
HAL_GPIO_Init(GPIOA, &gpio_initstruct);
//配置NVIC
HAL_NVIC_EnableIRQ(USART1_IRQn); /*串口公用函数*/
HAL_NVIC_SetPriority(USART1_IRQn, 2, 2);
__HAL_UART_ENABLE_IT(huart, UART_IT_RXNE); //RXNE就是表示数据寄存器不为0
__HAL_UART_ENABLE_IT(huart, UART_IT_IDLE); //空闲中断标志
}
}
//接收寄存器内容清空
void uart1_rx_clear(void)
{
memset(uart1_rx_buf, 0, sizeof(uart1_rx_buf));
uart1_rx_len = 0;
}
/*串口公用函数*/
void USART1_IRQHandler(void)
{
uint8_t receive_data = 0;
if(__HAL_UART_GET_FLAG(&uart1_handle, UART_FLAG_RXNE) != RESET) /*判断是否接收到数据寄存器*/
{
if(uart1_rx_len >= sizeof(uart1_rx_buf))
uart1_rx_len = 0;
HAL_UART_Receive(&uart1_handle, &receive_data, 1, 1000);
uart1_rx_buf[uart1_rx_len++] = receive_data;
//uart1_cnt++;
//HAL_UART_Transmit(&uart1_handle, &receive_data, 1, 1000);
}
if(__HAL_UART_GET_FLAG(&uart1_handle, UART_FLAG_IDLE) != RESET) /*判断是否接受到空闲中断*/
{
printf("recv: %s\r\n", uart1_rx_buf);
uart1_rx_clear();
//RXNE标志位不需要手动置0,UART_IT_IDLE 空闲中断标志位需要手动置0
__HAL_UART_CLEAR_IDLEFLAG(&uart1_handle);
}
}
int fputc(int ch, FILE *f)
{
while((USART1->SR & 0X40) == 0);
USART1->DR = (uint8_t)ch;
return ch;
}
dma.h
objectivec
#ifndef __DMA_H__
#define __DMA_H__
#include "sys.h"
void dma_init(void);
#endif
dma.c
objectivec
#include "dma.h"
#include "stdio.h"
#define BUF_SIZE 16
extern UART_HandleTypeDef uart1_handle;
DMA_HandleTypeDef dma_handle = {0}; //这是一个dma结构体
void dma_init(void)
{
__HAL_RCC_DMA1_CLK_ENABLE();
dma_handle.Instance = DMA1_Channel4; //根据手册,USART1 TX在通道4
dma_handle.Init.Direction = DMA_MEMORY_TO_PERIPH;
//内存相关配置
dma_handle.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; //数据对齐方式
dma_handle.Init.MemInc = DMA_MINC_ENABLE; //数据增长方式
//外设相关配置
dma_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; //外设对齐方式
dma_handle.Init.PeriphInc = DMA_PINC_DISABLE; //外设增长方式
dma_handle.Init.Priority = DMA_PRIORITY_MEDIUM;
dma_handle.Init.Mode = DMA_NORMAL; //DMA单次发送
HAL_DMA_Init(&dma_handle);
__HAL_LINKDMA(&uart1_handle,hdmatx,dma_handle); ////做一个链接,链接到串口1 ,hdmatx是一个句柄(连接tx【看后两位】),句柄是一个标识符或引用,通过它可以访问和管理某个资源(如外设、文件、内存等),而不需要直接操作资源的物理地址
}
main.c
objectivec
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "uart1.h"
#include "dma.h"
#include <stdint.h>
extern UART_HandleTypeDef uart1_handle;
uint8_t send_buf[1000] ={0};
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
led_init(); /* 初始化LED灯 */
uart1_init(115200);
dma_init();
int i=0;
for(i=0;i<1000;i++)
{
send_buf[i] = 'A';
}
HAL_UART_Transmit_DMA(&uart1_handle,send_buf,1000); //使用串口DMA的方式发出去
while(1)
{
led1_on();
delay_ms(500);
}
}
3.DMA外设到内存


因为接收,所以使用USART1_RX,要选通道5
uart1.h
objectivec
#ifndef __USART_H__
#define __USART_H__
#include "sys.h"
#define UART1_RX_BUF_SIZE 128
#define UART1_TX_BUF_SIZE 64
#define UART_EOK 0
#define UART_ERROR 1
#define UART_ETIMEOUT 2
#define UART_EINVAL 3
void uart1_init(uint32_t baudrate);
void uart1_receiv_test(void);
void USART1_IRQHandler(void);
void uart1_rx_clear(void);
void HAL_UART_MspInit(UART_HandleTypeDef *huart);
#endif
uart1.c
objectivec
#include "uart1.h"
#include "stdio.h"
#include "string.h"
#include "dma.h"
extern DMA_HandleTypeDef dma_handle; //DMA句柄
UART_HandleTypeDef uart1_handle; //uart1句柄
uint8_t uart1_rx_buf[UART1_RX_BUF_SIZE];
uint16_t uart1_rx_len = 0;
UART_HandleTypeDef uart1_handle = {0};
void uart1_init(uint32_t baudrate)
{
uart1_handle.Instance = USART1;
uart1_handle.Init.BaudRate = baudrate;
uart1_handle.Init.WordLength = UART_WORDLENGTH_8B;
uart1_handle.Init.StopBits = UART_STOPBITS_1;
uart1_handle.Init.Parity = UART_PARITY_NONE;
uart1_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
uart1_handle.Init.Mode = UART_MODE_TX_RX;
HAL_UART_Init(&uart1_handle);
}
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1)
{
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitTypeDef gpio_initstruct;
//调用GPIO初始化函数
gpio_initstruct.Pin = GPIO_PIN_9;
gpio_initstruct.Mode = GPIO_MODE_AF_PP;
gpio_initstruct.Pull = GPIO_PULLUP; // 上拉
gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH; // 高速
HAL_GPIO_Init(GPIOA, &gpio_initstruct);
gpio_initstruct.Pin = GPIO_PIN_10;
gpio_initstruct.Mode = GPIO_MODE_AF_INPUT;
HAL_GPIO_Init(GPIOA, &gpio_initstruct);
//配置NVIC
HAL_NVIC_EnableIRQ(USART1_IRQn); /*串口公用函数*/
HAL_NVIC_SetPriority(USART1_IRQn, 2, 2);
__HAL_UART_ENABLE_IT(huart, UART_IT_RXNE); //RXNE就是表示数据寄存器不为0
__HAL_UART_ENABLE_IT(huart, UART_IT_IDLE); //空闲中断标志
}
}
//接收寄存器内容清空
void uart1_rx_clear(void)
{
memset(uart1_rx_buf, 0, sizeof(uart1_rx_buf));
uart1_rx_len = 0;
}
/*串口公用函数*/
void USART1_IRQHandler(void)
{
uint8_t receive_data = 0;
if(__HAL_UART_GET_FLAG(&uart1_handle, UART_FLAG_RXNE) != RESET) /*判断是否接收到数据寄存器*/
{
if(uart1_rx_len >= sizeof(uart1_rx_buf))
uart1_rx_len = 0;
HAL_UART_Receive(&uart1_handle, &receive_data, 1, 1000);
uart1_rx_buf[uart1_rx_len++] = receive_data;
//uart1_cnt++;
//HAL_UART_Transmit(&uart1_handle, &receive_data, 1, 1000);
}
if(__HAL_UART_GET_FLAG(&uart1_handle, UART_FLAG_IDLE) != RESET) /*判断是否接受到空闲中断*/
{
__HAL_UART_CLEAR_IDLEFLAG(&uart1_handle); //1.清除空闲中断
HAL_UART_DMAStop(&uart1_handle); //2.停止DMA传输,防止干扰
uart1_rx_len = UART1_RX_BUF_SIZE-__HAL_DMA_GET_COUNTER(&dma_handle); //3.获取接收到的数据长度,总获取接收到的数据长度,长度-已经接受数据长度 = 剩余需要接收数据长度
printf("receive: %s, recv_len: %d\r\n",uart1_rx_buf,uart1_rx_len); //4.打印接收到的内容
uart1_rx_clear(); //5.清空接收缓冲
HAL_UART_Receive_DMA(&uart1_handle,uart1_rx_buf,UART1_RX_BUF_SIZE);//5.重新开启串口DMA传输
}
}
int fputc(int ch, FILE *f)
{
while((USART1->SR & 0X40) == 0);
USART1->DR = (uint8_t)ch;
return ch;
}
dma.h
objectivec
#ifndef __DMA_H__
#define __DMA_H__
#include "sys.h"
void dma_init(void);
#endif
dma.c
objectivec
#include "dma.h"
#include "stdio.h"
#include "uart1.h"
extern UART_HandleTypeDef uart1_handle;
extern uint8_t uart1_rx_buf[UART1_RX_BUF_SIZE]; //定义串口缓冲区
DMA_HandleTypeDef dma_handle = {0}; //这是一个dma结构体
void dma_init(void)
{
__HAL_RCC_DMA1_CLK_ENABLE();
dma_handle.Instance = DMA1_Channel5; //根据手册,USART1 TX在通道4
dma_handle.Init.Direction = DMA_PERIPH_TO_MEMORY;
//内存相关配置
dma_handle.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; //数据对齐方式
dma_handle.Init.MemInc = DMA_MINC_ENABLE; //数据增长方式
//外设相关配置
dma_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; //外设对齐方式
dma_handle.Init.PeriphInc = DMA_PINC_DISABLE; //外设增长方式
dma_handle.Init.Priority = DMA_PRIORITY_MEDIUM;
dma_handle.Init.Mode = DMA_NORMAL; //DMA单次发送
HAL_DMA_Init(&dma_handle);
__HAL_LINKDMA(&uart1_handle,hdmarx,dma_handle); //做一个链接,链接到串口1 ,hdmatx是一个句柄(连接rx【看后两位】),句柄是一个标识符或引用,通过它可以访问和管理某个资源(如外设、文件、内存等),而不需要直接操作资源的物理地址。
HAL_UART_Receive_DMA(&uart1_handle,uart1_rx_buf,UART1_RX_BUF_SIZE); // 创建串口接收
}
main.c
objectivec
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "uart1.h"
#include "dma.h"
#include <stdio.h>
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
led_init(); /* 初始化LED灯 */
uart1_init(115200);
dma_init();
printf("hello world!\r\n");
while(1)
{
led1_on();
delay_ms(500);
}
}
码完刚好下班yeah,博主先开溜了,Friday Friday Friday!!!