轉載:http://archive.eet-china.com/www.eet-china.com/ART_8800323770_617693_TA_eda530e7.HTM
隨着簡單易用的USB接口日益流行,在嵌入式系統中添加對USB接口的支持已成為大勢所趨。本文通過介紹Linux中支持USB的各種模塊和庫,分析了在Linux上利用USB實現高速串口和以太網連接等通信方式的具體方法。
通用串行總線(USB,Universal Serial Bus)是一種非常實用的通信接口,其應用日益廣泛。有三種方法可以使運行Linux操作系統的嵌入式系統支持USB接口,本文將對這三種方法逐一進行介紹。
基於Linux的USB設備與USB主機一般有以下三種通信方式:1.一些功能最完備結構也最復雜的設備采用用戶定制內核模塊來實現在標准USB總線上運行復雜的高級協議,而由USB主機上相應的用戶驅動程序和應用來完成連接。2.另一些基於Linux的USB設備則利用USB總線來實現與主機上所運行的某個應用的簡單的點對點串行連接。主機上的應用雖然利用了主操作系統所提供的USB編程接口,但表面看來卻似乎是在通過一個典型的串口進行通信。3.最后,還有些設備以主計算機作為網關,將USB設備連接到辦公局域網或互聯網上,從而使USB設備看起仿佛構成了一個以太網。這種方法專業性較強,但通常可行,是主機驅動程序使該方法成為可能。
在這三種方法中,您可以根據預留給開發的時間長短和期望USB接口在嵌入式應用中所扮演的角色來決定選用那一種方法比較恰當。為了幫助您做出正確的選擇,下一節將向您介紹這三種方法分別應用於基於Linux的USB設備時的情況,但首先讓我們對USB接口做一個大致介紹。
USB概述
USB是一種方便快捷的接口,可用於為計算機工作站連接一些小配件。根據USB規范的定義,鼠標、鍵盤、音頻播放和錄音設備、照相機、大容量存儲設備以及許多其他設備均可以通過USB接口,以高達480Mbps的速度連接到一台主計算機。協議定制者對USB上運行的這種復雜的主從式協議做出了仔細的說明,這就幫助保證了所有這些設備之間具備互操作性和兼容性。例如,該協議規定,USB設備只有在被詢問時才可以回答,並且USB主機會根據所連接的USB設備類型的不同,采用某些特定的格式,在某些特定的時間段從不同的設備獲取數據。
USB設備和主機之間通常通過專用的總線控制芯片建立連接。在USB主機上,名為UHCI或OHCI等的控制芯片通過插卡形式加入主機或直接集成到工作站的主板上。在主機一端的總線控制驅動程序管理着主機控制芯片,它同時還跟蹤監視着主機目前連接的是哪些USB設備,從而決定應如何與它們通信。
可用於連接照相機和鼠標之類USB設備的總線控制器有很多種。其中的一種就在一塊芯片上同時集成了USB接口以及另一端的串口、I2C接口或並口。USB控制器(包括主機上的和USB設備上的控制器)也可能集成到英特爾StrongARM或 Hitachi H8之類的微控制器中去。這些芯片及其外圍部件有點類似以太網和CAN控制器,不同的是他們用於連接USB設備,並運行USB協議。
很多人都知道Linux操作系統中包含了USB主機控制器的驅動程序,因而USB鍵盤、數碼相機以及其他一些USB設備都可以在一個運行Linux操作系統的桌面工作站上使用。但很少有人知道Linux中還包含了一組USB設備控制器的驅動程序,尤其是集成到StrongARM SA1110處理器中的控制器。有了這些控制器驅動程序,基於Linux的嵌入式系統就能利用USB接口來與主計算機(運行Linux或其他操作系統)通信。
大多數USB通信的實現過程都是雙端的。主機利用一個內核模塊或驅動程序來與USB設備通信,而USB設備則通過其自身的驅動程序來與主機通信。根據主機和USB設備所采用的通信風格的不同,驅動程序可以很簡單明白,也可以很復雜,很具挑戰性。本文主要關注USB設備端的通信過程,但也在適當的地方包含了關於主機端通信過程實現的信息。
以下討論的技術應當引起讀者的注意。本文的目的是介紹如何在數碼相機和PDA等基於Linux的USB設備上使用Linux。此處所指的USB設備是嚴格意義上的USB設備,即帶正方形連接器的完整的設備,而不是哪些連接器形狀為扁平矩形的設備。此外,USB連接的另一端(通常是一台PC工作站),應該是一台USB主機。
關於USB信息包的格式和通信參數的詳細信息,見本文的參考文獻。
通過編寫內核模塊添加USB接口
1. USB設備端通信過程
向一個基於Linux的設備中添加USB接口的第一種方法是編寫一個用戶定制的Linux內核模塊,這也是可實現最完備功能的一種做法。采用這種方法時通常需要針對主機的操作系統(Windows, Linux等)開發相應的驅動程序。
一旦在設備中實現了用戶定制的內核模塊,就可以使該設備完成相當復雜的功能,例如仿真一個文件系統,從而允許嵌入式應用將其USB主機當作一個遠程存儲設備。除此以外,采用這種方法之后,設備還可以具備存儲轉發(store-and-forward)的功能,因而能夠在與USB主機的連接建立之前對來自嵌入式應用的數據流進行緩沖。
在基於StrongARM的Linux設備中,內核代碼用於管理芯片所攜帶的USB設備控制器外設,通過調用函數sa1100_usb_open()來初始化。在初始化之后,內核模塊還會調用函數sa1100_usb_get_descriptor_ptr() 和sa1100_usb_set_string_descriptor()來設置在設備查詢期間傳送給USB主機的描述符,其中包含設備的數字廠商號和產品標識符,以及可以讓主機用來識別設備的字符串,甚至還有一個序列號域,以便主機可以唯一地識別一個連接在USB接口上的設備,或者在同種型號的多個設備中進行區分。
設備查詢過程是由USB設備控制器驅動的,並且一旦和USB主機連上之后會自動執行,所以內核模塊必須在USB通信開始之前設置好每個設備的描述符。當准備工作就緒之后,USB設備模塊就會調用函數sa1100_usb_start()來通知內核接收主機發來的USB連接請求。如果設備模塊在連上USB 主機之前調用了函數sa1100_set_configured_callback(),那么接着內核模塊就會在查詢過程結束時調用回調函數。回調函數很適合用來在設備上發出警告或給出一些形象的暗示,說明連接已經建立。
如果不再需要進行USB通信,那么設備的內核模塊就會先調用函數sa1100_usb_stop(),然后調用sa1100_usb_close(),來關閉SA1100上的USB控制器。
StrongARM的 USB控制器支持bulk-in和bulk-out兩種數據傳送方式。當接收來自USB主機的數據包時,內核模塊會調用sa1100_usb_recv(),將一個數據緩沖區的地址和一個回調函數送給它。然后內核中的USB設備控制代碼會從主機取回一個bulk-out數據包,將其內容存入制定的緩沖區,接着調用回調函數。
下一步,回調函數從接收緩沖區中提取出數據,將其存放到其他地方,或者將緩沖區空間添加到一個隊列中,然后分配一個新的緩沖區來接收下一個數據包。然后,如果還有數據需要接收,那么回調函數會重新調用sa1100_usb_recv(),准備接收另一個數據包。
向USB 主機發送數據的過程與此類似。內核模塊收集了一幀數據之后,將數據的存放地址、數據長度和回調函數的地址送給sa1100_usb_send()函數。接着,在數據傳送結束之后,內核模塊會調用回調函數。
在www.embedded.com/code.htm(arch/arm/mach-sa1100/usb-char.c)可以找到一個叫做usb-char的模塊,這是一個很好的設備端SA1110 Linux USB模塊的例子。該模塊將USB設備與USB 主機之間的連接變成一種高速串行鏈接。此外, usb-eth( arch/arm/mach-sa1100/usb-eth.c)模塊也是個不錯的例子,該模塊將USB變成了一種虛擬的以太型網絡。后面會深入探討這兩種模塊。
2. USB主機端通信過程
有些很好的主機端USB驅動程序的例子是隨主流Linux操作系統的發布而提供的,位於The Linux Kernel Archives (kernel.org)發布的原始內核源代碼中。其中,Handspring Visor 模塊(drivers/usb/serial/visor.c)是一個編寫得更清晰,也更易理解的模塊,它同時也是USB 主機端模塊(drivers/usb/usb-skeleton.c)的模板。
利用USB實現高速串行通信
1. USB設備端通信過程
為了達到最實用的效果,我們可以將USB總線簡單地看作一個高速串口,然后,在一些嵌入式設備和應用中,我們就可以用USB接口來模擬串口。StrongARM處理器的Linux內核就提供了一個名為usb-char的USB設備驅動程序,它所完成的恰好就是用USB模擬串口的功能。
當需要與USB 主機通信時,Linux操作系統中的USB設備應用只是簡單地打開一個與其usb-char設備節點的連接(連接類型為字符型,major number 為10, minor 為240),然后就開始讀寫數據。在與USB 主機的連接建立之前,read()和write()操作均返回一個錯誤信息。一旦連接建立好,並且設備查詢完成之后,USB接口就開始象一個點對點的串口一樣與主機進行通信。
這種進行USB數據傳送的方法非常簡單有效,因而usb-char設備模塊發布之后一直很受歡迎。而且,該模塊還為通過其他方法進行USB通信提供了一個參考。
在usb-char中,真正的操作開始於usbc_open()函數,列表1給出了函數的一部分代碼。筆者由於臨時的興趣,對該代碼做了一點修改,取消了錯誤和超時句柄。在此向代碼的原作者Brad Parker、Nicolas Pitre 和Ward Willats致歉。
twiddle_descriptors()函數用於設置設備的USB描述符。在描述符設置好之后,我們就可以開始進行設備查詢,並從USB 主機接收一幀數據。kick_start_rx()函數段的代碼主要用於調用sa1100_usb_recv(),建立回調。
在USB主機發送一個數據包時,設備的內核模塊會通過回調方式調用rx_done_callback_packet_buffer()函數,將數據包的內容送入一個FIFO隊列,以便能通過read()函數將該數據包返回給usb-char設備節點。
2. USB主機端通信過程
對於運行Linux操作系統的USB 主機,與usb-char相應的USB 主機模塊叫做usbserial。大多數Linux版本中都包含了該模塊,但它並不總能自動加載。通常應在主機與USB設備之間的連接建立之前利用modprobe 或insmod加載該模塊。
USB設備查詢完成之后,主機上的一項應用就會利用某個usbserial設備節點(字符型, major 為188, minor 大於等於0)與其通信。這些節點通常叫做/dev/ttyUSBn。Usbserial模塊會報告它將哪一個節點分配給了哪一台USB設備,並將這一信息按如下方式記載在內核消息記錄中:
===================================
usbserial.c:檢測到一般轉換器
usbserial.c:將一般轉換器加入ttyUSB0
==================================
這種連接一旦建立,USB 主機上的應用就可以通過向特定的節點讀或寫的方式與某USB設備通信。
此時,筆者並未考慮在運行Win32或其他類型操作系統的主機上已有類似usbserial的模塊。但用於這些主機上的任何USB驅動程序,只要能夠進行bulk-in 和 bulk-out數據傳輸,就很可能是一個近乎完整的驅動程序,只需進行一定的產品調整,並添加與產品綁定的廠商ID。
Linux主機上還有另一種類似usbserial模塊的庫,叫做libusb (參見libusb.sourceforge.net)。該庫通過低級的內核系統調用而不是通過usbserial模塊來完成USB數據傳輸,因而在Linux kernel版本上更容易設置和使用。同時,該庫還能提供大量實用的調試功能,十分利於對USB鏈接上運行的復雜的通信協議進行調試。
為了通過libusb與一個采用了usb-char模塊的USB設備進行通信,Linux主機應用首先通過庫中的usb_open()函數與設備建立連接,然后利用函數usb_bulk_read()和usb_bulk_write()與設備交換數據。Libusb中含有幾個程序范例。
利用USB實現以太網連接
1. USB 設備端通信過程
如果利用USB連接來實現高速串口並非您所希望,那么您還可以將所有USB連接用作一個以太網。不論在主機端還是在設備端,Linux均有模塊能實現這一功能。iPAQ(掌上電腦)的Linux內核就獨一無二地采用了這種通信策略,因為iPAQ硬件中既沒有可訪問的串口也沒有專門的網絡接口。
StrongARM Linux內核中,有一個叫做usb-eth的模塊(arch/arm/mach-sa1100/usb-eth.c),它利用USB作為物理媒介,模擬出一個虛構的以太網設備。一旦這種網絡接口創建起來之后,就可以為它分配IP地址,並且外部環境均將其作為一個普通的以太網硬件對待。一旦USB 主機連接建立起來,usb-eth模塊就允許USB設備“瀏覽”因特網,拼其他的IP地址,甚至通過DHCP、HTTP、NFS或者遠程網“交談”,以及收發電子郵件。簡而言之,任何能夠在真正的以太網接口上運行的應用都可以原封不動地在usb-eth 上運行,因為這些應用無法識別它們所使用的其實並非真正的以太網硬件。
2. USB 主機端通信過程
相應的,在運行Linux操作系統的主機一端,可用來在USB上實現以太網連接的內核模塊叫做usbnet。安裝了該模塊之后,一旦主機與USB設備的連接建立起來,它就會創建一個虛擬的以太網接口,在主機一端的內核模塊以及用戶應用看來,這個虛擬的接口與真正的以太網接口別無二致。主機端的應用可以通過拼一個USB設備的IP地址來檢查該設備是否已經連上,如果拼操作成功,那么就表示設備已經連接成功。
最近出現了一種針對Win32主機的usbnet風格的驅動,叫做Bahia網絡驅動,關於該驅動的詳細信息請訪問www.bahia21.com/download.htm。
USB通信的調試
遺憾的是,在USB 主機與Linux USB設備之間進行通信時,能夠幫助我們跟蹤通信過程中出現的問題的工具實在不多。除了libusb所提供的調試功能以外(該功能十分強大,但對於內核的系統調用接口則無能為力),在一次失敗的設備查詢或數據傳輸的嘗試過程中發生了什么問題?只有內核源代碼和記錄能夠提供一些線索。筆者嘗試在開發過程中向USB 主機和設備代碼中大量添加printk()函數調用,但這種方法會引入額外開銷,從而改變USB代碼自身的性能,這在有些情況下反而是事與願違。
對那些希望對 USB設備接口進行逆向工程處理,或者希望查找其產品缺陷的Linux開發者而言,一個叫做USB Snoopy (home.jps.net/~koma)的程序是個不錯的選擇。只是USB Snoopy僅能在Win32主機上運行。關於USB Snoopy的詳細信息或關於常規的USB調試,請參看本文末給出的參考文獻中Jan Axelson撰寫的 “USB Debug Tips”。
Linux已成為通用型操作系統
如今Linux已不再是USB 主機專用的操作系統了,USB設備也可以方便地選擇它。而且Linux下的USB通信太靈活易用了,因而筆者采用其他易用型串口(RS-232)的日子很可能就此結束,對我而言,這是件好事。
作者:Bill Gatliff
一位嵌入式領域的顧問,同時也是一位免費軟件熱愛者,熱衷於撰寫關於免費軟件的文章並在其項目中使用免費軟件
Email: bgat@billgatliff.com。
參考文獻:
1. Ganssle, Jack. "An Introduction to USB Development," Embedded Systems Programming, March 2000, p.79.
2. Axelson, Jan. "HIDs Up," Embedded Systems Programming, October 2000, p.61.