驅(qū)動程序是介于操作系統(tǒng)和設(shè)備之間的一 個代碼層,它的主要作用是為操作系統(tǒng)提供一個接口,以操作不同的硬件,包括物理的和虛擬的設(shè)備。雖然驅(qū)動程序有很多種,但從編程的角度來看,無非是往一個 固定的框架中添加相應(yīng)的代碼。這里的框架指的是一個接口,面向操作系統(tǒng)。代碼實現(xiàn)的宗旨是,在正確的時間往正確的寄存器中寫正確的值。
驅(qū)動程序的分類,從不同的角度有不同的 分法。拿串口驅(qū)動來說,你可以說它是一個分層驅(qū)動,你也可以說它是一個流驅(qū)動,你還可以說它是開機時自動加載的驅(qū)動……這似乎有點亂。如果你也這么認(rèn)為, 那建議往下看。如果這些你都了如指掌,那就不浪費時間了,當(dāng)然,您愿意找茬,我會很感謝!
先說本地驅(qū)動(Native Drivers)和流驅(qū)動(Stream Drivers)。WinCE下的驅(qū)動都可以歸類到這兩個里面,二者必居其一。這是從驅(qū)動程序提供給操作系統(tǒng)的接口來區(qū)分的。流驅(qū)動為操作系統(tǒng)提供了流接口函數(shù),如XXX_Init()、XXX_Open()、XXX_Read()、XXX_Write()、XXX_Close()等等。這一類的驅(qū)動由Device Manager來管理,它調(diào)用ActivateDeviceEx()函數(shù)來加載流驅(qū)動。ActivateDeviceEx()的參數(shù)是注冊表中相應(yīng)的鍵,用來設(shè)定加載流驅(qū)動的屬性,如Index、Order、Prefix等等。流驅(qū)動的注冊表配置信息一般存放在[HKEY_LOCAL_MACHINE\Drivers\BuiltIn]下。流驅(qū)動加載成功后,應(yīng)用程序通過調(diào)用CreateFile()、ReadFile()、WirteFile()等來訪問流驅(qū)動的設(shè)備。流驅(qū)動可以動態(tài)管理,驅(qū)動調(diào)試助手就是用來幫助調(diào)試這一類驅(qū)動的。
與流驅(qū)動相反,本地驅(qū)動提供給操作系統(tǒng)的不是標(biāo)準(zhǔn)的流接口,而是事先約定好的特定接口。不同的設(shè)備,接口也不一樣。WinCE中,常見的本地驅(qū)動有LCD顯示驅(qū)動、觸摸屏驅(qū)動、鼠標(biāo)和鍵盤驅(qū)動及打印機驅(qū)動等。可以看到,本地驅(qū)動主要是人機界面相關(guān)的驅(qū)動。它們由GWES管理,在系統(tǒng)啟動時加載。他們在注冊表中也有各自相應(yīng)的配置信息。如鍵鼠的注冊表配置如下:
[HKEY_LOCAL_MACHINE"System"CurrentControlSet"Control"Layouts"00000409]
"Layout File"="kbdmouse.dll"
"Layout Text"="US"
"PS2_AT"="kbdmouse.dll"
"Matrix"="kbdmouse.dll"
本地驅(qū)動由操作系統(tǒng)調(diào)用,應(yīng)用程序不能訪問。對于這類驅(qū)動,驅(qū)動調(diào)試助手是無能為力的,只能老老實實的編譯、下載、驗證。
WinCE驅(qū)動中經(jīng)常會聽到MDD(Model Device Driver)和PDD(Platform Dependent Driver)的概念,這是從驅(qū)動代碼實現(xiàn)的結(jié)構(gòu)來區(qū)分的。WinCE的驅(qū)動可以是單層的,也可以是PDD+MDD。這沒有硬性規(guī)定,一個驅(qū)動程序可以采用分層結(jié)構(gòu),也可以采用單層結(jié)構(gòu)。一般來說,單層結(jié)構(gòu)的驅(qū)動執(zhí)行效率更高,而分層結(jié)構(gòu)的驅(qū)動方便代碼維護和移植。拿串口驅(qū)動來說,完全可以采用單層結(jié)構(gòu)。而把它分為PDD和MDD,作為一般的開發(fā)者,我們只需實現(xiàn)PDD層就可以了,MDD層由微軟實現(xiàn)。這樣,驅(qū)動開發(fā)的工作量少很多,而代碼的可靠性則有了更好的保證。至于采用哪一種結(jié)構(gòu)的驅(qū)動,主要看你的需求。
WinCE 6.0引入了內(nèi)核態(tài)驅(qū)動和用戶態(tài)驅(qū)動的概念。在WinCE5.0及先前的版本中,驅(qū)動工作在用戶態(tài)。從代碼方面看,內(nèi)核態(tài)驅(qū)動和用戶態(tài)驅(qū)動沒太大差別。如果驅(qū)動中沒有采用什么特別的技術(shù),內(nèi)核態(tài)驅(qū)動和用戶態(tài)驅(qū)動甚至是二進制兼容的。我曾經(jīng)試過將一個DLL分 別加載到內(nèi)核態(tài)和用戶態(tài),都工作得很好。內(nèi)核態(tài)驅(qū)動被加載到內(nèi)核空間,用戶態(tài)驅(qū)動被加載到特定的用戶進程空間中。從執(zhí)行效率來看,內(nèi)核態(tài)的驅(qū)動效率比用戶 態(tài)的驅(qū)動高。從穩(wěn)定性方面考慮,用戶態(tài)的驅(qū)動不會對系統(tǒng)產(chǎn)生致命影響,而內(nèi)核態(tài)的驅(qū)動相對危險。同樣,采用哪一種類型的驅(qū)動,也是看你的需求。
從驅(qū)動加載的時間來看,可分為兩種:系統(tǒng)啟動時加載和需要時加載。一般來說本地驅(qū)動都是在啟動時加載的,所以這里說的主要是流驅(qū)動。如果想要驅(qū)動在系統(tǒng)啟動時加載,只需將它的注冊表配置信息放到[HKEY_LOCAL_MACHINE\Drivers\BuiltIn\]下,如[HKEY_LOCAL_MACHINE\Drivers\BuiltIn\Battery],系統(tǒng)啟動時,Device Manager會自動加載它。需要時加載,顧名思義,就是想加載就加載,想卸載就卸載,很靈活。這里很有必要說一下USB設(shè)備的驅(qū)動加載,如USB攝像頭驅(qū)動,它也屬于需要時加載的驅(qū)動。從驅(qū)動的接口來看,它屬于流驅(qū)動,但相對普通的流驅(qū)動,它增加了幾個函數(shù):USBDeviceAttach()、USBInstallDriver()、USBUnInstallDriver()等。USB攝像頭驅(qū)動的加載在USBDeviceAttach()中完成。所以,它無須,也不能,用驅(qū)動調(diào)試助手加載。需要時加載的驅(qū)動還有一個作用,在無法修改系統(tǒng)的情況下,應(yīng)用程序中動態(tài)加載該驅(qū)動,以完成對硬件的操作。
......
]]>
2 回聲產(chǎn)生的原理與消除方法
2.1 回聲的基本概念和產(chǎn)生原理
通常意義上的回聲可以分為電學(xué)回聲和聲學(xué)回聲,前一種是由于服務(wù)提供商的線路質(zhì)量所致,而后一種則是由于用戶端設(shè)備的質(zhì)量所致。
2.1.1 電學(xué)回聲
在PSTN (Public Switched Telephone Network,公共交換電話網(wǎng)絡(luò))中,為了降低電話中心局與電話用戶之間電話線的價格,用戶線間的連接采用兩線制,而電話中心局之間連接采用四線制。在這樣采用混合線制的電路中,由于阻抗的失配,會不可避免地產(chǎn)生電流泄漏。電流泄漏使得一部分信號的能量反射回信號源,這種反射和信道延遲結(jié)合在一起,使講話者聽到自己的聲音,即為電學(xué)回聲。
2.1.2 聲學(xué)回聲
聲學(xué)回聲是指揚聲器播出的聲音在被受話方聽到的同時,也通過多種路徑被麥克風(fēng)拾取到,傳輸?shù)秸f話方的一端,從而形成聲音回路。當(dāng)回聲返回時間超過10 ms時,人耳就可聽到明顯的回聲了。多路徑反射的結(jié)果產(chǎn)生了不同延時的回聲,包括直接回聲和間接回聲。
①直接回聲是指由揚聲器播出的聲音未經(jīng)任何反射直接進入麥克風(fēng)。這種回聲的延時最短,它同遠端說話者的語音能量,揚聲器與麥克風(fēng)之間的距離、角度,揚聲器的播放音量,麥克風(fēng)的拾取靈敏度等因素直接相關(guān)。
②間接回聲是指由揚聲器播出的聲音經(jīng)過不同的路徑(如房屋或房屋內(nèi)的任何物體)的一次或多次反射后,進入麥克風(fēng)所產(chǎn)生的回聲的集合。房屋內(nèi)的任何物體的任何變動都會改變回聲的通道,因此,這種回聲的特點是多路徑的、時變的。
對于電學(xué)回聲的消除,通常由服務(wù)提供商提供解決方案。本文研究的重點是聲學(xué)回聲的消除機制,為了防止聲音回路的產(chǎn)生,通常需要在硬件和軟件設(shè)計中采取一定的解決方案。
如圖1所示,雙方在使用PDA或者手機進行通話時,假設(shè)B是主話方,A是受話方:
①B說話的聲音經(jīng)過電信或者移動路徑傳輸?shù)紸的設(shè)備上;
②聲音經(jīng)過揚聲器發(fā)出,由于硬件和機構(gòu)設(shè)計上的局限性,會有部分聲音滲透到A的麥克風(fēng);
③滲透出的聲音又傳輸回到B,導(dǎo)致B能聽到自己的聲音。
這樣,便形成了聲音回路,即產(chǎn)生了回聲。
PDA或者手機內(nèi)部結(jié)構(gòu)聲音回路示意圖如圖2所示。在全雙工的情況下,揚聲器和麥克風(fēng)之間至少要保持4 cm以上的距離,才能比較好地避免回聲問題。在實際情況下,絕大部分的回聲回路形成于機構(gòu)內(nèi)部,而由機構(gòu)外部揚聲器回流到麥克風(fēng)的聲音,由于距離較長,基本可以忽略。
由于硬件和機構(gòu)設(shè)計上的局限性,在機構(gòu)內(nèi)部不可能完全隔離聲音的傳播,因此從揚聲器出來的聲音會有一部分會滲透到麥克風(fēng),從而產(chǎn)生回聲。
2.2 基于FM2010的回聲消除原理
FM2010使用的是自適應(yīng)回聲抵消原理,它的基本思想是:估計回聲路徑的特征參數(shù),產(chǎn)生一個模擬的回聲路徑,得出模擬回聲信號,從接收信號中減去該信號,實現(xiàn)回聲抵消。以圖3所示的受話端通話流程為例,圖中左上方的MIC IN是麥克風(fēng)進入的信號,即用戶的聲音輸入信號,其中包含由本機揚聲器漏進去的部分主話端的語音信號;右下方的Line IN接入是主話端的語音信號,即由本機揚聲器輸出的信號直接接入到FM2010回音消除芯片的Line IN輸入端。在FM2010內(nèi)部會經(jīng)過DSP芯片的運算處理對兩者進行比較,消除MIC IN信號中與LineIN端相同的信號(即回聲音頻信號),使用自適應(yīng)回聲抵消原理達到消除回聲的目的。MIC IN和Line OUT之間路徑上的寄存器需要進行配置和調(diào)試,DSP使用這些配置的數(shù)據(jù)作為特征參數(shù)來進行運算處理。
3 基于FM2010的回音消除驅(qū)動設(shè)計
3.1 硬件架構(gòu)設(shè)計
圖4是硬件架構(gòu)示意圖。CPU 通過I2C總線 來控制回音消除芯片讀/寫回音消除參數(shù)(echo parameter)。錄音信號先經(jīng)過回音消除芯片處理后經(jīng)移動信道傳輸?shù)綄Ψ浇邮赵O(shè)備,經(jīng)過對方音頻編解碼器處理后再通過揚聲器或者耳機輸出。
3.2 音頻設(shè)備機構(gòu)設(shè)計
音頻設(shè)備是否產(chǎn)生回聲,很大程度上取決于機構(gòu)的設(shè)計是否合理。不合理的機構(gòu)設(shè)計會大大增加出現(xiàn)回聲的概率,而且出現(xiàn)的回聲會比較嚴(yán)重,難于消除。我們改進了音頻設(shè)備機構(gòu)上的設(shè)計,在設(shè)計之初就盡可能避免回音的產(chǎn)生,而且揚聲器和麥克風(fēng)的距離盡可能保持一定遠的距離,如圖5所示。實踐中發(fā)現(xiàn),揚聲器和麥克風(fēng)之間的距離保持在10 cm以上,效果會比較好。
為了盡可能減少揚聲器和麥克風(fēng)之間的聲音傳播,可以采用以下2種設(shè)計方案:方案一是把麥克風(fēng)隔離開來,如圖5(a)所
示;方案二是把揚聲器隔離開來,如圖5(b)所示。當(dāng)然,為了達到更好的效果,可以把揚聲器和麥克風(fēng)分別隔離開;但是鑒于成本的考慮,使用其中的一種即可達到比較好的效果。具體的實現(xiàn)方法分別是:
①用橡膠套密封揚聲器的邊緣,盡可能避免聲音從機構(gòu)內(nèi)部回流到麥克風(fēng);同時把麥克風(fēng)密封在一個腔體之內(nèi),以隔離外
部噪聲的進入。
②把揚聲器密封在一個腔體之內(nèi),以盡可能隔離揚聲器的聲音向外部傳播;同時使用橡膠套密封麥克風(fēng)的邊緣,以避免揚聲器發(fā)出的聲音進入。
另外,揚聲器和麥克風(fēng)在設(shè)置的方向上也有講究,如圖6所示。兩者最好是相差180°,90°也可以接受;但是如果兩者的方向相同,如圖6(c)所示,則出現(xiàn)回聲的概率和程度都很大。
3.3 軟件架構(gòu)設(shè)計與實現(xiàn)
軟件架構(gòu)示意圖如圖7所示。
回音消除驅(qū)動的開發(fā)需要完成以下幾步:
①給回音消除芯片上電,并初始化其相關(guān)的寄存器和GPIO;
②初始化I2C總線,并配置I2C速率等的寄存器,使其處于正常運行狀態(tài);
③檢查I2C總線是否已準(zhǔn)備好;
④通過I2C總線讀取回音消除參數(shù);
⑤等待回音消除芯片處于可讀/寫狀態(tài),然后把參數(shù)寫入到芯片里,使配置的參數(shù)生效。
3.3.1 FM2010上電時序
當(dāng)對FM2010初始化時,比較重要的一點是要按照其規(guī)格說明書定義的上電時序進行,否則可能會導(dǎo)致錄音聲音時有時無的情況發(fā)生。具體的要求如圖8所示。在初始化FM2010時,PWD high的狀態(tài)必須要在RESET high狀態(tài)之前設(shè)定,兩者之間相差5 ms。
3.3.2 讀/寫Echo parameter
FM2010芯片的初始化比較簡單,只要按照上述的時序進行初始化即可。下面重點剖析回音消除驅(qū)動的開發(fā)要點,即如何讀/寫回音消除參數(shù)(詳見代碼中的①和④)、設(shè)置回音消除芯片的工作模式(詳見②處),以及配置I2C總線(詳見③處)。
結(jié) 語
本文在分析語音通話中回聲產(chǎn)生的機理的基礎(chǔ)上,詳細研究了回聲消除的原理、基本聲學(xué)回聲產(chǎn)生根源,提出了改進的PDA或者手機等音頻設(shè)備的機構(gòu)設(shè)計方案;針對FM2010芯片的特點,給出了回聲消除驅(qū)動開發(fā)的軟硬件設(shè)計方案,并且給出了回音消除驅(qū)動程序關(guān)鍵代碼的實現(xiàn)。本文設(shè)計的軟硬件解決方案已經(jīng)在基于WinCE操作系統(tǒng)平臺和FM2010硬件平臺上得以實現(xiàn),在實際項目開發(fā)中得到采用,并且獲得了良好的應(yīng)用效果。
MDD:Model Device Driver,模型設(shè)備驅(qū)動
DDSI:Device Driver Service Provider Interface,DDSI函數(shù)
單體驅(qū)動:
顧名思義,所有的驅(qū)動程序代碼——包括中斷處理、I/O操作及硬件控制都被放在了一起。這也是比較傳統(tǒng)的驅(qū)動程序編寫方法。單體驅(qū)動程序的代碼直接與硬件交互,因此它包含與特定的某款硬件相關(guān)聯(lián)的代碼。通常,單體驅(qū)動程序會暴露DDI接口(Device Driver Interface)給操作系統(tǒng),DDI函數(shù)是操作系統(tǒng)與驅(qū)動程序交互的接口協(xié)議。因為單體驅(qū)動程序?qū)︱?qū)動程序的代碼不做分層處理,因此驅(qū)動程序的代碼相對緊湊,對于一些效率要求較高的場合,選用單體驅(qū)動程序會提高驅(qū)動的性能,同時,對于一些較簡單的硬件設(shè)備驅(qū)動,使用單體驅(qū)動程序模型,可更加清晰明了。
附單體驅(qū)動和分層驅(qū)動圖如下:
原博主正文如下:
首先是wince驅(qū)動的分類問題。按照書上講的說CE下驅(qū)動分成單體驅(qū)動和分層驅(qū)動,而看到另一種說法是本機驅(qū)動和流式驅(qū)動。經(jīng)過microsun大哥的指點,把這兩種分類法分開了。在這里引用一下:
“單體與分層只是從代碼的形式上做的分類.分層驅(qū)動代碼上分為PDD與MDD,一般的微軟已經(jīng)實現(xiàn)了MDD,可能也實現(xiàn)了PDD,我們只需要對PDD做些修改就能使用,比如音頻的驅(qū)動,顯示的驅(qū)動。單層驅(qū)動是把PDD與MDD寫在一起,沒有做嚴(yán)格的區(qū)分,通常這種驅(qū)動比較簡單,比如:ATADISK。至于本地驅(qū)動和流式驅(qū)動是從驅(qū)動與系統(tǒng)其它模塊(調(diào)用者)的接口形式上做的分類.其實,本地驅(qū)動這個名稱不大恰當(dāng),可能叫專用驅(qū)動或其它名字更為合適.它是指調(diào)用它的模塊給它有特定的接口,比如電源驅(qū)動和通用LED驅(qū)動。而串口,網(wǎng)卡等就是流接口驅(qū)動程序. 所以,一個驅(qū)動程序可以是單體的流式驅(qū)動,例如:ATADISK.也可以是分層的流式: 如OHCI ”
按照我的理解,單體和分層是驅(qū)動實現(xiàn)方式上的分類,而本地和流式則是驅(qū)動模型上的分類,所謂本地驅(qū)動就是操作系統(tǒng)有保留專門的接口,所謂流式是指編寫的DLL文件里可以導(dǎo)出各種流式接口函數(shù)。
第二點:驅(qū)動的功能屬性。設(shè)備驅(qū)動程序是操作系統(tǒng)內(nèi)核和硬件的接口,操作系統(tǒng)定義了一組標(biāo)準(zhǔn)的接口,編寫驅(qū)動的過程也就是實現(xiàn)這些接口。從應(yīng)用程序到具體硬件間有如下這些環(huán)節(jié)起作用:應(yīng)用程序-調(diào)用OS函數(shù)-操作系統(tǒng)-驅(qū)動接口-驅(qū)動程序-硬件操作函數(shù)-硬件。在wince里驅(qū)動都以用戶態(tài)的DLL存在,需要通過進程加載到slot里。共有三類系統(tǒng)進程用來加載:Device.exe,GWES.exe,FileSys.exe.絕大多數(shù)設(shè)備驅(qū)動都是通過Device.exe加載的。需要注意的是,不同的OS保留的設(shè)備驅(qū)動接口是不一樣的,如桌面windows和wince就不同。
第三點:wince下設(shè)備的初始化分為兩個階段:Device.exe的初始化;外設(shè)的枚舉和加載。其流程是:上電-啟動bootloader-啟動NK-啟動注冊表init鍵(Device.exe啟動)-初始化數(shù)據(jù)結(jié)構(gòu),I/O,電源管理等-加載BusEnum.dll(總線枚舉器)-枚舉注冊表下Driver/buildin的所有子鍵。這里的枚舉過程就是循環(huán)調(diào)用ActivateDeviceEx()函數(shù)加載驅(qū)動的過程。在OS啟動完畢后,我們可以用PB的Remote Registry Tool查看H_L_M/drivers/active包含的子鍵,看哪些驅(qū)動隨啟動而加載 。
第四點:流接口驅(qū)動的概念。暴露流式接口函數(shù)的驅(qū)動即是流驅(qū)動,它把外設(shè)抽象成一個文件。過程是:應(yīng)用程序使用文件API對設(shè)備進行訪問,OS接受API調(diào)用FileSys.exe,轉(zhuǎn)到device.exe,調(diào)用流接口,與硬件交互。所謂流接口函數(shù)有十個,包括XXX_Init、XXX_Deinit、XXX_Open、XXX_Close、XXX_Read、XXX_Write、XXX_PowerUp、XXX_PowerDown、XXX_Seek、XXX_IOControl,在wince5.0中增加le了XXX_PreClose,XXX_PreDeinit.而我們在應(yīng)用程序里對應(yīng)的文件API有CreateFile、DeviceIoControl、 ReadFile、 WriteFile,CloseHandle,SetFilePointer.
第五點:編寫流驅(qū)動的步驟。有兩種實現(xiàn)途徑:1。寫DLL,做成Project,加入到OS里。2。改BSP,把驅(qū)動寫在BSP里,再選擇那個BSP做OS。第一種方法步驟是在PB中新建一個DLL項目,編寫一些輸入函數(shù),寄存器,外設(shè)的聲明,寫DLLENTRY函數(shù);實現(xiàn)流接口函數(shù);編寫DLL的導(dǎo)出函數(shù)文件.DEF;為驅(qū)動程序?qū)懭胱员眄棧需要修改bib文件。 第二種方法就是在platform/BSP/drivers下新建一個目錄,然后在drivers目錄中的dirs文件中加入新建的目錄名。在新建的目錄下,新建你的源代碼文件,在其中實現(xiàn)DLL函數(shù)。新建名稱分別為sources, makefile, ***.def的文件;修改platform.reg和platform.bib文件。
一.什么是USB設(shè)備驅(qū)動程序開發(fā)?
隨著USB設(shè)備的普及,USB設(shè)備驅(qū)動開發(fā)在嵌入式系統(tǒng)變得越來越重要了。為了支持不同類型的硬件可以連接到WinCE平臺上,微軟提供了具有定制接口的流接口驅(qū)動程序模型。WinCE的USB外圍設(shè)備一般是使用流接口驅(qū)動程序。流接口驅(qū)動程序是指通過系統(tǒng)提供的文件系統(tǒng)API與應(yīng)用程序交互;WinCE內(nèi)核系統(tǒng)會通過設(shè)備管理器來完成對流接口驅(qū)動程序的加載、卸載等管理工作;而流接口驅(qū)動程序則會通過調(diào)用USBD模塊提供的接口函數(shù)實現(xiàn)與底層USB設(shè)備通信。因此,在進行USB設(shè)備驅(qū)動程序開發(fā)之前,我們必須先了解USB設(shè)備驅(qū)動的結(jié)構(gòu)和分類。
(1)主機與USB攝像頭的通訊結(jié)構(gòu)
USB攝像頭驅(qū)動程序主要是利用系統(tǒng)提供的底層接口配置設(shè)備和攝像頭設(shè)備進行通訊。因此,WinCE的USB攝像頭驅(qū)動分為兩層:USBClient設(shè)備驅(qū)動程序和底層的WinCE函數(shù)實現(xiàn)層。而底層的函數(shù)層本身又由兩部分組成,即通用串行總線驅(qū)動程序(USBD)模塊和較低層的主控制器驅(qū)動程序(HCD)模塊。HCD負(fù)責(zé)最底層的處理,USBD模塊實現(xiàn)較高的USBD函數(shù)接口。因此,USB攝像頭驅(qū)動主要是利用USBD接口函數(shù)和外圍USB攝像頭打交道。
一般來說,主機和USB外設(shè)之間的通訊是由在主機端通過USBD模塊和HCD模塊使用的PIPE訪問一個通用的邏輯設(shè)備來完成。也就是說,USBD和HCD是一組抽象出來用于訪問USB設(shè)備的邏輯接口,它們主要是負(fù)責(zé)管理USB外設(shè)的連接、加載、移除、數(shù)據(jù)傳輸和通用的配置。其中HCD是由主機控制和驅(qū)動的,是為USBD提供底層的功能訪問服務(wù)。而USBD則是由USB總線驅(qū)動的,位于HCD的上層,是利用HCD的服務(wù)提供較高層次抽象的功能。
由于HCD和USBD都是面向一致的邏輯設(shè)備接口,因此如果嵌入式系統(tǒng)中擁有多種USB物理外設(shè)的話,那么就需要有唯一對應(yīng)的外設(shè)驅(qū)動程序,也就是要有最上層的PIPE所連接的物理設(shè)備和USB設(shè)備驅(qū)動程序。有了對這個結(jié)構(gòu)的認(rèn)識,那么我們在進行USB設(shè)備驅(qū)動程序開發(fā)時首先要寫的就是最上端的USB攝像頭客戶端驅(qū)動程序,在WinCE的樣例程序中它也被稱為USBClientDriver。它是工作于USBD之上,所以實際上我們的工作就變成了利用USBD提供的接口針對特定的物理設(shè)備來完成USB設(shè)備驅(qū)動程序。(見圖)
(2)流驅(qū)動程序的分類和函數(shù)結(jié)構(gòu)
WinCE驅(qū)動程序是介于內(nèi)核系統(tǒng)和物攝像頭驅(qū)動理設(shè)備之間的一個代碼層,它的主要作用是為內(nèi)核系統(tǒng)提供一個接口用來操作不同的外圍設(shè)備,包括物理設(shè)備和虛擬設(shè)備。驅(qū)動程序提供給內(nèi)核系統(tǒng)的接口一般可以分為:本地驅(qū)動(NativeDrivers)和流驅(qū)動(StreamDrivers)。我從這次項目實踐中得到的經(jīng)驗是,WinCE下的所有驅(qū)動都可以歸類到這兩個里面,二者必居其一。
流驅(qū)動是指通過為內(nèi)核系統(tǒng)提供流接口函數(shù)來實現(xiàn)驅(qū)動外圍設(shè)備,如XXX_Init()、XXX_Open()、XXX_Read()、XXX_Write()、XXX_Close()等。這一類的驅(qū)動由DeviceManager來管理,它是通過調(diào)用ActivateDeviceEx()函數(shù)來實現(xiàn)加載流驅(qū)動的。ActivateDeviceEx()的參數(shù)是注冊表中相應(yīng)的鍵,用來設(shè)定加載流驅(qū)動的屬性,如Index、Order、Prefix等等。流驅(qū)動加載成功后,應(yīng)用程序就可以通過調(diào)用CreateFile()、ReadFile()、WirteFile()等函數(shù)來訪問流驅(qū)動設(shè)備了。而與流驅(qū)動相反,軟裝,本地驅(qū)動提供給內(nèi)核系統(tǒng)的不是標(biāo)準(zhǔn)的流接口,而是事先約定好的特定接口。因此不同的本地驅(qū)動設(shè)備,接口也是不一樣的。在WinCE中,常見的本地驅(qū)動有LCD顯示驅(qū)動、觸摸屏驅(qū)動、鼠標(biāo)和鍵盤驅(qū)動及打印機驅(qū)動等。從這里可以看出,本地驅(qū)動主要是涉及與人機界面相關(guān)的驅(qū)動。它們是由GWES來管理的,由于他們在注冊表中有各自相應(yīng)的配置信息,因此它們會在系統(tǒng)啟動時自動加載。簡單的說,就是本地驅(qū)動是由內(nèi)核系統(tǒng)操作和調(diào)用的,一般的應(yīng)用程序是不能訪問和調(diào)用的
]]>1:CE下同名設(shè)備不能大于10
CE5.0中已經(jīng)沒有這個問題了,以前的版本可以這樣做:只給上層輸出一個設(shè)備,然后用一個IOCTL去打開一個個的物理設(shè)備這樣就可以做到不受任何限制了。
2:MDD與PDD
一個驅(qū)動程序通常會被分成硬件相關(guān)(PDD)與硬件無關(guān)(MDD)層兩部分。
當(dāng)然,這種分層不是必須的,只是采用這種分層以后可以少寫很多代碼,因為微軟提供了很多驅(qū)動程序的MDD。即使CE中沒有我們所寫的驅(qū)動程序的樣例,采用這種結(jié)構(gòu)以后,當(dāng)需要寫第二個程序時,就可以重用它的代碼,就可以提高開發(fā)效率。
MDD是提供同類型的設(shè)備(比如串口)都會有的功能,這樣PDD基本上就只有寄存器操作了。
像串口的中斷處理,Read/Write函數(shù),其大部分代碼都是在MDD中實現(xiàn)的,不同的串口實現(xiàn)中只需要提供一些實際操作寄存器的函數(shù)。不同的驅(qū)動程序,其MDD與PDD的接口不盡相同,
3:XXX_Init函數(shù)的返回句柄
通常,這個句柄是驅(qū)動程序自己保存數(shù)據(jù)的一個指針,我們在Init返回時告訴上層程序,以后上層調(diào)用其它函數(shù)(例如Open)時,會將這個值傳入,這樣,我們就可以訪問自己的一些私有數(shù)據(jù)。
當(dāng)然,也可以返回一個任意的非0值對于一個設(shè)備驅(qū)動程序,系統(tǒng)不用的層會有不同的句柄。我們在XXX_Init中返回的句柄保存在設(shè)備管理器中,別的程序中應(yīng)該是看不到的,而用CreateFile也會得到一個文件句柄,這個保存在哪我不知道,但和前者是不一樣的。也就是說不同層的軟件所關(guān)心的句柄也會不一樣
4:DEBUGMSG與RETAILMSG的區(qū)別
它們都是輸出調(diào)試信息用的,區(qū)別是:
DEBUGMSG只在DEBUG版中有效,RELEASE版中它被定義成了NULL
RETAILMSG在DEBUG和RELEASE版中都可以輸出,
而且DEBUGMSG可以在運行時刻用DEBUZONE控制要不要輸出信息。
在ship build 時,RETAILMSG 和DEBUGMSG都無效。
5:調(diào)試區(qū)與dpCurSettings
我們都是利用OutpubDebugString函數(shù)來實現(xiàn)調(diào)試信息的輸出的,但是由于系統(tǒng)底層的調(diào)試信息非常繁多,如果這樣大量的調(diào)試信息用于實時輸出的話一定會影響到系統(tǒng)的性能和實時性,也就影響到了系統(tǒng)的運行。如果有一種方式能允許開發(fā)人員自己選擇輸出哪些調(diào)試信息,不輸出哪些調(diào)試信息的話,那么就可以讓開發(fā)人員只看到關(guān)心的調(diào)試信息,而把諸如鍵盤按鍵、鼠標(biāo)移動等無用的調(diào)試信息隱去,則可以更好的提高開發(fā)效率。
調(diào)試區(qū)就是為了解決以上提出的問題的,對某一個驅(qū)動程序,它規(guī)定好自己向外輸出的調(diào)試信息的分類,比如初始化時的信息,出錯時的信息,釋放時的信息,激活時的信息等,然后分成幾個調(diào)試區(qū),在現(xiàn)有的CE版本中最多允許16個調(diào)試區(qū)。
開發(fā)人員通過Platform Builder中Target菜單下的CE Debug Zones命令來決定想要得到哪一個或哪幾個調(diào)試區(qū)的信息,在驅(qū)動程序中則可以根據(jù)開發(fā)人員的選擇來輸出指定調(diào)試區(qū)的信息。這就是調(diào)試區(qū)大體上的工作原理。
調(diào)試區(qū)的定義,聲明,注冊及使用。
在程序中使用調(diào)試區(qū)之前必須先定義它們,一個程序的16個調(diào)試區(qū)編號分別為0-15。代碼樣例如下所示:
#ifdef DEBUG
//
// For debug builds, use the real zones.
//
#define ZONE_TEST DEBUGZONE(0)
#define ZONE_PARAMS DEBUGZONE(1)
#define ZONE_VERBOSE DEBUGZONE(2)
……
#define ZONE_WARN DEBUGZONE(14)
#define ZONE_ERROR DEBUGZONE(15)
#else
//
// For retail builds, use forced messages based on the zones turned on below.
//
#define ZONE_TEST 0
#define ZONE_PARAMS 0
#define ZONE_VERBOSE 0
……
#define ZONE_WARN 0
#define ZONE_ERROR 0
#endif
這樣,就可以程序的DEBUG版本中使用調(diào)試區(qū)了,而在RELEASE版本中則將其全部定義為0,調(diào)試信息即不再輸出。
在程序中,除了以上的定義以外,還要聲明幾個專用的調(diào)試信息輸出函數(shù),這些函數(shù)與OutputDebugString函數(shù)的區(qū)別就在于在調(diào)用時需要指定對應(yīng)的調(diào)試區(qū),這些函數(shù)以及以上用到的DEBUGZONE宏的定義都在DbgApi.h頭文件中,因此只要在源程序中包含此頭文件即可。除此以外,還需要一個全局的DEBPARAM類型的變量命名為dpCurSettings,以供集成開發(fā)環(huán)境和調(diào)試信息輸出函數(shù)使用。其代碼樣例如下:
#ifdef DEBUG
DBGPARAM dpCurSettings = {
};
#endif
此例中還把ERROR和WARN調(diào)試區(qū)作為默認(rèn)被開發(fā)人員選中的調(diào)試區(qū)。
要想使用調(diào)試區(qū),還需要做的最后一件準(zhǔn)備的事情就是在程序中進行注冊,也就是在程序啟動時通知集成開發(fā)環(huán)境本程序中要使用調(diào)試區(qū),這個注冊很簡單,只要在程序的入口處使用DEBUGREGISTER宏即可,樣例如下:
DllEntry (
HANDLE hinstDLL,
DWORD Op,
LPVOID lpvReserved
)
{
switch (Op) {
case DLL_PROCESS_ATTACH :
DEBUGREGISTER((HINSTANCE)hinstDLL);
break;
……
1、基礎(chǔ)知識:
1)系統(tǒng)調(diào)用是操作系統(tǒng)內(nèi)核和應(yīng)用程序之間的接口,設(shè)備驅(qū)動程序是操作系統(tǒng)內(nèi)核和機器硬件之間的接
口。設(shè)備驅(qū)動程序為應(yīng)用程序屏蔽了硬件細節(jié),在應(yīng)用程序看來硬件只是一個設(shè)備文件,應(yīng)用程序可以
像操作普通文件一樣對硬件設(shè)備進行操作。設(shè)備驅(qū)動是內(nèi)核的一部分。
2)驅(qū)動程序完成以下功能:
——對設(shè)備初始化和釋放;
——把數(shù)據(jù)從內(nèi)核傳送到硬件和從硬件讀取數(shù)據(jù);
——讀取應(yīng)用程序傳送給設(shè)備文件的數(shù)據(jù)和回送應(yīng)用程序請求的數(shù)據(jù);
——檢測和處理設(shè)備出現(xiàn)的錯誤。
3)上層應(yīng)用程序運行在用戶模式(非特權(quán)模式,Ring 3),代碼被嚴(yán)格約束執(zhí)行。如不能執(zhí)行硬件IO指
令。所有的這些被阻止的操作如果想運行必須通過陷阱門來請求操作系統(tǒng)內(nèi)核。
4)操作系統(tǒng)內(nèi)核運行在內(nèi)核模式(特權(quán)模式,Ring 0),可以執(zhí)行所有有效的CPU指令。包括IO操作,
可訪問任何內(nèi)存區(qū)。
5)整個硬件系統(tǒng)資源在驅(qū)動程序面前是赤裸裸的,驅(qū)動可以使用所有系統(tǒng)資源,編寫驅(qū)動程序時我們必
須格外小心驅(qū)動代碼的邊界條件,確保它們不會損壞整個操作系統(tǒng)。
2、Windows支持的驅(qū)動:
1)虛擬設(shè)備驅(qū)動程序(Virtual Device Driver):Windows3.1(Windows95/98/Me)
2)內(nèi)核模式驅(qū)動程序(Kernel Mode Driver):Windows NT
3)Win32驅(qū)動程序模型(Win32 Driver Mode):從Windows98開始使用。
其中WDM是目前主流,然而在WinCE系統(tǒng)中,由于硬件資源有限和嵌入式系統(tǒng)的特點,對其的支持非常有
限。
3、WinCE系統(tǒng)驅(qū)動簡介:
1)WinCE畢竟是一個嵌入式系統(tǒng),有其自身的特殊性,為了提高運行效率,所有驅(qū)動皆為動態(tài)鏈接庫,
驅(qū)動實現(xiàn)中可以調(diào)用所有標(biāo)準(zhǔn)的API。而在其他Windows系統(tǒng)中可能的驅(qū)動文件還有.vxd, .sys和動態(tài)鏈
接庫。
2)WinCE驅(qū)動從結(jié)構(gòu)上講分為本地驅(qū)動(Native Driver)和流接口驅(qū)動(Stream Driver)。
——本地驅(qū)動主要用于低級、內(nèi)置的設(shè)備。實現(xiàn)它們的接口并不統(tǒng)一,而是針對不同類型的設(shè)備相應(yīng)設(shè)
計。因此開發(fā)過程相對復(fù)雜,沒有固定的模式,一般做法是通過移植、定制現(xiàn)有的驅(qū)動樣例來實現(xiàn)。
——流接口驅(qū)動是最基本的一種驅(qū)動結(jié)構(gòu),它的接口是一組固定的流接口函數(shù),具有很高的通用性,
WinCE的所有驅(qū)動程序都可以通過這種方式來實現(xiàn)。流接口驅(qū)動程序通過文件系統(tǒng)調(diào)用從設(shè)備管理器和應(yīng)
用程序接收命令。該驅(qū)動程序封裝了將這些命令轉(zhuǎn)換為它所控制的設(shè)備上的適當(dāng)操作所需的全部信息。
流接口驅(qū)動是動態(tài)鏈接庫,由一個叫做設(shè)備管理程序的特殊應(yīng)用程序加載、管理和卸載。與本地
驅(qū)動程序相比,所有流接口驅(qū)動程序使用同一組接口函數(shù)集,包括實現(xiàn)函數(shù):XXX_Init、XXX_Deinit、
XXX_Open、XXX_Close、XXX_Read、XXX_Write、XXX_PowerUp、XXX_PowerDown、XXX_Seek、
XXX_IOControl,這些函數(shù)與硬件打交道。用戶函數(shù):CreateFile、DeviceIoControl、 ReadFile、
WriteFile,這些函數(shù)方便用戶使用驅(qū)動程序。
3)WinCE下驅(qū)動的加載方式:
——通過GWES(Graphics, Windowing, and Events Subsystem):主要加載與顯示和輸入有關(guān)的驅(qū)動,
如鼠標(biāo)、鍵盤驅(qū)動等。這些驅(qū)動一般為本地驅(qū)動。
——通過設(shè)備管理器:兩種結(jié)構(gòu)的驅(qū)動都加載,加載的本地驅(qū)動主要由PCMCIA Host Controller,USB
Host Controller driver,主要是總線類的驅(qū)動;流接口驅(qū)動主要有音頻驅(qū)動,串并口驅(qū)動。
——動態(tài)加載:前兩者都是系統(tǒng)啟動時加載的,動態(tài)加載則允許設(shè)備掛載上系統(tǒng)時將驅(qū)動調(diào)入內(nèi)核,主
要有外接板卡驅(qū)動,USB設(shè)備驅(qū)動等。
4、流接口驅(qū)動函數(shù)介紹:
1)DWORD XXX_Init(LPCTSTR pContext, LPCVOID lpvBusContext);
pContext:指向一個字符串,包含注冊表中該流接口活動鍵值的路徑
lpvBusContext:
該函數(shù)是驅(qū)動掛載后第一個被執(zhí)行的。主要負(fù)責(zé)完成對設(shè)備的初始化操作和驅(qū)動的安全性檢查。由
ActiveDeviceEx通過設(shè)備管理器調(diào)用。其返回值一般是一個數(shù)據(jù)結(jié)構(gòu)指針,作為函數(shù)參數(shù)傳遞給其他流
接口函數(shù)。
2)BOOL XXX_Deinit(DWORD hDeviceContext);
hDeviceContext:XXX_Init的返回值。
整個驅(qū)動中最后執(zhí)行。用來停止和卸載設(shè)備。由DeactivateDevice觸發(fā)設(shè)備管理器調(diào)用。成功返回TRUE
。
3)DWORD XXX_Open(DWORD hDeviceContext, DWORD AccessCode , DWORD ShareMode);
hDeviceContext:XXX_Init的返回值。
AccessCode:訪問模式標(biāo)志,讀、寫或其他。
ShareMode:驅(qū)動的共享方式標(biāo)志。
打開設(shè)備,為后面的操作初始化數(shù)據(jù)就夠,準(zhǔn)備相應(yīng)的資源。應(yīng)用程序通過CreateFile函數(shù)間接調(diào)用之
。返回一個結(jié)構(gòu)指針,用于區(qū)分哪個應(yīng)用程序調(diào)用了驅(qū)動,這個值還作為參數(shù)傳遞給其他接口函數(shù)
XXX_Read、XXX_Write、XXX_Seek、XXX_IOControl。
4)BOOL XXX_Close(DWORD hOpenContext);
hOpenContext:XXX_Open返回值。
關(guān)閉設(shè)備,釋放資源。由CloseHandle函數(shù)間接調(diào)用。
5)DWORD XXX_Read(DWORD hOpenContext, LPVOID pBuffer, DWORD Count);
hOpenContext:XXX_Open返回值。
pBuffer:緩沖區(qū)指針,接收數(shù)據(jù)。
Count:緩沖區(qū)長度。
由ReadFile函數(shù)間接調(diào)用,用來讀取設(shè)備上的數(shù)據(jù)。返回讀取的實際數(shù)據(jù)字節(jié)數(shù)。
6)DWORD XXX_Write(DWORD hOpenContext, LPCVOID pBuffer, DWORD Count);
hOpenContext:XXX_Open返回值。
pBuffer:緩沖區(qū)指針,接收數(shù)據(jù)。
Count:緩沖區(qū)長度。
由WriteFile函數(shù)間接調(diào)用,把數(shù)據(jù)寫到設(shè)備上,返回實際寫入的數(shù)據(jù)數(shù)。
7)BOOL XXX_IOControl(DWORD hOpenContext, DWORD dwCode, PBYTE pBufIn, DWORD dwLenIn, PBYTE
pBufOut, DWORD dwLenOut, PDWORD pdwActualOut);
hOpenContext:XXX_Open返回值。
dwCode:控制命令字。
pdwActualOut:實際輸出數(shù)據(jù)長度。
用于向設(shè)備發(fā)送命令,應(yīng)用程序通過DeviceIoControl調(diào)用來實現(xiàn)該功能。要調(diào)用這個接口還需要在應(yīng)用
層和驅(qū)動之間建立一套相同的命令,通過宏定義CTL_CODE(DeviceType, Function, Method, Access來實
現(xiàn)。如:
#define IOCTL_INIT_PORTS \ CTL_CODE
(FILE_DEVICE_UNKNOWN,0X801,METHOD_BUFFERED,FILE_ANY_ACCESS)
8)void XXX_PowerDown(DWORD hDeviceContext);
hDeviceContext:XXX_Init的返回值。
]]>鍵 | 意義 |
"SysIntr"=dword:13 | 串口1的中斷ID為十進制13 |
"IoBase"=dword:02F8 | 串口1的IO空間首地址為十六進制2F8 |
"IoLen"=dword:8 | 串口1的IO空間長度為8個字節(jié) |
"DeviceArrayIndex"=dword:0 | 串口1的索引,是1的由來 |
"Order"=dword:0 | 串口1驅(qū)動的加載順序 |
"DeviceType"=dword:0 | 串口1的設(shè)備類型 |
"DevConfig"=hex: 10,00 .... | 串口1在與Modem設(shè)備通訊時的配置,如波特率、奇偶校檢等 |
"FriendlyName"="COM1:" | 串口1在撥號程序中顯示的名字 |
"Tsp"="Unimodem.dll" | 串口1 被用于與Modem設(shè)備通訊的時候要加載的TSP(TAPI Service provider)DLL |
"Prefix"="COM" | 串口1的流接口的前綴 |
"Dll"="com16550.Dll" | 串口1的驅(qū)動程序DLL |
SysIntr由CE在文件Nkintr.h中預(yù)定義,用于唯一標(biāo)識中斷設(shè)備。OEM可以在文件Oalintr.h中定義自己的SysIntr。常見的預(yù)定義SysIntr有SYSINTR_NOP(中斷只由ISR處理,IST不再處理),SYSINTR_RESCHED(重新調(diào)度線程),SYSINTR_DEVICES(由CE預(yù)定義的設(shè)備中斷ID的基值),SYSINTR_PROFILE、SYSINTR_TIMING、SYSINTR_FIRMWARE等都是基于SYSINTR_DEVICES定義的。IoBase是串口1的IO地址空間的首地址,IoLen是IO空間的大小。IO地址空間只存在于x86平臺,如果在其它平臺硬件寄存器必須映射到物理地址空間,那子鍵的名稱為MemBase和MemLen。在x86平臺更多硬件的寄存器由于IO空間的局限也映射到物理地址空間。DeviceArrayIndex是設(shè)備的索引,用于區(qū)分同類型的設(shè)備。Prefix是流驅(qū)動程序的前綴,當(dāng)應(yīng)用程序調(diào)用CreateFile函數(shù)傳遞COM1:參數(shù)時,文件系統(tǒng)負(fù)責(zé)與串口驅(qū)動程序通信,串口驅(qū)動程序是在CE啟動時由device.exe加載的。
下面從MDD層函數(shù)COM_Init開始探索串口驅(qū)動的初始化過程。COM_Init是在串口設(shè)備被檢測后由設(shè)備管理器device.exe調(diào)用的,主要的作用是初始化設(shè)備,它的唯一參數(shù)Identifier是由device.exe傳遞的,其類型是一個字符串指針,字符串的內(nèi)容是HLM\Drivers\Active\xx,xx是一個十進制數(shù)(device.exe會跟蹤系統(tǒng)中每個驅(qū)動程序,把加載的驅(qū)動程序記錄在Active鍵下)。COM_Init先分配一個HW_INDEP_INFO結(jié)構(gòu)體,這個結(jié)構(gòu)體是獨立于串口硬件的頭信息(MDD、PDD、SER16550都包含自己獨特的結(jié)構(gòu)體,具體的結(jié)構(gòu)體定義請參見串口驅(qū)動源碼),分配之后再初始化結(jié)構(gòu)體中每個成員,初始化結(jié)構(gòu)體后調(diào)用 OpenDeviceKey((LPCTSTR)Identifier)打開HLM\Drivers\Active\xx\Key包含的注冊表路徑,在這里路徑一般為HLM\Drivers\BuiltIn\Serial,即串口的驅(qū)動程序信息在注冊表中所處的位置。COM_Init接著在HLM\Drivers\BuiltIn\Serial下查詢DeviceArrayIndex、Priority256的值,Priority256指定了驅(qū)動程序的優(yōu)先級,如果沒有就用默認(rèn)的優(yōu)先級。接下來調(diào)用GetSerialObject(DeviceArrayIndex),這個函數(shù)由PDD層定義,返回HWOBJ結(jié)構(gòu)體,這個結(jié)構(gòu)體主要包含PDD層和SER16550定義的函數(shù)的指針。也就是說MDD通過調(diào)用這個函數(shù)才能調(diào)用底層實現(xiàn)的函數(shù)。接下來的大多數(shù)工作都是調(diào)用底層函數(shù)實現(xiàn)初始化。第一個調(diào)用的底層函數(shù)SerInit主要設(shè)置由用戶設(shè)置的硬件配置,例如線路控制、波特率。它調(diào)用Ser_GetRegistryData函數(shù)得到保存在注冊表中的硬件信息,Ser_GetRegistryData在內(nèi)部調(diào)用系統(tǒng)提供的DDKReg_GetIsrInfoDDK和DDKReg_GetWindowInfo函數(shù)得到在HLM\Drivers\BuiltIn\Serial下保存的IRQ、SysIntr、IsrDll、IsrHandler、IoBase、IoLen。IRQ是邏輯中斷號,IsrDll表示當(dāng)前驅(qū)動程序的可安裝ISR所在的DLL名稱,IsrHandler 表示可安裝ISR的函數(shù)名稱。在這里順便提一下可安裝ISR,讀者在我以前發(fā)表的關(guān)于OAL的文章中可以了解到OEM在OEMInit函數(shù)中關(guān)聯(lián)IRQ和SysIntr,當(dāng)硬件設(shè)備發(fā)生中斷時,ISR會禁止同級和低級中斷,然后根據(jù)IRQ返回關(guān)聯(lián)的SysIntr,內(nèi)核根據(jù)ISR返回的SysIntr喚醒相應(yīng)的IST(SysIntr與IST創(chuàng)建的Event關(guān)聯(lián)),IST處理中斷之后調(diào)用InterruptDone解除中斷禁止。在OEMInit中關(guān)聯(lián)的缺點是一旦編譯了CE內(nèi)核后就無法添加這種關(guān)聯(lián)了,而一些硬件設(shè)備會隨時插拔或者共享中斷,要關(guān)聯(lián)這樣的硬件設(shè)備解決方法就是可安裝ISR,可安裝ISR專用于處理指定的硬件設(shè)備發(fā)出的中斷,所以如果硬件設(shè)備需要可安裝ISR必須在注冊表中添加IsrDll、IsrHandler。多數(shù)硬件設(shè)備采用CE默認(rèn)的可安裝ISR giisr.dll,格式如下:
"IsrDll"="giisr.dll" "IsrHandler"="ISRHandler"
如果一個硬件驅(qū)動程序需要可安裝ISR而開發(fā)者又不想自己寫一個,那么可以利用giisr.dll來實現(xiàn)。除了在注冊表中添加如上所示外,還要在驅(qū)動程序中調(diào)用相關(guān)函數(shù)注冊可安裝ISR。偽代碼如下:
g_IsrHandle = LoadIntChainHandler(IsrDll, IsrHandler, (BYTE)Irq); GIISR_INFO Info; PHYSICAL_ADDRESS PortAddress = {PhysAddr, 0}; TransBusAddrToStatic(BusType, dwBusNumber, PortAddress, dwAddrLen, &dwIOSpace, &(PVOID)PhysAddr) Info.SysIntr = dwSysIntr; Info.CheckPort = TRUE; Info.PortIsIO = (dwIOSpace) ? TRUE : FALSE; Info.UseMaskReg = TRUE; Info.PortAddr = PhysAddr 0x0C; Info.PortSize = sizeof(DWORD); Info.MaskAddr = PhysAddr 0x10; KernelLibIoControl(g_IsrHandle, IOCTL_GIISR_INFO, &Info, sizeof(Info), NULL, 0, NULL);
LoadIntChainHandler函數(shù)負(fù)責(zé)注冊可安裝ISR,參數(shù)1為DLL名稱,參數(shù)2為ISR函數(shù)名稱,參數(shù)3為IRQ。TransBusAddrToStatic函數(shù)在后面講。如果要利用giisr.dll作為可安裝ISR,必須先填充GIISR_INFO結(jié)構(gòu)體,CheckPort=TRUE表示giisr要檢測指定的寄存器來確定當(dāng)前發(fā)出中斷的是否是這個設(shè)備。PortIsIO表示寄存器地址屬于哪個地址空間,F(xiàn)ALSE表示是內(nèi)定空間,TRUE表示IO空間。UseMaskReg=TRUE表示設(shè)備有一個掩碼寄存器,專用于指定當(dāng)前設(shè)備是否是中斷源,也就是發(fā)出中斷,而MaskAddr表示掩碼寄存器的地址。如果對Info.Mask賦值,那么PortAddr表示一個特殊的寄存器地址,這個寄存器的值與Mask的值&運算的結(jié)果如果為真,則證明當(dāng)前設(shè)備是中斷源,否則返回SYSINTR_CHAIN(表示當(dāng)前ISR沒有處理中斷,內(nèi)核將調(diào)用ISR鏈中下一個ISR),如果UseMaskReg=TRUE,那么MaskReg寄存器的值與PortAddr指定的寄存器的值&運算的結(jié)果如果為真,則證明當(dāng)前設(shè)備是中斷源。
函數(shù)SerInit接著調(diào)用函數(shù)Ser_InternalMapRegisterAddresses轉(zhuǎn)換IO地址并且映射地址,Ser_InternalMapRegisterAddresses在內(nèi)部調(diào)用系統(tǒng)提供的HalTranslateBusAddress(Isa, 0, ioPhysicalBase, &inIoSpace, &ioPhysicalBase)函數(shù)將與總線相關(guān)的地址轉(zhuǎn)換為系統(tǒng)地址,參數(shù)1為總線類型,參數(shù)2為總線號,參數(shù)3為要轉(zhuǎn)換的地址(PHYSICAL_ADDRESS類型,實際是LARGE_INTEGER型),參數(shù)4指定寄存器地址屬于IO地址空間還是物理地址空間,參數(shù)5返回轉(zhuǎn)換后的物理地址。觀察HalTranslateBusAddress的源碼得知如果是在x86平臺,這個函數(shù)除了把參數(shù)3賦給了參數(shù)5其余什么都沒有做,而非x86平臺將inIoSpace的值置為0,表示一定是物理地址。在調(diào)用HalTranslateBusAddress前要確定從注冊表中得到的寄存器地址到底是屬于哪個地址空間的
1.1 分層觸摸屏驅(qū)動層序結(jié)構(gòu)
本觸摸屏驅(qū)動采用分層驅(qū)動程序結(jié)構(gòu),其驅(qū)動模型如下圖所示,這種結(jié)構(gòu)將驅(qū)動程序代碼區(qū)分為上層模型設(shè)備驅(qū)動層(MDD),下層是依賴平臺的驅(qū)動層(PDD)。其中MDD層通常無需修改就可以直接使用,改部分提供面向GWES的DDI的接口,而MDD通過指定的DDSI函數(shù)接口調(diào)用PDD,這就是我們通常驅(qū)動要實現(xiàn)的部分。PDD部分和MDD部分除了DDSI函數(shù)集接口外,還要實現(xiàn)一些指定的變量的定義或變量初始化動作(比如,gIntrTouch和gIntrTouchChanged在PDD層定義,但主要在MDD層使用。),也就是說MDD層和PDD層之間并不一定是以嚴(yán)格的分層模型來實現(xiàn)的,有時候也要通過共享變量的方式來完成交互。
1.2 DDI函數(shù)集(MDD層)
TouchPanelPowerHandler(BOOL boff)
Touch Screen的電源管理函數(shù),boff:TRUE表示關(guān)閉電源,F(xiàn)ALSE表示打開電源,其只是調(diào)用DdsiTouchPanelPowerHandler()函數(shù),該函數(shù)在進入或退出poweroff狀態(tài)時產(chǎn)生。
TouchPanelCalibrationAPoint()
該函數(shù)用于校準(zhǔn)輸入的觸摸屏坐標(biāo),把觸摸屏坐標(biāo)轉(zhuǎn)換為顯示坐標(biāo),利用了公式Sx=A1*Tx+B1*Ty+C和Sy=A2*Tx+B2*Ty+C2。
TouchPanelReadCalibrationPoint()
在執(zhí)行觸摸屏校準(zhǔn)程序時,用這個函數(shù)獲得在當(dāng)前校準(zhǔn)點的十字形上點擊的觸摸屏坐標(biāo)。
TouchPanelReadCalibrationAbord()
該函數(shù)在校準(zhǔn)取消時被調(diào)用(在觸摸屏校準(zhǔn)程序運行過程中取消校準(zhǔn)),僅僅設(shè)置狀態(tài)位和事件后返回。
TouchPanelDisable()
禁用觸摸屏(touch panel)設(shè)備,該函數(shù)關(guān)閉ISR,停止中斷和注銷事件及其他同步手段,此函數(shù)調(diào)用了DdsiTouchPanelDisable()函數(shù)。
TouchPanelEnable(PFN_TOUCH_PANEL_CALLBACK pfnCallback)
PfnCallback是指向處理touch panel事件的回調(diào)函數(shù),該函數(shù)的執(zhí)行動作:
⑴創(chuàng)建事件hTouchPanelEvent和hCalibrationSampleAvailable,其中當(dāng)觸筆按下或抬起,或者定時器中斷時會觸發(fā)hTouchPanelEvent事件,而在校準(zhǔn)狀態(tài)下當(dāng)有校準(zhǔn)數(shù)據(jù)輸入時會觸發(fā)hCalibrationSampleAvailable事件。
⑵初始化臨界區(qū),初始化所需的觸摸屏中斷gIntrTouch和gIntrTouchChanged,并且把它們關(guān)聯(lián)到事件hTouchPanelEvent中。
⑶創(chuàng)建IST TouchPanelpISR,并設(shè)定其優(yōu)先級。
TouchPanelSetCalibration()
該函數(shù)通過運行觸摸屏校準(zhǔn)程序時的校準(zhǔn)動作獲得顯示坐標(biāo)(Sx,Sy)和觸筆在十字形上按下的觸摸坐標(biāo)(Tx,Ty)用于計算校準(zhǔn)參數(shù)A1,B1,C1和A2,B2,C2。
TouchPanelGetDeviceCaps()
用于查詢觸摸屏設(shè)備支持的具體功能,通過DDSI函數(shù)查詢相應(yīng)的信息,當(dāng)查詢屏幕坐標(biāo)信息時保存屏幕信息,供后面程序計算校準(zhǔn)參數(shù)所用。
TouchPanelSetMode()
用于設(shè)置觸摸屏的工作模式(采用低采樣率還是高采樣率),當(dāng)設(shè)置IST優(yōu)先級時直接通過內(nèi)核API來完成,而直接將其他設(shè)置交給DdsiTouchPanelSetMode()函數(shù)來處理。
1.3WinCE DDSI函數(shù)集(PDD層)
DdsiTouchPanelGetDeviceCaps(INT iIndex, LPVOID lpOutput)
查詢touch panel設(shè)備的相關(guān)信息。
IIndex:查詢的索引值,其取值如下:
TPDC_SAMPLE_RATE_ID:查詢采樣率信息。
TPDC_CALIBRATION_POINT_COUNT_ID:查詢用于校驗的點的個數(shù)。
TPDC_CALIBRATION_POINT_ID:查詢需要校驗的點的坐標(biāo)。
LpOutput:根據(jù)iIndex值分別指向相關(guān)的信息。
DdsiTouchPanelSetMode()
設(shè)置Touch Panel工作模式。
iIndex:模式索引
TPSM_SAMPLERATE_HIGH_ID:高采樣率
TPSM_SAMPLERATE_LOW_ID:低采樣率
lpInput:指向包含相關(guān)信息的內(nèi)存
DdsiTouchPanelEnable()
該函數(shù)所執(zhí)行的動作:
⑴為需要用到的I/O,ADC,PWM和INT寄存器分配內(nèi)存空間。
⑵配置觸摸屏控制器、中斷控制器和PWM的寄存器。
⑶申請觸摸屏中斷gIntrTouch和定時器中斷gIntrTouchChanged,并且對它們進行初始化,為物理中斷號分配相應(yīng)的系統(tǒng)邏輯中斷號。。
DdsiTouchPanelDisable()
屏蔽觸摸屏中斷和釋放為I/O,ADC,PWM和INT寄存器分配的WinCE內(nèi)存空間。
DdsiTouchPanelAttach()
只是簡單地返回1。
DdsiTouchPanelDetach()
只是簡單地返回0。
DdsiTouchPanelGetPoint (TOUCH_PANEL_SAMPLE_FLAGS * pTipStateFlags,
INT * pUncalX,
INT * pUncalY )
獲得Touch Panel上被按下的點的狀態(tài)和坐標(biāo)。
◆pTipState:當(dāng)前觸摸點的狀態(tài),比如無效點,有效點,被按下的點等。
◆pUnCalX:觸摸點的X坐標(biāo)
◆pUnCalY:觸摸點的Y坐標(biāo)
◆DdsiTouchPanelPowerHandler()
設(shè)置touch panel的電源狀態(tài),boff:TRUE表示關(guān)閉電源,F(xiàn)ALSE表示打開電源,
2.觸摸屏驅(qū)動程序的實現(xiàn)
Windows CE5.0觸摸屏驅(qū)動程序采用中斷方式對觸摸筆的按下狀態(tài)進行檢測,如果檢測到觸摸筆按下將產(chǎn)生中斷并觸發(fā)一個事件通知一個工作線程開始采集數(shù)據(jù)。同時,驅(qū)動將打開一個硬件定時器,只要檢測到觸摸筆仍然在按下狀態(tài)將定時觸發(fā)同一個事件通知工作線程采集數(shù)據(jù),直到觸摸筆抬起后關(guān)閉該定時器,并重新檢測按下狀態(tài)。驅(qū)動中采用了觸摸屏中斷以及定時器中斷2個中斷源,不僅可以監(jiān)控觸摸筆按下和抬起狀態(tài),而且可以檢測觸摸筆按下時的拖動軌跡。觸摸屏驅(qū)動流程下圖所示
3.四線電阻式觸摸屏的工作原理
四線電阻式觸摸屏的結(jié)構(gòu)如圖1,在玻璃或丙烯酸基板上覆蓋有兩層透平,均勻?qū)щ姷腎TO層,分別做為X電極和Y電極,它們之間由均勻排列的透明格 點分開絕緣。其中下層的ITO與玻璃基板附著,上層的ITO附著在PET薄膜上。X電極和Y電極的正負(fù)端由“導(dǎo)電條”(圖中黑色條形部分)分別從兩端引 出,且X電極和Y電極導(dǎo)電條的位置相互垂直。引出端X-,X+,Y-,Y+一共四條線,這就是四線電阻式觸摸屏名稱的由來。當(dāng)有物體接觸觸摸屏表面并施以 一定的壓力時,上層的ITO導(dǎo)電層發(fā)生形變與下層ITO發(fā)生接觸,該結(jié)構(gòu)可以等效為相應(yīng)的電路,
計算觸點的X,Y坐標(biāo)分為如下兩步:
1. 計算Y坐標(biāo),在Y+電極施加驅(qū)動電壓Vdrive, Y-電極接地,X+做為引出端測量得到接觸點的電壓,由于ITO層均勻?qū)щ姡|點電壓與Vdrive電壓之比等于觸點Y坐標(biāo)與屏高度之比。
2. 計算X坐標(biāo),在X+電極施加驅(qū)動電壓Vdrive, X-電極接地,Y+做為引出端測量得到接觸點的電壓,由于ITO層均勻?qū)щ姡|點電壓與Vdrive電壓之比等于觸點X坐標(biāo)與屏寬度之比。
測得的電壓由ADC轉(zhuǎn)化為觸摸點的原始坐標(biāo)(數(shù)值范圍由所選用的A/D轉(zhuǎn)換器位數(shù)決定)后,還要根據(jù)具體使用的液晶屏實際像素進行轉(zhuǎn)換,轉(zhuǎn)換后通過校準(zhǔn)直接轉(zhuǎn)化為屏幕上的坐標(biāo),供GWES使用。
4.觸摸屏的接口部分
◆X+:連接觸摸屏控制器的TSXP,。
◆X-:連接觸摸屏控制器的TSXM。
◆Y+:連接觸摸屏控制器的TSYP。
◆Y-:連接觸摸屏控制器的TSYM。
在觸摸屏接口使用時,TSXM或TSYM應(yīng)該接觸摸屏接口的。
5.配置控制器硬件
5.1 ADCCON-----ADC控制寄存器
ECFLG:ADCCON[15],AD轉(zhuǎn)換結(jié)束標(biāo)志,只讀,0表示AD轉(zhuǎn)換在過程中;1表示AD轉(zhuǎn)換結(jié)束。
PRSCEN:ADCCON[14],AD轉(zhuǎn)換器預(yù)分頻器使能,在此使能,故為1。
PRSCVL:ADCCON[13:6],AD轉(zhuǎn)換器預(yù)分頻器值,在此為49。
SEL_MUX:ADCCON[5:3],模擬信號輸入通道選擇,在此選擇XP,故為7。
STDBM:ADCCON[2],備用操作模式選擇,在此選擇普通操作模式,故為0。
READ_START:ADCCON[1],通過讀取來啟動A/D轉(zhuǎn)換,在此選擇通過讀取操作無效來啟動A/D轉(zhuǎn)換,故為0。
ENABLE_START,ADCCON[0],通過使能該位來啟動A/D轉(zhuǎn)換,在此選擇無操作。此位在A/D轉(zhuǎn)換開始后被使能。
5.2 ADCTSC-----ADC觸摸屏控制寄存器
UD_SEN:ADCTSC[8],在此選擇檢測到觸筆按下就產(chǎn)生中斷信號,故為0。
YM_SEN:ADCTSC[7],YM開關(guān)使能,在此選擇YM輸出驅(qū)動有效(GND),故為1。
YP_SEN:ADCTSC[6],YP開關(guān)使能,在此選擇YP輸出驅(qū)動無效(AIN5),故為1。
XM_SEN:ADCTSC[5],XM開關(guān)使能,在此選擇XM輸出驅(qū)動無效(Hi-Z),故為0。
XP_SEN:ADCTSC[4],XP開關(guān)使能,在此選擇XP輸出驅(qū)動無效(AIN7),故為1。
PULL_UP:ADCTSC[3],上拉開關(guān)使能,在此選擇XP上拉有效,故為0。
AUTO_PST:ADCTSC[2],初始化時,在此選擇自動連續(xù)測量X坐標(biāo)和Y坐標(biāo),故為0,但如果開始轉(zhuǎn)換時,應(yīng)該置1。
XY_PST:ADCTSC[1:0],手動測量X坐標(biāo)和Y坐標(biāo),在此選擇等待中斷模式,故為3。
注:當(dāng)?shù)却|摸屏中斷時,XP_SEN位(XP輸出無效)應(yīng)該置為1,且PULL_UP(XP上拉使能)位應(yīng)該置為0。
5.3 ADCDLY-----ADC開始延時寄存器
DELAY:ADCDLY[154:0],因為選擇等待中斷模式,此值表示,當(dāng)觸筆按下出現(xiàn)在睡眠模式時,產(chǎn)生一個用于推出睡眠模式的信號,有幾個毫秒的時間間隔。在此此值為40000?????????(40s,太長了吧?)
SUBINTMSK-----WinCE中子中斷屏蔽寄存器
該寄存器有11位,每位和一位中斷源相關(guān)。觸摸屏中斷請求有效,故第十位應(yīng)設(shè)為0。
v_pINTregs->INTSUBMSK &= ~(1<<IRQ_SUB_TC);
5.4 TCFG1-----5路多路器及DMA模式選擇寄存器
MUX3:TCFG1[15:12],為PWM計時器3選擇多路輸入,并初始化其值,每個定時器都有一個時鐘分頻器,其可以生成5鐘不同的分頻信號(1/2,1/4,1/8,1/16和TCLK),在此選擇1/16分頻。
v_pPWMregs->TCFG1 &= ~(0xf << 12); /* Timer3's Divider Value */
v_pPWMregs->TCFG1 |= (3 << 12); /* 1/16
TCNTB3-----PWM定時器3計數(shù)緩存寄存器,選擇定時器3為時鐘,比如定義10ms中斷一次,提供觸摸屏采樣時間基準(zhǔn),即10ms觸摸屏采樣一次。在此為17×1000/100=170,在此PCLK=400MHz/6,可以得出timer3的時鐘頻率=PCLK/(244+1).16,可以算數(shù)觸摸屏就是10ms產(chǎn)生一次定時中斷,進行一次采樣
]]>為了支持不同類型的外圍設(shè)備,WinCE平臺提供了具有定制接口的流接口驅(qū)動程序模型。因為大部分USB外圍設(shè)備由于功能性更適合流接口驅(qū)動的結(jié)構(gòu),所以一般都采用加載式流接口驅(qū)動程序模型來開發(fā)USB設(shè)備驅(qū)動程序。
(1)USB系統(tǒng)結(jié)構(gòu)分析
WinCE下USB系統(tǒng)軟件由兩層組成:較高USB設(shè)備驅(qū)動程序?qū)雍洼^低的USB函數(shù)層。較低的USB函數(shù)層本身又由兩部分組成:較高的通用串行總線驅(qū)動程序(USBD)模塊和較低的主控制器驅(qū)動程序(HCD)模塊。通過HCD模塊功能和USBD模塊實現(xiàn)高層的USBD接口函數(shù),USB設(shè)備驅(qū)動程序就能與外圍設(shè)備進行通訊。
在數(shù)據(jù)傳輸?shù)倪^程中,操作流程通常按下列的次序進行:①USB設(shè)備驅(qū)動程序進行數(shù)據(jù)傳輸?shù)某跏蓟赐ㄟ^USBD接口函數(shù)給USBD模塊發(fā)送數(shù)據(jù)傳輸?shù)恼埱蟆"赨SBD模塊將該請求分成一些單獨的事務(wù)。③HCD模塊排出事務(wù)次序。④主控制器硬件執(zhí)行事務(wù)。這里需要提醒的是,所有的事務(wù)都是從主機發(fā)出的,外圍設(shè)備完全是被動接受型的。
(2)USB設(shè)備驅(qū)動程序入口點函數(shù)
從結(jié)構(gòu)分析我們可知,所有的USB設(shè)備驅(qū)動程序必須在它們的DLL庫設(shè)置一定的入口點與USBD模塊進行適當(dāng)?shù)慕换ァTO(shè)置入口點函數(shù)有兩個作用:一是使得 USBD 模塊能與外部設(shè)備交互;二是使得驅(qū)動程序能創(chuàng)建和管理任何可能需要的注冊鍵。
下面簡要介紹相關(guān)函數(shù)的作用:USBDeviceAttach是當(dāng) USB 設(shè)備連接到主計算機時運行,USBD模塊會調(diào)用這個函數(shù)初始化USB設(shè)備,取得USB設(shè)備信息和配置USB設(shè)備,并且申請必需的資源。 USBInstallDrive是在第一次加載USB設(shè)備驅(qū)動程序時首先被調(diào)用,它使得驅(qū)動程序能創(chuàng)建需要的注冊鍵,用于將一個驅(qū)動程序所需的注冊表信息寫入到HKEY_LOCAL_MACHINE\Drivers\USB\ClientDrivers目錄下,例如設(shè)備名稱等。需要注意的是,USB設(shè)備驅(qū)動程序不使用標(biāo)準(zhǔn)的注冊表函數(shù),而是使用RegisterClientDriverID()、RegisterClientSettings()函數(shù)來注冊相應(yīng)的設(shè)備信息。
USBUninstallDriver是在用戶刪除USB設(shè)備驅(qū)動程序時調(diào)用,負(fù)責(zé)刪除注冊鍵并釋放其它相關(guān)資源。它通過調(diào)用 UnRegisterClientSettings()和UnRegisterClientDriverID()函數(shù)來刪除由驅(qū)動程序的 USBInstallDriver()函數(shù)創(chuàng)建的所有注冊鍵。因此,我們在驅(qū)動程序中就需要嚴(yán)格按照這三個函數(shù)的原型來實現(xiàn),否則就不能為設(shè)備管理器所識別。
3.USB設(shè)備流接口驅(qū)動的實現(xiàn)步驟
從WinCE USB設(shè)備驅(qū)動模型及結(jié)構(gòu)分析中,我們可以清晰的看到主機和外設(shè)之間的實現(xiàn)方式。在主機端,通過USBD模塊和HCD模塊使用默認(rèn)的PIPE訪問一個通用的邏輯設(shè)備,實際上就是說USBD和HCD是一組訪問所有USB設(shè)備的邏輯接口,它們負(fù)責(zé)管理所有USB設(shè)備的連接、加載、移除、數(shù)據(jù)傳輸和通用配置。其中HCD是主機控制驅(qū)動,是為USBD提供底層的功能訪問服務(wù),USBD是USB總線驅(qū)動,位于HCD的上層,利用HCD的服務(wù)提供較高層次的功能。因此,實現(xiàn)USB加載流驅(qū)動程序大致需要完成以下步驟:
(1)選擇代表設(shè)備的文件名前綴。前綴非常重要,設(shè)備管理器在注冊表中通過前綴來識別設(shè)備。同時,在流接口命名時也將這個前綴作為入口點函數(shù)的前綴,如果設(shè)備前綴為XXX,那么流接口對應(yīng)為XXX_Close,XXX_Init等。
(2)設(shè)置驅(qū)動的各個入口點函數(shù)。所謂入口點是指提供給設(shè)備管理器的標(biāo)準(zhǔn)文件I/O接口。在生成一個DLL后,就用設(shè)備文件名前綴替換名字中的XXX。因此,每個加載式流接口驅(qū)動程序必須實現(xiàn)XXX_Init()、XXX_IOControl()以及XXX_PowerUp()等一組標(biāo)準(zhǔn)的函數(shù),用來完成標(biāo)準(zhǔn)的文件I/O函數(shù)和電源管理等。
(3)建立.DEF文件。當(dāng)設(shè)備管理器初始化USB設(shè)備編譯出來的流接口函數(shù)后,還必須建立一個.def文件。DEF文件定義了DLL要導(dǎo)出的接口集,而且加載式流驅(qū)動大多是以DLL形式存在的,所以應(yīng)將DLL和DEF的文件名統(tǒng)一起來。DEF文件告訴鏈接程序需要輸出什么樣的函數(shù),最后將驅(qū)動程序編譯到內(nèi)核中去,這樣這個USB設(shè)備流接口驅(qū)動程序就可以被應(yīng)用程序調(diào)用。
(4)在注冊表中為驅(qū)動程序建立表項。在注冊表中建立驅(qū)動程序入口點,這樣設(shè)備管理器才能識別和管理這個驅(qū)動。此外,注冊表中還能存儲額外的信息,這些信息可以在驅(qū)動運行之后被使用到。
在這次USB驅(qū)動開發(fā)過程中,錯走許多冤枉路使我叫苦連天。我感受最深的是由于WinCE提供了通用串行總線驅(qū)動程序(USBD)模塊、USBD接口函數(shù)全集、樣本主機控制器驅(qū)動程序(HCD)模塊。所以,我們只需要根據(jù)USB設(shè)備硬件特性,利用USBD提供的不同函數(shù),實現(xiàn)流接口函數(shù)與外圍設(shè)備的交互。在沒有特別的情況下,我最大的收獲經(jīng)驗是把這些公用的源程序照搬過來,能極大的縮短開發(fā)周期,從而能更快速地進行嵌入式開發(fā)。
隨著USB設(shè)備的普及,擺在開發(fā)人員面前的驅(qū)動開發(fā)任務(wù)也是越來越繁重了,特別是對于一些嵌入式開發(fā)廠商來講,由于設(shè)備所采用的操作系統(tǒng)不同,相應(yīng)的硬件接口也是不一樣的,開發(fā)相關(guān)的USB 驅(qū)動程序更是難上加難。Windows CE.NET 是微軟推出的功能強大的嵌入式操作系統(tǒng),國內(nèi)采用此操作系統(tǒng)的廠商已經(jīng)很多了,本文就以windows ce.net為例,簡單介紹一下如何開發(fā)windows ce.net下的USB驅(qū)動程序。
首先要熟悉一些USB的基本概念,當(dāng)然最好把USB 1.1的協(xié)議看一遍,(當(dāng)然現(xiàn)在2。0的協(xié)議都已經(jīng)有了)http://www.usb.org
上可以下載,我記得好像有個中文版的,翻譯的還可以,http://www.driverdevolep.com
上有的,具體位置記不太清楚了,中文版的協(xié)議可以快速翻一邊,了解一些基本的概念,但是設(shè)計到一些關(guān)鍵性的東西最好還是看英文版的心里比較清楚些。
這里我就不介紹USB的基本協(xié)議了,假設(shè)用戶已經(jīng)熟悉了USB設(shè)備的一些基本的概念,并且對Winows CE.NET的開發(fā)有一定的了解。
下面簡略介紹一下Windows CE.NET中USB設(shè)備驅(qū)動開發(fā)的一些基礎(chǔ)知識。
Windows CE.NET 的USB系統(tǒng)軟件分為兩層: USB Client設(shè)備驅(qū)動程序和底層的Windows CE實現(xiàn)的函數(shù)層。USB設(shè)備驅(qū)動程序主要負(fù)責(zé)利用系統(tǒng)提供的底層接口配置設(shè)備,和設(shè)備進行通訊。底層的函數(shù)提本身又由兩部分組成,通用串行總線驅(qū)動程序(USBD)模塊和較低的主控制器驅(qū)動程序(HCD)模塊。HCD負(fù)責(zé)最最底層的處理,USBD模塊實現(xiàn)較高的USBD函數(shù)接口。USB設(shè)備驅(qū)動主要利用 USBD接口函數(shù)和他們的外圍設(shè)備打交道。
USB設(shè)備驅(qū)動程序主要和USBD打交道,所以我們必須詳細的了解USBD提供的函數(shù)。
主要的傳輸函數(shù)有:
AbourtTransfer IssueControlTransfer
CloseTransfer IssueInterrupTransfer
GetIsochResult IssueIsochTransfer
GetTransferStatus IstransferComplete
IssueBulkTransfer IssueVendorTransfer
主要的用于打開和關(guān)閉USBD和USB設(shè)備之間的通信通道的函數(shù)有:
AbortPipeTransfers ClosePipe
IsDefaultPipeHalted IsPipeHalted
OpenPipe ResetDefaultPipe
ResetPipe
相應(yīng)的打包函數(shù)接口有:
GetFrameLength GetFrameNumber ReleaseFrameLengthControl
SetFrameLength TakeFrameLengthControl
取得設(shè)置設(shè)備配置函數(shù):
ClearFeature SetDescriptor
GetDescriptor SetFeature
GetInterface SetInterface
GetStatus SyncFrame
與USB進行交互的實現(xiàn)方法相關(guān)的多任務(wù)函數(shù):
FindInterface RegisterClientDeviceId
GetDeviceInfo RegisterClientSettings
GetUSBDVersion RegisterNotificationRoutine
LoadGenericInterfaceDriver TranslateStringDescr
OpenClientRegisterKey UnRegisterNotificationRoutine
常見的Windows CE.NET下USB的設(shè)備驅(qū)動程序的編寫有以下幾種方法:
● 流式接口函數(shù)
這種驅(qū)動程序主要呈現(xiàn)流式函數(shù)接口,主要輸出XXX_Init,XXX_Deinit,XXX_Open,XXX_Close,XXX_Open,XXX_Close,XXX_Read,XXX_Write,
XXX_Seek, XXX_IOControl,XXX_PowerUp,XXX_PowerDown等流式接口,注意上述的幾個接口一定都要輸出,另外XXX必須為三個字符,否則會出錯。但是此類的驅(qū)動程序不是通過設(shè)備管理接口來加載的,所以必須手工的調(diào)用RegisterDevice()和 DeregisterDevice()函數(shù)來加載和卸載驅(qū)動程序。用戶可以將此類的設(shè)備作為標(biāo)準(zhǔn)的文件來操作,只要調(diào)用相應(yīng)的文件操作就可以和驅(qū)動程序打交道。
● 使用現(xiàn)有的Window CE.NET的應(yīng)用程序接口
此類設(shè)備主要是利用Windows CE.NET中已經(jīng)有了現(xiàn)成的函數(shù)接口,例如USB Mass Storage Disk,它主要利用現(xiàn)有的Windows CE.Net中已經(jīng)有的可安裝文件系統(tǒng)接口,呈現(xiàn)給系統(tǒng)可用的文件系統(tǒng),對于用戶來講,它是透明的,用戶僅僅感覺在操作一個文件夾。
● 創(chuàng)建指定到特定的USBD的用戶指定的API
這種方法在USBD呈現(xiàn)設(shè)備時不需要任何限制,主要是特制的提供API給用戶,一般不太常見。
USB設(shè)備驅(qū)動程序必須輸出的函數(shù)有:
● USBDeviecAttach
當(dāng)USB設(shè)備連接到計算機上時,USBD模塊就會調(diào)用此函數(shù),這個函數(shù)主要用于初始化USB設(shè)備,取得USB設(shè)備信息,配置USB設(shè)備,并且申請必需的資源。
● USBInstallDriver
主要用于創(chuàng)建一個驅(qū)動程序加載所需的注冊表信息,例如讀寫超時,設(shè)備名稱等。
● USBUninstallDriver
主要用于釋放驅(qū)動程序所占用的資源,以及刪除USBInstallDriver函數(shù)創(chuàng)建的注冊表等。
上述的三個函數(shù)接口是所有的USB驅(qū)動程序必須提供的,缺一不可。
另外比較重要的是USB設(shè)備驅(qū)動程序的注冊表配置,一般的USB設(shè)備驅(qū)動程序的注冊表配置在HKEY_LOCAL_MACHINE\Drivers\USB \LoadClients下,每個驅(qū)動程序的子鍵都有Group1_ID\Group2_ID\Group3_ID\DriverName格式,如果注冊表信息與USB設(shè)備信息符合,USBD就會加載此驅(qū)動程序。否則設(shè)備的子鍵應(yīng)該由供應(yīng)商,設(shè)備類和協(xié)議信息通過下劃線組成。
具體的配置舉個例子:
例如你有個PDA設(shè)備,它具有一個USB接口,它的供應(yīng)廠商ID假設(shè)為0x0888,設(shè)備ID為0x0999,沒有使用特殊的協(xié)議,那么它的加載注冊表應(yīng)該寫為:
[HKEY_LOCAL_MACHINE\Drivers\USB\LoadClients\2184_2457\Default\Default\PDA] "DLL"="pdausb.dll"
需要注意的是注冊表構(gòu)成都是十進制數(shù)值來標(biāo)識的,注意一下十進制和十六進制的轉(zhuǎn)換。
再舉個USB鼠標(biāo)的例子,USB鼠標(biāo)是標(biāo)準(zhǔn)的HID設(shè)備,它的協(xié)議為:InterfaceClassCode為3(HID類), InterfaceSubclassCode為1(引導(dǎo)接口類),InterfaceProtocolCode為2(鼠標(biāo)協(xié)議類),所以它的注冊如下:
[HKEY_LOCAL_MACHINE\Drivers\USB\LoadClients\Default\Default\3_1_2\USBMouse] "DLL"="usbmouse.dll"
到此為止,我們可以看出,其實驅(qū)動開發(fā)無非做兩件事情,一件是和硬件打交道,另外一件是和操作系統(tǒng)打交道。舉個簡單的例子,例如:我們需要開發(fā)一個USB鼠標(biāo)驅(qū)動程序,我們就需要了解USB鼠標(biāo)硬件上是怎么發(fā)送數(shù)據(jù)的?操作系統(tǒng)怎么才能得到鼠標(biāo)的控制事件?其實USB鼠標(biāo)是有一個中斷PIPE的,用于傳送鼠標(biāo)產(chǎn)生的數(shù)據(jù),Windwos CE.NET中有個接口函數(shù)叫做mouse_event(),專門用于產(chǎn)生鼠標(biāo)事件,但是它是不關(guān)心具體什么硬件的,甚至我們自己在應(yīng)用程序中調(diào)用這個函數(shù)都可以實現(xiàn)模擬鼠標(biāo),對應(yīng)的有個keybd_event(),用于產(chǎn)生鍵盤事件,知道了這個就好辦多了,只要將相應(yīng)的數(shù)據(jù)轉(zhuǎn)換一下,調(diào)用一下 mouse_event()即可
例如我們有個USB Mouse設(shè)備,設(shè)備信息描述如下:
Device Descriptor:
bcdUSB: 0x0100
bDeviceClass: 0x00
bDeviceSubClass: 0x00
bDeviceProtocol: 0x00
bMaxPacketSize0: 0x08 (8)
idVendor: 0x05E3 (Genesys Logic Inc.)
idProduct: 0x0001
bcdDevice: 0x0101
iManufacturer: 0x00
iProduct: 0x01
iSerialNumber: 0x00
bNumConfigurations: 0x01
ConnectionStatus: DeviceConnected
Current Config Value: 0x01
Device Bus Speed: Low
Device Address: 0x02
Open Pipes: 1
Endpoint Descriptor:
bEndpointAddress: 0x81
Transfer Type: Interrupt
wMaxPacketSize: 0x0003 (3)
bInterval: 0x0A
可以看出上述設(shè)備有一個中斷PIPE,包的最大值為3。可能有人問上述的值怎么得到的,win2k 的DDK中有個usbview的例程,編譯一下,將你的USB設(shè)備插到PC機的USB口中,運行usbview.exe即可看得相應(yīng)的設(shè)備信息。
有了這些基本信息,就可以編寫USB設(shè)備了,首先聲明一下,下面的代碼取自微軟的USB鼠標(biāo)樣本程序,版權(quán)歸微軟所有,此處僅僅借用來描述一下USB鼠標(biāo)驅(qū)動的開發(fā)過程,讀者如需要引用此代碼,需要得到微軟的同意。
首先,必須輸出USBD要求調(diào)用的三個函數(shù),首先到設(shè)備插入到USB端口時,USBD會調(diào)用USBDeviceAttach()函數(shù),相應(yīng)的代碼如下:
extern "C" BOOL
USBDeviceAttach(
USB_HANDLE hDevice, // USB設(shè)備句柄
LPCUSB_FUNCS lpUsbFuncs, // USBDI的函數(shù)集合
LPCUSB_INTERFACE lpInterface, // 設(shè)備接口描述信息
LPCWSTR szUniqueDriverId, // 設(shè)備ID描述字符串。
LPBOOL fAcceptControl, // 返回TRUE,標(biāo)識我們可以控制此設(shè)備, 反之表示不能控制
DWORD dwUnused)
{
*fAcceptControl = FALSE;
// 我們的鼠標(biāo)設(shè)備有特定的描述信息,要檢測是否是我們的設(shè)備。
if (lpInterface == NULL)
return FALSE;
// 打印相關(guān)的USB設(shè)備接口描述信息。
DEBUGMSG(ZONE_INIT,(TEXT("USBMouse: DeviceAttach, IF %u, #EP:%u, Class:%u, Sub:%u,Prot:%u\r\n"), lpInterface->Descriptor.bInterfaceNumber,lpInterface->Descriptor.bNumEndpoints, lpInterface->Descriptor.bInterfaceClass,lpInterface->Descriptor.bInterfaceSubClass,lpInterface->Descriptor.bInterfaceProtocol));
// 初試數(shù)據(jù)USB鼠標(biāo)類,產(chǎn)生一個接受USB鼠標(biāo)數(shù)據(jù)的線程
CMouse * pMouse = new CMouse(hDevice, lpUsbFuncs, lpInterface);
if (pMouse == NULL)
return FALSE;
if (!pMouse->Initialize())
{
delete pMouse;
return FALSE;
}
// 注冊一個監(jiān)控USB設(shè)備事件的回調(diào)函數(shù),用于監(jiān)控USB設(shè)備是否已經(jīng)拔掉。
(*lpUsbFuncs->lpRegisterNotificationRoutine)(hDevice,
USBDeviceNotifications, pMouse);
*fAcceptControl = TRUE;
return TRUE;
}
第二個函數(shù)是 USBInstallDriver()函數(shù),
一些基本定義如下:
const WCHAR gcszRegisterClientDriverId[] = L"RegisterClientDriverID";
const WCHAR gcszRegisterClientSettings[] = L"RegisterClientSettings";
const WCHAR gcszUnRegisterClientDriverId[] = L"UnRegisterClientDriverID";
const WCHAR gcszUnRegisterClientSettings[] = L"UnRegisterClientSettings";
const WCHAR gcszMouseDriverId[] = L"Generic_Sample_Mouse_Driver";
函數(shù)接口如下:
extern "C" BOOL
USBInstallDriver(
LPCWSTR szDriverLibFile) // @parm [IN] - Contains client driver DLL name
{
BOOL fRet = FALSE;
HINSTANCE hInst = LoadLibrary(L"USBD.DLL");
// 注冊USB設(shè)備信息
if(hInst)
{
LPREGISTER_CLIENT_DRIVER_ID pRegisterId = (LPREGISTER_CLIENT_DRIVER_ID)
GetProcAddress(hInst, gcszRegisterClientDriverId);
LPREGISTER_CLIENT_SETTINGS pRegisterSettings =
(LPREGISTER_CLIENT_SETTINGS) GetProcAddress(hInst,
gcszRegisterClientSettings);
if(pRegisterId && pRegisterSettings)
{
USB_DRIVER_SETTINGS DriverSettings;
DriverSettings.dwCount = sizeof(DriverSettings);
// 設(shè)置我們的特定的信息。
DriverSettings.dwVendorId = USB_NO_INFO;
DriverSettings.dwProductId = USB_NO_INFO;
DriverSettings.dwReleaseNumber = USB_NO_INFO;
DriverSettings.dwDeviceClass = USB_NO_INFO;
DriverSettings.dwDeviceSubClass = USB_NO_INFO;
DriverSettings.dwDeviceProtocol = USB_NO_INFO;
DriverSettings.dwInterfaceClass = 0x03; // HID
DriverSettings.dwInterfaceSubClass = 0x01; // boot device
DriverSettings.dwInterfaceProtocol = 0x02; // mouse
fRet = (*pRegisterId)(gcszMouseDriverId);
if(fRet)
{
在WinCE上,從鍵盤驅(qū)動的角度看,鍵盤驅(qū)動對按鍵動作的響應(yīng)過程大約可描述為:
按鍵產(chǎn)生中斷
鍵盤驅(qū)動讀取按鍵的scan code
鍵盤驅(qū)動把scan code映射成virtual key和unicode字符
鍵盤驅(qū)動把按鍵消息發(fā)送到圖形窗口子系統(tǒng)(GWES)。
鍵的scan code由keyboard matrix決定,跟鍵盤的硬件設(shè)計有關(guān)。因此從軟件角度看,鍵盤的scan code是不能改的。但是由于按鍵最終輸出的是可打印字符或者virtual key,這里面就有個映射關(guān)系,這個映射關(guān)系可以在鍵盤驅(qū)動理指定,甚至可以動態(tài)切換。WinCE的標(biāo)準(zhǔn)鍵盤驅(qū)動框架定義了兩張映射表:即Scan code到virtual key的映射表(Device layout),和virtual key到unicode的映射表(input language)。通過修改這兩張映射表的定義,我們就可以控制鍵盤上的每一個按鍵或者按鍵組合的輸出。
D:\WINCE500\PUBLIC\COMMON\OAK\DRIVERS\KEYBD目錄下有一些針對標(biāo)準(zhǔn)鍵盤的源代碼:DEVICELAYOUTS子目錄下是Scan code到virtual key映射表,INPUTLANGS子目錄下是virtual key到unicode映射表。具體做時主要是改這兩張表,加上其他一些輔助代碼編譯成DLL。除此之外,WinCE還提供一個工具 (D:\WINCE500\PUBLIC\COMMON\OAK\BIN\I386\kbdgen.exe),可以從Windows XP系統(tǒng)鍵盤驅(qū)動中提取映射表。比如下面命令生成法語鍵盤映射表的源代碼:
kbdgen.exe kbdfr.dll -o kbd_040c -i 0000040C
結(jié)果輸出三個文件:
kbd_040c.reg:注冊表文件
kbd_040cDL.cpp:scan code -> virtual key映射表
kbd_040cIL.cpp:virtual key -> wide character映射表
鍵盤驅(qū)動名在注冊表里[HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Keyboard Layouts]可以查到,比如法語的locale是040C,在0000040c子鍵下可以找到驅(qū)動為kbdfr.dll。
scan code到virtual key(即device layout)在ScanCodeToVKeyTable數(shù)組里定義,一般不用改:
#define ScanCodeTableFirst 0x00
#define ScanCodeTableLast 0x8f
static UINT8 ScanCodeToVKeyTable[] =
{
0, // Scan Code 0x0
VK_F9, // Scan Code 0x1
0, // Scan Code 0x2
VK_F5, // Scan Code 0x3
VK_F3, // Scan Code 0x4
VK_F1, // Scan Code 0x5
VK_F2, // Scan Code 0x6
VK_F12, // Scan Code 0x7
0, // Scan Code 0x8
VK_F10, // Scan Code 0x9
VK_F8, // Scan Code 0xA
VK_F6, // Scan Code 0xB
};
有時候你可能想知道鍵盤上每個鍵對應(yīng)的scan code,你可以在鍵盤驅(qū)動KeybdPdd_GetEventEx2函數(shù)中用RETAILMSG把scan code打印出來。
定制的重點是修改virtual key到unicode映射表,即 aVkToWch1~aVkToWch5等幾個數(shù)組,歐洲語言鍵盤還要改aDeadKey數(shù)組,這幾個數(shù)組控制各種組合按鍵輸出,比如用戶按下A, Shift+A, Ctrl+Shift+A, Dead key+A,分別輸出什么東西 。
舉例來說,標(biāo)準(zhǔn)美語鍵盤SHIFT+2輸出@,你想改成歐元符號。先查出的unicode值為20AC(利用MS Office的symbol對話框),然后修改aVkToWch2數(shù)組:
static VK_TO_WCHARS2 aVkToWch2[] = {
{'2' ,0 ,'2' ,0x20ac },
};
如果你同時還想讓CTRL+ALT+2輸出§(unicode 00A7),那么要改aVkToWch5而不是aVkToWch2:
static VK_TO_WCHARS5 aVkToWch5[] = {
{'2' ,0 ,'2' ,0x20ac ,WCH_NONE ,0x0000 ,0xb9 },
};
映射表的修改過程大致如此。有了DLL還要在注冊表中做些配置。在platform.reg中添加:
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Layouts\0000040C]
"Layout File"="kbd_040c.dll"
"Layout Text"="French"
"PS2_AT"="kbd_040c.dll"
如果你同時支持英語和法語鍵盤,可以把法語設(shè)為第二鍵盤:
[HKEY_CURRENT_USER\Keyboard Layout\Preload\2]
@="0000040C"
甚至還可以設(shè)置熱鍵在運行時切換鍵盤:
;Enabling ALT+SHIFT keyboard layout toggle short cut key
; "Hotkey"="1" => ALT+SHIFT
; "Hotkey"="2" => CTRL+SHIFT
; "Hotkey"="3" => None
; The toggle key is disabled even if the key is not defined.
[HKEY_CURRENT_USER\keyboard layout\toggle]
"Hotkey"="1"
接著調(diào)用父類CPdd16550的Init函數(shù),創(chuàng)建中斷服務(wù)線程(IST)事件,并通過InterruptInitialize函數(shù)將事件與邏輯中斷號關(guān)聯(lián)起來,最后調(diào)用CreateHardwareAccess和MapHardware函數(shù)將串口基地址及相關(guān)寄存器片內(nèi)地址映射到內(nèi)核進程的虛擬地址。
在MapHardware中,用GetWindowInfo根據(jù)串口的Active注冊表鍵獲得串口的全部I/O端口和內(nèi)存地址信息,然后用 MmMapIoSpace函數(shù)將串口物理地址和相關(guān)控制寄存器地址轉(zhuǎn)換成內(nèi)核進程的虛擬地址,以便后面對寄存器進行操作,部分代碼如下:
CreateHardwareAccess函數(shù)根據(jù)MapHardware得到的m_pBaseAddress,構(gòu)造一個CRegLPC32xx類實例,然后調(diào)用CRegLPC32xx類的Init函數(shù)確保串口控制器硬件進入穩(wěn)定的工作狀態(tài)。
根據(jù)LPC3250的數(shù)據(jù)手冊,設(shè)置標(biāo)準(zhǔn)UART的波特率需要設(shè)置小數(shù)波特率預(yù)分頻器和UART波特率發(fā)生器。當(dāng)不用小數(shù)波特率預(yù)分頻器(即X=Y=1) 時,將標(biāo)準(zhǔn)UART的{Baudrate,DLM:DLL}的值定義一個數(shù)組BaudPairs[]。GetDivisorOfRate根據(jù)這個數(shù)組得到分頻系數(shù),然后調(diào)用父類的成員函數(shù)SetBaudRate便可設(shè)置波特率。高速UART的波特率類似,只是波特率計算公式和分頻系數(shù)與標(biāo)準(zhǔn)UART不同。
用GetWaterMark得到接收器FIFO的觸發(fā)深度,分別為16、32、48和60位,然后在CPdd16550的InitReceive中設(shè)置FIFO控制寄存器,默認(rèn)的FIFO觸發(fā)深度是32位。
Clpc32xxPdd16550UART是個抽象類,實現(xiàn)通用功能,具體的要分別由繼承的標(biāo)準(zhǔn)串口Clpc32xxPdd16550Stan- dardUART類和高速串口Clpc32xxPdd16550HighUART類實現(xiàn)。在各自初始化時,主要是配置各種寄存器,實現(xiàn)具體硬件差異化,包括:配置UART時鐘控制寄存器、時鐘模式寄存器和時鐘選擇寄存器,分別使能UART時鐘、設(shè)置自動時鐘模式、選擇相應(yīng)的時鐘源作為分頻器的輸入時鐘;禁止UART3 Modem和UART6 IrDA功能;禁止UART的回送功能。
特別要強調(diào)的是關(guān)于中斷的處理,串口驅(qū)動中斷可以用動態(tài)映射,也可以用靜態(tài)映射。在OEMInter-ruptHandler、 Clpc32xxPdd16550UART::Init、CPdd16550::Init、CPdd16550::ThreadRun等處加入調(diào)試打印信息,可以較快地找到問題所在,確定硬件中斷是否映射為系統(tǒng)中斷、系統(tǒng)中斷與中斷事件是否綁定、中斷產(chǎn)生時是否進入相應(yīng)的處理程序。中斷處理好了,串口驅(qū)動就基本完成了。
上述工作結(jié)束后,就要添加串口的注冊表。以串口3為例,主要是設(shè)置動態(tài)鏈接庫DLL、設(shè)備基地址、中斷號、前綴名、被加載的順序等。根據(jù)注冊表的 DeviceArrayIn-dex、CreateSerialObject就可以構(gòu)造標(biāo)準(zhǔn)串口或高速串口類實例了, DeleteSerialObject在退出驅(qū)動時刪除實例。具體代碼如下:
在廣州致遠電子有限公司的SmartARM3250開發(fā)板上,通過WinCE的串口應(yīng)用程序與上位PC機進行發(fā)送接收實驗,本驅(qū)動已經(jīng)實現(xiàn)標(biāo)準(zhǔn)串口最高460 800 b/s、高速串口最高921 600 b/s的穩(wěn)定傳輸。
結(jié) 語
本文介紹了WinCE6.0下的串口驅(qū)動模型,結(jié)合LPC3250的硬件情況,詳細說明了串口驅(qū)動開發(fā)過程,包括配置串口相關(guān)的寄存器和處理中斷中重要函數(shù)的實現(xiàn),以及注冊表和Source文件編寫等。本驅(qū)動程序在廣州致遠電子有限公司的SmartARM3250開發(fā)板上實驗成功。在串口驅(qū)動開發(fā)中所用的思路,對其他類似的驅(qū)動設(shè)計有較高的參考價值。