Saturday, October 11, 2014

USB Video Class 簡介

UVC大概是我看過比較複雜的協定。基本上它複雜不是協定本身,而是裡面一堆[用語]非常的不人性化,導致看的人超痛苦。

基本上UVC跟CDC-ACM類似,它一定要支援兩個Interface,一個用來控制用,一個用來傳資料。控制用的Interface叫做Video Control Interface,另一個叫Video Streaming Interface。這兩個Interface得用IAD把他們接合在一起。

Video Control Interface,顧名思義,就是拿來控制用的,它裡面可以包含幾種元件(我亂翻的):
  • Input Terminal:基本上是描述這個裝置的輸入端,以及它支援的功能。
  • Output Terminal:基本上是描述這個裝置的輸出端,以及它支援的功能。
  • Camera Terminal:基本上是描述這個裝置的相機功能,像是Focus或Zoom等等的功能。
  • Select Unit:簡而言之就是MUX。
  • Processing Unit:基本上是描述這個裝置的影像處理功能,像是Hue或Brightness等等的功能。
  • Extension Unit:描述這個裝置支援的延伸性功能,可有可無。

請注意,每個元件都需要一個獨一無二的ID,並請他們要構成一個Topology(Topology真的不知道怎麼翻)。下圖是某個範例:


這個範例說明某個裝置的Topology,它有兩個輸入,可以用Select Unit選擇其中一個輸入,輸入的資料可以透過Processing Unit做一些處理,然後輸出到Output Terminal。

至於如何把你的裝置的Topology告訴HOST?當然是透過Descriptor啦。每個元件都有對應的Descriptor,基本上只要照規格的範例抄就可以了。

Video Streaming Interface主要用來控制資料的傳輸以及資料的格式。

先來講資料傳輸。UVC支援兩種資料傳輸模式,第一個是Isochronous模式,第二個是bulk模式。這邊不講bulk模式,因為市面上99.9999%的UVC裝置都是用Isochronous模式。

Isochronous模式有個特點,就是必須使用Alternate Setting,如果你忘了Alternate Setting的話,記得再去瞄一下USB2.0規格。基本上只要宣告Alternate Setting 0跟1就可以了。其中Alternate Setting 0是不包含任何Endpoint宣告的,而Alternate Setting 1則會包含一個Isochronous Endpoint宣告。當HOST想要接收影像資料時,會用Set Interface去選擇Alternate Setting 1,而這時DEVICE就得開始輸出資料。相同地,Alternate Setting 0就是不要傳資料。

資料的格式部分,因為UVC目的是支援大部分的裝置,所以裝置端必須提供它支援的格式。資料格式分兩種,第一種是Video Format,其下又分Video Frame。這很好理解,基本上你得先讓HOST選擇影像格式,才能再選擇它的解析度、bitrate跟frame rate。Video Format對應的就是影像格式,而Video Frame則是對應到解析度、bitrate跟frame rate。這樣做的好處是每個裝置可以支援多種影像格式,像是RGB或 MJPEG。

題外話,UVC要支援MJPEG或H264的原因很簡單,因為傳統的RGB格式真的佔用太多USB頻寬了,加上現在沒有HD賣不動,用RGB格式根本沒辦法達到720p30。1280*720*30*3 = 82,944,000 bytes,直接爆掉USB頻寬,所以你會看到有些Webcam號稱支援720p或1080p RGB格式,但是它的frame rate是可憐的1或5,這有意義嗎?

每個Video Format有對應的規格要K,這邊就不介紹了,基本上UVC規格都有給範例,照抄就是了。至於要怎麼跟HOST講這些資訊,當然還是Descriptor啦。他們對應到的分別是Class-specific VS Format Descriptor跟Class-specific VS Frame Descriptor。

整個UVC descriptor的結構如下:


 這個結構很重要,基本上所有USB class規格都會提供類似的結構圖,了解整個結構是快速理解整個通訊協定的關鍵。

接下來是Class Request的部分。UVC的Class Request有點多,而且它牽涉到你的裝置所宣告的內容。每個在Video Control Interface宣告的元件都會有對應的Class Request。在看這些Class Request之前,得先搞清楚它的格式,不然會看不懂。下面是Set Request的格式:



這個比較簡單,Entity ID就是每個元件的ID。

下面是Get Request的格式:

 有沒有看到每個元件可以支援這麼多bRequest?其實沒有很難,會這樣子的原因是,還是UVC是個通用型的協定的關係。所以它基本上不知道每個控制項可以支援的範圍,它只能訂這類泛用的bRequest,然後讓每個裝置提供不同的數值。舉例來說,HOST可以透過GET_DEF取得Hue控制項的預設值、透過GET_MAX跟GET_MIN取得Hue控制項的支援範圍,可以透過GET_CUR取得Hue控制項的現在值。

下面是Hue Control(Processing Unit)的Class Request: 

Class Request還分Video Control Interface跟Video Streaming Interface。Video Control Interface基本上沒有甚麼大困難,Video Streaming Interface就有點複雜了。

Video Streaming Interface只需要支援兩種Class Request,一個是Video Probe,另外一個是Video Commit。他們是用來讓HOST跟DEVICE協調出一組共通的影像傳輸格式。如前所述,Video Streaming Interface描述這個裝置所支援的影像格式,HOST得透過Video Probe跟DEVICE協調出一組雙方都可以接受的參數,然後透過Video Commit指定這種參數。下面是示意圖:

根據我的經驗,HOST幾乎都會發出符合Descriptor宣告的參數給DEVICE端,所以如果HOST遲遲不發COMMIT_CONTROL,那就表示DEVICE回PROBE_CONTROL(GET_CUR)的內容有誤。我遇過的那次是structure的endian問題造成的。

USB PTP/MTP 簡介

MTP其實就是PTP的延伸,所以這兩個是一樣的東西。

MTP之前幾年幾乎沒有人在用。有啦,Pictbridge是架在PTP之上,所以PTP還有存在的價值,但是近幾年來Pictbridge也漸漸地消失在市場中了....理由我猜是相容性問題吧。


MTP的用途跟MSC類似,不同的地方wiki解釋的很清楚,我就不講了。


PTP規格是ISO標準(ISO15740),提到ISO,第一個就要想到[錢]。是的,下載它是要錢地。但是呢,MTP規格下載是不用錢的...。所以現在大家都用MTP規格在玩。


請注意一點,MTP只是個資料交換的通訊協定,所以要透過哪種載具(transport)去傳輸這些資料,是每個載具要定義的,如果要透過USB傳輸MTP資料,請參考[Still Image Capture Device Definition]這份規格書。跟MSC很像,這份文件只定義MTP資料要透過那些Endpoint傳輸,以及容器的格式。資料內容的話,請參照[Media Transfer Protocol]這份規格書。


先談[Still Image Capture Device Definition]。


這份文件很簡單,MTP主要透過Bulk-In,Bulk-In,跟 Interrupt-In來傳輸資料。一般情況下,Bulk-In跟Bulk-In就夠了,Interrupt-In主要是提供一些即時資訊給HOST知道,沒提供也不會怎樣。


每筆MTP資料之前都要加上一個header,這邊叫container,格式如下:


基本上HOST一定是發Command Block,Device回Response Block,Data Block則是看哪一方要發資料,這沒甚麼好說的。


Class Request則是有四個,Cancel、Get Extended Event Data、Device Reset、跟Get Device Status。

  • Cancel有點特別,它通常用來取消某次的傳輸。為什麼說他很特別?因為這是MTP特有的問題。在MTP規格內,如果要傳一個大檔案,因為要等很久,使用者有可能會在中途取消。問題來了,MTP規格沒有定義取消方式,所以只能在Transport Layer做這個處理。
  • Get Extended Event Data:我沒用過...。
  • Device Reset:沒有甚麼特殊。
  • Get Device Status:我看過唯一有用的是在Cancel之後,HOST會用這個Request來問狀態。
這四個Class Request的使用時機其實沒甚麼標準,跟HOST作業系統要怎麼運用有關,所以準備好USB Analyzer,然後準備好相容性測試吧。

接下來是[Media Transfer Protocol]。


MTP資料格式全部都是little endian,比較特殊的有字串跟Array:



  • 字串:它是所謂的Pascal String,但是用的是unicode 16 little endian編碼。第一個byte表示後面有幾個[字元],這個字串還必須包含'\0'這個字元。跟strlen()不同,要小心。
  • Array:第一個integer表示後面還有幾個element。
  • 日期:它是個字串,格式是"YYYYMMDDThhmmss.s"。既然它是個字串,所以它也遵循字串的編碼規定。".s"可有可沒有。


基本上MTP的流程是Command -> Data -> Response。在MTP規格裡,Command稱為[Operation]。而每個物件(或稱為檔案)都必須有個獨一無二的ID,規格稱為[Handle],整個通訊協定裡,雙方基本上是在處理這些Handle。請注意,物件並不是只有檔案,還包含目錄,這就是為什麼要叫物件而非檔案。

透過這些資訊,大概的物件交換協定也可以猜得出來。我這邊直接用規格來說明:
  • HOSTGetObjectHandles命令取得DEVICE端的物件列表。
  • HOST取得列表後,可以用GetObjectInfo命令取得每個物件的資訊。
  • HOST可以用GetObject命令取得該物件的資料內容。
  • 如果要取得某個物件的縮圖,可以用GetThumb命令。
  • 要刪除某個物件,可以使用DeleteObject命令。
  • GetObjectPropSupportedGetObjectPropValueGetObjectPropDesc則是MTP特有命令,它可以支援更多關於物件的屬性。因為GetObjectInfo的屬性是固定的,所以只能用額外的命令去新增屬性。
當然,當USB MTP裝置一插上HOST時,HOST可以用GetDeviceInfo取得這個裝置的資訊,並用GetDevicePropDescGetDevicePropValue取得這個裝置支援的屬性。
等等,上面沒提到如何寫入物件到裝置啊?這有點複雜,因為物件一向都是裝置提供對應的IDHOST根本沒法存取一個不存在的物件啊?所以這個動作要分兩個命令,第一個是SendObjectInfoDEVICE說要新增一個物件以及它的屬性,然後DEVICE會把這個物件的ID放在Response內,之後HOST再用SendObject把這個物件的資料傳給DEVICE。這樣做很引發一些問題,例如不能重傳或從某個地方開始傳。傳到一半要取消也是很麻煩。

實作MTP基本上不難,但是有幾點很麻煩:
  • 相容性問題。這個基本上無解,因為MTP是微軟主推的規格,其他作業系統可能不太想支援太多。就我所知,至少MAC OS對它的支援就不是太好。有一些行為跟Windows差很多。
  • 多檔案處理:因為所有物件都是用Handle ID來表示了,所以DEVICE必須處理Handle ID所對應的實體檔案轉換。所以一個映射表免不了,當檔案多到99999時候,這個映射表會有多大?存取速度呢?
  • WHCK。這個真的很煩。

RS232 over USB 簡介

RS232 over USB就是所謂的USB UART。現在這個東西會流行的原因,主要是現在很多電腦根本就沒有COM port,所以只好用USB來橋接並模擬UART。下圖是個很簡單的示意圖,會有這種需求的大多是工程師吧...。


USB對於這個東西的規範是落在Communication Device Class內,縮寫就是CDC。以下都用CDC描述。因為CDC包含太多類別了,所以CDC規格書裡面有定義這些類別的對應規格:


RS232 over USB是屬於Abstract  Control Model這個類別裡,所以請參考USBPSTN這個規格書吧,以下都會用CDC-ACM來代表RS232 over USB。

翻開PSTN規格書,它裡面定義了三種Model:
  • Direct Line Control Model 
  • Abstract Control Model 
  • Telephony Control Model 
本文只介紹Abstract Control Model,因為它就是我們要的。注意一下,因為UART並不需要支援AT command,所以Class Protocol Code可以是0:

接著就是如何寫CDC-ACM的descriptor了。請注意,CDC規格定義一個CDC裝置必須支援兩個Interface,一個Interface描述這個function所支援的功能,而另外一個則是描述資料傳輸的通道。然而,如果一個裝置宣告它支援兩個Interface,一般就表示它支援兩個function。這很明顯跟USB 2.0規範相衝突,所以IAD就上場啦。IAD全名是Interface Association Descriptor,它的功用就是拿來宣告某幾個interface其實是屬於同一個function,這樣就解決這個問題。IAD的規格現在已經包含在USB2.0之內,usb.org可以下載的到。

CDC規格有規定第一個interface要額外支援的descriptor:
  • Header Functional Descriptor:沒甚麼特殊,照抄即可。
  • Union Functional Descriptor :沒甚麼特殊,照抄即可。
  • Country Selection Functional Descriptor:雖然說是必須的,但是好像不用提供也可以?
  • Interrupt Endpoint Descriptor (Optional) :雖然說是非必須,但是最好要提供?
PSTN規格還規定第一個Interface要額外支援的Descriptor:
  • Call Management Functional Descriptor:比較值得注意的是bmCapability這個欄位,其實都是0就可以了...。
  • Abstract Control Management Functional Descriptor:一樣,比較值得注意的是bmCapability這個欄位,只要支援D1這個bit就可以,其他的就看要不要支援了。

而第二個Interface則是很簡單,它單純只是宣告資料傳輸的途徑:
  • Standard Interface Descriptor
  • Bulk-In Endpoint Descriptor
  • Bulk-Out Endpoint Descriptor 
接下來是CDC-ACM必須支援的Class Requests:


如果只是要實作CDC-ACM,其實不用管SendEncapsulatedCommmand跟GetEncapsulatedResponse,因為不用支援AT command,所以Host根本就不會發這兩個Class Request。SetLineCoding、GetLineCoding、跟SetControlLineStatus則是要支援,因為我們在Abstract Control Management Functional Descriptor內的bmCapability欄位說有支援。

提供正確的descriptor給Host之後,Host就會認到你的CDC-ACM裝置,如果你用任何一個Terminal工具像是TeraTerm去開啟它的話,Host會透過SetLineCoding跟SetControlLineStatus去設定這個CDC-ACM的屬性,而你的裝置就應該可以開始傳輸資料了。運氣好的話,就可以在TeraTerm內看到一些字了。

市面上有蠻多便宜的CDC-ACM裝置,他們的主要缺點是,只要印多一點的字,就會開始印亂碼,我猜這主要是因為這些裝置通常內建記憶體很小,當從實體UART來的資料超多時,他們就會來不及把這些資料傳給Host,然後就開始掉字,如果很不幸,掉的是一些控制字元,就會出現亂碼了。便宜沒好貨啊。

Wednesday, October 01, 2014

USB Mass Storage Class 簡介

USB Mass Storage Class的規格很薄,只有22頁,這邊我是指[Bulk-Only Transport]。因為它是目前最廣泛支援的規格,所以只談這部分,其他的規範,用到再去看即可。

顧名思義,這個規格規範如何只用一個bulk-in跟一個bulk-out endpoint去達成資料傳輸的目的。它首先定義兩個它專屬的 class request:

  • Bulk-Only Mass Storage Reset:就是Transport Layer的reset。至於要做到怎樣,規格寫得很清楚。
  • Get Max LUN:這個就有點模糊了。其實LUN就是Logical Unit Number,也就是這個裝置所支援的[槽(Slot/Drive)]。如果這個裝置支援兩個槽,那Max LUN就是1。

接下來是整個資料的流程(規格書):


HOST先透過bulk-out endpoint送CBW(Command Block Wrapper),如果Device有回ACK,接下來HOST可以收送資料,然後Device送個CSW(Command Status Wrapper),一個命令完成。

下面是CBW的資料格式(規格書):




下面是CSW的資料格式(規格書):


接下來就沒啦,只剩下一些錯誤處理的方式描述。有這麼簡單嘛?當然沒有啦,請注意[Bulk-Only Transport]這個名詞,Transport就是表示這份規格只是一份載具的規格,至於這個載具要搭載甚麼樣的資料,不好意思喔,請看另外的文件。

一般而言,目前市面上隨身碟支援的大多是SCSI command,所以請用google搜尋[scsi command]。這些文件會描述USB Mass Storage Class應該傳輸的資料內容。我手邊的有[SCSI Block Commands - 2 (SBC-2)]、[SCSI Block Commands - 3 (SBC-3)],跟[SCSI Primary Commands - 4 (SPC-4)],但是都是Draft version,據說正式版已經要收錢才可以取得了...。

話說回來,這些文件又臭又長,不過也不必全部都實作啦,只要做一些大家都會用到的命令就可以。雖然如此,大部分的人都是用買來的實作,所以你也不太有機會碰到。但是呢,微軟超機車的WHCK可不會這樣放過你,它會一個一個根據你支援的命令,發出一些很機車的參數,然後看你的裝置可不可以通過它的測試。我曾經被玩過一次,那是在WHCK還叫做WLK的時代,真的是痛不欲生...因為有些通不過的地方,雙方文件都寫得很模糊,根本就是只能用猜的,那真的是只能賭運氣看看怎樣改會過。所以如果你買的實作無法通過WHCK,請乖乖K規格書,然後期待問題不會太多吧。

還有一點要提的是,SCSI commands其實是很底層的命令,所以基本上它都是會存取到實體層的資訊。如果你的USB Mass Storage Class要支援SD卡,那你還得跟SD Host controller打交道才行,如果是NAND Flash的話,那就是要跟NAND controller打交道。

花了一點時間喵了一下,SCSI Primary Commands,應該是定義所有基本的命令,而INQUERY命令內的[PERIPHERAL DEVICE TYPE]欄位則是告訴HOST要套用哪一份文件:



通常NAND跟SD卡都是用SBC-2。SBC-3好像是新版,它相容於SBC-2。

codeblock