【小经验】STM32H743/750系统中使用串口DMA和超时中断接收任意长度数据
0. 背景STM32H743/750中串口具有DMA传输功能,配合超时中断可实现灵活接收数据,网上流传的方法一般是把DMA设置为常规模式,其缺点是一旦一次性要接收的数据超过DMA缓冲区大小,就无法正确处理。 我们可以将DMA设置为循环模式,再结合超时中断,可以解决接收数据超过DMA缓冲区大小的问题。 1. 基本设置 file:///C:/Users/ThinkPad/AppData/Local/Temp/msohtmlclip1/01/clip_image002.jpg 2. 修改代码 (1) 新建暂停DMA接收函数static HAL_StatusTypeDef HAL_UART_DMAStopRx(UART_HandleTypeDef *huart){ /* Stop UART DMA Rx request if ongoing */ if ((huart->RxState == HAL_UART_STATE_BUSY_RX) && (HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAR))) { CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAR); /* Abort the UART DMA Rx channel */ if (huart->hdmarx != NULL) { HAL_DMA_Abort(huart->hdmarx); } //UART_EndRxTransfer(huart); /* Disable RXNE, PE and ERR (Frame error, noise error, overrun error) interrupts */ CLEAR_BIT(huart->Instance->CR1, (USART_CR1_RXNEIE | USART_CR1_PEIE)); CLEAR_BIT(huart->Instance->CR3, USART_CR3_EIE); /* At end of Rx process, restore huart->RxState to Ready */ huart->RxState = HAL_UART_STATE_READY; } return HAL_OK;} (2) 开始接收函数,在其中配置超时处理:void User_UART_Start(uint8_t index){ if (index == Huart1_Index) { HAL_UART_ReceiverTimeout_Config(&huart1, 220); HAL_UART_EnableReceiverTimeout(&huart1); __HAL_UART_DISABLE_IT(uartHandle, UART_IT_ERR); __HAL_UART_DISABLE_IT(uartHandle, UART_IT_PE); /*Enable the TIMEOUT Interrupt*/ __HAL_UART_ENABLE_IT(uartHandle, UART_IT_RTO); HAL_UART_Receive_DMA (&huart1, User_Usart_Buffer.RxBuf, UART_RX_BUF_SIZE); }} HAL_UART_ReceiverTimeout_Config函数中第2个参数是超时判断阈值,其时间单位为 (1/串口波特率)秒; (3)中断处理流程1)修改DMA传输半完成、全部完成的回调函数:void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart){ if (huart == &huart1) { SCB_InvalidateDCache_by_Addr((uint32_t *)(User_Usart_Buffer.RxBuf), sizeof(User_Usart_Buffer.RxBuf) / 8); // 接收到数据处理函数 User_UART_RxCpltCallback_1(User_Usart_Buffer.RxBuf, sizeof(User_Usart_Buffer.RxBuf) / 2); Uart_Rcv_HalfRcv_1 = 1; }} void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){ if (huart == &huart1) { SCB_InvalidateDCache_by_Addr((uint32_t *)(&(User_Usart_Buffer.RxBuf.RxBuf) / 2])), sizeof(User_Usart_Buffer.RxBuf) / 8); // 接收到数据处理函数 User_UART_RxCpltCallback_1(&(User_Usart_Buffer.RxBuf.RxBuf) / 2]), sizeof(User_Usart_Buffer.RxBuf) / 2); Uart_Rcv_HalfRcv_1 = 0; } } 注:lUart_Rcv_HalfRcv_1为传输半完成、全部完成的标记;lUser_UART_RxCpltCallback_1为用户处理函数,传递参数是本次收到数据首地址和数据字节长度;l这两个回调函数的目的是将DMA接收缓冲区分为上下两个区间,每个区间接收满了可独立处理,又不影响接着接收另一个区间;lSCB_InvalidateDCache_by_Addr这个函数在打开了数据Dcache时要使用,否则DMA可以报告收到数据的字节长度,但读取的数据都是0x00; 2)增加一个超时中断处理函数,用于处理区间未存满的情况 void UsartReceive_TIMEOUT(UART_HandleTypeDef *huart){ uint16_t i = 0, len; if ((__HAL_UART_GET_FLAG(huart, UART_FLAG_RTOF) != RESET)) { if (huart->Instance == USART1) { __HAL_UART_CLEAR_FLAG(huart, UART_CLEAR_RTOF); i = huart->Instance->ISR; i = huart->Instance->RDR; i = __HAL_DMA_GET_COUNTER(huart1.hdmarx); // 剩余 HAL_UART_DMAStopRx(huart); // 接收到的数据长度 len = UART_RX_BUF_SIZE - i; if (i != UART_RX_BUF_SIZE) // 有数据 { if (Uart_Rcv_HalfRcv_1 > 0) { SCB_InvalidateDCache_by_Addr((uint32_t *)(&(User_Usart_Buffer.RxBuf.RxBuf) / 2])), sizeof(User_Usart_Buffer.RxBuf) / 8); // 接收到数据处理函数 User_UART_RxCpltCallback_1(&(User_Usart_Buffer.RxBuf), len - UART_RX_BUF_SIZE / 2); } else { SCB_InvalidateDCache_by_Addr((uint32_t *)(&(User_Usart_Buffer.RxBuf)), sizeof(User_Usart_Buffer.RxBuf) / 8); User_UART_RxCpltCallback_1(User_Usart_Buffer.RxBuf, len); } } else { __HAL_UART_CLEAR_OREFLAG(huart); } // 开始接收 User_UART_Start(Huart1_Index); Uart_Rcv_HalfRcv_1 = 0; } }} 注:lUart_Rcv_HalfRcv_1 为传输半完成、全部完成的标记,由此可知本次超时中断发生时,DMA接收到的数据的位置,是位于DMA接收区的前半区和后半区;l__HAL_DMA_GET_COUNTER函数读取的是DMA剩余空间长度;l在此中断处理函数中,会重新开始一次DMA接收;3) 修改串口中断服务函数 void USART1_IRQHandler(void){/* USER CODE BEGIN USART1_IRQn 0 */UsartReceive_TIMEOUT(&huart1);/* USER CODE END USART1_IRQn 0 */HAL_UART_IRQHandler(&huart1);/* USER CODE BEGIN USART1_IRQn 1 */ //UsartReceive_IDLE(&huart1); /* USER CODE END USART1_IRQn 1 */} 将超时处理函数放置于默认处理函数HAL_UART_IRQHandler之前,是因为在HAL_UART_IRQHandler函数中仅将超时中断做ERROR标记,而不处理;谢谢楼主分享。 多谢楼主分享 半传输中断分割缓冲区这个思路很强 楼主能发一份源码参考参考吗?谢谢。 半传输中断分割缓冲区
楼主,我要赞美你! 学习了,感谢楼主的分享 HAL_UART_ReceiverTimeout_Config函数中第2个参数是超时判断阈值,其时间单位为 (1/串口波特率)秒;这个怎么和220匹配的?:o 一直没有数据,超时中断会周期中断吗? 感谢分享。 楼主厉害,请教个问题:串口具体是如何检测超时的呢?手册中只提到从停止位开始计数,那在不超时的情况下什么时候停止计数呢?每一次计数都必须检测到停止位才开始吗?
我用H750作主机,使用串口空闲中断+DMA来接收来自从机的不定长数据,为了确保传输完成,我在空闲中断里添加了DMA标志位等待,同时为了防止DMA数据过短无法触发DMA标志位导致无限等待,所以添加了RTOF超时标志位,但是遇到一个问题:故意断开从机后,偶尔会发生RTOF标志位无法重新置1的现象,导致我在接收中断里无限循环等待RTOF标志位。
我推测是因为RTOF标志位只有在检测到停止位之后才开始计数,在超时前检测到起始位就停止计数,所以如果H750在接收数据时,破坏这次接收,比如突然断开从机,使H750只检测到起始位,却没有检测到停止位,会导致RTOF始终为0,只有再进行一次完整的数据接收才会重新置一。
楼主有遇到过这类问题吗?我目前还没想到高效的解决办法,如果RTOF不能用,就只能用定时器或者简单延时来判断超时了:dizzy: 学习了,根据楼主的思路实现了一把,效果非常过,不管怎么整,串口依旧非常稳定。感谢! Devil_920 发表于 2023-8-1 17:33
学习了,根据楼主的思路实现了一把,效果非常过,不管怎么整,串口依旧非常稳定。感谢!
求一份源码学习一下 qq1646544 发表于 2023-8-3 13:58
求一份源码学习一下
抱歉,公司项目上的,暂时没法拿出来。楼主已经把思路写的非常好了 您好!俺想求一份源码学习一下,刚学习,有挺多没懂的。。。 学习一下,针对超过DMA缓存区大小的处理方式 jyklll 发表于 2023-7-31 18:08
楼主厉害,请教个问题:串口具体是如何检测超时的呢?手册中只提到从停止位开始计数,那在不超时的情况下什 ...
你断开串口线应该进了错误中断了,需要重启接收 感谢分享,学习学习下 我按照您的思路进行了一个简单的尝试写RTX5 消息队列+ UART1 DMA 双缓存 超时中断,遇到问题,有时候会丢帧,还请您帮忙看一下问题在哪里
页:
[1]