UART学习笔记一:如何判断一帧数据收完

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wuhenyouyuyouyu/article/details/53377946

    好久就想写个东东总结下,但是一直没有时间(都是借口,就是因为懒偷笑)。今天下午在整理UART模块驱动,正好记录下,想到哪里,写到哪里,大家勿怪。。。

    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();


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

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


UART学习笔记二:如何去check数据帧

UART学习笔记三:最优接收模型


展开阅读全文

没有更多推荐了,返回首页