STM32H7的MDMA基础知识


MDMA(Master direct memory access),可以这么理解其是DMA的加强版。
MDMA 位于 D1 域,使用的 64 位的 AXI 总线,可以操作 TCM 空间(前面章节讲解的 DMA2D,通
用 DMA 和 BDMA 不支持操作 TCM 空间)。
对于本节,重点要了解 MDMA 的 buffer 缓冲传输,block 块传输和 list 列表模式的区别。

MDMA的基础特性

  1. MDMA 有两个主控总线接口,一个是 AXI/AHB 总线接口,主要用存储器或者外设访问,另一个是AHBS 总线接口,仅用于 TCM 空间访问。
  2. 有个 16 个通道,32 个硬件触发源。每个通道都可以选择 1 个触发源,当然,也可以通过软件触发。
  3. 16 个通道的传输请求,既可以外设,也可以来自 DMA1 或 DMA2
  4. MDMA 具有一个 256 级的 DMA 空间,被分为两个 128 级空间使用。
  5. MDMA 的优先级可通过软件配置,支持 very high, high, medium, low 四个等级,如果配置的优先级相同,则由 channel 的序号决定,channel0 最高,channel15 最低。
  6. 数据宽度可以设置字节,半字,字和双字。源地址和目的地址的数据宽度可不同。
  7. 源地址和目标地址的大小和地址增量可以独立选择。
  8. 数据的打包和拆解是采用的小端格式。
  9. 支持突发模式,最大可传输 128 字节。
  10. 当源地址和目的地址的增量和数据大小相同,且位宽小于等于 32bit 时,才允许 TCM 使用突发模式。

MDMA的硬件框图

在这里插入图片描述
◆ mdma_it
mdma 的中断触发信号。
◆ mdma_str0 到 mdma_str31 触发源
mdma 的输入请求。
◆ mdma_hclk
MDMA 的 HCLK 时钟。

MDMA块传输、缓冲传输和列表传输的区别(重要)

初学MDMA、要搞清楚MDMA支持的块传输,缓冲传输和列表传输的区别。

缓冲传输(MDMA_BUFFER_TRANSFER)
这个模式主要用于 QSPI,DCMI,硬件 JPEG 等外设上。每个请求都会触发 BufferTransferLength
(最大 128 字节)大小的数据传输,此大小由 HAL_MDMA_Init 调用的参数配置。

块传输(MDMA_BLOCK_TRANSFER)
此方式与 DMA1 和 DMA2 的数据传输相似,每次请求,触发一次块传输,块大小由
HAL_MDMA_Start/HAL_MDMA_Start_IT 定义,或者列表模式里面的参数。

多块传输(MDMA_REPEAT_BLOCK_TRANSFER)
顾名思义,多块传输就是执行多次块传输,每次请求,触发多次的块传输,块大小和块数由
HAL_MDMA_Start/HAL_MDMA_Start_IT 定义,或者列表模式里面的参数。

列表传输(MDMA_FULL_TRANSFER)
这种模式可以方便的实现多种 MDMA 配置进行切换,轮番实现,而且可以实现列表的循环方式。每次请求,将触发所有块和节点的传输(如果用户调用了函数HAL_MDMA_LinkedList_CreateNode \HAL_MDMA_LinkedList_AddNode),

MDMA列表模式及其循环方式

列表模式包含多种 MDMA 的配置表,每个表包含一组完整 MDMA 配置。用户可以使用函数
HAL_MDMA_LinkedList_CreateNode 创建节点,再通过函数 HAL_MDMA_LinkedList_AddNode 将节点添加到列表里面。
◆ 使用列表模式的话,函数 HAL_MDMA_Init 创建的是节点 0。
◆ 使用函数 HAL_MDMA_LinkedList_EnableCircularMode 使能循环模式,注意是从节点 1 开始循环的,将节点 1 和末尾的节点相连,不包含 HAL_MDMA_Init 创建的节点 0。
◆ 节点 0 是初始配置,仅在初始化 MDMA 传输时使用一次,比如调用函数 HAL_MDMA_Start_IT。
◆ 如果要禁止循环模式,可以调用函数 HAL_MDMA_LinkedList_DisableCircularMode。
◆ 通过函数 HAL_MDMA_LinkedList_RemoveNode 还可以删除指定节点。

MDMA的触发源

MDMA支持的触发源如下,主要包含 DMA1,DMA2,DMA2D,LTDC,JPEG,QSPI,DSI,SDMMC和软件触发:

#define MDMA_REQUEST_DMA1_Stream0_TC ((uint32_t)0x00000000U) 
#define MDMA_REQUEST_DMA1_Stream1_TC ((uint32_t)0x00000001U) 
#define MDMA_REQUEST_DMA1_Stream2_TC ((uint32_t)0x00000002U) 
#define MDMA_REQUEST_DMA1_Stream3_TC ((uint32_t)0x00000003U) 
#define MDMA_REQUEST_DMA1_Stream4_TC ((uint32_t)0x00000004U)
#define MDMA_REQUEST_DMA1_Stream5_TC ((uint32_t)0x00000005U) 
#define MDMA_REQUEST_DMA1_Stream6_TC ((uint32_t)0x00000006U)
#define MDMA_REQUEST_DMA1_Stream7_TC ((uint32_t)0x00000007U) 
#define MDMA_REQUEST_DMA2_Stream0_TC ((uint32_t)0x00000008U) 
#define MDMA_REQUEST_DMA2_Stream1_TC ((uint32_t)0x00000009U) 
#define MDMA_REQUEST_DMA2_Stream2_TC ((uint32_t)0x0000000AU) 
#define MDMA_REQUEST_DMA2_Stream3_TC ((uint32_t)0x0000000BU) 
#define MDMA_REQUEST_DMA2_Stream4_TC ((uint32_t)0x0000000CU) 
#define MDMA_REQUEST_DMA2_Stream5_TC ((uint32_t)0x0000000DU)
#define MDMA_REQUEST_DMA2_Stream6_TC ((uint32_t)0x0000000EU) 
#define MDMA_REQUEST_DMA2_Stream7_TC ((uint32_t)0x0000000FU)
#define MDMA_REQUEST_LTDC_LINE_IT ((uint32_t)0x00000010U) 
#define MDMA_REQUEST_JPEG_INFIFO_TH ((uint32_t)0x00000011U) 
#define MDMA_REQUEST_JPEG_INFIFO_NF ((uint32_t)0x00000012U) 
#define MDMA_REQUEST_JPEG_OUTFIFO_TH ((uint32_t)0x00000013U) 
#define MDMA_REQUEST_JPEG_OUTFIFO_NE ((uint32_t)0x00000014U) 
#define MDMA_REQUEST_JPEG_END_CONVERSION ((uint32_t)0x00000015U) 
#define MDMA_REQUEST_QUADSPI_FIFO_TH ((uint32_t)0x00000016U) 
#define MDMA_REQUEST_QUADSPI_TC ((uint32_t)0x00000017U) 
#define MDMA_REQUEST_DMA2D_CLUT_TC ((uint32_t)0x00000018U) 
#define MDMA_REQUEST_DMA2D_TC ((uint32_t)0x00000019U) 
#define MDMA_REQUEST_DMA2D_TW ((uint32_t)0x0000001AU)
#if defined(DSI)
#define MDMA_REQUEST_DSI_TEARINGE_FFECT ((uint32_t)0x0000001BU) 
#define MDMA_REQUEST_DSI_END_REFRESH ((uint32_t)0x0000001CU) 
#endif /* DSI */
#define MDMA_REQUEST_SDMMC1_ED_DATA ((uint32_t)0x0000001DU) 
#define MDMA_REQUEST_SW ((uint32_t)0x40000000U)

MDMA软件触发

MDMA 配置为 MDMA_REQUEST_SW 软件触发时,可以通过函数
HAL_MDMA_GenerateSWRequest 产生触发请求,使用此函数要注意以下两个问题:
◆ 如果传输结束或者传输还没有启动,调用此函数会返回 error。
◆ 如果传输还在进行中断,调用此函数会返回 error,这次请求会被忽略。
◆ MDMA 配置为 MDMA_BUFFER_TRANSFER 模式,软件触发。
◆ 注册回调函数 HAL_MDMA_XFER_BUFFERCPLT_CB_ID。
◆ 调用函数 HAL_MDMA_Start_IT 会触发一次 BufferTransferLength 长度的数据传输。
◆ 传输结束会进入回调函数,用户可以在回调函数里面再次调用HAL_MDMA_GenerateSWRequest
启动传输。

MDMA的初始化流程总结

第 1 步:基本的初始化。
函数 HAL_MDMA_Init 配置 MDMA 的基本参数。
第 2 步:列表模式。
如果使用列表模式,用户可以使用函数 HAL_MDMA_LinkedList_CreateNode 创建节点,再通过函
数 HAL_MDMA_LinkedList_AddNode 将节点添加到列表里面。
第 3 步:查询模式。
函数 HAL_MDMA_Start 启动传输。
函数 HAL_MDMA_PollForTransfer 查询传输完成。
函数 HAL_MDMA_Abort 终止传输。
第 4 步:中断方式。
函数 HAL_NVIC_SetPriority 设置 MDMA 中断优先级。
函数 HAL_NVIC_EnableIRQ 使能中断。
函数 HAL_MDMA_Start_IT 启动中断传输。
MDMA 的中断服务程序 MDMA_IRQHandler 里面调用 HAL_MDMA_IRQHandler,如果用户
注册了各种回调函数,会在此函数里面执行。
函数 HAL_MDMA_Abort_IT 可以终止 MDMA 中断传输,终止完成后,会调用回调函数XferAbortCallback(如果设置了的话)
第 5 步:中断回调函数。
函数 HAL_MDMA_RegisterCallback 注册回调函数,函数 HAL_MDMA_UnRegisterCallback 取消
注册回调函数。
XferCpltCallback : 传输完成回调。
XferBufferCpltCallback : buffer 传输完成回调。
XferBlockCpltCallback : block 传输完成回调。
XferRepeatBlockCpltCallback : repeated block 传输完成回调。
XferErrorCallback : 传输错误回调。
XferAbortCallback : 传输终止回调。

以下是在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、付费专栏及课程。

余额充值