STM32中的DMA

DMA介绍

什么是DMA?

        DMA(Direct Memory Access,直接存储器访问)提供在外设与内存存储器和存储器之间的高速数据传输使用。它允许不同速度的硬件装置来沟通,而不需要依赖于CPU在这个时间中,CPU对于内存的工作来说就无法使用。

        简单描述: 就是一个数据搬运工

DMA的意义

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

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

2. 数据搬运工作时效要求高(有数据来就要搬走);

3. 没啥技术含量(CPU 节约出来的时间可以处理更重要的事)。

搬运数据的方式

有三种方式:存储器到存储器、存储器到外设、外设到存储器

  • 存储器存储器(例如:复制某特别大的数据buf
  • 存储器外设 (例如:将某数据buf写入串口TDR寄存器
  • 外设存储器 (例如:将串口RDR寄存器写入某数据buf

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

存储器存储器

存储器外设 

外设存储器 

 DMA框图

说明:利用DMA进行外设的数据搬运,首先,外设需向DMA1进项请求,然后,经过DMA的仲裁之后,DMA访问外设的数据进行搬运。 

DMA控制器 

STM32F103 2 DMA 控制器, DMA1 7 个通道 DMA 2 5 个通道。

注意:

一个通道每次只能搬运一个外设的数据!! 如果同时有多个外设的 DMA 请求,则按照优先级进行响应。 STM32F103C8T6 只有 DMA1
  • DMA17个通道:

  • DMA2 5 个通道: 

DMA优先级管理 

  • 优先级管理采用软件+硬件

软件: 每个通道的优先级可以在DMA_CCRx寄存器中设置,有4个等级:最高级>高级>中级>低级。

硬件: 如果2个请求,它们的软件优先级相同,则较低编号的通道比较高编号的通道有较高的优先权。比如:如果软件优先级相同,通道2优先于通道4。

 DMA传输方式与指针递增模式

  • 传输方式
DMA_Mode_Normal (正常模式)

一次DMA数据传输完后,停止DMA传送 ,也就是只传输一次。

DMA_Mode_Circular (循环传输模式)

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

  • 指针递增模式

        外设和存储器指针在每次传输后可以自动向后递增或保持常量。当设置为增量模式时,下一个要传输的地址将是前一个地址加上增量值。

情况1:

情况2:(目标只有一个存储数据的位置,例如:串口只有一个数据寄存器)

 DMA数据对齐方式

  • 数据宽度大的转移到数据宽度小的时候,低位保留高位截断

DMA寄存器 

  •  DMA中断状态寄存器(DMA_ISR)

  • DMA中断标志清除寄存器(DMA_IFCR) 

  •  DMA通道x配置寄存器(DMA_CCRx)(x = 1…7)

  •  DMA通道x传输数量寄存器(DMA_CNDTRx)(x = 1…7)

 16位寄存器,最多可以传输数量65536。

  • DMA通道x外设地址寄存器(DMA_CPARx)(x = 1…7)

  • DMA通道x存储器地址寄存器(DMA_CMARx)(x = 1…7) 

DMA的库函数

在hal.dma.c文件中的一些常用的函数:

打开dma1时钟的函数: 

在hal.dma.h文件中的一些常用的宏函数: 

在hal_dma_ex.h文件中,获取 传输完成标志位

在hal.def.h文件中还存在所需的下面的函数: 

 若要读取外设的数据还需要相关串口的函数,如下:

 小实验1:DMA内存到内存数据搬运

 实验目的

使用DMA将一个大数组的数组搬运到另一个位置。

硬件清单

开发板、ST-Link、USB转TTL

配置流程

文件代码 

  • dma.c文件代码
#include "dma.h"
#include "stdio.h"

#define BUF_SIZE 16

uint32_t src_buf[BUF_SIZE] = {0,1,2,3,
                              4,5,6,7,
                              8,9,10,11,
                              12,13,14,15};

uint32_t dst_buf[BUF_SIZE] = {0};

/**
* @breif    DMA的初始化函数
* @note     打开时钟,配置相关参数
* @param    无
* @retval   无
*/
DMA_HandleTypeDef dma_handle = {0};
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;     /* 源:内存数据对齐模式:一般是8位 */
    dma_handle.Init.MemInc = DMA_MINC_ENABLE;                   /* 源:内存数据指针递增的方式 */
    
    dma_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;  /* 目标:外设数据对齐模式:一般是8位 */
    dma_handle.Init.PeriphInc = DMA_PINC_ENABLE;                /* 目标:外设数据指针递增的方式 */
    
    dma_handle.Init.Mode = DMA_NORMAL;                          /* 传输的模式:循环,不循环。内存到内存不支持循环模式 */
    dma_handle.Init.Priority = DMA_PRIORITY_MEDIUM;             /* 通道的优先级设置:有四种*/
    HAL_DMA_Init(&dma_handle);
    
}

/**
* @breif    封装一个函数进行数据的转运
* @note     利用DMA_Start函数进行数据的搬运,当搬运完成后查看标志位是否置1,然后进行打印
* @note     在DMA_Start()函数中,数据的长度要写成sizeof(uint32_t)*BUF_SIZE,不能写成BUF_SIZE
* @param    无
* @retval   无
*/
void dma_transmit(void){
    
    HAL_DMA_Start(&dma_handle,(uint32_t)src_buf,(uint32_t)dst_buf,sizeof(uint32_t)*BUF_SIZE);
    while(__HAL_DMA_GET_FLAG(&dma_handle,DMA_FLAG_TC1) == RESET);
    for(uint16_t i = 0;i < BUF_SIZE;i++){
        printf("传输的数据是:%d \r\n",dst_buf[i]);
    }        
}
  • dma.h文件代码 
#ifndef __DMA_H__
#define __DMA_H__
#include "stm32f1xx.h"

void dma_init(void);
void dma_transmit(void);

#endif
  • mian.c文件代码 
#include "sys.h"
#include "led.h"
#include "delay.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);
    printf("hello,world");
    dma_init();
    dma_transmit();
    
    while(1)
    { 
    }
}

 注意事项:

  • 关于数据传输中最后一行出现异常值(如从15突然跳变到1073872904),如下所示:

原因:缓冲区溢出或内存越界

解决方式:将上面的等号去掉,就会正常。

  • *****关于dma.c文件中的while循环中的判断条件:==RESET,而不能用!=SET*****

原因:!= SET 还可能代表其他未定义的状态,例如:硬件错误、无效参数,导致条件判断不准确。

因此,要直接使用官方推荐条件,可避免兼容性的问题。

小实验2:内存到外设数据转运

 实验目的

使用DMA将一个大数据通过串口1发送

硬件清单

开发板、ST-Link、USB转TTL

配置流程 

文件代码 

  •  dma.c文件代码
#include "dma.h"
#include "stdio.h"
extern UART_HandleTypeDef uart1_handle;

/**
* @breif    DMA的初始化函数
* @note     打开时钟,配置相关参数
* @param    无
* @retval   无
*/
DMA_HandleTypeDef dma_handle = {0};
void dma_init(void){
    
    __HAL_RCC_DMA1_CLK_ENABLE();
     
    dma_handle.Instance = DMA1_Channel4;                        /* 查看表格:看所需的DMA通道:通道4, */
    dma_handle.Init.Direction = DMA_MEMORY_TO_PERIPH;           /* 搬运数据的方式:内存到内存,外设到内存,内存到外设 */
    
    //内存相关的配置
    dma_handle.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;     /* 源:内存数据对齐模式:一般是8位 */
    dma_handle.Init.MemInc = DMA_MINC_ENABLE;                   /* 源:内存数据指针递增的方式 */
    
    dma_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;  /* 目标:外设数据对齐模式:一般是8位 */
    dma_handle.Init.PeriphInc = DMA_PINC_DISABLE;               /* 目标:串口发送寄存器数据指针是不能递增的 */
    
    dma_handle.Init.Mode = DMA_NORMAL;                          /* 传输的模式:循环,不循环。内存到内存不支持循环模式 */
    dma_handle.Init.Priority = DMA_PRIORITY_MEDIUM;             /* 通道的优先级设置:有四种*/
    HAL_DMA_Init(&dma_handle);
    
    __HAL_LINKDMA(&uart1_handle,hdmatx,dma_handle);             /* 将内存和外设的地址进行连接,注意:dma句柄前面不用加&*/   
}

/**
* @breif    封装一个函数进行数据的转运
* @note     利用DMA_Start函数进行数据的搬运,当搬运完成后查看标志位是否置1,然后进行打印
* @note     在DMA_Start()函数中,数据的长度要写成sizeof(uint32_t)*BUF_SIZE,不能写成BUF_SIZE
* @param    无
* @retval   无
*/

 dma.h文件代码

#ifndef __DMA_H__
#define __DMA_H__
#include "stm32f1xx.h"

void dma_init(void);

#endif
  • main.c文件代码 
#include "sys.h"
#include "led.h"
#include "delay.h"
#include "uart1.h"
#include "dma.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);
//    printf("hello,world");
    dma_init();
    
    int i = 0;
    for(i = 0; i < 1000;i++){
        send_buf[i] = 'A';
    }
    HAL_UART_Transmit_DMA(&uart1_handle,send_buf,1000);
    while(1)
    { 

    }
}

总结:

  • 本代码是将内存中的数据转运到串口的发送数据的寄存器中,利用的函数HAL_UART_Transmit_DMA()函数,然后通过串口打印到串口调试助手的界面上。
  • 与利用printf函数不同;
  • 在将数据由内存转运到内存中时,利用的是HAL_DMA_Start()函数,通过判断传输完成标志位的函数__HAL_DMA_GET_FLAG()函数 ==SET(表明数据传输完成)。,然后利用printf()函数将数据打印出来。
  • 在dma.c和main.c文件中用到串口初始化函数的句柄,所以,要注意利用extern声明一下外部变量。
  • 实验现象

小实验3:DMA外设到寄存器内存数据搬运 

实验目的

使用DMA接收串口的数据

硬件清单

开发板、ST-Link、USB转TTL 

函数的意义:声明一个接收缓冲区(数组);这个函数返回接收完数据后剩余的数组长度。

可以用来计算,传输数据的长度: 

例:

uart1_rx_len = UART1_RX_BUF_SIZE-__HAL_DMA_GET_COUNTER(&dma_handle);

配置流程 

文件代码 

  • dma.c文件代码
#include "dma.h"
#include "stdio.h"
extern UART_HandleTypeDef uart1_handle;

extern uint8_t uart1_rx_buf[UART1_RX_BUF_SIZE];                                    /* UART1接收缓冲区 */

/**
* @breif    DMA的初始化函数
* @note     打开时钟,配置相关参数
* @param    无
* @retval   无
*/
DMA_HandleTypeDef dma_handle = {0};
void dma_init(void){
    
    __HAL_RCC_DMA1_CLK_ENABLE();
     
    dma_handle.Instance = DMA1_Channel5;                        /* 查看表格:UART1_RX的DMA通道:通道5 */
    dma_handle.Init.Direction = DMA_PERIPH_TO_MEMORY;           /* 搬运数据的方式:内存到内存,外设到内存,内存到外设 */
    
    //内存相关的配置
    dma_handle.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;     /* 源:内存数据对齐模式:一般是8位 */
    dma_handle.Init.MemInc = DMA_MINC_ENABLE;                   /* 源:内存数据指针递增的方式 */
    
    dma_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;  /* 目标:外设数据对齐模式:一般是8位 */
    dma_handle.Init.PeriphInc = DMA_PINC_DISABLE;               /* 目标:串口发送寄存器数据指针是不能递增的 */
    
    dma_handle.Init.Mode = DMA_NORMAL;                          /* 传输的模式:循环,不循环。内存到内存不支持循环模式 */
    dma_handle.Init.Priority = DMA_PRIORITY_MEDIUM;             /* 通道的优先级设置:有四种*/
    HAL_DMA_Init(&dma_handle);
    
    __HAL_LINKDMA(&uart1_handle,hdmarx,dma_handle);             /* 将内存和外设的地址进行连接,注意:dma句柄前面不用加&*/  
    
   HAL_UART_Receive_DMA(&uart1_handle,uart1_rx_buf,UART1_RX_BUF_SIZE);  /* 打开串口的DMA数据转运,*/
}
  •  dma.h文件代码
#ifndef __DMA_H__
#define __DMA_H__
#include "stm32f1xx.h"

#define UART1_RX_BUF_SIZE            128
void dma_init(void);

#endif
  • uart1.c文件代码 

  • main.c文件代码 
#include "sys.h"
#include "led.h"
#include "delay.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);
    printf("hello,world\r\n");
    dma_init();
    
    while(1)
    { 

    }
}

注意事项:

  • 注意在串口中断函数中书写的DMA转运数据的流程。 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值