本帖最后由 聪聪哥哥 于 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需要发送字节个数
- *
- * @retval None
- */
- 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
- *
- * @retval None
- */
- 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减轻负担,不用频繁的进入中断,只是我们在编写代码的时候,还是需要注意下何时清除状态位。
|