【11】STM32·HAL库·DMA

目录

一、DMA介绍

二、DMA结构框图介绍

三、DMA相关寄存器介绍

3.1、DMA数据流x配置寄存器(DMA_SxCR)

3.2、DMA中断状态寄存器

3.3、DMA中断标志清除寄存器

3.4、DMA通道x传输数量寄存器(DMA_CNDTR)

3.5、DMA数据流x外设地址寄存器(DMA_SxPAR)

3.6、DMA数据流x存储器地址寄存器

四、DMA相关HAL库驱动介绍

五、DMA配置步骤

六、编程实战


一、DMA介绍

DMA,全称 Direct Memory Access,即直接存储器访问。DMA 传输将数据从一个地址空间复制到另一个地址空间

DMA 传输无需 CPU 直接控制传输,也没有中断处理方式那样保留现场和恢复现场过程,通过硬件为 RAM 和 IO 设备开辟一条直接传输数据的通道,使得 CPU 的效率大大提高。

作用:为CPU减负

二、DMA结构框图介绍

DMA 存储器总线:DMA 通过该总线来执行存储器数据的传入和传出,包括 SARM1/2/3 及 F(S)MC 外部存储器。

DMA 外设总线:DMA 通过该总线访问 AHB 外设或执行存储器间的数据传输,包括 AHB 和 APB 外设以及 SARM1/2/3 及 F(S)MC 外部存储器。

DMA 可以理解为“数据搬运工”

DMA1 和 DMA2 控制器都具有 8 个数据流,而每个数据流对应有 8 个外设请求。

为了使传输数据更加快,对比 F1 的 DMA,还增加了双缓冲模式、突发 (Burst) 传输。并且通道请求更加灵活。

① AHB 从器件编程接口:对 DMA 相关控制寄存器设置,配置 DMA。

② ④ 存储器端口和外设端口:一个用于存储器访问,一个用于外设访问。

③ FIFO( 4 级 32 位存储器缓冲区):源数据传输到目标地址前的临时存储区。

FIFO 模式:软件设置阈值,达到即传输。

直接模式:立即启动对存储器的传输。

⑤ DMA 优先级(数据流优先级)

软件阶段:寄存器配置(非常高/高/中/低)。

硬件阶段:编号低获得更高优先级。

⑥ DMA 请求:每个数据流都与一个 DMA 请求相关联,DMA 请求可从 8 个通道中选出。

DMA_SxCR 控制数据流使用通道

DMA1 请求映射

DMA2 请求映射

每个外设请求都会占用一个通道,相同外设请求可以占用不同数据流通道。

注意:请求使用某个数据流的通道,该数据流其他通道不被选择,不可用

三、DMA相关寄存器介绍

以串口 1 DMA 发送为例

3.1、DMA数据流x配置寄存器(DMA_SxCR)

1、通道选择:F4/F7 会有该位,H7 就由 DMAMUX 寄存器决定通道。

2、外设/存储器数据宽度:字节、半字、字。

3、外设/存储器增量模式:使能地址自动递增。

4、循环模式:DMA 传输模式,传输一次/循环。

5、优先级:软件设置 非常高/高/中/低。
6、数据传输方向:三种传输方式。

7、中断使能:五种中断标志。

8、外设/存储器突发传输:单次、4/8/16 节拍增量突发模式。

9、双缓冲区设置:两个存储器寄存器循环调用。

10、外设流控制器配置:外设去控制传输数据项数目。

11、通道开启:大部分位受到写保护,只有 “EN” 位为 0 才可以写入。

3.2、DMA中断状态寄存器

DMA低中断状态寄存器(DMA_LISR)管理数据流 0~3

DMA高中断状态寄存器(DMA_HISR)管理数据流 4~7

DMA有五种标志:TCIF 传输完成;HTIF 半传输;TEIF 传输错误;DMEIF 直接模式错误;FEIF FIFO错误

注意:当设置了允许中断时,将会产生中断

3.3、DMA中断标志清除寄存器

DMA低中断标志清除寄存器(DMA_LIFCR)管理数据流 0~3

DMA高中断标志清除寄存器(DMA_HIFCR)管理数据流 4~7

3.4、DMA通道x传输数量寄存器(DMA_CNDTR)

这里指的是数据项,并不是字节数,最大数据传输数目:65535

 注意:当寄存器值为0表明数据传输已经全部发送完成 

3.5、DMA数据流x外设地址寄存器(DMA_SxPAR)

3.6、DMA数据流x存储器地址寄存器

DMA数据流x存储器0地址寄存器(DMA_SxM0AR)

DMA数据流x存储器1地址寄存器(DMA_SxM1AR)(用于双缓冲区模式)

四、DMA相关HAL库驱动介绍

相关 HAL 库函数介绍

CAN 外设相关重要结构体

DMA_HandleTypeDef

typedef struct __DMA_HandleTypeDef
{
    DMA_Channel_TypeDef	    *Instance            /* 数据流 */
    DMA_InitTypeDef 		Init                 /* DMA初始化结构体 */
    ...
    uint32_t	            StreamBaseAddress    /* 数据流基地址(HAL库自动计算) */
    uint32_t 	            StreamIndex          /* 数据流索引号(HAL库自动计算) */
}DMA_HandleTypeDef;

DMA_InitTypeDef

typedef struct
{
    uint32_t Request			    /* DMA请求 */
    uint32_t Direction			    /* DMA传输方向 */
    uint32_t PeriphInc			    /* 外设地址(非)增量 */
    uint32_t MemInc			        /* 存储器地址(非)增量*/
    uint32_t PeriphDataAlignment	/* 外设数据宽度 */
    uint32_t MemDataAlignment	    /* 存储器数据宽度 */
    uint32_t Mode				    /* 传输模式 */
    uint32_t Priority				/* DMA通道优先级 */
    /* 下面四个参数为突发配置,必须使能FIFO才有效 */
    uint32_t FIFOMode			    /* FIFO模式 */
    uint32_t FIFOThreshold		    /* FIFO阈值 */
    uint32_t MemBurst			    /* 存储器突发传输 */
    uint32_t PeriphBurst 			/*  外设突发传输 */
}DMA_InitTypeDef;

五、DMA配置步骤

1、使能 DMA 时钟:使用 __HAL_RCC_DMAx_CLK_ENABLE() 函数

2、初始化 DMA:使用 HAL_DMA_Init() 函数初始化DMA相关参数,__HAL_LINKDMA() 函数连接 DMA 和外设

3、使能串口的 DMA 发送,启动传输:使用 HAL_UART_Transmit_DMA() 函数

4、查询 DMA 传输状态:使用 __HAL_DMA_GET_FLAG() 查询通道传输状态,__HAL_DMA_GET_COUNTER() 获取当前传输剩余数据量

5、DMA 中断使用:HAL_NVIC_EnableIRQ() 使能 DMA 中断,HAL_NVIC_SetPriority() 配置 DMA,编写中断服务函数 xxx_IRQHandler()

六、编程实战

以串口 1 DMA 发送为例

main.c

#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/KEY/key.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/DMA/dma.h"

#define SEND_BUF_SIZE (sizeof(TEXT_TO_SEND) + 2) * 200 /* 发送数据长度, 等于sizeof(TEXT_TO_SEND) + 2的200倍. */

const uint8_t TEXT_TO_SEND[] = {"正点原子 STM32 DMA 串口实验"}; /* 要循环发送的字符串 */

uint8_t g_sendbuf[SEND_BUF_SIZE]; /* 发送数据缓冲区 */

int main(void)
{
    uint8_t key = 0;
    uint16_t i, k;
    uint16_t len;
    uint8_t mask = 0;
    float pro = 0; /* 进度:0~100 */

    HAL_Init();                         /* 初始化HAL库 */
    sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */
    delay_init(168);                    /* 延时初始化 */
    usart_init(115200);                 /* 串口初始化为115200 */
    led_init();                         /* 初始化LED */
    key_init();                         /* 初始化按键 */
    lcd_init();                         /* 初始化LCD */

    dma_init(DMA2_Stream7, DMA_CHANNEL_4); /* 初始化DMA */

    lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
    lcd_show_string(30, 70, 200, 16, 16, "DMA TEST", RED);
    lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
    lcd_show_string(30, 110, 200, 16, 16, "KEY0:Start", RED);

    len = sizeof(TEXT_TO_SEND);
    k = 0;

    for (i = 0; i < SEND_BUF_SIZE; i++) /* 填充ASCII字符集数据 */
    {
        if (k >= len) /* 加入换行符 */
        {
            if (mask)
            {
                g_sendbuf[i] = 0x0a;
                k = 0;
            }
            else
            {
                g_sendbuf[i] = 0x0d;
                mask++;
            }
        }
        else /* 复制TEXT_TO_SEND语句 */
        {
            mask = 0;
            g_sendbuf[i] = TEXT_TO_SEND[k];
            k++;
        }
    }

    i = 0;

    while (1)
    {
        key = key_scan(0);

        if (key == KEY0_PRES) /* KEY0按下 */
        {
            printf("\r\nDMA DATA:\r\n");
            lcd_show_string(30, 130, 200, 16, 16, "Start Transimit....", BLUE);
            lcd_show_string(30, 150, 200, 16, 16, "   %", BLUE); /* 显示百分号 */

            HAL_UART_Transmit_DMA(&g_uart1_handle, g_sendbuf, SEND_BUF_SIZE); /* 开始一次DMA传输! */

            /* 等待DMA传输完成,此时我们来做另外一些事情,比如点灯
             * 实际应用中,传输数据期间,可以执行另外的任务
             */
            while (1)
            {
                if (__HAL_DMA_GET_FLAG(&g_dma_handle, DMA_FLAG_TCIF3_7)) /* 等待DMA2_Stream7传输完成 */
                {
                    __HAL_DMA_CLEAR_FLAG(&g_dma_handle, DMA_FLAG_TCIF3_7); /* 清除DMA2_Stream7传输完成标志 */
                    HAL_UART_DMAStop(&g_uart1_handle);                     /* 传输完成以后关闭串口DMA */
                    break;
                }

                pro = __HAL_DMA_GET_COUNTER(&g_dma_handle); /* 得到当前还剩余多少个数据 */
                len = SEND_BUF_SIZE;                        /* 总长度 */
                pro = 1 - (pro / len);                      /* 得到百分比 */
                pro *= 100;                                 /* 扩大100倍 */
                lcd_show_num(30, 150, pro, 3, 16, BLUE);
            }
            lcd_show_num(30, 150, 100, 3, 16, BLUE);                            /* 显示100% */
            lcd_show_string(30, 130, 200, 16, 16, "Transimit Finished!", BLUE); /* 提示传送完成 */
        }

        i++;
        delay_ms(10);

        if (i == 20)
        {
            LED0_TOGGLE(); /* LED0闪烁,提示系统正在运行 */
            i = 0;
        }
    }
}

dma.c

#include "./BSP/DMA/dma.h"

DMA_HandleTypeDef g_dma_handle; /* DMA句柄 */

extern UART_HandleTypeDef g_uart1_handle; /* UART句柄 */

/**
 * @brief       串口TX DMA初始化函数
 *   @note      这里的传输形式是固定的, 这点要根据不同的情况来修改
 *              从存储器 -> 外设模式/8位数据宽度/存储器增量模式
 *
 * @param       dma_stream_handle : DMA数据流,DMA1_Stream0~7/DMA2_Stream0~7
 * @retval      无
 */
void dma_init(DMA_Stream_TypeDef *dma_stream_handle, uint32_t ch)
{
    if ((uint32_t)dma_stream_handle > (uint32_t)DMA2) /* 得到当前stream是属于DMA2还是DMA1 */
    {
        __HAL_RCC_DMA2_CLK_ENABLE(); /* DMA2时钟使能 */
    }
    else
    {
        __HAL_RCC_DMA1_CLK_ENABLE(); /* DMA1时钟使能 */
    }

    __HAL_LINKDMA(&g_uart1_handle, hdmatx, g_dma_handle); /* 将DMA与USART1联系起来(发送DMA) */

    /* Tx DMA配置 */
    g_dma_handle.Instance = dma_stream_handle;                   /* 数据流选择 */
    g_dma_handle.Init.Channel = ch;                              /* DMA通道选择 */
    g_dma_handle.Init.Direction = DMA_MEMORY_TO_PERIPH;          /* 存储器到外设 */
    g_dma_handle.Init.PeriphInc = DMA_PINC_DISABLE;              /* 外设非增量模式 */
    g_dma_handle.Init.MemInc = DMA_MINC_ENABLE;                  /* 存储器增量模式 */
    g_dma_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; /* 外设数据长度:8位 */
    g_dma_handle.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;    /* 存储器数据长度:8位 */
    g_dma_handle.Init.Mode = DMA_NORMAL;                         /* 外设流控模式 */
    g_dma_handle.Init.Priority = DMA_PRIORITY_MEDIUM;            /* 中等优先级 */
    g_dma_handle.Init.FIFOMode = DMA_FIFOMODE_DISABLE;           /* 关闭FIFO模式 */
    g_dma_handle.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;   /* FIFO阈值配置 */
    g_dma_handle.Init.MemBurst = DMA_MBURST_SINGLE;              /* 存储器突发单次传输 */
    g_dma_handle.Init.PeriphBurst = DMA_PBURST_SINGLE;           /* 外设突发单次传输 */

    HAL_DMA_DeInit(&g_dma_handle);
    HAL_DMA_Init(&g_dma_handle);
}

dma.h

#ifndef __DMA_H
#define __DMA_H

#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/delay/delay.h"

extern DMA_HandleTypeDef g_dma_handle;

void dma_init(DMA_Stream_TypeDef *dma_stream_handle, uint32_t ch); /* 配置DMAx_CHx */

#endif

如果是使用 VScode 出现串口输出乱码,需要修改 main.c 编码方式为 GB2312 重新保存编译下载

  • 30
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

HZU_Puzzle

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

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

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

打赏作者

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

抵扣说明:

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

余额充值