聪聪哥哥 发表于 2025-8-25 16:51

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

本帖最后由 聪聪哥哥 于 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对应的串口通道:

每一个 DMA 通道都有三种类型的中断事件,分别是:传输过半(HT)、传输完成(TC)、传输错误(TE)。
(一)传输过半的中断事件标志位为HT**,中断使能控制位为 HTINTEN
(二)传输完成的中断事件标志位为TC**,中断使能控制位为TCINTEN
(三)传输错误的中断事件标志位为TERR**,中断使能控制位为TERRINTEN
这里为了方便,我只编写和调试了传输完成(TC)的处理部分
二:软件编写过程如下:
2.1 串口1使能DMA发送配置过程:
初始化部分:
      RCM_EnableAHBPeriphClock(RCM_AHB_PERIPH_DMA1);      
      dmaConfigStruct.bufferSize          = sendlength1;
      dmaConfigStruct.memoryDataSize      = DMA_MEMORY_DATA_SIZE_BYTE;
      dmaConfigStruct.memoryInc         = DMA_MEMORY_INC_ENABLE;
      dmaConfigStruct.loopMode            = DMA_MODE_NORMAL;
      dmaConfigStruct.peripheralBaseAddr= (uint32_t) (&(USART1->DATA));
      dmaConfigStruct.peripheralDataSize= DMA_PERIPHERAL_DATA_SIZE_BYTE;
      dmaConfigStruct.peripheralInc       = DMA_PERIPHERAL_INC_DISABLE;
      dmaConfigStruct.priority            = DMA_PRIORITY_HIGH;
      /* USART TX DMA*/
      dmaConfigStruct.dir               = DMA_DIR_PERIPHERAL_DST;
      dmaConfigStruct.memoryBaseAddr      = (uint32_t)SendBuffer1;
      DMA_Config(DMA1_Channel4, &dmaConfigStruct);
      USART_EnableDMA(USART1,USART_DMA_TX);
      DMA_Enable(DMA1_Channel4);
//      USART_EnableDMA(USART1,USART_DMA_TX_RX);
      while(USART_ReadStatusFlag(USART1, USART_FLAG_TXC) == RESET)
      {
      }
      while(DMA_ReadStatusFlag(DMA1_FLAG_TC4) == RESET)
      {
      }
      /* Clear USART DMA flags*/
      DMA_ClearStatusFlag(DMA1_FLAG_TC4);
      USART_ClearStatusFlag(USART1, USART_FLAG_TXC);重新编写串口1的发送函数,使用DMA发送方式:
/**
* @brief   bool SendDataToUSART1(int length)
*
* @param   DMA需要发送字节个数
*
* @retvalNone
*/
char SendDataToUSART1(int length)
{   
DMA_Disable(DMA1_Channel4);
DMA_ConfigDataNumber(DMA1_Channel4,length);
DMA_Enable(DMA1_Channel4);
return 1;
}这里当时自己写了一个DMA的初始化配置,存在问题,导致DMA的发送存在问题,后来在官方的帮助下,解决了,自己重写函数判断标志位的程序位置写错了。当我们使用启动DMA发送时,可以直接移植官方的DMA发送过程,然后再次发送时,可以使用我上面的代码就可以,亲测通过,很稳定。
2.2 DMA接收的配置过程:
// 接收DMA配置
      dmaConfigStruct.bufferSize          = reclength1;
      dmaConfigStruct.memoryDataSize      = DMA_MEMORY_DATA_SIZE_BYTE;
      dmaConfigStruct.memoryInc         = DMA_MEMORY_INC_ENABLE;
      dmaConfigStruct.loopMode            = DMA_MODE_NORMAL;
      dmaConfigStruct.peripheralBaseAddr= (uint32_t) (&(USART1->DATA));
      dmaConfigStruct.peripheralDataSize= DMA_PERIPHERAL_DATA_SIZE_BYTE;
      dmaConfigStruct.peripheralInc       = DMA_PERIPHERAL_INC_DISABLE;
      dmaConfigStruct.priority            = DMA_PRIORITY_HIGH;

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

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

void usart1_DMARX_callback(void)
{
      uint8_t data;
      if(USART_ReadIntFlag(USART1, USART_INT_IDLE) != RESET)
      {
                data = USART1->DATA;
                USART_ClearIntFlag(USART1,USART_INT_IDLE);
                // 实际接收到的数据 = 定义的接收数据大小 - 当前接收buffer剩余个数
                usart1_rx_len = sizeof(RecBuffer1) - DMA_ReadDataNumber(DMA1_Channel5);
                memcpy(SendBuffer1, RecBuffer1, usart1_rx_len);      
                SendDataToUSART1(usart1_rx_len);
                DMA_ClearStatusFlag(DMA1_FLAG_TC5);               
                DMA_Disable(DMA1_Channel5);                                                            // 关闭接收DMA
                DMA_ConfigDataNumber(DMA1_Channel5, sizeof(RecBuffer1));   // 重新写入要传输的数据数量
                DMA_Enable(DMA1_Channel5);
      }
}三:实测图片如下所示:


使用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做为串口收发的外设接口。

嗯嗯 感谢 等等我弄好了测评,把代码放出来,现在代码写的有些乱

i1mcu 发表于 2025-9-2 16:18

持续接收,避免缓冲区溢出            

earlmax 发表于 2025-9-2 18:51

合理设置中断优先级,以避免高优先级中断过多地打断低优先级中断的执行。

mattlincoln 发表于 2025-9-2 19:38

串口接收线 在一段时间内无数据输入

pentruman 发表于 2025-9-2 21:25

配置DMA控制器以使用串口的接收和发送数据寄存器作为源地址或目的地址。

pmp 发表于 2025-9-4 14:09

适用于不定长数据            

ccook11 发表于 2025-9-4 16:39

总线空闲时自动触发,无需超时判断

聪聪哥哥 发表于 2025-9-4 21:11

ccook11 发表于 2025-9-4 16:39
总线空闲时自动触发,无需超时判断

这个不是需要等一两个字节时间才会触发空闲中断

聪聪哥哥 发表于 2025-9-4 21:11

pmp 发表于 2025-9-4 14:09
适用于不定长数据

对啊 很方便得

聪聪哥哥 发表于 2025-9-4 21:12

i1mcu 发表于 2025-9-2 16:18
持续接收,避免缓冲区溢出

嗯嗯 下次弄个环形队列接收,不过目前还没有尝试

聪聪哥哥 发表于 2025-9-4 21:12

发光的梦 发表于 2025-8-26 10:00
我也喜欢使用DMA做为串口收发的外设接口。

对啊 很方便得

SpiritSong 发表于 2025-9-6 10:48

串口接收也是系统重要负荷之一

hearstnorman323 发表于 2025-9-6 16:56

空闲中断 在串口接收完一帧数据后触发

zerorobert 发表于 2025-9-6 18:44

通过DMA同时处理ADC、SPI等外设数据,提升系统实时性。
页: [1] 2
查看完整版本: 【APM32F402R Micro-EVB】05:DMA与空闲中断完成数据的收发