UART学习总结:如何判断一帧数据收完

接口/总线/驱动

1139人已加入

描述

  UART

  通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),通常称作UART,是一种异步收发传输器,是电脑硬件的一部分。它将要传输的资料在串行通信与并行通信之间加以转换。作为把并行输入信号转成串行输出信号的芯片,UART通常被集成于其他通讯接口的连结上。

  具体实物表现为独立的模块化芯片,或作为集成于微处理器中的周边设备。一般是RS-232C规格的,与类似Maxim的MAX232之类的标准信号幅度变换芯片进行搭配,作为连接外部设备的接口。在UART上追加同步方式的序列信号变换电路的产品,被称为USART(Universal Synchronous Asynchronous Receiver Transmitter)。

  UART是一种通用串行数据总线,用于异步通信。该总线双向通信,可以实现全双工传输和接收。在嵌入式设计中,UART用于主机与辅助设备通信,如汽车音响与外接AP之间的通信,与PC机通信包括与监控调试器和其它器件,如EEPROM通信。

  UART接收数据,一个字节一个字节接收,底层硬件只能知道现在收到了一个字节,然后保存在一个buffer里面。怎么去判断现在一帧协议收完了呢?也就是说,我要发送一个协议帧,根据协议他是不定长的,怎么判断现在收完了呢?

  处理器

  方法一:

  也许有人会想到,我变收变判断,就是在接收驱动函数里,去解析协议,一般是这个样子:

  #pragma vector = INTSR2_vect

  __interrupt static void r_uart2_interrupt_receive(void)

  {

  buffer[CNT] = RXD2;

  CNT++;

  if(CNT 》 帧头长度){

  找帧头,然后记住帧头位置;

  }

  //找帧长位置

  //等待接收完

  //判断帧尾或者校验

  //通知APP,一帧接收完毕

  //请标志

  SRIF2 = 0;

  }

  这么做,我感觉是效率最高的,我的驱动层封装的时候,得暴露__interrupt static void r_uart2_interrupt_receive(void)给用户,同时提供用户底层接收完请标志等API。

  方法二:

  也会有人会想到我在协议前加一个字节长度不就完了,根据这个区接收,然后接收完了,告诉APP层或者协议解析层。这种方法实现的前提是大家都按这个做,否则通信失败,适合公司内部使用。

  方法三:

  我开始用的是TIMER_OUT方法,什么意思呢?举例说,9600波特率,发送一个字节大约需要1ms时间,假如认为发送是连续的而不是断续的,那么我是否可以认为你在超过1MS时间(为了留有足够的富裕时间,认为20MS)没有接收中断发生,我就可以认为接收完毕。接着通知APP或者协议解析模块去解析协议。但是这么处理有几个问题需要注意:

  1、如果对方用的查询方式发送,那个需要获得对方的最大中断处理时间;

  2、帧于帧之间发送间隔必须大于接收方设定的TIMER_OUT时间;(其实这段不满足的话,也可以处理,在RAM资源足够的情况下,协议解析模块按着解析多帧的思路去写)

  3、整个通信带宽被拉低,因为帧间隔是TIMER_OUT时间,肯定是MS级以上,一般20~50ms吧;

  但是这种方法是最简单的,也适合解耦,便于模块化封装。

  方法四:

  我现在用的方法是环形buffer。也就是UART一直在收数据,并且放到一个环形buffer里面(是为了防止溢出),APP或者协议解析模块不停的去读取数据,并做协议解析。这种方法优点就是处理速度快,没有了TIMER_OUT时间。目前问题是,我不知道怎么把buffer去解耦,索性我就把buffer全部丢给了协议解析模块。

  既然谈到了UART驱动封装,我就说说我目前做法,其实我也是刚学习封装这块,水平有限,就是想和大家聊聊,大神轻喷。

  首先我定义了几个接口:

  //UART0

  extern void uart0_drive_mode_init(void (*pRxCallBack)(uint8_t));

  extern bool uart0_cfg(uart_cfg_t *ptUartCfg,uint32_t wBaudRate,uint16_t hwErrorRange,void (*callback)());

  extern bool uart0_txd_query(uint8_t *pchTxdBuffer,uint16_t hwTxdNum);

  extern void uart0_enable_rx_interrupt(interrupt_level_t tLevel);

  extern void uart0_disable_rx_interrupt(void);

  extern void uart0_set_tx_interrupt_level(interrupt_level_t tLevel);

  extern bool uart0_txd_interrupt_start(uint8_t *pchBuffer,uint16_t hwTxdLong);

  /**************************** UART0 ****************************/

  //模块初始化

  #define USER_UART0_DRIVE_MODE_INIT(__CALLBACK) uart0_drive_mode_init(__CALLBACK)

  //USER0

  #define USER_UART0_CFG(_CONFIG,_BAUDRATE,_Rang,_CALLBACK) uart0_cfg((_CONFIG),(_BAUDRATE),(_Rang),(_CALLBACK))

  #define USER_UART0_TXD_QUERY(_BUFFER,_TXD_NUM) uart0_txd_query((_BUFFER),(_TXD_NUM))

  #define USER_UART0_ENABLE_RX_INTERRUPT(_LEVEL) uart0_enable_rx_interrupt(_LEVEL)

  #define USER_UART0_DISABLE_RX_INTERRUPT() uart0_disable_rx_interrupt()

  #define USER_UART0_SET_TX_INTERRUPT_LEVEL(_LEVEL) uart0_set_tx_interrupt_level(_LEVEL)

  #define USER_UART0_TXD_INTERRUPT_START(__BUFFER,__TXD_NUM) uart0_txd_interrupt_start((__BUFFER),(__TXD_NUM))

  解释:

  uart0_drive_mode_init(),模块初始化函数,需要传入一个void (*pRxCallBack)(uint8_t)型函数指针,这个函数是为了接收中断回调用户接收处理函数,把RXD0数据放到环形接收缓存区;

  uart0_cfg()配置函数,需要回调用户的I/O口配置函数,因为UARTI/O口是可选(偷懒了)

  uart0_txd_query()查询发送函数,线程安全的

  uart0_enable_rx_interrupt()使能接收中断,并设置优先级

  uart0_disable_rx_interrupt()关闭接收中断

  uart0_set_tx_interrupt_level()设置发送优先级

  uart0_txd_interrupt_start()中断发送,线程安全的

  然后,我把所有的中断和函数封装到底层里面。

  这里有一点我不明白怎么做,这个接收环形buffer怎么设计实现解耦?

  《-------------------------------------------------------------------------华丽分割线------------------------------------------------------------------------------》

  最近一直在学习OOPC和数据结构方面的知识,突然领悟到怎么把环形buffer分离出来,原来方法是如此简单,怪我以前想复杂了。

  首先我们定义一个queue的类,定义四个接口:

  bool init_byte_queue(byte_queue_t* ptQueue,uint8_t* pchBuffer,uint16_t hwSize);

  bool is_queue_empty(byte_queue_t* ptQueue);

  void enqueue_byte(byte_queue_t* ptQueue,uint8_t chInData);

  void dequeue_byte(byte_queue_t* ptQueue,uint8_t* pchOutData);

  然后定义个数据结构体:

  struct byte_queue_t{

  uint8_t *pchBuffer;

  uint16_t hwSize;

  uint16_t hwHead;

  uint16_t hwTail;

  uint16_t hwLength;

  };

  实现如下:

  #define this (*ptThis)

  bool init_byte_queue(byte_queue_t* ptQueue,uint8_t* pchBuffer,uint16_t hwSize)

  {

  CLASS(byte_queue_t) *ptThis = (CLASS(byte_queue_t) *)ptQueue;

  if(NULL == ptQueue){

  return false;

  }

  this.pchBuffer = pchBuffer;

  this.hwSize = hwSize;

  this.hwHead = 0;

  this.hwTail = 0;

  this.hwLength = 0;

  }

  bool is_queue_empty(byte_queue_t* ptQueue)

  {

  CLASS(byte_queue_t) *ptThis = (CLASS(byte_queue_t) *)ptQueue;

  if(NULL == ptQueue){

  return true;

  }

  return (0 == this.hwLength);

  }

  void enqueue_byte(byte_queue_t* ptQueue,uint8_t chInData)

  {

  CLASS(byte_queue_t) *ptThis = (CLASS(byte_queue_t) *)ptQueue;

  if(NULL == ptQueue){

  return;

  }

  this.pchBuffer[this.hwHead] = chInData;

  this.hwHead++;

  if(this.hwHead 》= this.hwSize){

  this.hwHead = 0;

  }

  this.hwLength++;

  }

  void dequeue_byte(byte_queue_t* ptQueue,uint8_t* pchOutData)

  {

  CLASS(byte_queue_t) *ptThis = (CLASS(byte_queue_t) *)ptQueue;

  if(NULL == ptQueue){

  return;

  }

  if(NULL == pchOutData){

  return;

  }

  if(!this.hwLength){

  return;

  }

  *pchOutData = this.pchBuffer[this.hwTail];

  this.hwTail++;

  if(this.hwTail 》= this.hwSize){

  this.hwTail = 0;

  }

  this.hwLength--;

  }

  到此基本UART就说完了,但是我今天看了篇关于环形队列同步加锁问题,有必要再补充下。

  上面的环形队列是有问题的,什么问题呢?就是hwLength的同步问题,hwLength--和hwLength++

  位于不同的线程,所以需要加锁,以确保原子性问题。

  QUEUE_ENTER_CRITICAL();

  this.hwLength--; //必须保证原子性

  QUEUE_LEAVE_CRITICAL();

  //QUEUE_ENTER_CRITICAL();

  this.hwLength++; //放在了中断中,本身任务优先级就高

  //QUEUE_LEAVE_CRITICAL();

  如果写入和读出线程优先级是平级的,那么都需要加锁;如果有一个优先级高,一个优先级低,那个低优先级

  线程需要加锁,高优先级不需要。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分