电子产业一站式赋能平台

PCB联盟网

搜索
查看: 47|回复: 0
收起左侧

单片机串口解析数据的几种方式

[复制链接]

461

主题

461

帖子

5053

积分

四级会员

Rank: 4

积分
5053
发表于 2024-12-13 11:45:00 | 显示全部楼层 |阅读模式
关注+星标公众,不错过精彩内容

xxenc5ekkom6409262750.jpg

xxenc5ekkom6409262750.jpg

作者 | strongerHuang
微信公众号 | strongerHuang

UART串口是嵌入式开发常见的一种通信方式,但还是有不少人不知道怎么使用串口。

今天就来围绕串口,简单分享几点内容:
  • 串口接收方式
  • 处理接收数据
  • 通信协议解析

    串口接收方式
    串口接收(通信另一端)的数据,常见的方式:
  • 轮询(查询)接收寄存器
  • 中断接收数据


    轮询,就是间隔一定时间(一般ms,甚至us)去查询一下接收寄存器是否有数据,如果有数据,就处理接收到的数据。

    中断,平时没有数据接收时,CPU干自己的事。当有接收数据时,UART串口控制器会响应中断,通知CPU有事干了。

    轮询方式,大家想过有哪些弊端吗?
    效率低:CPU大部分时间都是去做查询的工作;

    响应不实时:如果短时间内有多个接收数据,CPU正在处理一件相对耗时的事情(比如:发送一个数据包),没来得及查询接收到的数据,此时,数据就可能丢失。(特别是早些年串口没有FIFO功能的时候)

    所以,不管是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   {    //拷贝接收到的数据    gRxCnt = 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     {      RxCnt = 0;      OSMboxPost(EventMBox_Touch, gDgus_RxBuf);  //发送消息邮箱(执行触控操作)    }  }}
    中断函数解析完一帧数据,可以通过标志位通知应用(裸机时),也可以通过消息队列、邮箱等方式发送到应用(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]);}
    最后,以上内容,仅提供思路,代码不一定适合项目。
    ------------ END ------------

    ubcn0drpe3r6409262850.gif

    ubcn0drpe3r6409262850.gif


    ●专栏《嵌入式工具
    ●专栏《嵌入式开发》
    ●专栏《Keil教程》
    ●嵌入式专栏精选教程

    关注公众号回复“加群”按规则加入技术交流群,回复“1024”查看更多内容。
    点击“阅读原文”查看更多分享。
  • 回复

    使用道具 举报

    发表回复

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则


    联系客服 关注微信 下载APP 返回顶部 返回列表