DMA
1. DMA介绍
1.1 什么是DMA?
令人头秃的描述:
DMA(Direct Memory Access,直接存储器访问)提供在外设与内存、存储器和存储器之间的高速数 据传输使用。它允许不同速度的硬件装置来沟通,而不需要依赖于CPU,在这个时间中,CPU对于内存 的工作来说就无法使用。
简单描述:
就是一个数据搬运工!!
1.2 DMA的意义
代替 CPU 搬运数据,为 CPU 减负。
1. 数据搬运的工作比较耗时间;
2. 数据搬运工作时效要求高(有数据来就要搬走);
3. 没啥技术含量(CPU 节约出来的时间可以处理更重要的事)。
1.3 搬运什么数据?
存储器、外设
这里的外设指的是spi、usart、iic、adc 等基于APB1 、APB2或AHB时钟的外设,而这里的存储器包括 自身的闪存(flash)或者内存(SRAM)以及外设的存储设备都可以作为访问地源或者目的。
存储器→存储器
存储器→外设
外设→存储器
2. DMA框图
3. DMA控制器
STM32F103 有 2 个 DMA 控制器,DMA1 有 7 个通道,DMA 2 有 5 个通道。 一个通道每次只能搬运一个外设的数据!! 如果同时有多个外设的 DMA 请求,则按照优先级进行响应。 STM32F103C8T6 只有 DMA1 !
DMA1有7个通道:
DMA2 有 5 个通道:
4. DMA优先级管理
优先级管理采用软件+硬件:
软件: 每个通道的优先级可以在DMA_CCRx寄存器中设置,有4个等级 最高级>高级>中级>低级
硬件: 如果2个请求,它们的软件优先级相同,则较低编号的通道比较高编号的通道有较高的优先权。 比如:如果软件优先级相同,通道2优先于通道4
5. DMA传输方式
DMA_Mode_Normal(正常模式)
一次DMA数据传输完后,停止DMA传送 ,也就是只传输一次
DMA_Mode_Circular(循环传输模式)
当传输结束时,硬件自动会将传输数据量寄存器进行重装,进行下一轮的数据传输。 也就是多次传输 模式
6. 指针递增模式
外设和存储器指针在每次传输后可以自动向后递增或保持常量。当设置为增量模式时,下一个要传输的地址 将是前一个地址加上增量值。
7. DMA数据对齐方式
8 . D M A 寄 存 器 及 库 函 数 介 绍
__ H A L _ R C C _ D M A 1 _ C L K _ E N A B L E ( … )
H A L _ D M A _ I n i t ( … )
H A L _ D M A _ S t a r t ( … )
__ H A L _ L I N K D M A ( … )
H A L _ U A R T _ T r a n s m i t _ D M A ( … )
H A L _ U A R T _ R e c e i v e _ D M A ( … )
__ H A L _ D M A _ G E T _ F L A G ( … )
__ H A L _ D M A _ E N A B L E ( … )
__ H A L _ D M A _ D I S A B L E ( … )
小 实 验 1 : D M A 内 存 到 内 存 数 据 搬 运
实 验 目 的
使 用 D M A 将 一 个 大 数 组 的 数 据 搬 运 到 另 一 个 位 置
头文件
//目标的数组,大小一样
#include "dma.h"
#include "sys.h"
#include "stdio.h"
#include "sys.h"
void dma_transimt(void);
void dma_init(void);
#define BUF_SIZE 16
//原数组
uint32_t scr_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; //指定方向 内存->外设 内存->外设 内存到内存, 外设到外设
//内存相关配置
//数据对齐的方式 一般用8字节对齐
//对其增长方式
dma_handle.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
dma_handle.Init.MemInc = DMA_MINC_ENABLE;//指针往下偏移,如果不偏移每次转运的都是第一个字符
//外设相关配置
//外设对齐的方式 一般用8字节对齐
//外设对齐增长方式
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);
}
//转运数据
//*hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength
//句柄,数组,数组,长度
void dma_transimt(void)
{
//原数组传输到目标数组中
//如何判断数据转运完成要求的类型是uint32_t
HAL_DMA_Start(&dma_handle,(uint32_t)scr_buf,(uint32_t)dst_buf,sizeof(uint32_t) * BUF_SIZE);
//如何看 __HAL_DMA_GET_FLAG(__HANDLE__, __FLAG__) 句柄,通道
while(__HAL_DMA_GET_FLAG(&dma_handler,DMA_FLAG_TC1) == RESET);//获取TC位 看是否置1,如果置1循环跳出
//接收完成打印出来
int i = 0;
for(i = 0;i<BUF_SIZE; i++)
{
printf("buf[%d] = %X\r\n", i, dst_buf[i]);//x%为16进制符,想要大写用X
}
}
小 实 验 2 : D M A 内 存 到 外 设 数 据 搬 运
实 验 目 的 使 用 D M A 将 一 个 大 数 组 的 数 据 通 过 串 口 1 发 送 。
代码
#include "sys.h"
#include "dma.h"
#include "stdio.h"
void dma_init(void);
//目标的数组,大小一样
extern UART_HandleTypeDef uart1_handle; /* UART1句柄 */
DMA_HandleTypeDef dma_handle = {0};
dma初始化
void dma_init(void)
{
__HAL_RCC_DMA1_CLK_ENABLE();
//提供的表格只适用于外设,串口1的TX是通道4
dma_handle.Instance = DMA1_Channel4; //指令基地址
dma_handle.Init.Direction = DMA_MEMORY_TO_PERIPH; //指定方向 内存->外设 内存->外设 内存到内存, 外设到外设
//内存相关配置
//数据对齐的方式 一般用8字节对齐
//对其增长方式
dma_handle.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
dma_handle.Init.MemInc = DMA_MINC_ENABLE;//指针往下偏移,如果不偏移每次转运的都是第一个字符,地址递增
//外设相关配置
//外设对齐的方式 一般用8字节对齐
//外设对齐增长方式
dma_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;;//外设地址不能递增,如果递增,数据传数过来地址就跑到其他地方了,每次发到TDR寄存器内,不能递增
dma_handle.Init.PeriphInc = DMA_PINC_DISABLE;
dma_handle.Init.Priority = DMA_PRIORITY_MEDIUM; //如果只哟一个通道在使用优先级无所谓
dma_handle.Init.Mode = DMA_NORMAL;//传输模式 内存到内存只支持普通的模式,不支持循环模式
HAL_DMA_Init(&dma_handle);
//链接_HAL_LINK_DMA 是一个宏函数, 参数 串口句柄,句柄中对应的成员变量,DMA的句柄
__HAL_LINKDMA(&uart1_handle,hdmatx,dma_handle);//在函数中将句柄与dma建立关联
}
main函数
#include "sys.h"
#include "delay.h"
#include "led.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);
dma_init();
// led1_on();
// led1_off();
int i = 0;
for(i = 0;i < 1000; i++)
{
send_buf[i] = 'A';//都是A
HAL_UART_Transmit_DMA(&uart1_handle,send_buf,1000);//发出
}
while(1)
{
led1_on();
led3_off();
delay_ms(500);
led1_off();
led3_on();
delay_ms(500);
}
}
小 实 验 3 : D M A 外 设 到 内 存 数 据 搬 运
实 验 目 的
使 用 D M A 接 收 串 口 1 数 据 。
案例代码
dma功能代码
头文件
#include "dma.h"
#include "stdio.h"
#include "uart1.h"
#include "sys.h"
void dma_init(void);
//目标的数组,大小一样
extern UART_HandleTypeDef uart1_handle; /* UART1句柄 */
extern uint8_t uart1_rx_buf[UART1_RX_BUF_SIZE]; /* UART1接收缓冲区 */
DMA_HandleTypeDef dma_handle = {0};
dma初始化函数
dma1时钟使能
提供的表格只适用于外设,串口1的TX是通道5
内存相关配置
数据对齐的方式 一般用8字节对齐
对其增长方式
外设相关配置
外设对齐的方式 一般用8字节对齐
外设对齐增长方式
链接_HAL_LINK_DMA 是一个宏函数,参数 串口句柄,句柄中对应的成员变量,DMA的句柄
void dma_init(void)
{
dma1时钟使能
__HAL_RCC_DMA1_CLK_ENABLE();
提供的表格只适用于外设,串口1的TX是通道5
dma_handle.Instance = DMA1_Channel5; //指令基地址
dma_handle.Init.Direction = DMA_PERIPH_TOMEMORY; //外设到内存
内存相关配置
数据对齐的方式 一般用8字节对齐
对其增长方式
dma_handle.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;//8字节对齐
dma_handle.Init.MemInc = DMA_MINC_ENABLE;
外设相关配置
外设对齐的方式 一般用8字节对齐
外设对齐增长方式
dma_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;//8字节对齐
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);
链接_HAL_LINK_DMA 是一个宏函数,参数 串口句柄,句柄中对应的成员变量,DMA的句柄
__HAL_LINKDMA(&uart1_handle,hdmarx,dma_handle);//在函数中将句柄与dma建立关联
//将uart1_handle的数据保存到串口的缓冲区,串口大小
HAL_UART_Receive_DMA(&uart1_handle, uart1_rx_buf,UART1_RX_BUF_SIZE);
}
uart1功能代码
头文件
#include "stdio.h"
#include "sys.h"
/* 错误代码 */
#define UART_EOK 0 /* 没有错误 */
#define UART_ERROR 1 /* 通用错误 */
#define UART_ETIMEOUT 2 /* 超时错误 */
#define UART_EINVAL 3 /* 参数错误 */
/* UART收发缓冲大小 */
#define UART1_RX_BUF_SIZE 128
#define UART1_TX_BUF_SIZE 64
void uart1_init(uint32_t bound); /* 串口初始化函数 */
extern DMA_HandleTypeDef dma_handle;
UART_HandleTypeDef uart1_handle; /* UART1句柄 */
重定义fputc函数
printf函数最终会通过调用fputc输出字符串到串口
in fputc(int ch, FILE *f)
{
while((USART1->SR & 0x04) == 0);//改为是否变为1
USART1->DR = (uint8_t)ch;
return ch;
}
串口1初始化函数
void uart1_init(uint32_t baudrate)
{
uart1_handle.Instance = USART1; //选择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_MDOE_TX_RX; // 接收传输模式
HAL_UART_Init(&uart1_handle);
}
UART底层初始化函数msp
void HAL_UART_MspInit(UART_HandleTypeDef *Huart)
{
公共服务函数需要判断是否为USART1接收的数据
//gpio,NVIC,接收中断使能
GPIO_InitTypeDef gpio_init_struct;
if(huart->Instance == USART1)
{
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_USART1_CLK_ENABLE();
gpio_init_struct.Pin = GPIO_PIN_9;
gpio_init_struct.Mode = GPIO_MODE_AF_PP;
gpio_init_struct.Pull = GPIO_PULLUP;
gpio_init_struct.Speed= GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA,&gpio_init_struct);
gpio_init_struct.Pin = GPIO_PIN_10;
gpio_init_struct.Mode = GPIO_MODE_AF_INPUT;
__HAL_UART_ENABLE_IT(uart1_handle,UART_IT_RXNE);//不为空标志位使能
__HAL_UART_ENABLE_IT(uart1_handle,UART_IT_IDLE);//空闲中断标志位中断
}
}
UART1接收缓冲区清除
void uart1_rx_clear(void)
{
memset(uart1_rx_buf,0,sizeof(uart1_rx_buf));
uart1_rx_len = 0;
}
串口1中断服务函数 在此使用接收中断及空闲中断,实现不定长数据收发
void USART1_IRQHandler(void)
{
uint8_t receive_data = 0;
if(__HAL_UART_GET_FLAG(&uart_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_lem++] = receive_data;
}
/空闲中断,之前直接打印出来,现在需要搬运出来
if(__HAL_UART_GET_FLAG(&uart_handle,UART_FLAG_IDLE)! == RESET)
{
//清除空闲中断
__HAL_UART_CLEAR_IDLEFLAG(&uart1_handle);
//停止DMA传输干扰
HAL_UART_DMAStop(&uart1_handle);
//获取接收道德数据长度 当前还有多少个数据没有传输
uart1_rx_len = UART1_RX_BUF_SIZE - __HAL_DMA_GET_COUNTER(&dma_handle);
//打印接收到的内容
printf("recv:%s. recv_len: %d\r\n",uart1_rx_buf,uart1_rx_len);
//清空接收缓冲
uart1_rx_clear();
//重新开启dma传输
//使能串口接收 串口句柄,串口接收的缓冲区,size
HAL_UART_Receive_DMA(&uart1_handle,uart1_rx_buf,UART_RX_BUF_SIZE);
}
}
#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();
printf("hello world\r\n");
while(1)
{
}
}
实验现象