How to receive stably at high baud rate of serial port

Whether it is Bluetooth, WiFi, or 4G, 5G, or other modules, they all support AT command + transparent transmission mode.

In AT command mode, execute query command and operation (setting) command.

The response speed is fast, the logical interaction is clear, and no complex processing code is required.

For example, query MAC information commands, set baud rate commands, etc., you can just send them, and then wait for the interrupt to process data. In the middle, you only need a global variable to transfer the state, and a buffer pointer to cache the results, and then release them after use. .

Interaction between the main control and the module

In actual work scenarios, it is often necessary to enter the transparent transmission mode.

The data received by the module is directly passed to the MCU; when the MCU needs to send data, it directly throws the metadata to the module.

In the transparent transmission mode, there are generally two points to consider:

One is the protocol format of transparent transmission

The original data         can be passed directly to each other , or a fixed packet can be added at the head or tail (or head and tail) . This mainly depends on whether the communication between the two parties is reliable and the complexity of the communication.

The second is the transfer of link state

        To enter the transparent transmission mode, the premise must be that the connection is established successfully. If the connection is disconnected, feedback should be made in time, and the peer end should do logical processing in time.

        Among them, under 1 to 1, the best way to transfer state is to detect IO state (hardware connection). Judging by software, there are always inevitable bugs.

Data processing on the master side

The main control terminal is the sending of data, and the receiving of data.

Sending is generally not a problem.

Mainly how to receive data efficiently?

Take bluetooth file transfer as an example, using BR2x51e(-s) module, serial port communication baud rate 921600, sending files from tablet or mobile phone, one pack of 10K data size at a time, the maximum total file size is about 20M.

problems encountered

1. There is a data loss phenomenon in receiving data in DMA Normal/circular mode

At 115200 baud rate of 256KB or even 1K, basically only one idle interrupt will be triggered;

Even a packet of 256KB data at 921600 baud rate will trigger multiple serial port idle interrupts;

That is, the higher the baud rate, the greater the probability of the serial port idle interrupt being triggered due to clock asynchrony or inaccurate module timing.

2. DMA cycle mode + DMA transfer completion interrupt occurs when data is overwritten

Unlike the previous one that caused data loss due to hardware interruption, this time the data in the thread could not be processed, causing the data to be overwritten.

Solved after adding the secondary buffer mechanism.

Summarized as follows

2 commonly used serial port receiving methods

1. DMA Normal mode + idle interrupt reception

This method is applicable to the single transmission mode, that is, the interaction in the form of one receiving and one sending.

Because in Normal mode,

        1. When the received data overflows, the original data will be overwritten from the beginning, and there is a risk of data loss;

        2. In this mode, the size of DMA receiving buf is required to be greater than the maximum length of possible data, which wastes memory;

But it is very suitable for data interaction in AT command mode, and the buf area only needs a small length.

//典型代码

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 cycle mode + serial port DMA completion interrupt/serial port DMA semi-completed interrupt/serial port idle interrupt

In this mode, it is equivalent to using three interrupt sources to trigger data processing.

In this way, using the semi-complete interrupt and complete interrupt of DMA transmission, only a small buf can process a large amount of data. Here, it is no problem to use 1K buf to receive 16K data . When processing this 16K data reception, Always enter the half-finished interrupt to process the first half of the data first, and then enter the full interrupt to process the second half of the data, so that the data reception will not be affected when processing the data.

There is no data loss at the hardware level.

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;
}

secondary buffer

When DMA is used to complete the interrupt transmission of data, the data loss problem caused by hardware interruption can be solved. However, when the data continues to be transmitted at high speed, the application layer will not be able to handle it. At this time, another layer of buffer can be added to solve the problem.

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;
}

Guess you like

Origin blog.csdn.net/weixin_38743772/article/details/126356323
Recommended