基于stm32的HAL库的dma实验

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)
    { 
				
    }
}

实验现象

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值