在《HAL版本DMA循环模式串口数据收发》中介绍了利用DMA循环模式进行串口数据的收发,STM32F4xx的DMA还提供了双缓冲的功能,采用双缓冲模式,可以在一个DMA完成接收后,对其缓冲区内数据进行处理的过程中,将此时接收到的数据放入第二个DMA缓冲区。双缓冲模式尤其对高速数据接收有着明显的优势,本文以上述循环接收方案为基础,提供实现双缓冲接收数据的实现方式。

首先,从寄存器的角度讲,要实现双缓冲,需要将CR寄存器的DBM位置1,将该位置1后,硬件会强制使用循环模式,当一个缓冲满后,会自动交换缓冲区的地址。但我们采用HAL库版本的程序时,不太需要关注这些,但是值得注意的是,HAL库的stm32f4xx_hal_uart.c中并没有提供实现双缓冲的接口,因此需要我们对HAL库的程序进行一些改写:

HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

{

/*省略库中起始部分的程序*/

/* Set the DMA abort callback */

huart->hdmarx->XferAbortCallback = NULL;

/* Enable the DMA stream */

//HAL_DMA_Start_IT(huart->hdmarx, (uint32_t)&huart->Instance->DR, *(uint32_t *)tmp, Size);

HAL_DMAEx_MultiBufferStart_IT(huart->hdmarx, (uint32_t)&huart->Instance->DR, (uint32_t)pData, (uint32_t)(pData+Size), Size);

/* Clear the Overrun flag just before enabling the DMA Rx request: can be mandatory for the second transfer */

__HAL_UART_CLEAR_OREFLAG(huart);

/*省略库中结尾部分的程序*/

}

上文中已经介绍采用HAL_UART_Receive_DMA接口使能DMA的接收,真正起到作用的是接口内部的HAL_DMA_Start_IT这个接口,如果想采用双缓冲模式,需要采用HAL_DMAEx_MultiBufferStart_IT这个接口才可以,这个接口的参数中需要提供两个缓冲区的地址,为了保证对外接口的一致性,我暂采用的是将第一个缓冲区首地址偏移缓冲区的长度作为第二个缓冲区的首地址。当然也可以直接改写HAL_UART_Receive_DMA,让使用者提供两个缓冲区的首地址,或者单独写一个接口都OK。

当第一个缓冲区数据满后会产生完成中断,回调HAL_UART_TxCpltCallback这个函数,当第二个缓冲区满后,也会回调一个函数,但这个函数需要我们事先注册一下,在串口初始化的时候添加第二个缓冲区满后回调的函数:

int8_t MW_UART_Init(int8_t id,UART_HandleTypeDef *handle)

{

MX_UART_ATTR *pUartAttr = &sUartAttr;

pUartAttr->id = id;

pUartAttr->handle = handle;

pUartAttr->DamOffset = 0;

pUartAttr->TransFlag = MW_TRANS_IDLE;

pUartAttr->DmaSize = MW_UART_DMA_LEN / 2;

pUartAttr->pReadDma = Uart1RxDma;

pUartAttr->pWriteDma = Uart1TxDma;

CFIFO_Init(&pUartAttr->ReadCFifo, Uart1RxBuff, MW_UART_BUFFER_LEN);

CFIFO_Init(&pUartAttr->WriteCFifo, Uart1TxBuff, MW_UART_BUFFER_LEN);

/*注册DMA的第二个缓冲区接收完成时回调的函数*/

HAL_DMA_RegisterCallback(pUartAttr->handle->hdmarx,HAL_DMA_XFER_M1CPLT_CB_ID,UART_DMAReceiveMem1Cplt);

/*使能串口空闲中断*/

__HAL_UART_ENABLE_IT(handle, UART_IT_IDLE);

/*使能DMA工作*/

if(HAL_OK != HAL_UART_Receive_DMA(pUartAttr->handle, pUartAttr->pReadDma, pUartAttr->DmaSize))

{

return MW_FAIL;

}

return MW_SUCCESS;

}

添加那句HAL_DMA_RegisterCallback,将UART_DMAReceiveMem1Cplt注册进去,这样当第二个DMA缓冲区满后就会调用UART_DMAReceiveMem1Cplt接口对第二个DMA缓冲区内的数据进行处理。当然串口空闲中断内的函数也需要进行处理,我们应该知道当产生空闲中断的时候,哪个DMA缓冲区在工作:

void32_t MW_UART_IRQHandler(UART_HandleTypeDef *huart)

{

uint8_t *pDmaMem = NULL;

int32_t RecvNum = 0;

int32_t WriteNum = 0;

int32_t DmaIdleNum = 0;

MX_UART_ATTR *pUartAttr = &sUartAttr;

if((__HAL_UART_GET_FLAG(huart,UART_FLAG_IDLE) != RESET))

{

__HAL_UART_CLEAR_IDLEFLAG(huart);

DmaIdleNum = __HAL_DMA_GET_COUNTER(huart->hdmarx);

if((huart->hdmarx->Instance->CR & DMA_SxCR_CT) == RESET)

{

/*实际指向第一个DMA缓冲区的首地址*/

pDmaMem = pUartAttr->pReadDma;

}

else

{

/*实际指向第二个DMA缓冲区的首地址*/

pDmaMem = pUartAttr->pReadDma + pUartAttr->DmaSize;

}

RecvNum = pUartAttr->DmaSize - DmaIdleNum - pUartAttr->DamOffset;

WriteNum = CFIFO_Write(&pUartAttr->ReadCFifo,pDmaMem + pUartAttr->DamOffset,RecvNum);

if(WriteNum != RecvNum)

{

loge("Uart ReadFifo is not enough\r\n");

}

pUartAttr->DamOffset += RecvNum;

}

}

当产生空闲中断的时候,通过DMA的CR寄存器中的CT值来判断当前哪个DMA缓冲区在工作即可。至此,便可实现利用DMA双缓冲进行不定长数据的接收。本文中采用双缓冲的本质是为了才DMA完成中断响应的时候对其内存就行数据处理的时候,仍有空闲的内存来接收串口传递的数据。文中采用的两个内存实际是连续的,那采用DMA的半完成中断代替双缓冲一样可以实现预期功能,且HAL库中提供了半完成中断的回调函数,并且半完成中断时默认开启的,因此我们只要复写HAL库提供的HAL_UART_RxHalfCpltCallback接口函数就可以,不需要像采用双缓冲时需要改写HAL库内函数和注册回调函数。

采用半完成中断时需要注意的一点是,在空闲完成中断回调函数内,如何配合两个DMA中断获取数据:

void32_t MW_UART_IRQHandler(UART_HandleTypeDef *huart)

{

uint8_t *pDmaMem = NULL;

int32_t RecvNum = 0;

int32_t WriteNum = 0;

int32_t DmaIdleNum = 0;

MX_UART_ATTR *pUartAttr = &sUartAttr;

if((__HAL_UART_GET_FLAG(huart,UART_FLAG_IDLE) != RESET))

{

__HAL_UART_CLEAR_IDLEFLAG(huart);

/*计算DMA缓冲空闲大小*/

DmaIdleNum = __HAL_DMA_GET_COUNTER(huart->hdmarx);

/*计算产生空闲中断时,DMA数据写到了DMA缓冲区的前半部分还是后半部分*/

if(DmaIdleNum <= (pUartAttr->DmaSize / 2))

{

pDmaMem = pUartAttr->pReadDma + pUartAttr->DmaSize / 2;

RecvNum = pUartAttr->DmaSize / 2 - DmaIdleNum - pUartAttr->DamOffset;

}

else

{

pDmaMem = pUartAttr->pReadDma;

RecvNum = pUartAttr->DmaSize - DmaIdleNum - pUartAttr->DamOffset;

}

/*将接收到的数据写到接收循环临时缓冲区*/

WriteNum = CFIFO_Write(&pUartAttr->ReadCFifo,pDmaMem + pUartAttr->DamOffset,RecvNum);

if(WriteNum != RecvNum)

{

loge("Uart ReadFifo is not enough\r\n");

}

pUartAttr->DamOffset += RecvNum;

}

}

可以在空闲中断回调函数里根据获取DMA缓冲区剩余的大小来判断当前数据写到了缓冲区的前半部分还是后半部分,以进行不同的操作。笔者最后采用的就是这种DMA半完成中断,DMA完成中断和串口空闲中断三者配合使用实现串口数据接收的方案,不需要改写HAL库内的程序,也可实现类似双缓冲带来的优势。

java串口设备中断_利用DMA双缓冲或半完成中断实现串口不定长数据的接收相关推荐

  1. STM32CubeMX系列教程8:配置工程模板(串口+不定长数据收发+DMA+IDLE中断+软中断)

    文章目录 摘要 生成工程 配置外设 1.配置时钟与Debug 2.配置串口与DMA 3.配置定时器与中断 配置时钟树 配置工程设置 点击`GENERATE CODE`生成工程 修改源码 配置软中断 配 ...

  2. dma接收双缓存 stm32_STM32和WM8960 I2S 利用DMA双缓冲音频播放和录音(二)

    前面简单讲解了WM8960语音芯片工作方式,WM8960做master,之前参数配置ADC/DAC采样速率的是44.1K,有点问题,现在改为16K,下面会解释为什么要改成16K. WM8960参数配置 ...

  3. STM32单片机串口空闲中断+DMA接收不定长数据

    在上一篇文章STM32单片机串口空闲中断接收不定长数据中介绍了利用串口空闲中断接收不定长数据,这种方式有一个问题就是串口每接收到一个字节就会进入一次中断,如果发送的数据比较频繁,那么串口中断就会不停打 ...

  4. STM32从零到一,从标准库移植到HAL库,UART串口1以DMA模式收发不定长数据代码详解+常见问题 一文解析

    前言 本文的参考资料 感谢提供标准库版本的CSDN同学:这两篇文章至少是我看过的最详细的标准库配置DMA版本.而且代码实测稳定能用. STM32 | DMA配置和使用如此简单(超详细)_...| .. ...

  5. STM32单片机串口空闲中断接收不定长数据

    在使用单片机的串口通信功能时,常用的接收数据方法是通过固定的字节数来判断一帧数是否发送完成,或者是通过固定的结束标志位来表示一帧数据发送完成.但是有时候会遇到发送的数据长度不固定,也没有固定的结束标志 ...

  6. STM32F407学习之DMA双缓冲模式HAL库实现

    本学期第一次培训要求用单片机实现1到40KHz的单步进变频正弦波输出,本菜鸡一开始用了通过修改定时器预分频系数和自动重装载值改变输出波表频率.修改定时器 + DDS两种方法,但实现的效果不太理想,不仅 ...

  7. 串口IDLE空闲中断+DMA实现接收不定长数据基于stm32cubemx

    引言:对于串口接收一些不定长的数据,必须面对一个问题:怎么判断一帧数据接收是否完成?通常使用RXNE非空中断配合简单的数据协议,在数据中加入帧头.帧尾,在程序中判断是否接收到帧尾来确定数据接收完毕,因 ...

  8. MM32F3277空闲中断+DMA接收不定长数据

    摘要:在实际项目中经常用到串口接收一些不定长的数据,怎么判断这一帧数据接收完成了呢?通常使用UART非空中断配合简单的数据协议,在数据中加入帧头.帧尾,在程序中判断是否接收到帧尾来确定数据接收完毕,对 ...

  9. android 串口一直打开_STM32之串口DMA接收不定长数据

    STM32之串口DMA接收不定长数据 引言 在使用stm32或者其他单片机的时候,会经常使用到串口通讯,那么如何有效地接收数据呢?假如这段数据是不定长的有如何高效接收呢? 同学A:数据来了就会进入串口 ...

最新文章

  1. Chrome使用技巧和编辑框拖动怪问题。
  2. hdu 5285 二分图黑白染色
  3. 2020 年的第 1 个重磅彩蛋!
  4. c 引用mysql报错_安装TPCC-MySQL报错
  5. C++编程思想:继承与虚函数以及多态
  6. datagrid 重载本地数据_音视频系列3:使用ffmpeg + nginx搭建本地转发服务器
  7. floatmap 二维数组_用J中的多维数组进行Arrays.fill
  8. C++ 文件读写操作01
  9. 【转】通过身边小事解释机器学习是什么?
  10. 炼丹手册——数据增广
  11. 从实例中学习grid布局
  12. 文字处理技术:视图坐标都是在父视图的相对坐标
  13. ZebraDesigner3 打印到.prn文件乱码
  14. qunee for html5,Qunee for HTML5(二)
  15. JAVA-生命游戏多线程
  16. 对Linux内核tty设备的一点理解(转)
  17. 小米mix2android o升级,小米MIX2升级Android 8.0 全面屏手势操作将一同推送
  18. 如何取消文件关联,恢复文件默认的图标,最简单的办法!!!!!
  19. mysql登录报错“Access denied for user ‘root‘@‘localhost‘ (using password: YES”的处理方法
  20. 腾讯轻量免费升配申请方法

热门文章

  1. K8s稳居容器榜首,Docker冲顶技术热词,微服务应用热度不减,2021云原生开发者现状
  2. 从 Java 替代品到打造完整生态,Kotlin 10 岁了!
  3. 开源3问:95%的技术人都不知道的开源真相
  4. 雷军回应小米新 Logo 争议;马化腾排名第 15 位,2021 全球亿万富豪榜出炉!谷歌将停止使用甲骨文的财务软件|极客头条...
  5. Go语言中时间轮的实现
  6. 30 行代码实现蚂蚁森林自动“偷”能量
  7. 湖南区块链服务网络(BSN)门户运营商确定
  8. 独家对话 HybridOS 操作系统掌门人魏永明:“我们的目标是取代物联网中的安卓” | 人物志
  9. 200 个工具分析机器学习十年:前途未卜、工程师是核心!
  10. 腾讯云加码 IDaaS,加速 SaaS 时代进程