【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 重新保存编译下载
作者:HZU_Puzzle