串口接收中断+空闲中断实现多个数据帧接收与处理

本文介绍了一种通过使用FIFO结构改进串口接收数据处理的方法,解决了帧缓存空间浪费的问题,提高了帧解析效率。通过定义两个FIFO分别存储帧数据和帧长度,避免了固定大小数组的局限,同时增加了稳定性检查机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在一些应用场合中,要求串口接收的数据不能丢同时又方便帧解析,我之前的做法是定义一个二维数组data[n][m],m的大小要大于最大帧长度,n用来指定帧缓存个数,每次接收到一帧数据二维数组下标n加1,将数组的最后两个字节用来存放帧长度,同时还需要用全局变量来维护帧个数,以及维护待解析的帧下标,以及即将新增的帧下标,这种做法有个缺点就是会浪费很多空间,因为帧的长度是不确定的,使得m的大小不好确定,实际使用时会尽量多定义一些。现提供一个更好的办法,解决上述的缺点。

这个方法通过使用两个FIFO来帧数据和帧解析位置,结构体定义如下:

/* FIFO */
typedef struct
{
  uint32_t in;  /* 写入数据时位置 */
  uint32_t out; /* 读取数据时位置 */
  uint32_t size; /* 缓存大小必须是2^n */
  void* pbuf;    /* 缓存指针 */
}s_kfifo_t;


typedef struct
{
  uint8_t buff[512];
  uint16_t frame[32];  /* 最大缓存的帧个数,每个成员保存帧长度,实际使用最大帧个数和帧长度有关 */
  uint16_t frame_len;  /* 当前帧长度 */
  s_kfifo_t d_kfifo;   /* 帧数据FIFO */
  s_kfifo_t f_kfifo;   /* 帧长度FIFO */
}rec_fifo_t;

s_kfifo_t 类型用于实现环形FIFO,rec_fifo_t类型用于定义接收数据缓存大小以及帧长度缓存,具体实现函数如下:

/* 变量定义 */

rec_fifo_t Rs232_Fifo=
{
  .frame_len = 0,
  
  .d_kfifo.in = 0,
  .d_kfifo.out = 0,
  .d_kfifo.size = 512,
  .d_kfifo.pbuf = Rs232_Fifo.buff,

  .f_kfifo.in = 0,
  .f_kfifo.out = 0,
  .f_kfifo.size = 32,
  .f_kfifo.pbuf = Rs232_Fifo.frame  
};

/**
  * 说明 : 写一个数据到串口接收FIFO
  * 参数 : rec_fifo, 串口接收帧控制体指针
  *        data, 输入数据
  * 返回 : 0->写FIFO成功, 1->FIFO满了
  */ 
uint8_t Kfifo_Data_Put_One( rec_fifo_t *rec_fifo, uint8_t data)
{
    uint32_t i;
    
    /* 判断帧缓存是否满 */
	if((rec_fifo->f_kfifo.size - rec_fifo->f_kfifo.in + rec_fifo->f_kfifo.out) < 1)
	{
        /** 帧满了,无法写入 */
        return 1;			
	}    
	if((rec_fifo->d_kfifo.size - rec_fifo->d_kfifo.in + rec_fifo->d_kfifo.out) < 1)
	{
        /** fifo满了,无法写入 */
        return 1;			
	}
    /* 写数据到数据缓存 */
    i=( rec_fifo->d_kfifo.in & ( rec_fifo->d_kfifo.size - 1 ) );
    ((uint8_t*)rec_fifo->d_kfifo.pbuf)[i] = data;
    
    rec_fifo->frame_len += 1;
	rec_fifo->d_kfifo.in += 1;
    return 0;
}

/**
  * 说明 : 写入一个数据到帧缓存
  * 参数 : rec_fifo, 串口接收帧控制体指针
  * 返回 : 0->写FIFO成功, 1->FIFO满了, 2->帧长度为0
  */ 
uint8_t Kfifo_Frame_Put_One( rec_fifo_t *rec_fifo)
{
    uint32_t i;
    /* 判断帧缓存是否满 */
	if((rec_fifo->f_kfifo.size - rec_fifo->f_kfifo.in + rec_fifo->f_kfifo.out) < 1)
	{
       /** 帧满了,无法写入 */
       return 1;			
	}    
    /* 写数据到数据缓存 */
    i=( rec_fifo->f_kfifo.in & ( rec_fifo->f_kfifo.size - 1 ) );
    ((uint16_t*)rec_fifo->f_kfifo.pbuf)[i] = rec_fifo->frame_len;
    rec_fifo->frame_len = 0;
		rec_fifo->f_kfifo.in += 1;
    return 0;
}

/**
  * 说明 : 获取帧数据
  * 参数 : fifo, FIFO指针
  *        ptr, 数据输出地址
  * 返回 : 读取的数据个数
  */ 
uint16_t Kfifo_Get_Frame_Data( rec_fifo_t *rec_fifo, uint8_t* pdata)
{
	uint32_t len;
    uint16_t i, j;
    uint16_t endlen;
    uint8_t *pbuf;  
    /** 判断是否有数据可以读 */
	if((rec_fifo->f_kfifo.in - rec_fifo->f_kfifo.out) < 1)
	{
       /** 帧为空 */
       return 0;
	}
    /* 获取帧长度 */
    i = rec_fifo->f_kfifo.out & ( rec_fifo->f_kfifo.size - 1 );
    len = ((uint16_t*)rec_fifo->f_kfifo.pbuf)[i];  
    
    /* 读取帧数据,长度由上面计算获得 */
    endlen = MIN( len, rec_fifo->d_kfifo.size - ( rec_fifo->d_kfifo.out & ( rec_fifo->d_kfifo.size - 1 ) ) );
    pbuf = (uint8_t*)rec_fifo->d_kfifo.pbuf + ( rec_fifo->d_kfifo.out & ( rec_fifo->d_kfifo.size - 1 ) );
    for( i = 0; i < endlen; i++ )
    {
        *( pdata++ ) = *( pbuf++ );
    }
    j = len - endlen;
    if ( j > 0 )
    {
        pbuf = (uint8_t*)rec_fifo->d_kfifo.pbuf;

        for( i = 0; i < j; i++ )
        {
            *( pdata++ ) = *( pbuf++ ) ;
        }
    }
    rec_fifo->d_kfifo.out += len;    
    rec_fifo->f_kfifo.out += 1;

    return len;
}

/**
  * 说明 : USART1串口中断函数(调试串口RS232)
  * 参数 : 无
  * 返回 : 无
  */ 
void USART2_IRQHandler(void)
{
  if(__HAL_UART_GET_FLAG(&Rs232_Uart_Handle, UART_FLAG_RXNE) != RESET)
  {
    __HAL_UART_CLEAR_FLAG(&Rs232_Uart_Handle, UART_FLAG_RXNE);
    /* 接收到的数据写入FIFO */
    Kfifo_Data_Put_One(&Rs232_Fifo, Rs232_Uart_Handle.Instance->DR);
  }
  if(__HAL_UART_GET_FLAG(&Rs232_Uart_Handle, UART_FLAG_IDLE) != RESET)
  {
    __HAL_UART_CLEAR_IDLEFLAG(&Rs232_Uart_Handle);
    /* 接收完一帧 */
    Kfifo_Frame_Put_One(&Rs232_Fifo);
    if(Rs232_Usart_Callback != NULL)
    {
      /* 回调函数 */
      Rs232_Usart_Callback();
    }    
  }
  if(__HAL_UART_GET_FLAG(&Rs232_Uart_Handle, UART_FLAG_ORE) != RESET)
  {
    __HAL_UART_CLEAR_OREFLAG(&Rs232_Uart_Handle);
  }  
}

/**
  * 说明 : RS232接收处理函数
  * 参数 : 无
  * 返回 : 无
  */
void Rs232_Rec_Handle(void)
{
  /* 轮询这个帧FIFO,不为空则返回帧长度,同时返回帧数据到指定缓存 */
  Device.len = Kfifo_Get_Frame_Data(&Rs232_Fifo, Device.buff);
  if(Device.len != 0)
  {
    if(Device.len < 4)
    {
      return;
    }
    if(Device.buff[0] == 0x68 && Device.buff[Device.len-1] == 0x16)
    {
      if(Device.buff[1] == 0x01) /* 设置运行模式 */
      {
        if(Device.buff[2] == 1) /* 4G测试模式 */
        {
          Device.mode = 1;
        }
        else if(Device.buff[2] == 2) /* GNSS测试模式 */
        {
          Device.mode = 2;
        }
        else /* 普通外设测试模式 */
        {
          Device.mode = 3;
        }
      }
    }
  }
}

上述方法简单易懂,移植也很简单,后续优化可以使用DMA接收,避免频繁中断,稳定性上上述方法带有数据长度溢出检查,以及帧个数溢出检查,经测试该方法能够稳定运行。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值