【DMA】DMA入门:理解DMA与CPU的并行

目录

[1. 一个简单实验](#1. 一个简单实验)

[mian.c 主要代码](#mian.c 主要代码)

DMA2通道2中断服务函数

串口接收完成回调函数

[2. 上述实验补充](#2. 上述实验补充)

[1. 区别两种模式的串口接收:"基于中断的串口接收" 和 "基于DMA的串口接收":](#1. 区别两种模式的串口接收:“基于中断的串口接收” 和 “基于DMA的串口接收”:)

[1. HAL_UART_Receive_IT](#1. HAL_UART_Receive_IT)

[2. HAL_UART_Receive_DMA](#2. HAL_UART_Receive_DMA)

[2. 其他相关函数](#2. 其他相关函数)

[1. USARTx_IRQHandler](#1. USARTx_IRQHandler)

[2. HAL_UART_RxCpltCallback](#2. HAL_UART_RxCpltCallback)

[3. CPU在 执行指令 与 执行访存、数据转运 的速度差异](#3. CPU在 执行指令 与 执行访存、数据转运 的速度差异)


1. 一个简单实验

使用串口DMA接收测试DMA中断何时进入:

mian.c 主要代码

cpp 复制代码
// DMA接收5个字节
HAL_UART_Receive_DMA(&huart1,buffer_uart,5);
/* USER CODE BEGIN 2 */
uint32_t src_data[50] = {0x12};
/* USER CODE END 2 */

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
      // 通过硬件io的翻转,我们可以知道CPU执行主要任务的时间有多久
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_3,GPIO_PIN_SET);
    
    // 模拟CPU在执行任务
    uint32_t i = 0;
for ( i = 0 ; i < 50 ; i++ )    
{
    *(uint32_t *)(DEST_ADDRESS + i ) = src_data[i];
}

HAL_GPIO_WritePin(GPIOB,GPIO_PIN_3,GPIO_PIN_RESET);

/* USER CODE BEGIN 3 */
}

DMA2通道2中断服务函数

cpp 复制代码
/**
  * @brief This function handles DMA2 stream2 global interrupt.
  */
void DMA2_Stream2_IRQHandler(void)
{
  /* USER CODE BEGIN DMA2_Stream2_IRQn 0 */
  HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,GPIO_PIN_SET);
  /* USER CODE END DMA2_Stream2_IRQn 0 */
  HAL_DMA_IRQHandler(&hdma_usart1_rx);
  /* USER CODE BEGIN DMA2_Stream2_IRQn 1 */
  HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,GPIO_PIN_RESET);
  /* USER CODE END DMA2_Stream2_IRQn 1 */
}

串口接收完成回调函数

cpp 复制代码
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  /* Prevent unused argument(s) compilation warning */
  UNUSED(huart);
  HAL_GPIO_WritePin(GPIOB,GPIO_PIN_4,GPIO_PIN_SET);
  //HAL_UART_Receive_IT(&huart1,buffer_uart,5);
  HAL_UART_Receive_DMA(&huart1,buffer_uart,5);
  HAL_GPIO_WritePin(GPIOB,GPIO_PIN_4,GPIO_PIN_RESET);
}

通过 XCOM 串口调试助手发送5个字节,通过逻辑分析仪得到硬件io翻转时间:

2. 上述实验补充

1. 区别两种模式的 串口 接收:"基于中断的串口接收" 和 "基于 DMA 的串口接收":

1. HAL_UART_Receive_IT

  • 函数功能:配置串口接收模式为++基于中断模式的串口接收++

    调用HAL_UART_Receive_IT 后,串口会进入中断接收模式。在此模式下,每当串口接收到一个字节的数据,都会触发一次串口中断,CPU会暂停当前工作并进入USARTx_IRQHandler中断处理函数。在该函数中,CPU会将接收到的1字节数据从串口的接收数据寄存器搬运到指定的缓冲区。

    详细过程:

    1. 当串口外设未接收到数据时,CPU会执行主循环中的业务逻辑。

    2. 当串口接收到一个字节的数据,硬件会自动将串口的RXNE标志位置位。该标志位会触发串口中断。

    3. CPU会进入USARTx_IRQHandler中断处理函数,响应中断并将接收到的一字节数据从串口的接收数据寄存器搬运到SRAM中的指定缓冲区。

    4. CPU将数据搬运完成后,硬件会清除RXNE标志位,CPU返回并执行之前被中断打断的业务逻辑。

    5. 每当串口接收到一个字节的数据后都会重复步骤2~4,直到接收完指定字节的数据。

    6. 接收完成后,会触发并进入HAL_UART_RxCpltCallback中断,在此回调函数内部可对接收到的、指定字节数的数据包进行解包。

    性能分析:

    • 在这种模式下,CPU需要手动处理每个字节的数据搬运工作。相比于CPU执行指令的速度,CPU访存和数据搬运耗时较长;并且CPU在搬运数据时并没有充分发挥其计算能力。总的来说,这种模式下得CPU得时间消耗较高,且并未高效利用其计算能力。
  • 函数调用示例:

    cpp 复制代码
    HAL_UART_Receive_IT(&huart1, buffer_uart, 5);

    该调用配置串口1为中断接收模式,接收5个字节后触发串口接收完成中断,接收到的数据被存储在buffer_uart中

2. HAL_UART_Receive_DMA

  • 配置串口接收模式为++基于++ ++DMA++ ++的串口接收++

    调用HAL_UART_Receive_DMA 后**,**串口被配置为DMA模式接收。在DMA模式下,每当串口接收到一个字节数据时,DMA会自动将接收到的一字节数据从串口接收寄存器搬运到指定的缓冲区,无需CPU干预。

    相比于中断模式的串口接收,CPU不需要在每次接收到数据时进入中断服务程序去处理数据搬运工作,而是专注于执行主循环中的业务逻辑。只有当接收到指定数量的数据后,CPU才会进入HAL_UART_RxCpltCallback回调函数处理接收到的完整数据包。

    整个过程分工明确、物尽其用:DMA做它擅长的数据搬运工作,CPU充分发挥其算力优势。

    详细过程:

    配置完成后,当串口接收到一字节数据,串口外设的RXNE标志位会置位,此置位检测到此置位后会自动将串口接收寄存器中的一字节数据搬运到指定缓冲区中。此过程中CPU无需进行任何干预,它可以专注于主循环中的其他任务。

    当串口接收完指定字节数的数据后,会触发串口接收完成中断并进入HAL_UART_RxCpltCallback回调函数,处理接收到的完整数据包。
    性能分析:

    相比于中断模式,CPU不需要处理每个字节的接收、搬运任务。DMA完成了数据搬运,避免了CPU在每个字节的接收和搬运过程中停顿,从而能更高效地执行其他业务逻辑。

  • 函数调用示例:

    cpp 复制代码
    HAL_UART_Receive_DMA(&huart1, buffer_uart, 5);

    该调用配置串口1为DMA接收模式,接收5个字节数据后触发串口接收完成中断,接收的数据被存储在buffer_uart中。

2. 其他相关函数

1. USARTx_IRQHandler

  • 功能:这是于USARTx外设相关的中断服务函数,当USARTx外设发生中断事件时(例如该串口接收到数据、发送完成、发生错误等),硬件会触发中断并自动调用此函数

  • 在本实验中的应用:

    当配置串口为中断模式接收时,每当串口接收到一个字节数据时,都会触发RXNE标志位并进入此中断处理函数。

    HAL_UART_Receive_IT(&huart1, buffer_uart, 5); 为例,每次串口1接收到一个字节数据时,串口外设的RXNE标志位会被置位,并触发中断,CPU将进入USARTx_IRQHandler中断函数中完成数据的搬运工作,接收5次数据就进入这个中断搬运5次。

2. HAL_UART_RxCpltCallback

  • 功能:这是串口接收完成回调函数。

  • 在本实验中的应用:

    无论是中断模式还是DMA模式下的串口接收,在调用相应函数时都会指定接收的数据字节个数。当串口接收到指定数据量的字节后,会触发接收完成中断,并进入此回调函数。在回调函数内,应用程序可以处理接收到的完整数据包,进行解析或其他相关操作。

    接收完指定字节数后串口会关闭接收,如果后续仍有串口接收需求的话需要在此回调函数中再次开启对应模式的串口接收。例如:

cpp 复制代码
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  /* Prevent unused argument(s) compilation warning */
  UNUSED(huart);
  HAL_GPIO_WritePin(GPIOB,GPIO_PIN_4,GPIO_PIN_SET);
  //HAL_UART_Receive_IT(&huart1,buffer_uart,5); 重启中断模式串口接收
  //HAL_UART_Receive_DMA(&huart1,buffer_uart,5); 重启DMA模式串口接收
  HAL_GPIO_WritePin(GPIOB,GPIO_PIN_4,GPIO_PIN_RESET);
}

3. CPU在 执行指令 与 执行访存、数据转运 的速度差异

CPU执行指令的时间很短,并且在执行指令时能充分发挥CPU的算力优势;但是CPU进行访存和数据转运的时间相对较长,并且这种机械的搬运工作并不能发挥CPU的算力优势。

我们更希望CPU专注于程序内部业务逻辑相关指令的执行,访存及数据转运的工作不涉及复杂计算或决策,这种不能体现CPU算力优势的工作应该交给DMA去处理。

相关推荐
liujing102329294 小时前
Day08_单片机-ADC和DMA
单片机·嵌入式硬件
华清远见IT开放实验室4 小时前
华清远见携STM32全矩阵产品及创新机器狗亮相2025 STM32研讨会,共启嵌入式技术探索新程
linux·人工智能·stm32·单片机·嵌入式硬件·虚拟仿真
蜀黍@猿5 小时前
【GD32】中断系统
单片机·嵌入式硬件
小莞尔6 小时前
【51单片机】【protues仿真】基于51单片机心形流水灯系统
c语言·stm32·单片机·嵌入式硬件·51单片机
沐欣工作室_lvyiyi6 小时前
基于单片机的老年人身体健康蓝牙监测手环(论文+源码)
stm32·单片机·嵌入式硬件·毕业设计·老年人监测
the sun347 小时前
模电基础:静态工作点稳定的典型电路
单片机·嵌入式硬件
Skylar_.7 小时前
ARM(15) - LCD(2)显示字母数字+touch
arm开发·单片机·嵌入式硬件
2301_1472583697 小时前
STM32 单片机 - ADC
stm32·单片机·嵌入式硬件
大聪明-PLUS9 小时前
关于Linux中的软链接和硬链接
linux·嵌入式·arm·smarc