单片机接收不定长的数据,最优解是DMA+串口空闲中断

如果单片机不支持串口空闲中断和DMA,可以参考之前写的,串口只用接收中断,完成不定长的分包。

这里以stm32L4的单片机举例,思路可拓展到GD32等支持DMA和串口空闲中断的单片机。

串口DMA接收,就是当串口有数据接收的时候,不会触发串口中断,等串口空闲后,(一帧数据包接收完成,硬件会自己保证)会促发一次串口中断,此时可以对一个完整的数据包进行处理。

只开串口接收中断是每个字节都会促发一次串口中断服务函数,这样会让CPU多次执行,入栈出栈等多余操作,也不能保证,每次都正确分包接收到的数据,毕竟是软件时间来分的包。

先初始化单片机的DMA和GPIO复用成串口。stm32单片机,可以用cubemx生成代码。下面是stm32l431的串口初始化。

#include <stdio.h>
#include "stm32l4xx.h"
#include "stm32l4xx_hal.h"

#define UART1_PORT			GPIOB
#define UART1_TX_PIN		GPIO_PIN_6
#define UART1_RX_PIN		GPIO_PIN_7

UART_HandleTypeDef  UART1_Handler;      //UART1 Handler
DMA_HandleTypeDef hdma_usart1_rx;       //UART1 dma Handler

//USART1 RX SIZE
#define USART1_RX_SIZE				256
//USART1 RX buff
uint8_t USART1_rxBuffer[USART1_RX_SIZE];
/**
  * @brief  UART1 initialize
  * @param  None
  * @retval None
  */
void BSP_UART1_Init(void)
{
	GPIO_InitTypeDef  GPIO_InitStruct;
	
	//Enable GPIO and USART clock
	__HAL_RCC_GPIOB_CLK_ENABLE();
	__HAL_RCC_USART1_CLK_ENABLE();
	__HAL_RCC_DMA1_CLK_ENABLE();
	
	//UART TX GPIO pin configuration
	GPIO_InitStruct.Pin       = UART1_TX_PIN | UART1_RX_PIN;
	GPIO_InitStruct.Mode      = GPIO_MODE_AF_PP;
	GPIO_InitStruct.Pull      = GPIO_PULLUP;
	GPIO_InitStruct.Speed     = GPIO_SPEED_FREQ_HIGH;
	GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
	HAL_GPIO_Init(UART1_PORT, &GPIO_InitStruct);
	
	UART1_Handler.Instance        = USART1;
	UART1_Handler.Init.BaudRate   = 115200;
	UART1_Handler.Init.WordLength = UART_WORDLENGTH_8B;
	UART1_Handler.Init.StopBits   = UART_STOPBITS_1;
	UART1_Handler.Init.Parity     = UART_PARITY_NONE;
	UART1_Handler.Init.HwFlowCtl  = UART_HWCONTROL_NONE;
	UART1_Handler.Init.Mode       = UART_MODE_TX_RX;
	UART1_Handler.Init.HwFlowCtl = UART_HWCONTROL_NONE;
	UART1_Handler.Init.OverSampling = UART_OVERSAMPLING_16;
	UART1_Handler.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
	UART1_Handler.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
	HAL_UART_Init(&UART1_Handler);
	
	/* USART1 DMA Init */
    hdma_usart1_rx.Instance = DMA1_Channel5;
    hdma_usart1_rx.Init.Request = DMA_REQUEST_2;
    hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_usart1_rx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    hdma_usart1_rx.Init.Mode = DMA_NORMAL;
    hdma_usart1_rx.Init.Priority = DMA_PRIORITY_VERY_HIGH;
    HAL_DMA_Init(&hdma_usart1_rx);
   
    __HAL_LINKDMA(&UART1_Handler,hdmarx,hdma_usart1_rx);
	
	//开启DMA接收,收完USART1_RX_SIZE字节后,会停止接收,因为没有用循环模式。使用建议,串口收的一包数据不大于USART1_RX_SIZE。串口空闲的时候,可以再次重新设置接收长度,这样DMA传输不会停止,不用循环模式,也能避免数据覆盖。
	HAL_UART_Receive_DMA(&UART1_Handler,USART1_rxBuffer,USART1_RX_SIZE);
	//只开串口空闲中断
	__HAL_UART_ENABLE_IT(&UART1_Handler, UART_IT_IDLE);
	
	HAL_NVIC_SetPriority(USART1_IRQn,1,0);
	HAL_NVIC_EnableIRQ(USART1_IRQn);
}

//prinft输出重定义到串口发送,串口发送也可以用DMA,需要封装一下DBG打印,感觉不是很必要,CPU累一点也没啥。
int fputc(int ch, FILE *f)
{
  HAL_UART_Transmit(&UART1_Handler, (uint8_t *)&ch, 1, 0xff);
  return ch;
}

驱动完成后,是应用。可以在串口空闲中断来的时候,置一个标志,完成对这包数据的处理后再开启DMA串口接收。还可以,把数据拷贝到fifo存着,直接开启DMA串口接收,下面用自己写fifo存储方式应用,(这个存储方式,不知道速度上能不能再优化了,欢迎大佬指正)。

在中断服务函数中,把数据拷贝到fifo里面。

/**
  * @brief  UART1 IRQ Handler
  * @param  None
  * @retval None
  */
void USART1_IRQHandler(void)
{
	if((__HAL_UART_GET_FLAG(&UART1_Handler,UART_FLAG_IDLE)!=RESET))
	{
		__HAL_UART_CLEAR_IDLEFLAG(&UART1_Handler);	
		HAL_UART_DMAStop(&UART1_Handler);
		uint16_t rx_len;
		//__HAL_DMA_GET_COUNTER(&hdma_usart1_rx) 这个寄存器里面初值是USART1_RX_SIZE大小,每收到一个字节会减一,减到0的话,DMA不再从外设把数据搬到定义的数组内存。下面的计算,是算出这包数据帧的长度。
		rx_len = USART1_RX_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);

		//长度有效,写入fifo
		if(rx_len)
		Write_RingBuff(USART1_rxBuffer,rx_len);
		//再次促发DMA搬运USART1_RX_SIZE个字节到USART1_rxBuffer		
		HAL_UART_Receive_DMA(&UART1_Handler,USART1_rxBuffer,USART1_RX_SIZE);
	}
	HAL_UART_IRQHandler(&UART1_Handler);
}

下面是fifo的实现,用到产品一段时间,目前没发现bug。思想就是两个循环fifo,一个数据存储的fifo,一个是记录包长度的fifo。最大存10包,buff长度最大100字节,根据实际使用调正。

#define RINGBUFF_LEN 100
#define PACKET_LEN 10
typedef struct
{
    uint16_t Head;           
    uint16_t Tail;
    uint8_t Ring_Buff[RINGBUFF_LEN];
}RingBuff_t;
//ringbuff structure
RingBuff_t ringBuff = {0};
//read index
uint8_t r_index = 0;
//write index
uint8_t w_index = 0;
//packet count
uint8_t count = 0;
//Record packet length
uint16_t Packet_len[PACKET_LEN];

/**
  * @brief  Write_RingBuff
  * @param  Which of the buff
  * @param  date len
  * @retval none
  */
void Write_RingBuff(uint8_t *data,uint16_t len)
{	
	if(count >= 10) 
	{
		printf("the packet is full!\r\n");
		return ;
	} 
	uint8_t *p = data;	
	Packet_len[w_index] = len;
	if(++w_index >= PACKET_LEN) w_index = 0;
	uint16_t i;
	if(ringBuff.Tail + len < RINGBUFF_LEN)
	{	
		for(i = 0; i < len; ++i)
		{
			ringBuff.Ring_Buff[ringBuff.Tail] = *p;
			++ringBuff.Tail;
			++p;
		}
	}
	else
	{
		uint16_t size = RINGBUFF_LEN - ringBuff.Tail;	
		for(i = 0; i < size; ++i)
		{
			ringBuff.Ring_Buff[ringBuff.Tail] = *p;
			++ringBuff.Tail;
			++p;
		}
		ringBuff.Tail = 0;		
		for(i = size; i < len; ++i)
		{
			ringBuff.Ring_Buff[ringBuff.Tail] = *p;
			++ringBuff.Tail;
			++p;
		}
	}	
	++count;
}

/**
  * @brief  read_RingBuff
  * @param  Which of the buff
  * @param  date len
  * @retval -1 :No package 0:have package
  */
int8_t Read_RingBuff(uint8_t *rData,uint16_t *len)
{
	if(count == 0) 
	{
		return -1;
	}
	uint8_t *p = rData;
	*len = Packet_len[r_index];
	if(++r_index >= PACKET_LEN) r_index = 0;
	uint16_t i;
	
	if(ringBuff.Head + *len < RINGBUFF_LEN)
	{		
		for(i = 0; i < *len; ++i)
		{
			*p = ringBuff.Ring_Buff[ringBuff.Head];
			++ringBuff.Head;
			++p;
		}	
	}	
	else
	{	
		uint16_t size = RINGBUFF_LEN - ringBuff.Head;		
		for(i = 0; i < size; ++i)
		{
			*p = ringBuff.Ring_Buff[ringBuff.Head];
			++ringBuff.Head;
			++p;
		}	
		ringBuff.Head = 0;
		for(i = size; i < *len; ++i)
		{
			*p = ringBuff.Ring_Buff[ringBuff.Head];
			++ringBuff.Head;
			++p;
		}
	}
	--count;
	return 0;
}

使用fifo里面包的应用如下。

uint8_t uart1_rx_buff[300];
//uart1 rx len
uint16_t rx_len;

		if(0==Read_RingBuff(uart1_rx_buff,&rx_len))
		{				
			printf("uart_recv len = %d , date= %.*s\r\n",rx_len,rx_len,uart1_rx_buff);	
            ....
             if((bool)strstr((char*)uart1_rx_buff,"CMD:xxxx"))
             {

			 }
             //处理数据后,清理一下buff,不清理,strstr函数是可能找到这个字符的,引发bug。
			 memset(uart1_rx_buff,0,sizeof(uart1_rx_buff));
		}				
		插入代码片
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值