[APM32F4] 【APM32F402R Micro-EVB】05:DMA与空闲中断完成数据的收发

[复制链接]
聪聪哥哥 发表于 2025-8-25 16:51 | 显示全部楼层 |阅读模式
本帖最后由 聪聪哥哥 于 2025-8-22 20:19 编辑

一:串口中断接收的通讯方式:
1.0 在APM32F402中,串口接收数据时的经常使用的方式就是使用CPU的串口中断的方式,即串口每次接收到一个字节时候,就会产生串口中断,将接收到数据放到接收数组内,同时清除标志位。这种接收方式比较简单,之前的学习记录中也尝试过该接收方式,这种接收方式比较浪费CPU的资源。在串口通讯中,经常会遇到接收不定长的数据,这时候没有必要采用上述方式进行数据的接收处理,使用官方提供的串口接收函数对串口接收中断的,再到中断处理函数,CPU需要执行很多的程序,频繁的中断为CPU的运行增加了不少的负担,也可能会出现接收出错的情况,而且不确定接收数据长度时,也不确定处理函数应该何时进行处理。
  为了解决上述问题,我们尝试使用串口的空闲中断与DMA接收数据,即CPU触发的空闲中断时,告知CPU本次接收数据完成了,可以进行下一步骤的处理;当然串口中断判断的依据,我个人的理解就是:使用串口与DMA接收字节数据时,当串口检测到在1-2个字节通讯时间内,串口没有接收到数据时,就会判定串口空闲了,使用DMA将数据拷贝到其他数据内进行处理即可。
1.1  我们先来了解一下APM32F402的DMA知识:
DMA(Direct Memory Access:直接存储器存取)在无须 CPU 干预的情况下,可实现外设与存储器或存储器与存储器之间数据的高速传输,从而节省 CPU 资源来做其他操作。
产品一共有两个 DMA 控制器,DMA1有7个通道,DMA2有5个通道。每个通道可管理多个 DMA 请求,但每个通道同一时刻只能响应1个 DMA 请求。每个通道可设置优先级,仲裁器可根据通道的优先级协调各个DMA通道对应的 DMA请求的优先级。
1.2 DMA的主要特征:
  (一)  DMA1有7个通道,DMA2有5个通道
(二)数据传输有三种:外设到存储器、存储器到外设、存储器到存储器
(三)每个通道都有连接专门的硬件 DMA 请求
(四)多个请求同时发生时支持软件优先级和硬件优先级
(五)每个通道都有3个事件标志和独立中断
(六)支持循环传输模式
(七)数据传输数目可编程,最大到 65535
1.3 串口1对应的串口通道:
05-1.png
每一个 DMA 通道都有三种类型的中断事件,分别是:传输过半(HT)、传输完成(TC)、传输错误(TE)。
(一)传输过半的中断事件标志位为HT**,中断使能控制位为 HTINTEN
(二)传输完成的中断事件标志位为TC**,中断使能控制位为TCINTEN
(三)传输错误的中断事件标志位为TERR**,中断使能控制位为TERRINTEN
这里为了方便,我只编写和调试了传输完成(TC)的处理部分
二:软件编写过程如下:
2.1 串口1使能DMA发送配置过程:
初始化部分:
  1.         RCM_EnableAHBPeriphClock(RCM_AHB_PERIPH_DMA1);      
  2.         dmaConfigStruct.bufferSize          = sendlength1;
  3.         dmaConfigStruct.memoryDataSize      = DMA_MEMORY_DATA_SIZE_BYTE;
  4.         dmaConfigStruct.memoryInc           = DMA_MEMORY_INC_ENABLE;
  5.         dmaConfigStruct.loopMode            = DMA_MODE_NORMAL;
  6.         dmaConfigStruct.peripheralBaseAddr  = (uint32_t) (&(USART1->DATA));
  7.         dmaConfigStruct.peripheralDataSize  = DMA_PERIPHERAL_DATA_SIZE_BYTE;
  8.         dmaConfigStruct.peripheralInc       = DMA_PERIPHERAL_INC_DISABLE;
  9.         dmaConfigStruct.priority            = DMA_PRIORITY_HIGH;
  10.         /* USART TX DMA*/
  11.         dmaConfigStruct.dir                 = DMA_DIR_PERIPHERAL_DST;
  12.         dmaConfigStruct.memoryBaseAddr      = (uint32_t)SendBuffer1;
  13.         DMA_Config(DMA1_Channel4, &dmaConfigStruct);
  14.         USART_EnableDMA(USART1,USART_DMA_TX);
  15.         DMA_Enable(DMA1_Channel4);
  16. //        USART_EnableDMA(USART1,USART_DMA_TX_RX);
  17.         while(USART_ReadStatusFlag(USART1, USART_FLAG_TXC) == RESET)
  18.         {
  19.         }
  20.         while(DMA_ReadStatusFlag(DMA1_FLAG_TC4) == RESET)
  21.         {
  22.         }
  23.         /* Clear USART DMA flags*/
  24.         DMA_ClearStatusFlag(DMA1_FLAG_TC4);
  25.         USART_ClearStatusFlag(USART1, USART_FLAG_TXC);
重新编写串口1的发送函数,使用DMA发送方式:
  1. /**
  2. * @brief   bool SendDataToUSART1(int length)
  3. *
  4. * @param   DMA需要发送字节个数
  5. *
  6. * @retval  None
  7. */
  8. char SendDataToUSART1(int length)
  9. {   
  10.   DMA_Disable(DMA1_Channel4);
  11.   DMA_ConfigDataNumber(DMA1_Channel4,length);
  12.   DMA_Enable(DMA1_Channel4);
  13.   return 1;
  14. }
这里当时自己写了一个DMA的初始化配置,存在问题,导致DMA的发送存在问题,后来在官方的帮助下,解决了,自己重写函数判断标志位的程序位置写错了。当我们使用启动DMA发送时,可以直接移植官方的DMA发送过程,然后再次发送时,可以使用我上面的代码就可以,亲测通过,很稳定。
2.2 DMA接收的配置过程:
  1. // 接收DMA配置
  2.         dmaConfigStruct.bufferSize          = reclength1;
  3.         dmaConfigStruct.memoryDataSize      = DMA_MEMORY_DATA_SIZE_BYTE;
  4.         dmaConfigStruct.memoryInc           = DMA_MEMORY_INC_ENABLE;
  5.         dmaConfigStruct.loopMode            = DMA_MODE_NORMAL;
  6.         dmaConfigStruct.peripheralBaseAddr  = (uint32_t) (&(USART1->DATA));
  7.         dmaConfigStruct.peripheralDataSize  = DMA_PERIPHERAL_DATA_SIZE_BYTE;
  8.         dmaConfigStruct.peripheralInc       = DMA_PERIPHERAL_INC_DISABLE;
  9.         dmaConfigStruct.priority            = DMA_PRIORITY_HIGH;

  10.         /* USART RX DMA*/
  11.         dmaConfigStruct.dir                 = DMA_DIR_PERIPHERAL_SRC;
  12.         dmaConfigStruct.memoryBaseAddr      = (uint32_t)RecBuffer1;
  13.         DMA_Config(DMA1_Channel5, &dmaConfigStruct);

  14.         DMA_Enable(DMA1_Channel5);
  15.         
  16.         /* Enable USART DMA RX request*/
  17.         USART_EnableDMA(USART1,USART_DMA_RX);
这里需要注意的是,需要使能串口1的空闲中断,如下所示:
  1. USART_EnableInterrupt(USART1, USART_INT_RXBNE);//开启串口接收中断
  2. USART_EnableInterrupt(USART1, USART_INT_IDLE);  //开启空闲中断
然后编写空闲中断的回调函数就可以了。如下所示:
  1. /**
  2. * @brief   void usart1_DMARX_callback(void)
  3. *
  4. * @param   
  5. *
  6. * @retval  None
  7. */

  8. void usart1_DMARX_callback(void)
  9. {
  10.         uint8_t data;
  11.         if(USART_ReadIntFlag(USART1, USART_INT_IDLE) != RESET)
  12.         {
  13.                 data = USART1->DATA;
  14.                 USART_ClearIntFlag(USART1,USART_INT_IDLE);
  15.                 // 实际接收到的数据 = 定义的接收数据大小 - 当前接收buffer剩余个数
  16.                 usart1_rx_len = sizeof(RecBuffer1) - DMA_ReadDataNumber(DMA1_Channel5);
  17.                 memcpy(SendBuffer1, RecBuffer1, usart1_rx_len);        
  18.                 SendDataToUSART1(usart1_rx_len);
  19.                 DMA_ClearStatusFlag(DMA1_FLAG_TC5);               
  20.                 DMA_Disable(DMA1_Channel5);                                                            // 关闭接收DMA
  21.                 DMA_ConfigDataNumber(DMA1_Channel5, sizeof(RecBuffer1));     // 重新写入要传输的数据数量
  22.                 DMA_Enable(DMA1_Channel5);
  23.         }
  24. }
三:实测图片如下所示:
05-2.png
05-3.png
使用DMA+空闲中断的方式,在接收不定长的数据时,还有有一定的优越性的,可以方便为CPU减轻负担,不用频繁的进入中断,只是我们在编写代码的时候,还是需要注意下何时清除状态位。
记忆花园 发表于 2025-8-25 17:41 | 显示全部楼层
空闲中断的优势还是相当明显的
cooldog123pp 发表于 2025-8-25 18:29 | 显示全部楼层
DMA的硬件搬运数据效率确实很高,要学会好好运用这个功能。
发光的梦 发表于 2025-8-26 10:00 | 显示全部楼层
我也喜欢使用DMA做为串口收发的外设接口。
 楼主| 聪聪哥哥 发表于 2025-8-26 11:09 | 显示全部楼层
记忆花园 发表于 2025-8-25 17:41
空闲中断的优势还是相当明显的

啊 非常可观
 楼主| 聪聪哥哥 发表于 2025-8-26 11:09 | 显示全部楼层
cooldog123pp 发表于 2025-8-25 18:29
DMA的硬件搬运数据效率确实很高,要学会好好运用这个功能。

对对,效果很好
 楼主| 聪聪哥哥 发表于 2025-8-26 11:10 | 显示全部楼层
发光的梦 发表于 2025-8-26 10:00
我也喜欢使用DMA做为串口收发的外设接口。

嗯嗯 感谢 等等我弄好了测评,把代码放出来,现在代码写的有些乱
您需要登录后才可以回帖 登录 | 注册

本版积分规则

93

主题

238

帖子

1

粉丝
快速回复 在线客服 返回列表 返回顶部