UART
通用異步收發(fā)傳輸器(Universal Asynchronous Receiver/Transmitter),通常稱作UART,是一種異步收發(fā)傳輸器,是電腦硬件的一部分。它將要傳輸?shù)?a href="http://www.qldv.cn/soft/special/" target="_blank">資料在串行通信與并行通信之間加以轉(zhuǎn)換。作為把并行輸入信號轉(zhuǎn)成串行輸出信號的芯片,UART通常被集成于其他通訊接口的連結(jié)上。
具體實物表現(xiàn)為獨立的模塊化芯片,或作為集成于微處理器中的周邊設備。一般是RS-232C規(guī)格的,與類似Maxim的MAX232之類的標準信號幅度變換芯片進行搭配,作為連接外部設備的接口。在UART上追加同步方式的序列信號變換電路的產(chǎn)品,被稱為USART(Universal Synchronous Asynchronous Receiver Transmitter)。
UART是一種通用串行數(shù)據(jù)總線,用于異步通信。該總線雙向通信,可以實現(xiàn)全雙工傳輸和接收。在嵌入式設計中,UART用于主機與輔助設備通信,如汽車音響與外接AP之間的通信,與PC機通信包括與監(jiān)控調(diào)試器和其它器件,如EEPROM通信。
UART接收數(shù)據(jù),一個字節(jié)一個字節(jié)接收,底層硬件只能知道現(xiàn)在收到了一個字節(jié),然后保存在一個buffer里面。怎么去判斷現(xiàn)在一幀協(xié)議收完了呢?也就是說,我要發(fā)送一個協(xié)議幀,根據(jù)協(xié)議他是不定長的,怎么判斷現(xiàn)在收完了呢?
方法一:
也許有人會想到,我變收變判斷,就是在接收驅(qū)動函數(shù)里,去解析協(xié)議,一般是這個樣子:
#pragma vector = INTSR2_vect
__interrupt static void r_uart2_interrupt_receive(void)
{
buffer[CNT] = RXD2;
CNT++;
if(CNT 》 幀頭長度){
找?guī)^,然后記住幀頭位置;
}
//找?guī)L位置
//等待接收完
//判斷幀尾或者校驗
//通知APP,一幀接收完畢
//請標志
SRIF2 = 0;
}
這么做,我感覺是效率最高的,我的驅(qū)動層封裝的時候,得暴露__interrupt static void r_uart2_interrupt_receive(void)給用戶,同時提供用戶底層接收完請標志等API。
方法二:
也會有人會想到我在協(xié)議前加一個字節(jié)長度不就完了,根據(jù)這個區(qū)接收,然后接收完了,告訴APP層或者協(xié)議解析層。這種方法實現(xiàn)的前提是大家都按這個做,否則通信失敗,適合公司內(nèi)部使用。
方法三:
我開始用的是TIMER_OUT方法,什么意思呢?舉例說,9600波特率,發(fā)送一個字節(jié)大約需要1ms時間,假如認為發(fā)送是連續(xù)的而不是斷續(xù)的,那么我是否可以認為你在超過1MS時間(為了留有足夠的富裕時間,認為20MS)沒有接收中斷發(fā)生,我就可以認為接收完畢。接著通知APP或者協(xié)議解析模塊去解析協(xié)議。但是這么處理有幾個問題需要注意:
1、如果對方用的查詢方式發(fā)送,那個需要獲得對方的最大中斷處理時間;
2、幀于幀之間發(fā)送間隔必須大于接收方設定的TIMER_OUT時間;(其實這段不滿足的話,也可以處理,在RAM資源足夠的情況下,協(xié)議解析模塊按著解析多幀的思路去寫)
3、整個通信帶寬被拉低,因為幀間隔是TIMER_OUT時間,肯定是MS級以上,一般20~50ms吧;
但是這種方法是最簡單的,也適合解耦,便于模塊化封裝。
方法四:
我現(xiàn)在用的方法是環(huán)形buffer。也就是UART一直在收數(shù)據(jù),并且放到一個環(huán)形buffer里面(是為了防止溢出),APP或者協(xié)議解析模塊不停的去讀取數(shù)據(jù),并做協(xié)議解析。這種方法優(yōu)點就是處理速度快,沒有了TIMER_OUT時間。目前問題是,我不知道怎么把buffer去解耦,索性我就把buffer全部丟給了協(xié)議解析模塊。
既然談到了UART驅(qū)動封裝,我就說說我目前做法,其實我也是剛學習封裝這塊,水平有限,就是想和大家聊聊,大神輕噴。
首先我定義了幾個接口:
//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(),模塊初始化函數(shù),需要傳入一個void (*pRxCallBack)(uint8_t)型函數(shù)指針,這個函數(shù)是為了接收中斷回調(diào)用戶接收處理函數(shù),把RXD0數(shù)據(jù)放到環(huán)形接收緩存區(qū);
uart0_cfg()配置函數(shù),需要回調(diào)用戶的I/O口配置函數(shù),因為UARTI/O口是可選(偷懶了)
uart0_txd_query()查詢發(fā)送函數(shù),線程安全的
uart0_enable_rx_interrupt()使能接收中斷,并設置優(yōu)先級
uart0_disable_rx_interrupt()關閉接收中斷
uart0_set_tx_interrupt_level()設置發(fā)送優(yōu)先級
uart0_txd_interrupt_start()中斷發(fā)送,線程安全的
然后,我把所有的中斷和函數(shù)封裝到底層里面。
這里有一點我不明白怎么做,這個接收環(huán)形buffer怎么設計實現(xiàn)解耦?
《-------------------------------------------------------------------------華麗分割線------------------------------------------------------------------------------》
最近一直在學習OOPC和數(shù)據(jù)結(jié)構(gòu)方面的知識,突然領悟到怎么把環(huán)形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);
然后定義個數(shù)據(jù)結(jié)構(gòu)體:
struct byte_queue_t{
uint8_t *pchBuffer;
uint16_t hwSize;
uint16_t hwHead;
uint16_t hwTail;
uint16_t hwLength;
};
實現(xiàn)如下:
#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就說完了,但是我今天看了篇關于環(huán)形隊列同步加鎖問題,有必要再補充下。
上面的環(huán)形隊列是有問題的,什么問題呢?就是hwLength的同步問題,hwLength--和hwLength++
位于不同的線程,所以需要加鎖,以確保原子性問題。
QUEUE_ENTER_CRITICAL();
this.hwLength--; //必須保證原子性
QUEUE_LEAVE_CRITICAL();
//QUEUE_ENTER_CRITICAL();
this.hwLength++; //放在了中斷中,本身任務優(yōu)先級就高
//QUEUE_LEAVE_CRITICAL();
如果寫入和讀出線程優(yōu)先級是平級的,那么都需要加鎖;如果有一個優(yōu)先級高,一個優(yōu)先級低,那個低優(yōu)先級
線程需要加鎖,高優(yōu)先級不需要。
評論
查看更多