【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减轻负担,不用频繁的进入中断,只是我们在编写代码的时候,还是需要注意下何时清除状态位。
空闲中断的优势还是相当明显的 DMA的硬件搬运数据效率确实很高,要学会好好运用这个功能。 我也喜欢使用DMA做为串口收发的外设接口。
记忆花园 发表于 2025-8-25 17:41
空闲中断的优势还是相当明显的
啊 非常可观 cooldog123pp 发表于 2025-8-25 18:29
DMA的硬件搬运数据效率确实很高,要学会好好运用这个功能。
对对,效果很好 发光的梦 发表于 2025-8-26 10:00
我也喜欢使用DMA做为串口收发的外设接口。
嗯嗯 感谢 等等我弄好了测评,把代码放出来,现在代码写的有些乱 持续接收,避免缓冲区溢出 合理设置中断优先级,以避免高优先级中断过多地打断低优先级中断的执行。 串口接收线 在一段时间内无数据输入 配置DMA控制器以使用串口的接收和发送数据寄存器作为源地址或目的地址。 适用于不定长数据 总线空闲时自动触发,无需超时判断 ccook11 发表于 2025-9-4 16:39
总线空闲时自动触发,无需超时判断
这个不是需要等一两个字节时间才会触发空闲中断 pmp 发表于 2025-9-4 14:09
适用于不定长数据
对啊 很方便得 i1mcu 发表于 2025-9-2 16:18
持续接收,避免缓冲区溢出
嗯嗯 下次弄个环形队列接收,不过目前还没有尝试 发光的梦 发表于 2025-8-26 10:00
我也喜欢使用DMA做为串口收发的外设接口。
对啊 很方便得 串口接收也是系统重要负荷之一 空闲中断 在串口接收完一帧数据后触发 通过DMA同时处理ADC、SPI等外设数据,提升系统实时性。
页:
[1]
2