UART:如何判断一帧数据收完
0 2022-12-01

  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 stac void r_uart2_interrupt_receive(void)

  {

  buffer[CNT] = RXD2;

  CNT ;

  if(CNT 》 帧头长度){

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

  }

  //找帧长位置

  //等待接收完

  //判断帧尾或者校验

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

  //请标志

  SRIF2 = 0;

  }

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

  方法二:

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

  方法三:

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

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

  2、帧于帧之间发送间隔必须大于接收方设定的MER_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();

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

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

声明: 本文转载自其它媒体或授权刊载,目的在于信息传递,并不代表本站赞同其观点和对其真实性负责,如有新闻稿件和图片作品的内容、版权以及其它问题的,请联系我们及时删除。(联系我们,邮箱:evan.li@aspencore.com )
0
评论
  • 相关技术文库
  • 硬件
  • 原理图
  • 信号完整性
  • EMI
  • TVS管在通信电路的防护应用

    TVS管(Transient Voltage \x0aSuppressor),直译过来就是瞬态电压抑制器,在电磁兼

    昨天
  • EEROM和EPROM的性能/应用区别

    EEPROM&EPROMEEPROM是指带电可擦可编程只读存储器,是一种掉电后数据不丢失的存储芯片,EEPROM可以在电脑上或专用设备上擦除已有信息,重

    昨天
  • EMC相关基础知识

    从本文开始将围绕“开关噪声-EMC”这一主题,对开关电源相关的EMC及其对策等进行解说。计划先介绍EMC相关的基础知识,然后再探讨噪声对策相关的内容。第一篇将以

    昨天
  • 6Gbps双通道单信道SATA转接驱动器

    日前,德州仪器(TI)宣布推出一款在目前可用6Gbps转接驱动器/均衡器中具有最低工作功耗与最低自动低功耗(ALP)模式的双通道单信道SATA转接驱动器及信号调

    昨天
  • 继电器驱动电路的保护设计

    在开始选择继电器驱动的时候,习惯性选择现有的集成芯片,比如NUD3126和NUD3124,没有仔细想过为什么要选用它们,是否可以选择分立的三极管或者达林顿管。这

    昨天
  • 白光LED模组驱动电路设计方案

    由于当前温室效应和能源危机的影响,使得人们对节能技术越来越关注。LED照明具有节能、寿命长等优点,LED照明技术作为新型绿色照明技术,目前的应用日趋广泛。LED

    昨天
  • 基于AP3605设计的典型白光LED驱动电路解析

    小尺寸的LCD显示模块早已成为手持式数码产品的重要组成部分,随着消费者对视觉方面要求的提高,LCD显示模块的设计变得越来越重要。如何在1.8寸至2.8寸的LCD

    昨天
  • 新型LED驱动器集成电路HV9925的主要参数及功能

    HV9925是Supertex公司2006年推出的一款新型LED驱动器集成电路。其实质上是一个高输入电压的DC/DC转换器,市电(85~264Vac)经全波整流

    昨天
  • 信号完整性效应的经验法则

    随着现代数字电子系统突破1GHz的壁垒,PCB板级设计和IC封装设计必须都要考虑到信号完整性和电气性能问题。凡是介入物理设计的人都可能会影响产品的性能。所有的设

    前天
  • 可降低变压器的漏感和尖峰电压的RC电路

    开关电源设计中,我们常常使用到一个电阻串联一个电容构成的RC电路,RC电路性能会直接影响到产品性能和稳定性。本文将为大家介绍一种既能降低开关管损耗,且可降低变压

    前天
  • 基于DSP+IPM硬件结构的变频调速系统设计方案

    引言变频调速技术广泛应用于工业领域。随着电力电子控制技术及元器件的不断发展,变频调速系统的集成度、智能化程度越来越高,硬件构成也越来越紧凑、简单。DSP(数字信

    02-06
  • 数码相机闪光灯的驱动控制电路设计

    在光线较弱的条件下,胶卷或数码摄影的高端设备需要氙气闪光灯管来进行拍摄。氙气闪光灯管可提供瞬间的高强度光源,在对较远处、高速移动或弱光条件下的物体进行拍摄时,这

    02-06
下载排行榜
更多
广告