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();
如果寫入和讀出線程優先級是平級的,那么都需要加鎖;如果有一個優先級高,一個優先級低,那個低優先級
線程需要加鎖,高優先級不需要。
評論
查看更多