STM32 DMA

DMA(Direct Memory Access) 是直接存储器访问的缩写,它是一种允许外设直接与内存进行数据传输而不需要CPU参与的技术。就是数据搬运工

目录

一.DMA的意义

二.搬运数据类型

三.数据传输方式+DMA优先级管理

四.DMA传输方式

五.指针递增

六.常用函数

七.传输流程+三种方式代码

[1. DMA内存到内存数据搬运](#1. DMA内存到内存数据搬运)

[2. DMA内存到外设数据搬运](#2. DMA内存到外设数据搬运)


一.DMA的意义

代替CPU搬运数据,为CPU减负

1.因为数据搬运的工作比较耗时间

  1. 数据搬运工要求时效高,一有数据来就要被搬走

  2. 因为数据搬运没啥技术含量(CPU空出来可以做其他工作)

二.搬运数据类型

存储器、外设

这里的外设是指spi,usart, IIC, adc等基于APB1、APB2或AHB时钟的外设,而这里的存储器包括自身的闪存(flash)或者内存(SRAM)以及外设的存储设备都可以作为访问地源或者目的。

三种搬运方式

  1. 存储器->存储器

2.存储器->外设 eg:将某数据buf写入串口TDR寄存器

  1. 外设->存储器 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(循环传输模式)

当传输结束后,硬件自动会将传输数据量寄存器进行重装,进行下一轮的数据传输。也就是多次传输。

五.指针递增

源和数据都可以递增,如下图

六.常用函数

  1. 打开DMA时钟

  2. DMA进行链接

5/6. DMA的发送与接收

  1. 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!!!

相关推荐
BreezeJuvenile3 小时前
外设模块学习(17)——5V继电器模块(STM32)
stm32·单片机·嵌入式硬件·学习·5v继电器模块
GilgameshJSS3 小时前
STM32H743-ARM例程40-U_DISK_IAP
c语言·arm开发·stm32·单片机·嵌入式硬件
hazy1k5 小时前
51单片机基础-GPIO结构详解
stm32·单片机·嵌入式硬件·51单片机
集和诚JHCTECH5 小时前
专为严苛环境而生:高防护等级工业防水平板WPPC-H1520T(P)
人工智能·嵌入式硬件·平板
m0_748248025 小时前
C++与C#布尔类型深度解析:从语言设计到跨平台互操作
c++·stm32·c#
云山工作室5 小时前
基于协同过滤算法的话剧购票系统(论文+源码)
单片机·物联网·毕业设计·课程设计·毕设
辰哥单片机设计7 小时前
STM32项目分享:智能水产养殖系统
stm32·单片机·嵌入式硬件
一枝小雨7 小时前
【OTA专题】2 初级bootloader架构和基础工程移植
stm32·单片机·嵌入式·ota·bootloader·固件升级·加密升级
BreezeJuvenile14 小时前
外设模块学习(15)——MQ-2烟雾气体传感器(STM32)
stm32·单片机·学习·mq-2·烟雾气体传感器