硬汉嵌入式论坛

 找回密码
 立即注册
查看: 10829|回复: 18
收起左侧

[UART] 【小经验】STM32H743/750系统中使用串口DMA和超时中断接收任意长度数据

  [复制链接]

12

主题

75

回帖

111

积分

初级会员

积分
111
发表于 2020-4-6 14:10:32 | 显示全部楼层 |阅读模式
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[Huart1_Index].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[Huart1_Index].RxBuf),
                                     sizeof(User_Usart_Buffer[Huart1_Index].RxBuf) / 8);
        // 接收到数据处理函数
        User_UART_RxCpltCallback_1(User_Usart_Buffer[Huart1_Index].RxBuf,
                                   sizeof(User_Usart_Buffer[Huart1_Index].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[Huart1_Index].RxBuf[sizeof(User_Usart_Buffer[Huart1_Index].RxBuf) / 2])),
                                     sizeof(User_Usart_Buffer[Huart1_Index].RxBuf) / 8);
        // 接收到数据处理函数
        User_UART_RxCpltCallback_1(&(User_Usart_Buffer[Huart1_Index].RxBuf[sizeof(User_Usart_Buffer[Huart1_Index].RxBuf) / 2]),
                                   sizeof(User_Usart_Buffer[Huart1_Index].RxBuf) / 2);
        Uart_Rcv_HalfRcv_1 = 0;
    }
}
注:
l  Uart_Rcv_HalfRcv_1为传输半完成、全部完成的标记;
l  User_UART_RxCpltCallback_1为用户处理函数,传递参数是本次收到数据首地址和数据字节长度;
l  这两个回调函数的目的是将DMA接收缓冲区分为上下两个区间,每个区间接收满了可独立处理,又不影响接着接收另一个区间;
l  SCB_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[Huart1_Index].RxBuf[sizeof(User_Usart_Buffer[Huart1_Index].RxBuf) / 2])),
                                                 sizeof(User_Usart_Buffer[Huart1_Index].RxBuf) / 8);
                    // 接收到数据处理函数
                    User_UART_RxCpltCallback_1(&(User_Usart_Buffer[Huart1_Index].RxBuf[UART_RX_BUF_SIZE / 2]),
                                               len - UART_RX_BUF_SIZE / 2);
                }
                else
                {
                    SCB_InvalidateDCache_by_Addr((uint32_t *)(&(User_Usart_Buffer[Huart1_Index].RxBuf[0])),
                                                 sizeof(User_Usart_Buffer[Huart1_Index].RxBuf) / 8);
                    User_UART_RxCpltCallback_1(User_Usart_Buffer[Huart1_Index].RxBuf, len);
                }
            }
            else
            {
                __HAL_UART_CLEAR_OREFLAG(huart);
            }
            // 开始接收
            User_UART_Start(Huart1_Index);
            Uart_Rcv_HalfRcv_1 = 0;
        }
    }
}
注:
l  Uart_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标记,而不处理;

评分

参与人数 5金币 +170 收起 理由
971503750 + 20
regsofweb + 10 很给力!
xiaoe5201314 + 20
hill91 + 20 很给力!
eric2013 + 100 很给力!

查看全部评分

回复

使用道具 举报

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
106660
QQ
发表于 2020-4-7 07:33:22 | 显示全部楼层
谢谢楼主分享。
回复

使用道具 举报

18

主题

321

回帖

375

积分

高级会员

积分
375
发表于 2020-4-7 08:46:39 | 显示全部楼层
多谢楼主分享
回复

使用道具 举报

3

主题

58

回帖

67

积分

初级会员

积分
67
发表于 2020-4-8 10:34:05 | 显示全部楼层
半传输中断分割缓冲区这个思路很强
回复

使用道具 举报

0

主题

2

回帖

2

积分

新手上路

积分
2
发表于 2020-4-21 10:27:19 | 显示全部楼层
楼主能发一份源码参考参考吗?谢谢。
回复

使用道具 举报

3

主题

111

回帖

120

积分

初级会员

积分
120
发表于 2020-4-21 12:20:53 | 显示全部楼层
半传输中断分割缓冲区
楼主,我要赞美你!
回复

使用道具 举报

0

主题

19

回帖

19

积分

新手上路

积分
19
发表于 2020-4-21 18:21:51 | 显示全部楼层
学习了,感谢楼主的分享
回复

使用道具 举报

33

主题

203

回帖

302

积分

高级会员

积分
302
发表于 2021-5-21 16:26:56 | 显示全部楼层
HAL_UART_ReceiverTimeout_Config函数中第2个参数是超时判断阈值,其时间单位为 (1/串口波特率)秒;这个怎么和220匹配的?
回复

使用道具 举报

8

主题

78

回帖

102

积分

初级会员

积分
102
发表于 2021-6-16 08:11:38 来自手机 | 显示全部楼层
一直没有数据,超时中断会周期中断吗?
回复

使用道具 举报

0

主题

2

回帖

2

积分

新手上路

积分
2
发表于 2023-4-17 17:08:32 | 显示全部楼层
感谢分享。
回复

使用道具 举报

1

主题

4

回帖

7

积分

新手上路

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

使用道具 举报

8

主题

21

回帖

45

积分

新手上路

积分
45
发表于 2023-8-1 17:33:53 | 显示全部楼层
学习了,根据楼主的思路实现了一把,效果非常过,不管怎么整,串口依旧非常稳定。感谢!
回复

使用道具 举报

13

主题

89

回帖

128

积分

初级会员

积分
128
发表于 2023-8-3 13:58:47 | 显示全部楼层
Devil_920 发表于 2023-8-1 17:33
学习了,根据楼主的思路实现了一把,效果非常过,不管怎么整,串口依旧非常稳定。感谢!

求一份源码学习一下
回复

使用道具 举报

8

主题

21

回帖

45

积分

新手上路

积分
45
发表于 2023-8-4 08:55:46 | 显示全部楼层
qq1646544 发表于 2023-8-3 13:58
求一份源码学习一下

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

使用道具 举报

0

主题

1

回帖

1

积分

新手上路

积分
1
发表于 2024-1-30 09:25:36 | 显示全部楼层
您好!俺想求一份源码学习一下,刚学习,有挺多没懂的。。。
回复

使用道具 举报

2

主题

10

回帖

16

积分

新手上路

积分
16
发表于 2024-2-5 09:54:52 | 显示全部楼层
学习一下,针对超过DMA缓存区大小的处理方式
回复

使用道具 举报

5

主题

192

回帖

212

积分

高级会员

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

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

使用道具 举报

1

主题

50

回帖

53

积分

初级会员

积分
53
发表于 2024-2-6 08:59:28 | 显示全部楼层
感谢分享,学习学习下
回复

使用道具 举报

4

主题

14

回帖

26

积分

新手上路

积分
26
发表于 2024-3-7 14:28:09 | 显示全部楼层
我按照您的思路进行了一个简单的尝试写RTX5 消息队列+ UART1 DMA 双缓存 超时中断,遇到问题,有时候会丢帧,还请您帮忙看一下问题在哪里

UART_CR.7z

14.3 MB, 下载次数: 7

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|Archiver|手机版|硬汉嵌入式论坛

GMT+8, 2024-4-28 23:03 , Processed in 0.240303 second(s), 29 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2023, Tencent Cloud.

快速回复 返回顶部 返回列表