新聞中心

EEPW首頁 > 嵌入式系統(tǒng) > 設(shè)計應(yīng)用 > WinCE串口驅(qū)動分析

WinCE串口驅(qū)動分析

作者: 時間:2007-05-11 來源:網(wǎng)絡(luò) 收藏
雖然串口通訊已經(jīng)是普遍的標準而且廣為大家熟知,但驅(qū)動中涉及的部分內(nèi)容也可能在平時的應(yīng)用中并不是很常用到,在這里做一個簡單的介紹待后面說明到具體代碼的時候可以連貫一些。

串行通訊接口是目前十分流行的通訊接口之一。由于其電氣界面的簡單性使其在計算機領(lǐng)域的應(yīng)用相當?shù)膹V泛。在這里提到的串行通訊接口主要是指(通用串行)和IRDA兩種。通常的串行連接電氣連接上有3wire和9wire兩種。3wire的接線方式下定義了發(fā)送、接收和地三根連接。其用途就如名稱一樣分別用于發(fā)送、接收。

通常在串行接口控制器上會有兩個FIFO用作接收和發(fā)送的緩沖,當接收到數(shù)據(jù)后會直接將接收到的數(shù)據(jù)置入該緩沖器,并同時由控制電路向本地總線發(fā)出通知,以便讓本地總線將緩沖器內(nèi)的數(shù)據(jù)讀走,這樣在響應(yīng)(等待和讀取)的過程中仍然能通過緩沖器來接收數(shù)據(jù)。而發(fā)送發(fā)送的過程剛剛相反,本地總線可一直向發(fā)送緩沖寫入數(shù)據(jù)直到器填滿為止,而無需對每個數(shù)據(jù)的發(fā)送進行等待。這就是基本的收發(fā)流程(這部分邏輯流程相信大家是最熟悉的)。這一點在3wire和9wire中都是相同的。但是我們考慮下面的情況,如果接收一方的響應(yīng)由于某種原因的干擾(如處理器被其他中斷服務(wù)占用)的時候可能就來不及相應(yīng)之前ReceiveFIFO就可能被填滿了,這樣后續(xù)發(fā)送過來的數(shù)據(jù)就會丟失,這樣在需要數(shù)據(jù)可靠傳輸?shù)那闆r下串行通訊的弊端也就顯示出來了。如需要數(shù)據(jù)的可靠傳輸就需要對數(shù)據(jù)流的收發(fā)進行控制。在9wire中將串行連接定義為如下形式。

針號
1
2
3
4
5
6
7
8
9
縮寫
DCD
RXD
TXD
DTR
GND
DSR
RTS
CTS
DELL
功能說明
數(shù)據(jù)載波檢測
接收數(shù)據(jù)
發(fā)送數(shù)據(jù)
數(shù)據(jù)終端就緒
信號地
數(shù)據(jù)設(shè)備就緒
請求發(fā)送
清除發(fā)送
振鈴指示

也就是說在原3wire的基礎(chǔ)上增加了DCD,DTR,DSR,RTS,CTS,DELL六個控制線。其中RTS/CTS用于流控制,另外的DCD和DELL則留作連接modem使用。有了專門的硬件流控制引腳也就使得流控制成為可能,以完成收發(fā)兩端的匹配使得數(shù)據(jù)可以可靠的傳輸。用RTS/CTS(請求發(fā)送/清除發(fā)送)流控制時,應(yīng)將通訊兩端的RTS、CTS線對應(yīng)相連).在發(fā)送端準備發(fā)送數(shù)據(jù)之前設(shè)置RTS(Request to send)也就使發(fā)送請求線,若接收端以作好接收準備,就啟動響應(yīng)的CTS(Clear to send)引線。這樣,收發(fā)雙發(fā)就進入數(shù)據(jù)傳輸狀態(tài),在此過程中如若接收端處理數(shù)據(jù)的速度低于發(fā)送端的發(fā)送速度,接收一端還可以設(shè)置CTS引線恢復(fù)原來阻塞得狀態(tài)以暫時中斷數(shù)據(jù)傳輸,之后若需要恢復(fù)數(shù)據(jù)傳輸恢復(fù)CTS狀態(tài)即可。這樣的傳輸即實現(xiàn)了流控制,保障了數(shù)據(jù)傳輸?shù)耐陚湫浴?br />
在這里還要說一下軟件流控制,雖然硬件已經(jīng)可以完成流控制的任務(wù)但很多少時候受到連線數(shù)的限制不能使用硬件流控制也就設(shè)計了專門的軟件流控制的方法。現(xiàn)在回到3線傳輸?shù)那榫?,若接收端接收?shù)據(jù)過程中緩沖器的負載到達某一限制(也就是留出一定的緩沖空間)時接收端向發(fā)送端發(fā)送一個特殊的標示位(接收停止位),當發(fā)送端收到該標示的時候就停止發(fā)送,直到接收端緩沖器低于另一限制后發(fā)送標示(接收許可位)給發(fā)送端,這樣就可以控制數(shù)據(jù)流的傳輸起停。這種軟件流控制是在給緩沖器留余量來完成的,在收發(fā)雙端處理器速度差很大的時候就不太適用了,就必須要用硬件流控制。

其他幾個引腳都是與modem相關(guān)的,DSR數(shù)據(jù)裝置準備好(Data set ready)用于表明MODEM處于可以使用的狀態(tài)。DTR數(shù)據(jù)終端準備好(Data terminal ready)表明數(shù)據(jù)終端可以使用。這兩個信號用于檢查Modem是否連接。DELL腳當有電話撥入時Modem將會設(shè)置這個引腳。DCD信號是當Modem接收到數(shù)字載波信號的時候被設(shè)置,用于了解Modem接收信號的情況。
至于剩下的奇偶效驗和停止位設(shè)置就只是需要針對寄存器設(shè)置無需軟件干涉就可以完成了。下面我們來看具體的驅(qū)動程序。

架構(gòu)

在wince中串口的驅(qū)動實現(xiàn)是有固定模型的,ce中的串口模型遵循ISO/OSI網(wǎng)絡(luò)通訊模型(7層),就是說串口屬于CE網(wǎng)絡(luò)模塊的一個部分。其中rs232界面(或其它的物理介質(zhì))實現(xiàn)網(wǎng)絡(luò)的物理層,而驅(qū)動和serialAPI共同組成數(shù)據(jù)鏈路層,其它部分都沒有做定義。在典型的應(yīng)用中,serialAPI與間接通過TAPI或直接與ActiveSync交互,組成CE網(wǎng)絡(luò)的一部分。而紅外本身的協(xié)議就相對復(fù)雜的多,它有專門的一套模型來描述其使用規(guī)則,對紅外設(shè)備本身了解不多也就不能深入下去。在串口的這一側(cè),整個驅(qū)動模型也是相當?shù)膹?fù)雜的,但所幸的是驅(qū)動僅僅使用到SerialAPI這一層,在這個層次上串口的行為還是相對簡單的。


我們這里僅僅涉及上面所提到的Serial/irda Driver這部分(綠色部分)。在wince提供的驅(qū)動例程中串口/紅外驅(qū)動采用分層結(jié)構(gòu)設(shè)計,MDD提供框架性的實現(xiàn),負責提供OS所需的基本實現(xiàn),并將代碼設(shè)計與具體的硬件設(shè)計無關(guān)。而PDD提供了對硬件操作相應(yīng)的代碼。這些代碼通過結(jié)構(gòu)HWOBJ來相互聯(lián)系。對于MDD+PDD的整體驅(qū)動來看,串口驅(qū)動模型是作為Stream來實現(xiàn)的。 兩者合一以達到實現(xiàn)驅(qū)動的目的。DDSI就是指這兩個部分之間的接口,這個接口并非受到強制的物理/邏輯關(guān)系來約束,而是人為的規(guī)定的。在涉及到一種特定硬件我們進行針對實現(xiàn)的時候往往需要的是了解硬件的物理特性和控制邏輯,然后根據(jù)DDSI的約束就來進行實現(xiàn)。對于這里描述的驅(qū)動模型而言結(jié)合關(guān)鍵在于結(jié)構(gòu)指針HWOBJ的使用和具體實現(xiàn)。在實際的驅(qū)動應(yīng)用中僅僅需要實現(xiàn)HWOBJ相關(guān)的一系列函數(shù),而無需從驅(qū)動頂層完全開發(fā)。串口驅(qū)動模型作為一種常用驅(qū)動模型在windowsCE中常常用于串口/紅外/USB Client的具體實現(xiàn)。該驅(qū)動模型中對全功能的串口進行了定義,除了常用的TX和RX引線定義以外,針對DTR、RTS等功能引腳都進行了支持,使得用該模型設(shè)計的串口驅(qū)動支持流控制、具備驅(qū)動Modem等設(shè)備的能力。
事實上,如果需要的話完全可以將該驅(qū)動一體化設(shè)計(拋開PDD-MDD的劃分,也就無須DDSI)。也就是不使用現(xiàn)有的驅(qū)動架構(gòu)來進行實現(xiàn)??紤]到串口驅(qū)動的使用頻率和執(zhí)行效率要求都不是很苛刻的情況下拋棄驅(qū)動架構(gòu)另外實現(xiàn)的就沒有多大必要了。
對于驅(qū)動本身而言,串行驅(qū)動從功能和實現(xiàn)上相當?shù)暮唵?,確具被相當全面的成分,對該驅(qū)動的分析和了解無疑是學(xué)習(xí)流式驅(qū)動程序很好的典范。
代碼分析

在開始具體代碼之前我們先來看看,相關(guān)的一些結(jié)構(gòu)。 HWOBJ是相應(yīng)的硬件設(shè)備操作的抽象集合。結(jié)構(gòu)的定義后的注釋與實際的用途有點點出入,BandFlags指定IST的啟動時間,可選為在初始化過程啟動或是在打開設(shè)備的時候起動ISR.而第二個參數(shù)則是指定攔截的具體的系統(tǒng)中斷號。最后一個參數(shù)是一個結(jié)構(gòu),該結(jié)構(gòu)定義了硬件操作的各式行為函數(shù)的指針,MDD正是通過這些函數(shù)來訪問具體的PDD操作。
typedef struct __HWOBJ {
ULONG BindFlags; // Flags controlling MDD behaviour. Se above.
DWORD dwIntID; // Interrupt Identifier used if THREAD_AT_INIT or THREAD_AT_OPEN
PHW_VTBL pFuncTbl;
} HWOBJ, *PHWOBJ;

而HW_VTBL則是代表具體硬件操作函數(shù)指針的集合,該結(jié)構(gòu)所指向的函數(shù)包括了初始化、打開、關(guān)閉、接收、發(fā)送、設(shè)置Baudrate等一系列操作。結(jié)構(gòu)存在就像紐帶一樣聯(lián)系著PDD中的具體實現(xiàn)和MDD中的抽象操作。PDD的實現(xiàn)必須遵循HW_VTBL中所描述的函數(shù)形式,并構(gòu)造出相應(yīng)的HW_VTBL實例。驅(qū)動的編寫就是針對這些函數(shù)來一一進行實現(xiàn)。
typedef struct __HW_VTBL {
PVOID (*HWInit)(ULONG Identifier, PVOID pMDDContext, PHWOBJ pHWObj);
BOOL (*HWPostInit)(PVOID pHead);
ULONG (*HWDeinit)(PVOID pHead);
BOOL (*HWOpen)(PVOID pHead);
ULONG (*HWClose)(PVOID pHead);
INTERRUPT_TYPE (*HWGetIntrType)(PVOID pHead);
ULONG (*HWRxIntrHandler)(PVOID pHead, PUCHAR pTarget, PULONG pBytes);
VOID (*HWTxIntrHandler)(PVOID pHead, PUCHAR pSrc, PULONG pBytes);
VOID (*HWModemIntrHandler)(PVOID pHead);
VOID (*HWLineIntrHandler)(PVOID pHead);
ULONG (*HWGetRxBufferSize)(PVOID pHead);
BOOL (*HWPowerOff)(PVOID pHead);
BOOL (*HWPowerOn)(PVOID pHead);
VOID (*HWClearDTR)(PVOID pHead);
VOID (*HWSetDTR)(PVOID pHead);
VOID (*HWClearRTS)(PVOID pHead);
VOID (*HWSetRTS)(PVOID pHead);
BOOL (*HWEnableIR)(PVOID pHead, ULONG BaudRate);
BOOL (*HWDisableIR)(PVOID pHead);
VOID (*HWClearBreak)(PVOID pHead);
VOID (*HWSetBreak)(PVOID pHead);
BOOL (*HWXmitComChar)(PVOID pHead, UCHAR ComChar);
ULONG (*HWGetStatus)(PVOID pHead, LPCOMSTAT lpStat);
VOID (*HWReset)(PVOID pHead);
VOID (*HWGetModemStatus)(PVOID pHead, PULONG pModemStatus);
VOID (*HWGetCommProperties)(PVOID pHead, LPCOMMPROP pCommProp);
VOID (*HWPurgeComm)(PVOID pHead, DWORD fdwAction);
BOOL (*HWSetDCB)(PVOID pHead, LPDCB pDCB);
BOOL (*HWSetCommTimeouts)(PVOID pHead, LPCOMMTIMEOUTS lpCommTO);
BOOL (*HWIoctl)(PVOID pHead, DWORD dwCode,PBYTE pBufIn,DWORD dwLenIn,
PBYTE pBufOut,DWORD dwLenOut,PDWORD pdwActualOut);
} HW_VTBL, *PHW_VTBL;交待了上述兩個結(jié)構(gòu)以后我們來看看具體的代碼,為保障對系統(tǒng)架構(gòu)的清晰認識,我們將MDD的代碼和PDD的代碼分開進行分析。

MDD部分

由于串口驅(qū)動由Device.exe直接調(diào)用,所以MDD部分是以完整的Stream接口給出的. 也就具備基于Stream接口的驅(qū)動程序所需的函數(shù)實現(xiàn),包括COM_Init,COM_Deinit
,COM_Open,COM_Close ,COM_Read ,COM_Write, COM_Seek,, COM_PowerUp, COM_PowerDown, COM_IOControl幾個基本實現(xiàn)。由于串口發(fā)送/接收的信息并不能定位,而僅僅是簡單的傳送,所以COM_Seek僅僅是形式上實現(xiàn)了一下。

COM_Init
COM_Init是該驅(qū)動的初始化函數(shù),在設(shè)備管理器加載該驅(qū)動后首先調(diào)用,用于初始化所需的變量,硬件設(shè)備等資源。該過程分配代表設(shè)備硬件實例的數(shù)據(jù)結(jié)構(gòu),并通過硬件抽象接口HWInit初始化硬件。同時該函數(shù)會調(diào)用InterruptInitialize為接收內(nèi)核中的邏輯中斷創(chuàng)建相應(yīng)事件并初始化臨界區(qū)。該函數(shù)還需要得到硬件緩沖器的物理地址和獲知該緩沖器的大小(該沖器最小為2K)。最后它將建立相應(yīng)的緩沖作為接收的中介。下面我們來看這個函數(shù)的實現(xiàn)過程。
在函數(shù)中定義了兩個重要的變量。pSerialHead和pHWHead.前者用于描述相應(yīng)的串口的狀態(tài),后者則是對應(yīng)硬件的數(shù)據(jù)抽象。首先為pSerialHead分配空間和初始化鏈表和臨界區(qū)等數(shù)據(jù)并同時為接收和發(fā)送中斷創(chuàng)建事件。然后再從注冊表中獲得當前的注冊項值(由于device.exe是根據(jù)注冊表鍵值來調(diào)用驅(qū)動的,當前鍵注冊表項指的就是與上述鍵值在同一根下的注冊項)。得到DeviceArrayIndex、Priority256鍵下的具體值后就可以調(diào)用GetSerialObject(在PDD中實現(xiàn))來獲得具體的HWObj對象,并通過該對象來調(diào)用硬件初始化函數(shù)了。(由于在這里已經(jīng)對硬件進行了初始化,之后的返回都需要調(diào)用COM_Deinit來完成。)由于硬件初始化(實際的驅(qū)動初始化代碼)已經(jīng)得到執(zhí)行這個時候就只有分配和初始化緩沖的工作需要做了。所以調(diào)用HWGetRxBufferSize(PDD代碼)來獲取PDD中設(shè)定的緩沖大小,并根據(jù)返回的具體值分配緩沖。最后如果BindFlags被設(shè)定為THREAD_AT_INIT就再調(diào)用StartDispatchThread啟動分發(fā)線程(實際的IST)。這樣就完成了系統(tǒng)初始化的操作。
COM_Deinit
當驅(qū)動被稱被卸下的時候該事件啟動,用作與COM_Init相反的操作。這個過程大致會釋放驅(qū)動中所使用的資源,停止期間創(chuàng)建的線程等操作。具體說來,大致為停止在MDD中的所有IST,和釋放內(nèi)存資源和臨界區(qū)等系統(tǒng)資源。同時還需調(diào)用HWDeinit來釋放PDD中所使用到的系統(tǒng)資源。
COM_Open
COM_Oepn在CreateFile后被調(diào)用,用于以讀/寫模式打開設(shè)備,并初始化所需要的空間/資源等,創(chuàng)建相應(yīng)的實例,為后面的操作做好準備。這里的代碼相對比較容易,下面就簡單講一下。既然是初始化,肯定就免不了對參數(shù)的檢查。首先檢查通過COM_Init返回的pHead結(jié)構(gòu)是否有效,這里雖然沒有顯式的在這兩個函數(shù)之間傳遞參數(shù),而是在設(shè)備管理器的內(nèi)部傳遞這個參數(shù)的。
之后是檢查文件系統(tǒng)傳遞過來的Open句柄中的Open模式是否有效,這個參數(shù)由應(yīng)用程序產(chǎn)生,通過文件系統(tǒng)直接傳遞到驅(qū)動。之后就開始初始化的操作,在這里將會建立相應(yīng)的HW_OPEN_INFO實體。下面和為該結(jié)構(gòu)的定義。
typedef struct __HW_OPEN_INFO {
PHW_INDEP_INFO pSerialHead; // @field Pointer back to our HW_INDEP_INFO
DWORD AccessCode; // @field What permissions was this opened with
DWORD ShareMode; // @field What Share Mode was this opened with
DWORD StructUsers; // @field Count of threads currently using struct.
COMM_EVENTS CommEvents; // @field Contains all in…. handling
LIST_ENTRY llist; // @field Linked list of OPEN_INFOs
} HW_OPEN_INFO, *PH
結(jié)構(gòu)中的第一個參數(shù)指向我們前面提到的HW_INDEP_INFO結(jié)構(gòu),第二個參數(shù)為操作權(quán)限碼,也就是READ/WRITE這類的權(quán)限。第三個參數(shù)為共享模式,以確定是否支持獨占。這兩個參數(shù)都是與文件系統(tǒng)的內(nèi)容對應(yīng)的。而CommEvent則對應(yīng)于本實例的事件。由于驅(qū)動架構(gòu)支持多個OPEN操作實例的存在,所以這里維護了一個鏈表來聯(lián)系這些結(jié)構(gòu)。在這里由于IST的啟動可以在COM_Init和COM_Open中進行,還有處理器啟動IST的內(nèi)容。準備好HW_OPEN_INFO結(jié)構(gòu)后就可以調(diào)用HWOpen(PDD)來進行PDD所需的Open操作了。Open操作完成后調(diào)用HWPurgeComm(PDD)來處理(取消或等待)當前仍在通訊狀態(tài)的任務(wù)。然后重置軟件FIFO就基本完成了COM_Open的動作了。
事實上這里主要是對所需的數(shù)據(jù)結(jié)構(gòu)進行處理,對于硬件的具體操作都留給PDD去做了,MDD所維護的僅僅是一個架構(gòu)性的代碼。Open操作完成后,驅(qū)動就進入了工作狀態(tài)這個時候。
COM_Close
COM_Close為與COM_Open相對應(yīng)的操作。這期間的目的是釋放COM_Open所使用的系統(tǒng)資源,除此以外如果在COM_Open期間創(chuàng)建了相應(yīng)的IST還需要停止該線程,在最后將該HW_OPEN_INFO脫鏈。這樣一來驅(qū)動狀態(tài)就得以恢復(fù)。當然這期間還做了一寫避免線程競爭的處理,使得代碼看起來不是那么簡單。
StartDispatchThread/StopDispatchThread
這兩個函數(shù)都不是Stream所需要的標準接口,但卻是中斷服務(wù)程序所需的IST啟動和關(guān)閉的手段,所以在這里順便說一下。
StartDispatchThread函數(shù)用于啟動IST,主要的工作為調(diào)用InterruptInitialize將系統(tǒng)中斷與相應(yīng)的事件聯(lián)系起來。并啟動SerialDispatchThread作為IST.其中調(diào)用了叫做 InterruptDone的函數(shù),該函數(shù)會調(diào)用OAL中的OEMInterruptDone來完成中斷的響應(yīng)。
StopDispatchThread用于與StartDispatchThread相反的操作。停止的過程相對要復(fù)雜一些,該函數(shù)首先設(shè)定當前線程的優(yōu)先級與分發(fā)線程相同,以便于在停止該線程的動作不會比釋放內(nèi)存的動作快以避免出錯。停止的動作是讓線程主動完成的,具體的方法是提交表示位KillRxThread然后通過Sleep請求調(diào)度,待到IST自己停止。這個時候由于IST已經(jīng)停止所以在程序的最后調(diào)用InterruptDisable來屏蔽中斷。
SerialDispatchThread/ SerialEventHandler
SerialDispatchThread/ SerialEventHandler就是串口驅(qū)動的中斷分發(fā)程序(也就是IST的所在)。整個IST被分開寫成兩個部分---循環(huán)主體和事件處理程序。循環(huán)主體SerialDispatchThread內(nèi)容相對比較簡單,反復(fù)等待串口事件并調(diào)用SerialEventHandler對具體的中斷進行處理,直到pSerialHead->KillRxThread被設(shè)置后退出。SerialEventHandler為中斷處理的具體實現(xiàn),程序在獲得串口事件后運行,目的在于對中斷進行進一步的判斷并執(zhí)行相應(yīng)的處理。
下面參考兩個結(jié)構(gòu)體來完成接受和發(fā)送中斷服務(wù)的分析。我們先來看RX_BUFFER_INFO結(jié)構(gòu)。
typedef struct __RX_BUFFER_INFO {
ULONG Read; /* @field Current Read index. */
ULONG Write; /* @field Current Write index. */
ULONG Length; /* @field Length of buffer */
BOOL DataAvail; /* @field BOOL reflecting existence of data. */
PUCHAR RxCharBuffer; /* @field Start of buffer */
CRITICAL_SECTION CS; /* @field Critical section */
} RX_BUFFER_INFO, *PRX_BUFFER_INFO;
用該結(jié)構(gòu)的原因是在驅(qū)動內(nèi)部維護了一個緩沖器用作驅(qū)動和應(yīng)用程序之間的緩沖.
在硬件內(nèi)部已經(jīng)有一個FIFO緩沖器,這保障了驅(qū)動的數(shù)據(jù)接收,但由于應(yīng)用不一定能保障在驅(qū)動獲得數(shù)據(jù)后及時將數(shù)據(jù)取走,因此在驅(qū)動內(nèi)部維護了另外一個緩沖器。在RX_BUFFER_FIFO結(jié)構(gòu)中的read成員為MDD取數(shù)據(jù)時在FIFO的位置標志,而PDD向軟件寫入數(shù)據(jù)的位標則對應(yīng)被稱作Write,DataAvail用作表示緩沖器內(nèi)的數(shù)據(jù)是否有效。而RxCharBuffer則是指向軟件FIFO的指針。當收到數(shù)據(jù)的時候就將write標示往上遞增,而程序向驅(qū)動取數(shù)得時候Read遞增,這樣就可以根據(jù)Read和Write成員來維護這個FIFO。有了這個基本思路墊底我們接著看RX的中斷服務(wù)具體如何實現(xiàn)。這間還會涉及到流控制的成分。
接收分支:在接收分支的開始計算軟件緩沖器的剩余空間,如果有剩余的空間的話直接調(diào)用HWRxIntrHandler(PDDa實現(xiàn))來從硬件緩沖區(qū)獲取剩余空間大小的數(shù)據(jù),若已無剩余空間則建立一個16byte的臨時緩沖區(qū),將數(shù)據(jù)讀入該區(qū)域,實際上這個緩沖區(qū)在后面根本就不會被讀取所以這些數(shù)據(jù)全部丟失掉了這也就自然需要統(tǒng)計硬件/軟件緩沖導(dǎo)致的數(shù)據(jù)丟失(接收不及時導(dǎo)致)。接下來就是所謂XFlow的流程了,所謂XFlow就是我們上面提到的軟件流控制。也就是用軟件的方法來協(xié)調(diào)發(fā)送和接收端的收發(fā),保障數(shù)據(jù)的完整接收。當接收到XOFF/XON標記的時候由于這個標記本身不數(shù)據(jù)數(shù)據(jù)而是控制標志,所以要講后面的數(shù)據(jù)全部前置一位,覆蓋掉XON/XOFF的位置。同時還需要根據(jù)標示的具體狀態(tài)來設(shè)置DCB結(jié)構(gòu)中的控制標示來控制數(shù)據(jù)收發(fā)流程。如果是XON標志,還需要在處理完接收流程后恢復(fù)發(fā)送流程。接收的動作會改變write標記的位置,這里需要重新計算該標示。至于硬件流控制的流程中該驅(qū)動模型是以緩沖器的75%為分位點來起停收發(fā)的,可用的硬件連線可以是DTR,也可以是RTS(模式相同僅僅是連線不同),這里的操作很簡單,僅僅是通過計算緩沖器的存儲狀態(tài)來清除該標志就完成了硬件流控制的操作。由于在此過程中IST與其他部分是同步執(zhí)行的,所以這個時候如果使用XFlow可能還會需要做一次安全檢查。這樣接收的流程就結(jié)束了。
發(fā)送分支: 我們同樣來看看TX_BUFFER_INFO結(jié)構(gòu),看樣子似乎該結(jié)構(gòu)維護了一個和TX緩沖類似的緩沖區(qū),但事實上這個緩沖區(qū)域是不獨立存在的,發(fā)送的流程因為可以直接使用所需發(fā)送的數(shù)據(jù)的存儲區(qū)域來作為發(fā)送緩沖,所以這個緩沖沒有獨立存在的必要。由于使用其它進程的數(shù)據(jù)區(qū)域,所以這里增加了權(quán)限控制項的成分,用于突破進程間的訪問限制。
typedef struct __TX_BUFFER_INFO {
DWORD Permissions; /* @field Current permissions */
ULONG Read; /* @field Current Read index. */
ULONG Length; /* @field Length of buffer */
PUCHAR TxCharBuffer; /* @field Start of buffer */
CRITICAL_SECTION CS; /* @field Critical section */
} TX_BUFFER_INFO, *PTX_BUFFER_INFO;
下面來看看代碼的具體內(nèi)容。首先是對OpenCnt的檢查,也就是設(shè)備是否被打開。若當會話已經(jīng)關(guān)閉也就沒有必要繼續(xù)將數(shù)據(jù)送出了,并同時重置緩沖器的各個標志位。整個流程比較簡單,在需要流控制時設(shè)置RTS或檢查Xflow的情況后將數(shù)據(jù)送入硬件緩沖器.如果在沒有數(shù)據(jù)需要發(fā)送的情況下簡單的清除中斷標示并發(fā)出發(fā)送結(jié)束事件就可以了。至于SetProcPermissions設(shè)置的目的在于獲得訪問其它線程數(shù)據(jù)空間的手段。
至于所謂的Modem和Line的情況則全部交給PDD來處理,我們后面再討論。在這些全部都處理完了以后如果前面處理了接收,則發(fā)出接收(有可用的數(shù)據(jù)用于接收)的消息,讓程序開始接收。后面還跟進了一個EvaluateEventFlag 函數(shù),這個函數(shù)用于產(chǎn)生標準的Communication Events EV_RXFLAG,而且由于該驅(qū)動程序本身支持mult-open模式,所以需要將該事件送發(fā)到所有的實例中去。在ist期間有一些互鎖、臨界區(qū)的操作,因為不影響流程,同步化的內(nèi)容這里我沒有提。中斷服務(wù)的分析大致就是如此,沒有涉及到邏輯環(huán)節(jié)在后面的PDD部分再進行討論。

COM_Read
COM_Read是獲取串口所接收到數(shù)據(jù)的操作,在前面的IST中沒有看到對RX buffer進行修改Read標記的操作,也就是這兒來完成的。該函數(shù)有三個參數(shù),第一個參數(shù)是從上面的COM_OPEN通過設(shè)備管理器交換來的,后兩個參數(shù)與文件系統(tǒng)的使用方法完全一樣,一個是接受緩沖指針,另一個是長度。代碼的開始照樣是例行公事的參數(shù)檢查,包括對存取權(quán)限,OpenCnt等。之后計算超時時間,如果設(shè)定了超時讀取動作會在超時后返回,不管是否讀到了足夠長度的數(shù)據(jù)。隨后就是簡單對軟件緩沖進行讀取的操作了,讀取的操作是在RX_CS中完成的。下面要處理器的主要就是幾種異常的情形,讀取過程中設(shè)備被關(guān)閉/取消讀取和超時。最后在讀取的過程中需要處理的就只是流控制的成本了。首先是軟件流的情形,如果緩沖的狀態(tài)由高于分位點至分位點以下就發(fā)出XON標記,啟動發(fā)送端的發(fā)送。而硬件流的情形無論是RTS還是DTR與軟件流的相類似,同樣由一個分為點(50%)來決定發(fā)出啟動發(fā)送端的信號,僅僅是這里使用的具體措施的不同。這些硬件信號的發(fā)出都是由PDD來完成的,其中包括HWSetRTS和HWSetDTR(2選一)。至此Read的流程就結(jié)束了。
COM_Write
COM_Write是與COM_Read相對應(yīng)的操作。所傳遞的參數(shù)的形式也是很相似的,僅僅是數(shù)據(jù)流向的不同。在程序的開始,同樣也是參數(shù)檢查,內(nèi)容與COM_Read一致。在數(shù)據(jù)檢查完成之后進入臨界區(qū)(保障多線程下的獨占)將送入的目標地址和長度設(shè)置為TX buffer,待到數(shù)據(jù)發(fā)送完成事件后調(diào)用DoTxData來啟動發(fā)送。這里啟動發(fā)送的目的在于獲得硬件中斷維持發(fā)送流程。在這里DoTxData是作為兩種狀態(tài)來執(zhí)行的,在通過COM_Write的執(zhí)行的過程中是在device.exe所創(chuàng)建的線程空間內(nèi)執(zhí)行的,但由系統(tǒng)中斷事件主動啟動的過程中屬于IST本身的的進程空間,這樣在COM_Write中調(diào)用DoTxData之前設(shè)置的權(quán)限代碼(由GetCurrentPermissions獲得)就可以由TxBufferInfo傳遞到IST中去使得中斷過程也具備了訪問緩沖的權(quán)限(結(jié)合前面說明IST的流程)。當提交中斷處理發(fā)送后待到pSerialHead->hTransmitEvent被設(shè)置或是異?;虺瑫r后就結(jié)束了發(fā)送流程,在這部分的最后。與COM_Read類似需要處理一些異常情況,當然如果使用了硬件流控制還需要在這里清除掉發(fā)送請求信號,當這些狀態(tài)處理完成以后發(fā)送EV_TXEMPTY事件通告所有open的句柄發(fā)送結(jié)束就完成了該部分的流程。
COM_PowerUp/ COM_PowerDown
這兩個函數(shù)的調(diào)用都由CE的電源事件來引發(fā),MDD并沒有對這兩個函數(shù)進行處理,僅僅是將其傳遞給PDD。
COM_IOControl
該函數(shù)用于實現(xiàn)向設(shè)備發(fā)送命令的功能。由于代碼本身沒有什么流程或邏輯性可言,全都是單獨的實現(xiàn),下面就用列表的方式大致的說一下這些命令字和其實現(xiàn)。
Command Note
IOCTL_PSL_NOTIFY 在調(diào)用驅(qū)動的進程退出時產(chǎn)生,并不是串行驅(qū)動專有的IO命令。這里會調(diào)用 ProcessExiting函數(shù)進行處理。這個函數(shù)的內(nèi)容放到后面來看。
IOCTL_SERIAL_SET_BREAK_ON 中斷(暫停)serial當前的發(fā)送或是接收,具體實現(xiàn)在PDD中
IOCTL_SERIAL_SET_BREAK_OFF 從中斷(暫停)狀態(tài)恢復(fù),具體實現(xiàn)在PDD中
IOCTL_SERIAL_SET_DTR 將DTR引線拉高。(直接調(diào)用PDD實現(xiàn))
IOCTL_SERIAL_CLR_DTR 將DTR引線拉低。(直接調(diào)用PDD實現(xiàn))
IOCTL_SERIAL_SET_RTS 將RTS引線拉高。(直接調(diào)用PDD實現(xiàn))
IOCTL_SERIAL_CLR_RTS 將RTS引線拉低。(直接調(diào)用PDD實現(xiàn))
IOCTL_SERIAL_SET_XOFF 軟件流模式下中止數(shù)據(jù)發(fā)送(Xflow控制)
IOCTL_SERIAL_SET_XON 軟件流模式下啟動數(shù)據(jù)發(fā)送(XFlow控制)
IOCTL_SERIAL_GET_WAIT_MASK 獲取當前的事件對象
IOCTL_SERIAL_SET_WAIT_MASK 設(shè)置事件對象,這個過程相對比較麻煩,要將當前獲得的事件對象mask設(shè)置到所有的Open實例中,這和前面的 EvaluateEventFlag過程相似。
IOCTL_SERIAL_WAIT_ON_MASK 等待與提供的事件相同的事件發(fā)生,實現(xiàn)實體是 WaitCommEvent后面再討論。
IOCTL_SERIAL_GET_COMMSTATUS 清除異常并返回當前狀態(tài)(由PDD實現(xiàn))
IOCTL_SERIAL_GET_MODEMSTATUS 獲取modem狀態(tài)(由PDD實現(xiàn))
IOCTL_SERIAL_GET_PROPERTIES 獲取通訊************************(由PDD實現(xiàn))
IOCTL_SERIAL_SET_TIMEOUTS 設(shè)置超時時間(包含PDD實現(xiàn))
IOCTL_SERIAL_GET_TIMEOUTS 獲取超時時間
IOCTL_SERIAL_PURGE 清除制定的發(fā)送或接收緩沖內(nèi)的數(shù)據(jù)(含PDD實現(xiàn))
IOCTL_SERIAL_SET_QUEUE_SIZE 不明,若知道請告知
IOCTL_SERIAL_IMMEDIATE_CHAR 為擴展功能,在發(fā)送數(shù)據(jù)前設(shè)置一個標志數(shù)
IOCTL_SERIAL_GET_DCB 獲取DCB數(shù)據(jù)結(jié)構(gòu)
IOCTL_SERIAL_SET_DCB 設(shè)置DCB數(shù)據(jù)結(jié)構(gòu)
IOCTL_SERIAL_ENABLE_IR 啟動紅外模式(由PDD實現(xiàn))
IOCTL_SERIAL_DISABLE_IR 禁用紅外模式(由PDD實現(xiàn))
到這里MDD的主要函數(shù)都已經(jīng)介紹過了,下面幾個函數(shù)是在DeviceIOControl中用到的。這里順便也來看一下:
ProcessExiting
該函數(shù)在IOCTL_PSL_NOTIFY命令的執(zhí)行過程中被調(diào)用,之前的情景是使用驅(qū)動的進程在被取消的過程中,在這里主要是清除所有正在會話中的線程。以便直接kill掉該進程。
WaitCommEvent
事實上該函數(shù)為SerialAPI WaitCommEvent在驅(qū)動內(nèi)的實現(xiàn),其作用為阻塞線程直道某一固定的串口通告(事件消息)發(fā)生。在具體的實現(xiàn)中,是用WaitForSingleObject來實現(xiàn)阻塞。在進入阻塞之前,函數(shù)適用一個循環(huán)主體首先查詢是否存在已有的通告與等待通告相符,若沒有就等待下一次事件發(fā)生,待事件發(fā)生再次進行檢查。如此循環(huán)達到阻塞的目的。
ApplyDCB
DCB數(shù)據(jù)結(jié)構(gòu)是描述串行口波特率,流控制,奇偶效驗等資料的載體。該函數(shù)是MDD設(shè)置DCB數(shù)據(jù)結(jié)構(gòu)至驅(qū)動內(nèi)部和硬件的手段,這里使用了大量的PDD操作來完成硬件設(shè)置。
總結(jié):
在驅(qū)動實現(xiàn)方面,除去所謂Multi-Open的處理外,串口的MDD并沒有什么特別的之處,在掌握了硬件行為和應(yīng)用軟件行為后很容易能讀懂其間的代碼。


關(guān)鍵詞: UART IrDA

評論


相關(guān)推薦

技術(shù)專區(qū)

關(guān)閉