STM32H7的MDMA、DMA2D和通用DMA性能比较


MDMA,DMA2D 和每个都测试了四种情况:
◆ 64 位带宽的 AXI SRAM 内部做 64KB 数据传输。
◆ 32 位带宽的 D2 域 SRAM1 内部 64KB 数据传输。
◆ AXI SRAM 向 SDRAM 传输 64KB 的数据传输。
◆ 32 位带宽的 SDRAM 内部做 64KB 数据传输。
MDMA:
在 D1 域,支持 64 位带宽的 DMA 数据传输。
DMA2D:
在 D1 域,主要用图形 2D 加速。
DMA1 和 DMA2:
在 D2 域,支持 32 位带宽的 DMA 数据传输。

MDMA测试

采用块传输,源地址和目的地址都是 64bit 数据传输,并设置 16beat 突发,也就是
连续传输 16 组 64bit 数据。

初始化MDMA

	__HAL_RCC_MDMA_CLK_ENABLE();  

	MDMA_Handle.Instance = MDMA_Channel0;  

	/* MDMA配置 **********************************************************************/
	__HAL_RCC_MDMA_CLK_ENABLE();  

	MDMA_Handle.Instance = MDMA_Channel0;  

	MDMA_Handle.Init.Request              = MDMA_REQUEST_SW;         /* 软件触发 */
	MDMA_Handle.Init.TransferTriggerMode  = MDMA_BLOCK_TRANSFER;     /* 块传输 */
	MDMA_Handle.Init.Priority             = MDMA_PRIORITY_HIGH;      /* 优先级高*/
	MDMA_Handle.Init.Endianness           = MDMA_LITTLE_ENDIANNESS_PRESERVE; /* 小端 */
	MDMA_Handle.Init.SourceInc            = MDMA_SRC_INC_DOUBLEWORD;         /* 源地址自增,双字,即8字节 */
	MDMA_Handle.Init.DestinationInc       = MDMA_DEST_INC_DOUBLEWORD;        /* 目的地址自增,双字,即8字节 */
	MDMA_Handle.Init.SourceDataSize       = MDMA_SRC_DATASIZE_DOUBLEWORD;    /* 源地址数据宽度双字,即8字节 */
	MDMA_Handle.Init.DestDataSize         = MDMA_DEST_DATASIZE_DOUBLEWORD;   /* 目的地址数据宽度双字,即8字节 */
	MDMA_Handle.Init.DataAlignment        = MDMA_DATAALIGN_PACKENABLE;       /* 小端,右对齐 */                    
	MDMA_Handle.Init.SourceBurst          = MDMA_SOURCE_BURST_16BEATS;      /* 源数据突发传输,SourceBurst*SourceDataSize <  BufferTransferLength*/
	MDMA_Handle.Init.DestBurst            = MDMA_DEST_BURST_16BEATS;        /* 目的数据突发传输,DestBurst*DestDataSize < BufferTransferLength */

	MDMA_Handle.Init.BufferTransferLength = 128;    /* 每次传输128个字节 */

	MDMA_Handle.Init.SourceBlockAddressOffset  = 0; /* 用于block传输,地址偏移0 */
	MDMA_Handle.Init.DestBlockAddressOffset    = 0; /* 用于block传输,地址偏移0 */

	/* 初始化MDMA */
	if(HAL_MDMA_Init(&MDMA_Handle) != HAL_OK)
	{
		 Error_Handler(__FILE__, __LINE__);
	}
	/* 设置传输完成回调和中断及其优先级配置 */
	HAL_MDMA_RegisterCallback(&MDMA_Handle, HAL_MDMA_XFER_CPLT_CB_ID, MDMA_TransferCompleteCallback);
	HAL_NVIC_SetPriority(MDMA_IRQn, 0, 0);
	HAL_NVIC_EnableIRQ(MDMA_IRQn);  
	//配置中断
	void MDMA_IRQHandler(void)
{
	HAL_MDMA_IRQHandler(&MDMA_Handle);
}
static void MDMA_TransferCompleteCallback(MDMA_HandleTypeDef *hmdma)
{
	TransferCompleteDetected = 1;
}

需要注意的是
◆ 第 1 行,务必优先初始化 MDMA 时钟,测试发现没有使能时钟的情况下就配置 MDMA 很容易失败。
◆ 第 14-15 行,突发传输的配置非常考究,每次突发传输的总数据大小不能超过 128 字节。
对于源地址就是 SourceBurst * SourceDataSize <= BufferTransferLength。
对于目的地址就是 DestBurstDestDataSize <= BufferTransferLength。
比如当前的程序配置:
SourceBurst * SourceDataSize = 16
8 =128 字节
DestBurstDestDataSize = 168 =128 字节
这里要特别注意一点,如果实际应用中最好小于 BufferTransferLength,防止不稳定。

数据传输(DWT单位2.5n)

    /* AXI SRAM的64KB数据传输测试 ***********************************************/
	TransferCompleteDetected = 0;
	HAL_MDMA_Start_IT(&MDMA_Handle, 
				      (uint32_t)0x24000000, 
				      (uint32_t)(0x24000000 + 64*1024), 
				      64*1024, 
				      1);


	start = DWT_CYCCNT;
	while(TransferCompleteDetected == 0) {}
	end = DWT_CYCCNT;
	cnt = end - start;
 
	//64*1024/(cnt/400/1000/1000)/1024/1024 = 64*1000*1000*400/1024/cnt = 25000000/cnt
	printf("MDMA---AXI SRAM内部互传64KB数据耗时 =  %dus %dMB/S\r\n", cnt/400, 25000000/cnt);
		
    /* D2域SRAM1的64KB数据传输测试 ***********************************************/
	TransferCompleteDetected = 0;
	HAL_MDMA_Start_IT(&MDMA_Handle, 
				      (uint32_t)0x30000000, 
				      (uint32_t)(0x30000000 + 64*1024), 
				      64*1024, 
				      1);

	start = DWT_CYCCNT;
	while(TransferCompleteDetected == 0) {}
	end = DWT_CYCCNT;
	cnt = end - start;

	printf("MDMA---D2域SRAM1内部互传64KB数据耗时 =  %dus %dMB/S\r\n", cnt/400, 25000000/cnt);
		
	/* AXI SRAM向SDRAM的64KB数据传输测试 ***********************************************/
	TransferCompleteDetected = 0;
	HAL_MDMA_Start_IT(&MDMA_Handle, 
				      (uint32_t)0x24000000, 
				      (uint32_t)0xC0000000, 
				      64*1024, 
				      1);

	start = DWT_CYCCNT;
	while(TransferCompleteDetected == 0) {}
	end = DWT_CYCCNT;
	cnt = end - start;

	printf("MDMA---AXI SRAM传输64KB数据到SDRAM耗时 =  %dus %dMB/S\r\n", cnt/400, 25000000/cnt);
		
	/* SDRAM的64KB数据传输测试 ***********************************************/
	TransferCompleteDetected = 0;
	HAL_MDMA_Start_IT(&MDMA_Handle, 
				      (uint32_t)0xC0000000, 
				      (uint32_t)(0xC0000000 + 64*1024), 
				      64*1024, 
				      1);


	start = DWT_CYCCNT;
	while(TransferCompleteDetected == 0) {}
	end = DWT_CYCCNT;
	cnt = end - start;

	printf("MDMA---SDRAM内部互传64KB数据耗时 =  %dus %dMB/S\r\n", cnt/400, 25000000/cnt);

DMA2D

DMA2D初始化

配置 DMA2D 采用存储器到存储器模式,前景区和输出区都采用 ARGB8888 格式,传输 64256 次,每次 4 字节,即 64256*4 = 64KB 数据。

	__HAL_RCC_DMA2D_CLK_ENABLE();  
	
	/* DMA2D采用存储器到存储器模式, 这种模式是前景层作为DMA2D输入 */  
	DMA2D->CR      = 0x00000000UL;
	DMA2D->FGOR    = 0;
	DMA2D->OOR     = 0;
	
	/* 前景层和输出区域都采用的RGB565颜色格式 */
	DMA2D->FGPFCCR = LTDC_PIXEL_FORMAT_ARGB8888;
	DMA2D->OPFCCR  = LTDC_PIXEL_FORMAT_ARGB8888;
	
	DMA2D->NLR     = (uint32_t)(64 << 16) | (uint16_t)256;

数据传输

 /* AXI SRAM的64KB数据传输测试 ***********************************************/
	DMA2D->FGMAR = (uint32_t)0x24000000;
	DMA2D->OMAR  = (uint32_t)(0x24000000 + 64*1024);
	DMA2D->CR   |= DMA2D_CR_START;   
	
	start = DWT_CYCCNT;
	/* 等待DMA2D传输完成 */
	while (DMA2D->CR & DMA2D_CR_START) {} 
	end = DWT_CYCCNT;
	cnt = end - start;
	
	printf("DMA2D---AXI SRAM内部互传64KB数据耗时 =  %dus %dMB/S\r\n", cnt/400, 25000000/cnt);
		
    /* D2域SRAM1的64KB数据传输测试 ***********************************************/
	DMA2D->FGMAR = (uint32_t)0x30000000;
	DMA2D->OMAR  = (uint32_t)(0x30000000 + 64*1024);
	DMA2D->CR   |= DMA2D_CR_START;  
	
	start = DWT_CYCCNT;
	/* 等待DMA2D传输完成 */
	while (DMA2D->CR & DMA2D_CR_START) {} 
	end = DWT_CYCCNT;
	cnt = end - start;
		
	printf("DMA2D---D2域SRAM1内部互传64KB数据耗时 =  %dus %dMB/S\r\n", cnt/400, 25000000/cnt);
		
	/* AXI SRAM向SDRAM的64KB数据传输测试 ***********************************************/
	DMA2D->FGMAR = (uint32_t)0x24000000;
	DMA2D->OMAR  = (uint32_t)0xC0000000;
	DMA2D->CR   |= DMA2D_CR_START;  
	
	start = DWT_CYCCNT;
	/* 等待DMA2D传输完成 */
	while (DMA2D->CR & DMA2D_CR_START) {} 
	end = DWT_CYCCNT;
	cnt = end - start;
		
	printf("DMA2D---AXI SRAM传输64KB数据到SDRAM耗时 =  %dus %dMB/S\r\n", cnt/400, 25000000/cnt);	

	/* SDRAM的64KB数据传输测试 ***********************************************/
	DMA2D->FGMAR = (uint32_t)0xC0000000;
	DMA2D->OMAR  = (uint32_t)(0xC0000000 + 64*1024);
	DMA2D->CR   |= DMA2D_CR_START;
	
	start = DWT_CYCCNT;
	/* 等待DMA2D传输完成 */
	while (DMA2D->CR & DMA2D_CR_START) {} 
	end = DWT_CYCCNT;
	cnt = end - start;	
		
	printf("DMA2D---SDRAM内部互传64KB数据耗时 =  %dus %dMB/S\r\n", cnt/400, 25000000/cnt);	

DMA

程序代码如下,采用存储区到存储区传输方式,源地址和目的地址都是 32bit 数据传输,并设置 4beat突发,也就是连续传输 4 组 32bit 数据。

	__HAL_RCC_DMA1_CLK_ENABLE();

	DMA_Handle.Instance                 = DMA1_Stream1;
	DMA_Handle.Init.Request             = DMA_REQUEST_MEM2MEM;  
	DMA_Handle.Init.Direction           = DMA_MEMORY_TO_MEMORY;
	DMA_Handle.Init.PeriphInc           = DMA_PINC_ENABLE;
	DMA_Handle.Init.MemInc              = DMA_MINC_ENABLE;
	DMA_Handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
	DMA_Handle.Init.MemDataAlignment    = DMA_PDATAALIGN_WORD;
	DMA_Handle.Init.Mode                = DMA_NORMAL;
	DMA_Handle.Init.Priority            = DMA_PRIORITY_VERY_HIGH;
	DMA_Handle.Init.FIFOMode            = DMA_FIFOMODE_ENABLE;
	DMA_Handle.Init.FIFOThreshold       = DMA_FIFO_THRESHOLD_FULL;
	DMA_Handle.Init.MemBurst            = DMA_MBURST_INC4;     /*WORD方式,仅支持4次突发 */
	DMA_Handle.Init.PeriphBurst         = DMA_PBURST_INC4;      /*WORD方式,仅支持4次突发 */
	DMA_Handle.XferCpltCallback         = DMA_TransferCompleteCallback;

	HAL_DMA_Init(&DMA_Handle);
	HAL_NVIC_SetPriority(DMA1_Stream1_IRQn, 0, 0);
	HAL_NVIC_EnableIRQ(DMA1_Stream1_IRQn);
	void DMA1_Stream1_IRQHandler(void)
	{
	 HAL_DMA_IRQHandler(&DMA_Handle);
	}
	static void DMA_TransferCompleteCallback(DMA_HandleTypeDef *hdma)
	{
	TransferCompleteDetected = 1;
	}

第 1 行,务必优先初始化 DMA 时钟,测试发现没有使能时钟的情况下就配置 DMA 很容易失败。
第 14-15 行,突发传输的配置非常考究,这里要特别注意数据位宽,FIFO 以及突发的配置。
在这里插入图片描述

	HAL_DMA_Start_IT(&DMA_Handle, (uint32_t)0x24000000, (uint32_t)(0x24000000 + 64*1024), 64*256);

	start = DWT_CYCCNT;
	while(TransferCompleteDetected == 0) {}
	end = DWT_CYCCNT;
	cnt = end - start;
 
	//64*1024/(cnt/400/1000/1000)/1024/1024 = 64*1000*1000*400/1024/cnt = 25000000/cnt
	printf("DMA1---AXI SRAM内部互传64KB数据耗时 =  %dus %dMB/S\r\n", cnt/400, 25000000/cnt);
		
	/* D2域SRAM1的64KB数据传输测试 ***********************************************/
	TransferCompleteDetected = 0;
	HAL_DMA_Start_IT(&DMA_Handle, (uint32_t)0x30000000, (uint32_t)(0x30000000 + 64*1024), 64*256);

	start = DWT_CYCCNT;
	while(TransferCompleteDetected == 0) {}
	end = DWT_CYCCNT;
	cnt = end - start;

	printf("DMA1---D2域SRAM1内部互传64KB数据耗时 =  %dus %dMB/S\r\n", cnt/400, 25000000/cnt);
		
		
	/* AXI SRAM向SDRAM的64KB数据传输测试 ***********************************************/
	TransferCompleteDetected = 0;
	HAL_DMA_Start_IT(&DMA_Handle, (uint32_t)0x24000000, (uint32_t)0xC0000000, 64*256);

	start = DWT_CYCCNT;
	while(TransferCompleteDetected == 0) {}
	end = DWT_CYCCNT;
	cnt = end - start;

	printf("DMA1---AXI SRAM传输64KB数据到SDRAM耗时 =  %dus %dMB/S\r\n", cnt/400, 25000000/cnt);
		
	/* SDRAM的64KB数据传输测试 ***********************************************/
	TransferCompleteDetected = 0;
	HAL_DMA_Start_IT(&DMA_Handle, (uint32_t)0xC0000000, (uint32_t)(0xC0000000 + 64*1024), 64*256);	

	start = DWT_CYCCNT;
	while(TransferCompleteDetected == 0) {}
	end = DWT_CYCCNT;
	cnt = end - start;

	printf("DMA1---SDRAM内部互传64KB数据耗时 =  %dus %dMB/S\r\n", cnt/400, 25000000/cnt);

最终结论(参考armfly)

在这里插入图片描述

以下是在Linux环境下使用DMA输出频率的STM32MP157 DAC例程: 1. 首先,需要在设备树中启用DAC和DMA: ``` &dac { status = "okay"; dmas = <&mdma1 0 0>, <&mdma1 1 0>; dma-names = "tx", "rx"; #address-cells = <1>; #size-cells = <0>; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_dac>; clock-names = "kernel", "pll3_p_ck"; clocks = <&rcc SCK_DAC>, <&rcc SCK_PLL3_Q>; }; &mdma1 { status = "okay"; }; ``` 这里我们使用了mdma1作为DMA控制器,同时启用了DAC和DMA。 2. 在Linux驱动程序中初始化DAC和DMA: ``` static int stm32_dac_probe(struct platform_device *pdev) { struct stm32_dac *dac; struct resource *res; int ret; dac = devm_kzalloc(&pdev->dev, sizeof(*dac), GFP_KERNEL); if (!dac) return -ENOMEM; /* 获取DAC的资源 */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); dac->base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(dac->base)) return PTR_ERR(dac->base); /* 获取DMA的资源 */ dac->dma_tx = of_dma_request_slave_channel(pdev->dev.of_node, 0); if (!dac->dma_tx) { dev_err(&pdev->dev, "No Tx DMA channel\n"); return -ENODEV; } /* 初始化DAC */ ret = stm32_dac_hw_init(dac); if (ret) { dev_err(&pdev->dev, "Failed to initialize DAC\n"); return ret; } /* 初始化DMA */ ret = stm32_dac_dma_init(dac); if (ret) { dev_err(&pdev->dev, "Failed to initialize DMA\n"); return ret; } platform_set_drvdata(pdev, dac); return 0; } static int stm32_dac_hw_init(struct stm32_dac *dac) { /* 使能DAC时钟 */ clk_prepare_enable(dac->clk); /* 初始化DAC */ writel(DAC_CR_EN1 | DAC_CR_TSEL1(7), dac->base + DAC_CR); writel(DAC_DHR12R1(0x800), dac->base + DAC_DHR12R1); return 0; } static int stm32_dac_dma_init(struct stm32_dac *dac) { /* 初始化DMA */ dac->dma_tx_desc = dmaengine_prep_slave_sg(dac->dma_tx, dac->sg_tx, 1, DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT); if (!dac->dma_tx_desc) { dev_err(dac->dev, "Failed to prepare Tx DMA descriptor\n"); return -EINVAL; } return 0; } ``` 在probe函数中,我们首先获取DAC和DMA的资源,然后分别调用`stm32_dac_hw_init`和`stm32_dac_dma_init`函数进行初始化。 在`stm32_dac_hw_init`函数中,我们使能DAC时钟,初始化DAC,并将DAC的输出信号设置为定时器7的触发信号。 在`stm32_dac_dma_init`函数中,我们初始化DMA,并为DMA分配一个描述符。 3. 在驱动程序中添加输出函数`stm32_dac_output`: ``` static ssize_t stm32_dac_output(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct stm32_dac *dac = dev_get_drvdata(dev); int ret; /* 将数据写入DMA缓冲区 */ ret = kstrtoint(buf, 10, &dac->value); if (ret) return ret; dac->buf[0] = dac->value; /* 启动DMA */ dmaengine_submit(dac->dma_tx_desc); dma_async_issue_pending(dac->dma_tx); return count; } static DEVICE_ATTR(output, S_IWUSR, NULL, stm32_dac_output); ``` `stm32_dac_output`函数将用户传入的数据写入DMA缓冲区,并启动DMA传输。 4. 在设备树中添加属性节点`output`: ``` &dac { ... output { compatible = "sysfs"; type = "int"; mode = "w"; }; }; ``` 这里我们使用sysfs节点来进行属性传输。 5. 在用户空间中通过sysfs节点来向DAC写入数据: ``` #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <string.h> #define SYSFS_DAC "/sys/devices/platform/ff0c0000.dac/output" int main(int argc, char *argv[]) { int fd; char buf[16]; int value; if (argc < 2) { printf("Usage: %s <value>\n", argv[0]); return 0; } value = atoi(argv[1]); snprintf(buf, sizeof(buf), "%d", value); fd = open(SYSFS_DAC, O_WRONLY); if (fd < 0) { perror("open"); return -1; } if (write(fd, buf, strlen(buf)) < 0) { perror("write"); close(fd); return -1; } close(fd); return 0; } ``` 这里我们通过sysfs节点向DAC写入数据。 以上就是在Linux环境下使用DMA输出频率的STM32MP157 DAC例程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值