串口高波特率下如何稳定接收

2023-05-16

无论是蓝牙、WiFi,还是4G、5G,亦或是其它模组,都支持AT指令+透传模式。

AT指令模式下,执行查询指令和操作(设置)指令。

响应速度快,逻辑交互明确,不需要复杂的处理代码。

比如查询MAC信息指令、设置波特率指令等等操作,可以只管发,然后等待中断处进行数据处理,中间只需要一个全局变量传递状态,和一个缓冲区指针缓存结果,用掉之后再释放掉。

主控与模组之间的交互

到实际工作场景中,往往需要进入透传模式。

模组收到的数据,直接传递给MCU;MCU需要发数据时,直接将元数据扔给模组。

透传模式下一般考虑的就两点:

一是 透 传 的协议格式

        可以相互之间直接传递原始数据,也可以在头或尾(或头尾)添加固定封包。这主要取决于双方通信是否可靠,以及通信的复杂度。

二是 链 接 状态的传递

        进入透传模式,前提肯定是连接建立成功。如果连接断开,要及时反馈,对端同时要及时做好逻辑处理。

        其中1对1下,状态传递最好的方式就是检测IO状态(硬件连接)。通过软件方式判断,总有不可避免的bug出现。

主控端的数据处理

主控端一是数据的发送,二是数据的接收。

发送一般不会有什么问题。

主要是如何高效的接收数据?

以蓝牙传输文件为例,采用BR2x51e(-s)模块,串口通讯波特率921600,由平板或手机下发文件,一次一包10K数据大小,总文件大小最大20M左右。

遇到的问题

1、DMA Normal/循环模式下接收数据存在丢数据现象

115200波特率下256KB甚至到1K,基本只会触发一次空闲中断;

921600波特率下即使是256KB的一包数据也会触发多次串口空闲中断;

即波特率越高,由于时钟不同步或者模组定时不精准等原因,串口空闲中断触发的几率更大。

2、DMA循环模式+DMA传输完成中断下出现数据被覆盖

不同于上一个导致硬件中断丢数据,这次是线程中数据处理不过来,导致数据被覆盖。

添加二级缓冲区机制后解决。

总结如下

2种常用的串口接收方式

1、DMA Normal模式+空闲中断接收

这种方式适用于,单次传输模式,即一收一发形式的交互。

因为Normal模式下,

        1、接收数据溢出时会从头开始覆盖原有数据,有数据丢失的风险;

        2、这种模式下要求DMA接收buf的大小,要大于可能出现的数据最大长度,浪费内存;

但是非常适合处理AT指令模式下的数据交互,buf区只需要很小的长度。

//典型代码

uint8_t g_logRxBuf[LOG_RX_BUF_NUM_MAX];//定义DMA接收缓冲区


void LogInit(void)
{
    __HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE);//使能空闲中断
    HAL_UART_Receive_DMA(&huart2, g_logRxBuf, LOG_RX_BUF_NUM_MAX);//配置DMA接收地址和长度
}


__weak void ConsoleRxCallback(uint8_t *pData,uint16_t unDataLen) { }//回调函数


void LogRxIdleCallBack(void)
{
  if(__HAL_UART_GET_FLAG(&huart2,UART_FLAG_IDLE)!=RESET)
  {
    __HAL_UART_CLEAR_IDLEFLAG(&huart2);//清中断标志位

    HAL_UART_DMAStop(&huart2);//关闭DMA传输
    __HAL_UNLOCK(&huart2);

    ConsoleRxCallback(g_logRxBuf,LOG_RX_BUF_NUM_MAX - __HAL_DMA_GET_COUNTER(&hdma_usart2_rx));//数据处理

    HAL_UART_Receive_DMA(&huart2, g_logRxBuf, LOG_RX_BUF_NUM_MAX);//重新配置DMA传输
  }
}

2、DMA循环模式+串口DMA完成中断/串口DMA半完成中断/串口空闲中断

这种模式下,相当于用了3个中断源触发数据的处理。

这样利用DMA传输的半完成中断和完全中断,只需要很小的buf就能处理很大的数据,这里用1K的buf去接收16K的数据是完全没问题的,处理这个16K的数据接收时,总是先进入半完成中断处理前一半数据,再进入完全中断处理后一半数据,这样处理数据时不影响数据的接收。

硬件层面不会导致数据丢失。

static uint8_t g_bt_dma_buf[BT_BUF_SIZE];//DMA接收buf,1K大小
static volatile uint16_t unBtDmaReadOffset;//接收偏移量(volatile防止被优化)

void BlueToothInit(void)
{
	//BlueToothConfig(921600);
	__HAL_UART_ENABLE_IT(&huart3,UART_IT_IDLE);
	HAL_UART_Receive_DMA(&huart3,g_bt_dma_buf,BT_BUF_SIZE);
}

__weak void BTUartRxCallBack(uint8_t *pData,uint16_t unDataLen) {}

void BtUartRxIdleCallback(void)
{
	uint32_t ulLen;
	
    if((__HAL_UART_GET_FLAG(&huart3,UART_FLAG_IDLE) != RESET))
    {
        __HAL_UART_CLEAR_IDLEFLAG(&huart3);
       
		ulLen = BT_BUF_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart3_rx) - unBtDmaReadOffset;
		
		BTUartRxCallBack(&g_bt_dma_buf[unBtDmaReadOffset], ulLen);

		//空闲中断:累加DMA接收偏移量
		unBtDmaReadOffset += ulLen;
    }
}

void BtUartDmaRxHalfCpltCallback(void)
{
    uint16_t unLen = 0;
    //半完成中断:DMA接收偏移量为buf的一半大小
    unLen = BT_BUF_SIZE/2 - unBtDmaReadOffset;
    BTUartRxCallBack(&g_bt_dma_buf[unBtDmaReadOffset], unLen);
    unBtDmaReadOffset = BT_BUF_SIZE/2;
}

void BtUartDmaRxCpltCallback(void)
{
    uint16_t unLen = 0;
    //完成中断:DMA接收偏移量就等于buf的大小,即0
    unLen = BT_BUF_SIZE - unBtDmaReadOffset;
    BTUartRxCallBack(&g_bt_dma_buf[unBtDmaReadOffset], unLen);
    unBtDmaReadOffset = 0;
}

二级缓冲区

利用DMA完成中断传输数据时,可以解决硬件中断的数据丢失问题,但是当数据持续高速传输时,会导致应用层处理不过来,这时可以再加一层缓冲区来解决。

static uint8_t pBtRxDataBuff[BT_UART_RX_BUFF_MAX_LEN];//接收缓冲区,4K大小
static volatile uint16_t unBtRxBuffDealOffset = 0;//缓冲区读取偏移量
static volatile uint16_t unBtRxBuffRxOffset = 0;//缓冲区写入偏移量
static volatile uint16_t unBtRxBuffNeedDealLen = 0;//缓冲区写入总长度

void BTUartRxCallBack(uint8_t *pData,uint16_t unDataLen)
{
	WriteBlueToothRxData(pData, unDataLen);
    OSSemPost(UpperRxSem);
}

//将数据写入“循环”缓冲区
void WriteBlueToothRxData(uint8_t *pData,uint16_t unDataLen)
{
	uint16_t unWriteLen = 0,unWriteOffset = 0;
	
	while(1)
	{
		if(unWriteOffset == unDataLen)
			break;
	
		unWriteLen = unDataLen - unWriteOffset;
	
        //当写入长度大于剩余长度时,1、先写入剩余长度
        //2、再将多余长度从buf首地址开始写入
		if(unWriteLen >= (BT_UART_RX_BUFF_MAX_LEN - unBtRxBuffRxOffset))
			unWriteLen = BT_UART_RX_BUFF_MAX_LEN - unBtRxBuffRxOffset;
	    
        //拷贝数据
		memcpy(&pBtRxDataBuff[unBtRxBuffRxOffset],pData + unWriteOffset,unWriteLen);
		unWriteOffset += unWriteLen;
		unBtRxBuffNeedDealLen += unWriteLen;
		unBtRxBuffRxOffset += unWriteLen;

        //写满时将写入位置拉到buf首地址
		if(unBtRxBuffRxOffset >= BT_UART_RX_BUFF_MAX_LEN)
			unBtRxBuffRxOffset = 0;
	}
}

//获取缓冲区数据长度
//unBtRxBuffDealOffset buf读取的偏移量,用来追接收偏移量
uint16_t ReadBlueToothRxDataLen(void)
{
	if(unBtRxBuffDealOffset != unBtRxBuffRxOffset)
	{
		if(unBtRxBuffRxOffset > unBtRxBuffDealOffset)	
			return unBtRxBuffRxOffset - unBtRxBuffDealOffset;
		else
			return unBtRxBuffRxOffset + BT_UART_RX_BUFF_MAX_LEN - unBtRxBuffDealOffset;
	}
	else
		return 0;
}


//读取缓冲区数据
uint8_t ReadBlueToothRxData(void)
{
    uint8_t byData;

	unBtRxBuffNeedDealLen--;

	byData = pBtRxDataBuff[unBtRxBuffDealOffset++];
	if(unBtRxBuffDealOffset >= BT_UART_RX_BUFF_MAX_LEN)
		unBtRxBuffDealOffset = 0;

    return byData;
}

void ReadBlueToothRxDataStr(uint8_t *pBuf,uint16_t len)
{
	uint16_t i;

	for(i=0; i<len; i++)
		pBuf[i] = ReadBlueToothRxData();

    return;
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

串口高波特率下如何稳定接收 的相关文章

  • 深入理解计算机系统 -- 大端与小端字节序

    一 大端字节序 vs 小端字节序 字节序指一个多字节对象在内存中存储的方式 xff0c 小端字节序机器在存储多字节对象时采用低地址存低有效字节的策略 xff0c 大端则恰恰相反 字节序由CPU架构决定 xff0c 与操作系统无直接关系 像常
  • TCP连接建立

    TCP 一种面向来连接的 可靠的 基于字节流的传输层通信协议 面向连接 xff1a 数据在发送之前必须在两端建立连接 xff0c 方法就是我们熟知的三次握手连接 可靠传输 xff1a 通过多种机制来保证数据的正确传输 xff0c 比如序列号
  • UDP接收端收不到广播的消息问题排查

    网络调试助手可以互相发送 xff0c 而你的UDP广播代码却不行 你是广播 是不会被路由器转发的 但是在同一个交换机下 是可以收到广播的 还有就是 电脑的虚拟网卡会拦截广播操作 xff0c 因为你没有指定一个地址 xff0c 所以代码正确的
  • STM32带FIFO的DMA传输应用示例

    STM32系列芯片都内置DMA外设 xff0c 其中很多系列的DMA配备了FIFO 这里以STM32F429芯片及开发板为例 xff0c 演示一下带FIFO的DMA传输实现过程 大致情况是这样的 xff0c 我用TIMER1通道1的比较事件
  • 两种方式判断内存的大小端存储方式

    1 目的 xff1a 判断ubuntu操作系统的内存属于大端还是小端存储 2 源代码 两种方法判断大小端 xff08 处理器取值时的字节序 xff09 xff1a 1 字符指针 2 联合体 法1 if 1 include lt stdio
  • 字符串:求str1在str2中首次出现的位置。

    span class token macro property span class token directive keyword include span span class token string lt stdio h gt sp
  • 基于ROS利用客户端和服务端实现C++节点和python节点间传送图像

    基于ROS利用客户端和服务端实现C 43 43 节点和python节点间传送图像 配置ROS下和python3通信以及配置python3可用的cv bridge 环境安装和使用 参考 xff1a https blog csdn net qq
  • iMaxB6充电介绍

    iMaxB6是一款多用途充电器 xff0c 能够为Li ion Li Poly Li Fe Ni Cd Ni MH和Pb类型电池充电 xff0c 支持6串以内的平衡充电 简要步骤 xff1a 1 连接正负电源 xff1b 2 连接平衡线 x
  • 基于stm32串口环形缓冲队列处理机制

    原文链接 xff1a 基于stm32串口环形缓冲队列处理机制 入门级 xff08 单字节 xff09 串口环形缓冲区实验 1 1 实验简介 最简单的串口数据处理机制是数据接收并原样回发的机制是 xff1a 成功接收到一个数 xff0c 触发
  • 源码安装nginx 1.23.1

    先看看仓库们 yum list nginx 已加载插件 xff1a fastestmirror langpacks Loading mirror speeds from cached hostfile base mirrors aliyun
  • Sublime Text运行C和C++程序

    原文链接 xff1a Sublime Text运行C和C 43 43 程序 Sublime Text 是一款当下非常流行的文本编辑器 xff0c 其功能强大 xff08 提供有众多的插件 xff09 界面简洁 还支持跨平台使用 xff08
  • keilC51编译常见错误和警告说明

    如对编译出错感兴趣的网友能否把你们常遇到的错误信息收集起来并提出最终的解决办法加以归纳以期共享 xff01 1 L15 重复调用 WARNING L15 MULTIPLE CALL TO SEGMENT SEGMENT PR SPI REC
  • RS485的电路以及相关波形

    1 RS485的电路 xff0c 要注意RE引脚一般是和DE引脚接在一起的 2 差分信号AB的波形 xff0c 高电平6 2v左右 xff0c 低电平 3v 3 A点的波形 4 B点波形 5 接收RX的波形
  • Ubuntu安装cmake

    Ubuntu18 04安装cmake 转载自https www cnblogs com yanqingyang p 12731855 html 一 使用安装命令 span class token function sudo span apt
  • C/C++混淆点-strcat和strcpy区别

    一 原因分析 假设 xff1a char str 61 NULL str 61 new char 11 你想为字符串str开辟一个存储十个字符的内存空间 xff0c 然后你现在有两个字符串 xff1a char c1 61 34 abc 3
  • QGC 添加电机测试功能

    组装过程中为了测试电机的连接以及转向 xff0c 现将电机测试功能单独制作一个页面 xff0c 以便使用 一 xff0c 效果 原型 实际效果总是差那么一丢丢 二 xff0c 实现思路 MavlinkConsole 功能 xff0c 可以在
  • 川崎duAro机器人 ROS_moveit demo

    说明 demo cpp Author hiics include lt ros ros h gt include lt iostream gt MoveIt include lt moveit move group interface mo
  • Windows10 下C/C++网络编程基本:socket实现tcp的例子

    1 说明 待编辑 2 代码 test server h span class token macro property span class token directive keyword ifndef span INCLUDE TEST
  • C++ 实现简单Tcp服务器端 -- Select方式

    test server h span class token macro property span class token directive keyword ifndef span INCLUDE TEST SERVER H span

随机推荐