|
來(lái)自:微軟嵌入式中文社區(qū)(http://www.msembed.com/)郝明 串行通訊接口是目前十分流行的通訊接口之一,串口通訊也已是普遍的標(biāo)準(zhǔn)而被大家廣為熟悉。 基本架構(gòu) 在WinCE中,串口的驅(qū)動(dòng)實(shí)現(xiàn)是有固定模型的,其串口模型遵循ISO/OSI網(wǎng)絡(luò)通訊模型。在典型的應(yīng)用中,serialAPI與間接通過TAPI或直接與ActiveSync交互,組成CE網(wǎng)絡(luò)的一部分。其實(shí)整個(gè)驅(qū)動(dòng)模型是相當(dāng)復(fù)雜的,好在驅(qū)動(dòng)僅僅使用到SerialAPI這一層,在這個(gè)層次上串口的行為相對(duì)比較簡(jiǎn)單。在WinCE中,串口驅(qū)動(dòng)模型是作為Stream來(lái)實(shí)現(xiàn)的(即:流設(shè)備驅(qū)動(dòng)),其架構(gòu)如圖所示:
串口驅(qū)動(dòng)本身分為MDD層和PDD層。 MDD提供框架性的實(shí)現(xiàn),負(fù)責(zé)提供OS所需的基本實(shí)現(xiàn),它對(duì)上層的設(shè)備管理器,提供了標(biāo)準(zhǔn)的流設(shè)備驅(qū)動(dòng)接口(COM_xxx);而PDD提供了對(duì)硬件操作相應(yīng)的代碼,它實(shí)現(xiàn)了HWOBJ結(jié)構(gòu)及結(jié)構(gòu)中若干針對(duì)于串口硬件操作的函數(shù)指針。 DDSI是指MDD與PDD兩個(gè)部分之間的接口,這個(gè)接口是人為的規(guī)定的,在串口驅(qū)動(dòng)中實(shí)際上就是指HWOBJ,PDD層會(huì)傳給MDD層一個(gè)HWOBJ的結(jié)構(gòu)指針,這樣MDD層就可以調(diào)用PDD層的函數(shù)來(lái)操作串口。在實(shí)際的驅(qū)動(dòng)應(yīng)用中僅僅需要實(shí)現(xiàn)HWOBJ相關(guān)的一系列函數(shù),而無(wú)需從驅(qū)動(dòng)頂層完全開發(fā)。 通常的串行連接有3wire和9wire兩種。3wire的接線方式下定義了發(fā)送、接收和地三根連接。而在9wire中將串行連接定義為如下形式。
這就是在原3wire的基礎(chǔ)上增加了DCD、DTR、DSR、RTS、CTS、DELL六個(gè)控制線。其中RTS/CTS用于流控制,另外的DCD和DELL則留作連接modem使用。有了專門的硬件流控制引腳也就使得流控制成為可能,以完成收發(fā)兩端的匹配使得數(shù)據(jù)可以可靠的傳輸,即實(shí)現(xiàn)了流控制,保障了數(shù)據(jù)傳輸?shù)耐陚湫浴?br /> 其他幾個(gè)引腳都是與modem相關(guān)的,DSR數(shù)據(jù)裝置準(zhǔn)備好用于表明MODEM處于可以使用的狀態(tài)。 函數(shù)分析 1、HWOBJ HWOBJ是相應(yīng)的硬件設(shè)備操作的抽象集合,實(shí)現(xiàn)了對(duì)串口硬件的操作,并在MDD層被調(diào)用。 typedef struct __HWOBJ { ULONG BindFlags;. DWORD dwIntID; PHW_VTBL pFuncTbl; } HWOBJ, *PHWOBJ; 其中,BandFlags用于控制MDD層指定IST的啟動(dòng)時(shí)間,MDD正是通過這些函數(shù)來(lái)訪問具體的PDD操作。 dwInitID是系統(tǒng)的中斷號(hào)。 pFuncTbl則是指向一個(gè)PHW_VTBL結(jié)構(gòu),該結(jié)構(gòu)中包含一個(gè)函數(shù)指針列表,這些函數(shù)指針指向串口硬件操作函數(shù),用于操作串口。
2、MDD MDD層向上提供了流設(shè)備接口,用于管理串口,由Device.exe直接調(diào)用。 * COM_Init (ULONG Identifier): 它是該驅(qū)動(dòng)的初始化函數(shù),通過硬件抽象接口HWInit初始化硬件。如果驅(qū)動(dòng)被設(shè)備管理器加載,參數(shù)Identifier包含一個(gè)注冊(cè)表鍵值在“HKEY_LOCAL_MACHINE\Drivers\Active”的路徑下。 * COM_Deinit(void): 當(dāng)驅(qū)動(dòng)被稱被卸下的時(shí)候該事件啟動(dòng),用作與COM_Init相反的操作。停止在MDD中的所有IST,釋放內(nèi)存資源和臨界區(qū)等系統(tǒng)資源。 * COM_Open(HANDLE pContext, DWORD AccessCode, DWORD ShareMode): COM_Oepn在CreateFile后被調(diào)用,用于以讀/寫模式打開設(shè)備,并初始化所需要的空間/資源等,創(chuàng)建相應(yīng)的實(shí)例。Open操作完成后,驅(qū)動(dòng)就進(jìn)入了工作狀態(tài)。 * COM_Close(DWORD pContext): COM_Close釋放COM_Open所使用的系統(tǒng)資源,停止IST線程,恢復(fù)驅(qū)動(dòng)狀態(tài)。 * COM_Read(HANDLE pContext, PUCHAR pTargetBuffer, ULONG BufferLength, PULONG pBytesRead): COM_Read是獲取串口所接收到數(shù)據(jù)的操作,在前面的IST中沒有看到對(duì)RX buffer進(jìn)行修改Read標(biāo)記的操作,也就是這兒來(lái)完成的。 * COM_Write(HANDLE pContext, PUCHAR pSourceBytes, ULONG NumberOfBytes): COM_Write是與COM_Read相對(duì)應(yīng)的操作,是寫串口數(shù)據(jù)的。應(yīng)用程序調(diào)用WriteFile函數(shù)寫串口的時(shí)候,該函數(shù)被調(diào)用。在程序的開始,同樣也是參數(shù)檢查,內(nèi)容與COM_Read一致。其中pContext參數(shù)是COM_Open函數(shù)返回的Handle。pSourceBytes指向一個(gè)Buffer,該Buffer包含要寫入串口的數(shù)據(jù)。NumberOfBytes表示要寫入串口的數(shù)據(jù)的大小。 * COM_PowerUp/ COM_PowerDown (HANDLE pContext): 這兩個(gè)函數(shù)的調(diào)用都由CE的電源事件來(lái)引發(fā),MDD并沒有對(duì)這兩個(gè)函數(shù)進(jìn)行處理,僅僅是將其傳遞給PDD。 * COM_IOControl (DWORD dwOpenData, DWORD dwCode, PBYTE pBufIn, DOWRD dwLenIn, PBYTE pBufOut, DWORD dwLenOut, PDWORD pdwActualOut): 該函數(shù)主要實(shí)現(xiàn)了一些串口的IO控制,他會(huì)被應(yīng)用層的一些串口函數(shù)調(diào)用來(lái)獲得或者設(shè)置串口的狀態(tài)。 3、PDD 實(shí)際上,在PDD層的主要工作就2個(gè):一是控制硬件;二是和上層打好關(guān)系。先說(shuō)上層接口,上層用了GetSerialHead()來(lái)獲得接口,所以PDD里面要實(shí)現(xiàn)GetSerialHead()的函數(shù),并且將接口返回給上層。 GetSerialObject( DWORD DeviceArrayIndex ) { PHWOBJ pSerObj; pSerObj=(PHWOBJ)LocalAlloc( LPTR ,sizeof(HWOBJ) ); if ( !pSerObj ) return (NULL); pSerObj->BindFlags = THREAD_IN_PDD; pSerObj->dwIntID = DeviceArrayIndex; pSerObj->pFuncTbl = (HW_VTBL *) & IoVTbl; return (pSerObj); } PDD層的函數(shù)主要是實(shí)現(xiàn)了對(duì)串口硬件的操作,函數(shù)不少,可以參考以下列表: 序號(hào) 函數(shù) 說(shuō)明 1 GetSerialObject 返回一個(gè)指向HWOBJ結(jié)構(gòu)的指針,該結(jié)構(gòu)包含了相關(guān)硬件接口函數(shù)的函數(shù)指針 2 HWClearBreak 清除串口中斷狀態(tài),用于串口從中斷狀態(tài)恢復(fù) 3 HWClearDTR 設(shè)置串口的DTR管腳為低 4 HWClearRTS 設(shè)置串口的RTS管腳為低 5 HWClose 關(guān)閉由HWInit函數(shù)初始化的設(shè)備 6 HWDisableIR 禁用串口的紅外模式 7 HWEnableIR 啟用串口的紅外模式 8 HWGetCommProperties 重新獲得當(dāng)前串口設(shè)備的硬件屬性 9 HWGetIntrType 獲得當(dāng)前的中斷類型 10 HWGetModemStatus 獲得Modem的狀態(tài) 11 HWGetRxBufferSize 獲得串口硬件接收Buffer的大小 12 HWGetRxStart 返回硬件接收Buffer的起始位置 13 HWGetStatus 獲得硬件狀態(tài)信息 14 HWInit 初始化串口硬件設(shè)備 15 HWIoctl 執(zhí)行I/O控制 16 HWLineIntrHandler 線路狀態(tài)信息中斷處理函數(shù) 17 HWOpen 打開串口設(shè)備 18 HWPowerOff 串口硬件進(jìn)入Suspend模式 19 HWPowerOn 串口硬件從Suspend模式恢復(fù)到工作模式 20 HWSetDCB 設(shè)置串口硬件設(shè)備信息 21 HWSetDTR 設(shè)置串口的DTR管腳為高 22 HWSetRTS 設(shè)置串口的RTS管腳為高 23 HWPurgeComm 清除串口硬件buffer的信息 24 HWPutBytes 通過寫數(shù)據(jù)到硬件中來(lái)直接發(fā)送數(shù)據(jù) 25 HWReset 復(fù)位串口硬件 26 HWRxIntrHandler 接收數(shù)據(jù)中斷處理函數(shù) 27 HWSetBreak 設(shè)置串口為中斷狀態(tài),停止發(fā)送接收數(shù)據(jù) 28 HWTxIntrHandler 串口發(fā)送中斷處理函數(shù) 非獨(dú)占式串口驅(qū)動(dòng) 用過串口進(jìn)行過開發(fā)的兄弟們都知道,串口驅(qū)動(dòng)是一個(gè)典型的獨(dú)占設(shè)備。簡(jiǎn)單點(diǎn)來(lái)說(shuō),就是在成功地調(diào)用CreateFile打開串口之后,沒有通過CloseHandle進(jìn)行關(guān)閉,是無(wú)論如何都不能再次調(diào)用CreateFile來(lái)再次打開相同的串口,這樣做可以避免產(chǎn)生數(shù)據(jù)丟失,也避免獲取數(shù)據(jù)的線程是反復(fù)讀取。 但是現(xiàn)在的嵌入式設(shè)備功能都非常多,需要非獨(dú)占式串口驅(qū)動(dòng),也就是虛擬串口驅(qū)動(dòng)。它主要是處理數(shù)據(jù)的分發(fā),可以和具體的硬件分開,優(yōu)勢(shì)也很明顯,可以不用理會(huì)具體的硬件規(guī)格,只要采用的是WinCE系統(tǒng),虛擬串口驅(qū)動(dòng)就能正常工作。 在設(shè)計(jì)驅(qū)動(dòng)的時(shí)候需要注意,同一時(shí)間只能有一個(gè)進(jìn)程對(duì)外輸出數(shù)據(jù),其余進(jìn)程只能在該進(jìn)程輸出完畢之后才能進(jìn)行。當(dāng)然,程序不應(yīng)該主動(dòng)調(diào)用ReadFile來(lái)輪詢獲取數(shù)據(jù),而是通過WaitCommEvent進(jìn)行檢測(cè)。為了不丟失數(shù)據(jù),緩沖大小一定要等于或大于READ_BUFFER_LENGTH。 在寫代碼的時(shí)候需要注意一點(diǎn),WaitCommEvent函數(shù)只能被一個(gè)線程調(diào)用,同一時(shí)間只有唯一的一個(gè)線程通過WaitCommEvent函數(shù)進(jìn)入等待狀態(tài)。因此對(duì)于IOCTL_SERIAL_WAIT_ON_MASK控制碼的處理,可以通過調(diào)用WaitForSingleObject進(jìn)行線程等待。這時(shí)虛擬串口驅(qū)動(dòng)會(huì)額外開放一個(gè)線程,該線程主要是通過調(diào)用WaitCommEvent來(lái)獲取原生串口的狀態(tài),當(dāng)狀態(tài)有通知時(shí),再發(fā)送event給等待的線程。 switch(xxxx){ ... case IOCTL_SERIAL_WAIT_ON_MASK: { if(dwBufOutSize < sizeof(DWORD) || WaitForSingleObject(g_hEventComm,INFINITE) == WAIT_TIMEOUT) { *pBytesReturned = 0; return FALSE; } else { InterlockedExchange(reinterpret_cast *pBytesReturned = sizeof(DWORD); return TRUE; } }... 多串口擴(kuò)展 在WinCE應(yīng)用環(huán)境中,對(duì)擴(kuò)展的多串口的編程方法,實(shí)際上與標(biāo)準(zhǔn)的串口應(yīng)用程序完全一樣。 注意在打開串口號(hào)大于9的串口時(shí),需要使用“\\$device\\COMxx”,而不是通常的“COMx:”。 考慮到共享中斷的異步特性,各個(gè)串口可能同時(shí)請(qǐng)求中斷,從而產(chǎn)生極高的中斷頻率,所以建議客戶把低波特率的串口通道,如9600bps或以下的波特率,配置在擴(kuò)展串口上,以均衡CPU對(duì)各個(gè)硬件設(shè)備的開銷;相應(yīng)地把需要使用高波特率的通道配置到嵌入式主板自帶的串口通道上,比如:EM9360的COM2-COM7,這些串口均配置有獨(dú)立的硬件中斷。
在WinCE標(biāo)準(zhǔn)的串口驅(qū)動(dòng)程序中,為每個(gè)串口分配了2KB的接收數(shù)據(jù)緩沖區(qū),所以各個(gè)串口上層處理線程可參考buffer的深度,采用合適的響應(yīng)方式,以最大限度的避免線程空轉(zhuǎn)所帶來(lái)的CPU時(shí)間的無(wú)謂消耗。 WinCE下的驅(qū)動(dòng)開發(fā)減少了開發(fā)者的工作量,不過其中還存在很多細(xì)節(jié)性的問題,動(dòng)手實(shí)踐中就能慢慢體會(huì)到了。 |