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

无论是蓝牙、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;
}

猜你喜欢

转载自blog.csdn.net/weixin_38743772/article/details/126356323