yangskyhigh 发表于 2020-4-6 14:10:32

【小经验】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标记,而不处理;

eric2013 发表于 2020-4-7 07:33:22

谢谢楼主分享。

王海靖 发表于 2020-4-7 08:46:39

多谢楼主分享

ruboss 发表于 2020-4-8 10:34:05

半传输中断分割缓冲区这个思路很强

fengqiyunyong52 发表于 2020-4-21 10:27:19

楼主能发一份源码参考参考吗?谢谢。

萌军总司令 发表于 2020-4-21 12:20:53

半传输中断分割缓冲区
楼主,我要赞美你!

jackfrost 发表于 2020-4-21 18:21:51

学习了,感谢楼主的分享

waterx3 发表于 2021-5-21 16:26:56

HAL_UART_ReceiverTimeout_Config函数中第2个参数是超时判断阈值,其时间单位为 (1/串口波特率)秒;这个怎么和220匹配的?:o

he2002512 发表于 2021-6-16 08:11:38

一直没有数据,超时中断会周期中断吗?

进阶的熊猫 发表于 2023-4-17 17:08:32

感谢分享。

jyklll 发表于 2023-7-31 18:08:43

楼主厉害,请教个问题:串口具体是如何检测超时的呢?手册中只提到从停止位开始计数,那在不超时的情况下什么时候停止计数呢?每一次计数都必须检测到停止位才开始吗?
我用H750作主机,使用串口空闲中断+DMA来接收来自从机的不定长数据,为了确保传输完成,我在空闲中断里添加了DMA标志位等待,同时为了防止DMA数据过短无法触发DMA标志位导致无限等待,所以添加了RTOF超时标志位,但是遇到一个问题:故意断开从机后,偶尔会发生RTOF标志位无法重新置1的现象,导致我在接收中断里无限循环等待RTOF标志位。
我推测是因为RTOF标志位只有在检测到停止位之后才开始计数,在超时前检测到起始位就停止计数,所以如果H750在接收数据时,破坏这次接收,比如突然断开从机,使H750只检测到起始位,却没有检测到停止位,会导致RTOF始终为0,只有再进行一次完整的数据接收才会重新置一。
楼主有遇到过这类问题吗?我目前还没想到高效的解决办法,如果RTOF不能用,就只能用定时器或者简单延时来判断超时了:dizzy:

Devil_920 发表于 2023-8-1 17:33:53

学习了,根据楼主的思路实现了一把,效果非常过,不管怎么整,串口依旧非常稳定。感谢!

qq1646544 发表于 2023-8-3 13:58:47

Devil_920 发表于 2023-8-1 17:33
学习了,根据楼主的思路实现了一把,效果非常过,不管怎么整,串口依旧非常稳定。感谢!

求一份源码学习一下

Devil_920 发表于 2023-8-4 08:55:46

qq1646544 发表于 2023-8-3 13:58
求一份源码学习一下

抱歉,公司项目上的,暂时没法拿出来。楼主已经把思路写的非常好了

971503750 发表于 2024-1-30 09:25:36

您好!俺想求一份源码学习一下,刚学习,有挺多没懂的。。。

Yhlr 发表于 2024-2-5 09:54:52

学习一下,针对超过DMA缓存区大小的处理方式

旮旯旭 发表于 2024-2-6 08:57:55

jyklll 发表于 2023-7-31 18:08
楼主厉害,请教个问题:串口具体是如何检测超时的呢?手册中只提到从停止位开始计数,那在不超时的情况下什 ...

你断开串口线应该进了错误中断了,需要重启接收

jackhbkarm 发表于 2024-2-6 08:59:28

感谢分享,学习学习下

VDVA 发表于 2024-3-7 14:28:09

我按照您的思路进行了一个简单的尝试写RTX5 消息队列+ UART1 DMA 双缓存 超时中断,遇到问题,有时候会丢帧,还请您帮忙看一下问题在哪里
页: [1]
查看完整版本: 【小经验】STM32H743/750系统中使用串口DMA和超时中断接收任意长度数据