搜索
查看: 66607|回复: 81

[讨论] Cube下实现串口+DMA+空闲中断接收不定长数据

  [复制链接]

该用户从未签到

10

主题

35

帖子

0

蝴蝶豆

中级会员

最后登录
2021-1-29
发表于 2015-7-30 16:38:54 | 显示全部楼层 |阅读模式

采用STM32cube完成ADC的DMA功能

STM32F051C8T6 STM32CubeMX ADC-DMA-UART教程


一直困惑怎么在Cube上实现串口+DMA+空闲中断接收不定长数据。
使用下来发现Cube好的地方就是你按照图形画方式配置后会帮你省掉很多初始化的工作。但是从标准库走过来略有点别扭,可能用用就习惯了。
想把这几天搞的跟大家聊聊,有不对的地方请指正啊。同时感谢网友baifernlover对本菜的指导。
USART1+DMA配置如下图:

串口1-DMA接收

串口1-DMA接收

生成工程后,在主函数里添加:
  1. __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);    //使能空闲中断
  2.   while (1)
  3.   {
  4.         if(recv_end_flag ==1)                                           //recv_end_flag 结束标志
  5.         {
  6.                 printf("rx_len=%d\r\n",rx_len);                    //rx_len 此次接收到了多少数据
  7.                 for(i=0;i<rx_len;i++)
  8.                 {
  9.                                 printf("%x\r\n",rx_buffer);
  10.                 }
  11.                 rx_len=0;
  12.                 recv_end_flag=0;
  13.         }
  14.         HAL_UART_Receive_DMA(&huart1,rx_buffer,BUFFER_SIZE);
  15.   }
复制代码

中断函数里面:
  1. void USART1_IRQHandler(void)
  2. {

  3.         uint32_t tmp_flag = 0;
  4.         uint32_t temp;
  5.         tmp_flag =  __HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE);
  6.         if((tmp_flag != RESET))
  7.        {
  8.                 __HAL_UART_CLEAR_IDLEFLAG(&huart1);
  9.                 temp = huart1.Instance->SR;  
  10.                 temp = huart1.Instance->DR;
  11.                 HAL_UART_DMAStop(&huart1);
  12.                 temp  = hdma_usart1_rx.Instance->NDTR;            
  13.                 rx_len =  BUFFER_SIZE - temp;                           
  14.                  recv_end_flag = 1;
  15.          }

  16. }
复制代码

之前看到帖子说HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)和HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)接收需要指定接收Size长度值,但是这两个函数的Size有所区别。
HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)主要做了两件事:
1.   huart->pRxBuffPtr = pData;       /*!< Pointer to UART Rx transfer Buffer */
      huart->RxXferSize = Size;           /*!< UART Rx Transfer size              */        这个赋值好像没什么用
     huart->RxXferCount = Size;        /*!< UART Rx Transfer Counter           */
2.使能中断
/* Enable the UART Data Register not empty Interrupt */
    __HAL_UART_ENABLE_IT(huart, UART_IT_RXNE);
在中断处理函数中,去判断中断标志位;然后再根据相应的中断标志位去处理:
  tmp1 = __HAL_UART_GET_FLAG(huart, UART_FLAG_RXNE);
  tmp2 = __HAL_UART_GET_IT_SOURCE(huart, UART_IT_RXNE);
  /* UART in mode Receiver ---------------------------------------------------*/
  if((tmp1 != RESET) && (tmp2 != RESET))
  {
    UART_Receive_IT(huart);
  }

UART_Receive_IT函数里感觉做了两件事:
1.*huart->pRxBuffPtr++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x00FF);  //存数据
2.if(--huart->RxXferCount == 0)                                          //之前赋值的 huart->RxXferCount = Size;
   {
         __HAL_UART_DISABLE_IT(huart, UART_IT_RXNE);     //最后一个数据接收完了就关闭中断
    }
   HAL_UART_RxCpltCallback(huart);                                     //调用接收完成回调函数
整个过程中,来一个数据,huart->RxXferCount做一次减法,直到等于0,去调用回调函数。


HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)中:
1. HAL_DMA_Start_IT(huart->hdmarx, (uint32_t)&huart->Instance->DR, *(uint32_t*)tmp, Size);--->
2.DMA_SetConfig(hdma, SrcAddress, DstAddress, DataLength);这里的DataLength就是之前的Size吧---->
3. /* Configure DMA Stream data length */
   hdma->Instance->NDTR = DataLength;   
对NDTR寄存器描述:This register can be written only
when the stream is disabled. When the stream is enabled, this register is read-only,
indicating the remaining data items to be transmitted. This register decrements after each
DMA transfer.

这里的定义的Size赋给了DMA_SxNDTR。使能之后,只能读取,表示还有多少字节需要被“传送”。所以空闲中断里面
temp  = hdma_usart1_rx.Instance->NDTR;  //读取还没有被传送的个数            
rx_len =  BUFFER_SIZE - temp;                    //DMA缓存大小减去没有被传送的个数,就等于已经被传送的个数,也就是接收到的个数。

先这样子理解了。所以感觉如果是DMA接收,即使外部过来的数据个数不等于设定的Size,那么数据也还是被接收到了指定的buffer中。
DMA中断处理函数HAL_DMA_IRQHandler里面会判断DMA传输的状态,完了去调用各自的回调函数。比如传输完成的话会去调用UART_DMAReceiveCplt()--->HAL_UART_RxCpltCallback(huart);最后还是调用了HAL_UART_RxCpltCallback(huart)函数。

最后附上工程文件,欢迎大家多多讨论。


STM32_USART_DMA_IDLE.rar

6.17 MB, 下载次数: 19869, 下载积分: ST金币 -1

Cube_USART_DMA_IDLE

评分

参与人数 1ST金币 +1 收起 理由
zcshou + 1 总体思路没错,但是stop后需要HAL_UART_DMA.

查看全部评分

回复

使用道具 举报

该用户从未签到

43

主题

296

帖子

38

蝴蝶豆

金牌会员

最后登录
2022-3-31
发表于 2018-4-9 14:02:28 | 显示全部楼层
本帖最后由 kylongmu 于 2018-4-9 14:12 编辑

我觉得大家把这个路走歪了点,
__HAL_DMA_DISABLE_IT(huart1.hdmarx, DMA_IT_TC | DMA_IT_HT | DMA_IT_TE);//关闭DMA 错误 传输一半 全部完成 中断

这里如果将DMA配置成一锤子买卖,肯定会丢东西。
--------------------------------------------------------------------------------------------------------------------------------------------------------------
不如将DMA配置成Circular模式,不要停止DMA,另外开启
“传输一半、全部完成” 中断,然后把DMA缓冲区当作乒乓结构来用,当“传输一半”中断时处理前半部分数据,当“全部完成”中断时处理后半部分数据。处理方法:依据上次读指针读出到当前点数据至一个缓冲区,并更新读指针。

那么在“空闲中断”时,依据上次读指针读出到当前点数据,追加写入缓冲区,并将缓冲区数据整体复制为未处理数据包(带有效数据长度),然后更新缓冲区写入指针至头部。

这样就能解决DMA缓冲区必须设置为很大的问题,还能解决关闭DMA造成数据丢失的问题,仅是维护一个读DMA缓冲区指针(这个指针与Circula模式一样无限循环)+一个写缓冲区指针(每次拷贝数据后复位)。

DMA缓冲区可以设置为256字节,这样缓冲区读指针可以采用基地址+一个UINT8偏移实现,UINT8自动溢出循环256。
回复 支持 1 反对 0

使用道具 举报

该用户从未签到

8

主题

307

帖子

2

蝴蝶豆

金牌会员

最后登录
2021-6-4
发表于 2017-6-2 11:28:17 | 显示全部楼层
这个 需要配合 串口数据 收发 协议来,否则无法确认 到底接收多少数据。
                HAL_UART_Receive_DMA(&huart1, RX_DATA, RX_DATA_NUM);
                __HAL_DMA_DISABLE_IT(huart1.hdmarx, DMA_IT_TC | DMA_IT_HT | DMA_IT_TE);//关闭DMA 错误 传输一半 全部完成 中断

                __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);//使能 串口 空闲中断

                HAL_NVIC_SetPriority(USART1_IRQn, 1, 0);
                HAL_NVIC_EnableIRQ(USART1_IRQn);                        //使能中断

void USART1_IRQHandler(void)
{
        if((__HAL_DMA_GET_COUNTER(huart1.hdmarx) + RX_DATA[3]) == (RX_DATA_NUM - 6))
        {
                Flag_RX = 1;
        }
       
        __HAL_UART_CLEAR_IT(&huart1, UART_CLEAR_IDLEF);
       
        __HAL_DMA_DISABLE(huart1.hdmarx);
        huart1.hdmarx->Instance->CNDTR = RX_DATA_NUM;
        __HAL_DMA_ENABLE(huart1.hdmarx);                                //DMA接收数据 必须在 DMA 禁止 时 重新写入
}
回复 支持 1 反对 0

使用道具 举报

该用户从未签到

47

主题

3456

帖子

30

蝴蝶豆

版主

最后登录
2022-12-25
发表于 2015-7-30 16:52:30 | 显示全部楼层
多谢分享,学习了
回复 支持 反对

使用道具 举报

该用户从未签到

307

主题

3432

帖子

0

蝴蝶豆

论坛元老

最后登录
2020-7-17
发表于 2015-7-30 17:07:38 | 显示全部楼层
抱歉,图片右下角因为当时正在调整水印功能,覆盖了,楼主可以重新上传一下,麻烦了
回复 支持 反对

使用道具 举报

该用户从未签到

57

主题

303

帖子

0

蝴蝶豆

金牌会员

最后登录
2019-7-18
发表于 2015-7-30 17:12:44 | 显示全部楼层
我也差不多的逻辑,不过,我按HAL的回调方式写的。
回复 支持 反对

使用道具 举报

该用户从未签到

10

主题

35

帖子

0

蝴蝶豆

中级会员

最后登录
2021-1-29
 楼主| 发表于 2015-7-30 17:16:59 | 显示全部楼层
Tension 发表于 2015-7-30 17:12
我也差不多的逻辑,不过,我按HAL的回调方式写的。

按HAL的回调方式写,是指在HAL_UART_IRQHandler()中添加   
tmp1 = __HAL_UART_GET_FLAG(huart, UART_FLAG_IDLE);
  tmp2 = __HAL_UART_GET_IT_SOURCE(huart, UART_IT_IDLE);
  /* UART in mode Receiver ---------------------------------------------------*/
  if((tmp1 != RESET) && (tmp2 != RESET))
  {
    UART_XXX_IT(huart);
  }
这个意思吗?
还有我在while(1)里面一直HAL_UART_Receive_DMA(&huart1,rx_buffer,BUFFER_SIZE);这样子会不会有额外的开销。这里一点我有点不确定,在while一直这样调用会不会有问题。
回复 支持 反对

使用道具 举报

该用户从未签到

10

主题

35

帖子

0

蝴蝶豆

中级会员

最后登录
2021-1-29
 楼主| 发表于 2015-7-30 17:20:05 | 显示全部楼层
DMA串口配置图片
DMA_串口配置.jpg
回复 支持 反对

使用道具 举报

该用户从未签到

9

主题

242

帖子

0

蝴蝶豆

金牌会员

最后登录
2018-3-23
发表于 2015-7-30 17:41:58 | 显示全部楼层
支持楼主, 这个功能很有用,
回复 支持 反对

使用道具 举报

该用户从未签到

57

主题

1216

帖子

0

蝴蝶豆

金牌会员

最后登录
2023-6-4
发表于 2015-7-30 21:44:52 | 显示全部楼层
谢楼主              
回复 支持 反对

使用道具 举报

该用户从未签到

9

主题

445

帖子

0

蝴蝶豆

金牌会员

最后登录
2022-9-18
发表于 2015-7-30 23:20:29 | 显示全部楼层
收藏个

回复

使用道具 举报

该用户从未签到

40

主题

1635

帖子

1

蝴蝶豆

论坛元老

最后登录
2018-7-23
发表于 2015-7-30 23:38:23 | 显示全部楼层
多谢分享!!!
回复

使用道具 举报

您需要登录后才可以回帖 注册/登录

本版积分规则

关闭

站长推荐上一条 /3 下一条

Archiver|手机版|小黑屋|论坛-意法半导体STM32/STM8技术社区

GMT+8, 2024-4-20 13:48 , Processed in 1.222374 second(s), 47 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

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