良许Linux教程网 干货合集 串口通信,接收并解析数据的方式

串口通信,接收并解析数据的方式

最近有个新手问了这个问题:“如何接收和解析串口数据?”今天我们就围绕这个话题来进行讨论,并简单说明几点要点:

  • 串口数据的接收方式
  • 接收数据的处理
  • 通信协议的解析

串口数据的接收方式

在接收串口数据时,常见的方式包括:

  • 轮询(查询)接收寄存器
  • 中断方式接收数据

轮询方式是指间隔一定时间(通常是毫秒或微秒级别)去查询接收寄存器是否有数据,如果有数据,则进行相应的处理。

中断方式是指当没有数据接收时,CPU可以继续执行其他任务。而当有数据接收时,串口控制器会触发中断,并通知CPU有数据需要处理。

对于轮询方式,大家是否考虑过其中的弊端呢?

  • 效率低:CPU大部分时间都在进行查询操作,导致效率低下。
  • 响应不实时:如果短时间内有多个数据需要接收,而CPU正在执行相对耗时的任务(比如发送一个数据包),此时可能会错过接收数据的时机,造成数据丢失。尤其是在早期串口没有FIFO(First-In-First-Out)功能的情况下更为明显。

对于这个问题,中断方式通常被认为是更好的选择,因为它可以充分利用CPU的空闲时间,并且能够实时响应数据接收的事件,避免数据丢失的问题。

所以,不管是UART串口,还是I2C、 SPI、 CAN等串行通信,用的最多,最常见的还是中断接收,很少有用轮询的方式。

我之前维护一个老代码(坑),CLI串口用轮询方式,出现丢数据、溢出错误等众多问题,让我还加了好几个班。。。

处理接收数据

中断有数据来了,大家怎么处理接收到的数据?

我见过有些小项目,直接在中断函数里面做一些应用的情况。比如:串口中断接收一个传感器发过来的数据,显示数据并做一些响应的动作。

中断函数,代码能少尽少,耗时能少尽少,不能处理太多耗时的复杂的逻辑、应用等。

中断有数据来了,一般是通过FIFO方式处理。

1.简单的数组接收、应用解析并处理

比如:

static uint8_t gRxCnt = 0;
static uint8_t gRxBuf[10];

void USART1_IRQHandler(void)
{
  //...
  gDgus_RxBuf[gRxCnt] = (uint8_t)USART_ReceiveData(USART1);
  gRxCnt++;
  //...

}

void App(void)
{
  //...
  if(0 

2.中断函数接收一帧完整数据再处理

比如:

void USART1_IRQHandler(void)
{
  static uint8_t RxCnt = 0;                      //计数值
  static uint8_t RxNum = 0;                      //数量

  if((USART1->SR & USART_FLAG_RXNE) == USART_FLAG_RXNE)
  {
    gDgus_RxBuf[RxCnt] = (uint8_t)USART_ReceiveData(USART1);
    RxCnt++;

    /* 判断帧头 */
    if(gDgus_RxBuf[0] != DGUS_FRAME_HEAD1)       //接收到帧头1
    {
      RxCnt = 0;
      return;
    }
    if((2 == RxCnt) && (gDgus_RxBuf[1] != DGUS_FRAME_HEAD2))
    {
      RxCnt = 0;
      return;
    }

    /* 确定一帧数据长度 */
    if(RxCnt == 3)
    {
      RxNum = gDgus_RxBuf[2] + 3;
    }

    /* 接收完一帧数据 */
    if((6 

中断函数解析完一帧数据,可以通过标志位通知应用(裸机时),也可以通过消息队列、邮箱等方式发送到应用(RTOS时)。

3.RTOS队列、邮箱接收

比如:

void DEBUG_COM_IRQHandler(void)
{
  static uint8_t Data;

  if(USART_GetITStatus(DEBUG_COM, USART_IT_RXNE) != RESET)
  {
    Data = USART_ReceiveData(DEBUG_COM);
    CLI_RcvDateFromISR(Data); //下面把这个函数分离出来了
  }
}

void CLI_RcvDateFromISR(uint8_t RcvData)
{
  static portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;

  if(xCLIRcvQueue != NULL)
  {
    xQueueSendFromISR(xCLIRcvQueue, &RcvData, &xHigherPriorityTaskWoken);
  }
}

中断来一字节数据,就通过消息队列发送一个字节数据,如果没有及时出来这个数据,也是存储在队列中。

通信协议解析

像上面第2种,简单通信协议,项目相对较小的情况下,可以直接在中断函数里面处理。

但是,如果项目相对较大、复杂一点,协议也先对复杂一点,上面第2种在函数内部出来方式就不可取。

1.裸机环境

裸机的情况下,建议用第一种:中断数组缓存数据(FIFO),应用解析通信协议。

2.RTOS环境

RTOS情况下,建议用第三种方式:消息队列、邮箱等方式接收数据,然后发送(通知)应用解析协议。

当然,以上说的都只是常见的方式,具体还需要结合你项目实际情况。

同时,其它类似I2C、CAN等通信,如有协议解析,也是类似。

比如之前给大家分享的MavLink,我就用CAN实现过:

void CAN_RX_IRQHandler(void)
{
  static CanRxMsg RxMessage;
  static MAVRCV_QUEUE_TypeDef MAVRcvQueue_Union;

  CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);
                                                 //拷贝长度、 数据
  MAVRcvQueue_Union.MAVRcvStruct.MAVLink_Len = RxMessage.DLC;
  memcpy(&MAVRcvQueue_Union.MAVRcvStruct.MAVLink_Buf[0], &RxMessage.Data[0], RxMessage.DLC);

  MAVLink_RcvDateFromISR(&MAVRcvQueue_Union.MAVLinkRcv_Queue[0]);
}

最后,以上内容,仅提供思路,代码不一定适合项目。

以上就是良许教程网为各位朋友分享的Linu系统相关内容。想要了解更多Linux相关知识记得关注公众号“良许Linux”,或扫描下方二维码进行关注,更多干货等着你 !

137e00002230ad9f26e78-265x300
本文由 良许Linux教程网 发布,可自由转载、引用,但需署名作者且注明文章出处。如转载至微信公众号,请在文末添加作者公众号二维码。
良许

作者: 良许

良许,世界500强企业Linux开发工程师,公众号【良许Linux】的作者,全网拥有超30W粉丝。个人标签:创业者,CSDN学院讲师,副业达人,流量玩家,摄影爱好者。
上一篇
下一篇

发表评论

联系我们

联系我们

公众号:良许Linux

在线咨询: QQ交谈

邮箱: yychuyu@163.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

关注微博
返回顶部