STM32·HAL库开发(十四)DMA直接存储器访问——案例:DMA_MEMTOMEMO and DMA_USART

目录

【DMA的简介】

【DMA的意义】

【搬运什么数据?如何搬运数据?】

【DMA框图】

【DMA数据传输方向】

【DMA 控制器】

【DMA通道的优先级】

【DMA的传输方式】

【指针递增模式】

【注意事项】

【demo · DMA_memory_to_memory】

【demo · DMA_USART】


【DMA的简介】

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

【DMA的意义】

        代替 CPU 搬运数据,为 CPU 减负
                ●  数据搬运的工作比较耗时间
                ●  数据搬运工作时效要求高(有数据来就要搬走)
                ●  没啥技术含量(CPU 节约出来的时间可以处理更重要的事)

【搬运什么数据?如何搬运数据?】

        
        存储器、外设
        
        这里的外设指的是:spi、usart、iic、adc 等基于APB1 、APB2或AHB时钟的外设
        这里的存储器包括:自身的闪存(flash)、内存(SRAM)、外设的存储设备,它们都可以作为访问地源或者目的

【DMA框图】

【DMA数据传输方向】

        ●  存储器→ 存储器(例如:复制某特别大的数据buf、内部FLASH 向内部SRAM 复制数据
        ●  存储器→外设   (例如:将某数据 buf 写入串口 TDR 寄存器)
        ●  外设→存储器   (例如:将串口 RDR 寄存器写入某数据 buf、ADC采集
        ●  外设→外设

【DMA 控制器】

        如果外设要想通过DMA 来传输数据,必须先给DMA 控制器发送DMA 请求,DMA 收到请求信号之后,控制器会给外设一个应答信号,当外设应答后且DMA 控制器收到应答信号之后,就会启动DMA 的传输,直到传输完毕。
        STM32F103有2 DMA 控制器, DMA1 7 个通道, DMA2 5 个通道。 一个通道每次只能搬运一个外设的数据!! 如果同时有多个外设的 DMA 请求,则按照优先级进行响应。
        注意:DMA2 只存在于大容量的单片机中
        DMA1有 7 个通道:

         DMA25个通道

【DMA通道的优先级

        当发生多个DMA 通道请求时,就意味着有先后响应处理的顺序问题,这个就由仲裁器管理。仲裁器 优先级管理采用软件+硬件:
        ●  软件: 每个通道的优先级可以在DMA_CCRx 寄存器中设置,有 4 个等级:最高级>高级> 中级 > 低级
        ●  硬件: 如果2 个请求,它们的软件优先级相同,则较低编号的通道优先级高的优先权。
         比如:如果软件优先级相同,通道2优先于通道4
        
        在大容量产品和互联型产品中,DMA1 控制器拥有高于DMA2 控制器的优先级。

【DMA的传输方式】

        ●  DMA_Mode_Normal(正常模式)
                一次DMA数据传输完后,停止 DMA 传送 ,也就是只传输一次
        ●  DMA_Mode_Circular(循环传输模式)
                当传输结束时,硬件自动会将传输数据量寄存器进行重装,进行下一轮的数据传输。 也就是多次传输模式

【指针递增模式】

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

【注意事项】

        若把 HAL_ADC_Start_DMA 的最后一个参数明确写值时,需要改为实际采集数据个数的2倍

        如:HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adcConvertValue,8); 则一次DMA仅搬运4次

        ADC采集时,只能采集到一半的数据_adc采样突然变为一半-优快云博客

【demo · DMA_memory_to_memory】

        demo概述:使用DMA 的方式将数组 A 的内容复制到数组 B 中,搬运完之后打印数组 B 的内容
        该demo包含两个方向:DMA_memory_to_peripheral,DMA_peripheral_to_memory

        所用函数:

HAL_DMA_Start    // DMA启动函数


HAL_StatusTypeDef HAL_DMA_Start(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t
DstAddress, uint32_t DataLength)

// 参数一:DMA_HandleTypeDef *hdma,DMA通道句柄
// 参数二:uint32_t SrcAddress,    源内存地址
// 参数三:uint32_t DstAddress,    目标内存地址
// 参数四:uint32_t DataLength,    传输数据长度  注意:需要乘以sizeof(uint32_t)
// 返回值:HAL_StatusTypeDef,HAL状态(OK,busy,ERROR,TIMEOUT)

        注意:数据长度需要乘以sizeof(uint32_t),或者直接用sizeof(srcBuf)

__HAL_DMA_GET_FLAG

#define __HAL_DMA_GET_FLAG(__HANDLE__, __FLAG__) (DMA1->ISR & (__FLAG__))

// 参数一:HANDLE,DMA通道句柄
// 参数二:FLAG,数据传输标志。DMA_FLAG_TCx表示数据传输完成标志
    // DMA_FLAG_TCx:传输完成标志(Transfer Complete)。当DMA传输完成时,该标志位被置位。可以使用该标志位来检测DMA传输是否已经完成。
    // DMA_FLAG_HTx:半传输标志(Half Transfer)。当DMA传输的一半数据大小完成时,该标志位被置位。可以使用该标志位来检测DMA传输是否已经完成了一半。
    // DMA_FLAG_TEx:传输错误标志(Transfer Error)。当DMA传输过程中发生错误时,该标志位被置位。可以使用该标志位来检测DMA传输是否发生错误。
    // DMA_FLAG_GLx:全局标志(Global Flag)。该标志位是一个组合标志位,用于检测DMA传输过程中的各种状态。包括TCx、HTx和TEx标志位。如果任何一个标志位被置位,该标志位也将被置位。可以使用该标志位检测DMA传输的整体状态。
// 返回值:FLAG的值(SET/RESET)

        句柄:

        CubeMX在CubeMX_USART_NVIC基础上增加DMA功能

/* main.c */

#include "main.h"
#include "dma.h"
#include "usart.h"
#include "gpio.h"

#include "printf.h"
#include "string.h"

#define BUF_SIZE 16

uint32_t srcBuf[BUF_SIZE] = 	// 源数组
{		
	0x00000000,0x11111111,0x22222222,0x33333333,
	0x44444444,0x55555555,0x66666666,0x77777777,
	0x88888888,0x99999999,0xAAAAAAAA,0xBBBBBBBB,
	0xCCCCCCCC,0xDDDDDDDD,0xEEEEEEEE,0xFFFFFFFF
};

uint32_t desBuf[BUF_SIZE];		// 目标数组

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_DMA_Init();
    MX_USART1_UART_Init();

	HAL_UART_Receive_IT(&huart1, &buf, 1);
	printf("hello zzq\r\n");
	HAL_Delay(1000);

	// 开启数据传输
	HAL_DMA_Start(&hdma_memtomem_dma1_channel1,(uint32_t)srcBuf, (uint32_t)desBuf, sizeof(uint32_t) * BUF_SIZE);
	while(__HAL_DMA_GET_FLAG(&hdma_memtomem_dma1_channel1, DMA_FLAG_TC1) == RESET);	// 等待数据传输完成
	// 打印数组内容
	for (int i = 0; i < BUF_SIZE; i++)
	{
		printf("Buf[%d] = %X\r\n", i, desBuf[i]);
	}

    while (1)
    {
        if(UART1_RX_STA & 0x8000)
		{
			printf("收到数据:");														// 把“”中的内容打印到串口
			printf("%s",UART1_RX_Buffer);						// 将收到的数据发送到串口
			//HAL_UART_Transmit(&huart1, UART1_RX_Buffer, UART1_RX_STA & 0x3fff, 0xffff);		// 将收到的数据发送到串口
			while(huart1.gState != HAL_UART_STATE_READY);		// 等待发送完成
			printf("\r\n");
			UART1_RX_STA = 0;																// 重新开始下一次接收
			memset(UART1_RX_Buffer,0,sizeof(UART1_RX_Buffer));	// 清空缓冲区
		}

【demo · DMA_USART】

        demo概述:使用DMA的方式接收串口数据,并将数据搬运串到口发送,同时CPU用于控制LED闪烁

        所用函数:

HAL_UART_Transmit_DMA    // 串口DMA发送

HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData,
uint16_t Size)

// 参数一:UART_HandleTypeDef *huart,串口句柄,即别名
// 参数二:uint8_t *pData,待发送数据首地址
// 参数三:uint16_t Size,待发送数据长度
// 返回值:HAL_StatusTypeDef,HAL状态(OK,busy,ERROR,TIMEOUT)
HAL_UART_Receive_DMA    // 串口DMA接收

HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)


// 参数一:UART_HandleTypeDef *huart,串口句柄,即别名
// 参数二:uint8_t *pData,待接收数据首地址
// 参数三:uint16_t Size,待接收数据长度
// 返回值:HAL_StatusTypeDef,HAL状态(OK,busy,ERROR,TIMEOUT)
__HAL_DMA_GET_FLAG

#define __HAL_DMA_GET_FLAG(__HANDLE__, __FLAG__) (DMA1->ISR & (__FLAG__))

// 参数一:HANDLE,DMA通道句柄
// 参数二:FLAG,数据传输标志。DMA_FLAG_TCx表示数据传输完成标志
    // DMA_FLAG_TCx:传输完成标志(Transfer Complete)。当DMA传输完成时,该标志位被置位。可以使用该标志位来检测DMA传输是否已经完成。
    // DMA_FLAG_HTx:半传输标志(Half Transfer)。当DMA传输的一半数据大小完成时,该标志位被置位。可以使用该标志位来检测DMA传输是否已经完成了一半。
    // DMA_FLAG_TEx:传输错误标志(Transfer Error)。当DMA传输过程中发生错误时,该标志位被置位。可以使用该标志位来检测DMA传输是否发生错误。
    // DMA_FLAG_GLx:全局标志(Global Flag)。该标志位是一个组合标志位,用于检测DMA传输过程中的各种状态。包括TCx、HTx和TEx标志位。如果任何一个标志位被置位,该标志位也将被置位。可以使用该标志位检测DMA传输的整体状态。
// 返回值:FLAG的值(SET/RESET)
__HAL_UART_CLEAR_IDLEFLAG

#define __HAL_UART_CLEAR_IDLEFLAG(__HANDLE__) __HAL_UART_CLEAR_PEFLAG(__HANDLE__)

// 参数一:HANDLE,串口句柄
// 返回值:无
HAL_UART_DMAStop

HAL_StatusTypeDef HAL_UART_DMAStop(UART_HandleTypeDef *huart)

// 参数一:UART_HandleTypeDef *huart,串口句柄
// 返回值:HAL_StatusTypeDef,HAL状态(OK,busy,ERROR,TIMEOUT)
__HAL_DMA_GET_COUNTER

#define __HAL_DMA_GET_COUNTER(__HANDLE__) ((__HANDLE__)->Instance->CNDTR)

// 参数一:HANDLE,串口句柄
// 返回值:未传输数据大小

        CubeMX中打开GPIO·PC13,打开UART1,开启UART1_NVIC,开启DMA_UART1

代码逻辑:

        如何判断串口接收是否完成?如何知道串口收到数据的长度?

        使用串口空闲中断IDLE):

                ●  串口空闲时,触发空闲中断
                ●  空闲中断标志位由硬件置1,软件清零
        完整流程:
                ●  使能IDLE空闲中断
                ●  使能DMA接收中断
                ●  收到串口接收中断,DMA不断传输数据到缓冲区
                ●  一帧数据接收完毕,串口暂时空闲,触发串口空闲中断
                ●  在中断服务函数中,清除中断标志位,关闭DMA传输(防止干扰)
                ●  计算刚才收到了多少个字节的数据
                ●  处理缓冲区数据,开启DMA传输,开始下一帧接收
/* stm32f1xx.it.c */

// 在void USART1_IRQHandler(void)函数中修改

void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */

  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */
	if((__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE) == SET))// 判断IDLE标志位是否被置位,SET表示一帧数据发送完毕
	{
		__HAL_UART_CLEAR_IDLEFLAG(&huart1);					   // 清除IDLE空闲标志位
		HAL_UART_DMAStop(&huart1); 							   // 停止DMA传输,防止干扰
		uint8_t temp=__HAL_DMA_GET_COUNTER(&hdma_usart1_rx);   // 获得未传输完成数据长度
		rcvLen = BUF_SIZE - temp; 							   // 计算还需要传输数据长度
		
		HAL_UART_Transmit(&huart1, (const unsigned char *)"收到数据:", strlen("收到数据:"),100);		
		
		HAL_UART_Transmit_DMA(&huart1, rcvBuf, rcvLen);		   // 发送数据
		HAL_UART_Receive_DMA(&huart1, rcvBuf, BUF_SIZE);	   // 开启DMA
	}
  /* USER CODE END USART1_IRQn 1 */
}
/* main.h */

#define BUF_SIZE 10

extern uint8_t rcvBuf[BUF_SIZE];	// 接收数据缓存数组
extern uint8_t rcvLen;	

/* main.c */

#include "main.h"
#include "dma.h"
#include "usart.h"
#include "gpio.h"

uint8_t rcvBuf[BUF_SIZE] = {0}; 	// 接收数据缓存数组
uint8_t rcvLen = 0; 				// 接收一帧数据的长度

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_DMA_Init();
    MX_USART1_UART_Init();

	__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); 		 // 使能IDLE空闲中断
	HAL_UART_Receive_DMA(&huart1,rcvBuf,BUF_SIZE); 	 // 使能DMA接收中断

    while (1)
    {
		HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
		HAL_Delay(300);
    }
}

参考:STM32 HAL库 STM32CubeMX -- DMA(直接存储区访问)_Dir_xr的博客-优快云博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值