天天看點

基于Linux的USB 主/從裝置之間通訊的三種方式 - 路之遙_其漫漫

基于Linux的USB 主/從裝置之間通訊的三種方式

轉載: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: [email protected]

參考文獻:

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.