1. 說明
由於.Net Micro Framework的USB驅動架構中,沒有為Mass Storage功能提供原生支持,所以除了要編 寫Mass Storage主體代碼外,還需要在原有的USB驅動中添加部分枚舉代碼。其實從結構上來說,該部分 代碼應該添加在PAL層,不過考慮到這層代碼為.Net Micro Framework Poring Kit Rtm 3.0標准代碼,所 以把這部分代碼添加到我們自己編寫的USB驅動之中去了。
此外,由於Mass Storage功能需要不斷地檢測和處理USB端口的數據,需要一個進程(或線程)去進行 驅動。.Net Micro Framework在應用層僅支持一個進程(單個用戶程序),所以必須在應用程序中專開一 個線程去進行驅動,考慮到這樣實現需要用戶做額外的工作,最後摒棄了這一實現。最終的做法是,在 Mass Storage驅動中添加了時鐘中斷處理函數,Mass Storage被初始化後,該時鐘中斷被激活,以一個用 戶可設定的間隔去監控和處理USB端口的數據。
在實現Mass Storage功能的代碼中,並沒有直接去讀寫相關Flash,而是借助PAL層的SectorCache模塊 間接訪問Flash,這樣有兩個好處,一是讀寫有緩存,操作速度較快,二是程序比較通用,代碼在不用修 改的情況下可以訪問不同的Flash、SD卡等存儲模塊。
本Mass Storage驅動僅實現了一個功能子集,僅支持單個存儲模塊,不支持從PC機進行格式化(可通 過本地提供的接口進行格式化,文件系統目前必須是FAT32)。
2. USB Config
Mass Storage要求的USB Config和我們.Net Micro Framework的標准驅動不同,一是PID和VID不同, 由於Mass Storage設備是免驅動安裝的,這PID和VID應該沒有多大用處,但是由於以前PC上已經安裝了該 PID和VID的.Net Micro Framework驅動,所以Mass Storage設備插入時,優先去找匹配PID和VID的設備驅 動(這樣也可以讓一些廠家有機會安裝自己專門的Mass Storage設備驅動),所以對我們已有該驅動的PC ,肯定會有問題,所以我們僅需要調整一下VID的值即可。
其二我們USB接口描述類中,接口類必須為0x08(Mass storage class),子類為0x06(SCSI transparent command set)或0x04(UFI),接口協議為0x50(Bulk-only transport)。
表1 子類表
表2 接口協議
其它描述信息由於非關鍵,所以可修改,也可以不改。
3. 調用接口
Mass Storage驅動位於"DeviceCode"Drivers目錄下, 屬於通用驅動,其它設備都可以調用。包括如 下三個文件:UsbMassStorage.h、UsbMassStorage.cpp、UsbMassStorage_config.cpp。
3.1 PAL層接口
3.1.1 UsbMassStorage_Start
Mass Storage功能初始化和啟動函數。該函數執行時,先關閉原先的USB驅動接口,再用新USB Config 初始化USB接口,最後啟動一個定時中斷函數。
函數原型:int UsbMassStorage_Start(UINT32 value);
參 數:value為時鐘中斷間隔,單位us。
返 回 值:0。
3.1.2 UsbMassStorage_Stop
Mass Storage功能停止函數。該函數先關閉時鐘中斷函數及自己定義的USB接口,最後恢復默認USB接 口(即UsbDefaultConfiguration)。
函數原型:int UsbMassStorage_Stop();
返 回 值:0。
3.2 P/Invoke接口
考慮到用戶也可以自由開啟和關閉Mass Storage功能,所以為用戶提供了P/Invoke接口。該接口文件 位於:"Solutions"DM335"DeviceCode目錄下,其實該程序通用,可以放在PAL層。包括兩部分代碼,一是 托管代碼,二是本地代碼。
接口聲明如下,函數功能同PAL層接口。
namespace YFSoft
{
public static class MassStorage
{
[MethodImplAttribute(MethodImplOptions.InternalCall)]
extern static public int Start(UInt32 value);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
extern static public int Stop();
}
}
注意:用戶在調用MassStorage.Start()函數之前,先執行以下代碼,以使磁盤緩存中的內容真實寫入 存儲設備。
VolumeInfo[] vis = VolumeInfo.GetVolumes();
foreach (VolumeInfo vi in vis)
{
vi.FlushAll();
}
4.USB枚舉代碼添加
Mass Storage類規范定義了兩個請求:Get_Max_LUN和Mass Storage Reset,所有的Mass Storage類設 備都必須支持這兩個請求。
處理GET MAX LUN命令時,我們返回實際的邏輯單元(LUN:0~15)個數即可,由於我們的Mass Storage驅動僅支持一個存儲設備,所以直接返回0即可。其實該命令也可以不應答,這時PC會重試三次, 不過重試過程比較緩慢,用戶體驗體驗很不好。
對於Mass Storage Reset命令,由於我們目前沒有任何工作可做,所以直接返回空數據即可。
相關代碼如下:
if(len>0)
{
USB_SETUP_PACKET* Setup= (USB_SETUP_PACKET*)State->Data;
//GET MAX LUN
if (Setup->bmRequestType == 0xA1 && Setup->bRequest == 0xFE)
{
*(volatile UINT8 *)((UINT32)&usb.FIFO[0]) = 0;
usb.Indexed.PERI_CSR0=DM335_USB_Indexed::PERI_CSR0_TXPKTRDY | DM335_USB_Indexed::PERI_CSR0_DATAEND;
return;
}
//Mass Storage Reset
if(Setup->bmRequestType == 0x21 && Setup->bRequest == 0xFF)
{
usb.Indexed.PERI_CSR0=DM335_USB_Indexed::PERI_CSR0_TXPKTRDY | DM335_USB_Indexed::PERI_CSR0_DATAEND;
return;
}
}
該部分代碼直接添加在"DeviceCode"Targets"Native"DM335"DeviceCode"DM335_USB.cpp中的 DM335_USB_Driver::EP0_ISR()函數即可。
5. Mass Storage功能實現
其實只要實現了標准的USB驅動接口,在此基礎上實現Mass Storage功能應該算不太難,這裡不打算詳 細介紹Mass Storage功能的方方面面,這會涉及到太多的相關知識,我這裡只是從我們實際的這部分功能 出發,簡明扼要地介紹一下Mass Storage功能實現的原理。
這裡需要特別指出的是,Bulk-only transport協議,僅需要USB驅動提供兩個端點即可,一個是端點1 (輸入端點),一個是端點2(輸出端點),兩者的類型都為BULK模式。很幸運的是我們的.Net Micro Framework的標准驅動和這個要求是一致的。
5.1 命令/數據/狀態
Mass Storage設備枚舉成功後,PC會通過端點2向Mass Storage設備發送各種命令,Mass Storage設備 根據相應的命令,進行不同的應答。
其命令、數據、狀態相關的流程圖如下:
PC機發送的數據必須符合CBW格式(31byte,小端模式),而Mass Storage設備的應答,其格式必須符 合CSW格式(13byte,小端模式)。至於中間過程傳輸的數據,根據不同的命令,格式也有不同地要求。
5.1.1 CBW命令塊(Command Block Wrapper)
表3 CBW命令塊
dCBWSignature:常數0x43425355,標識為CBW命令塊。
dCBWTag: 由主機發送的CBW標簽。設備應該在相關的CSW的dCSWTag以相同的值應答主機。
dCBWDataTransferLength: 在本命令執行期間,主機期望通過Bulk-In或Bulk-Out端點傳輸的數據長 度。如果為0,則表示這之間沒有數據傳輸。
bmCBWFlags: 定義如下(Bit7 Direction(dCBWDataTransferLength為0時,該值無意義) :
0= DataOut,數據從主機到設備
1= DataIn, 數據從設備到主機
Bit6 Obsolete 0
Bits 5..0 Reserved 0
bCBWLUN: 表示正在發送命令字的設備的邏輯單元號(LUN)。對於支持多個LUN的設備,主機設置相對 應的LUN值。否則,該值為0。
bCBWCBLength: CBWCB的有效字節長度。有效值是在1到16之間。
CBWCB: 被設備解析執行的命令塊。
注:該部分是重中之重,通過對這部分的命令的解析,實現實際的Mass Storage功能。
5.1.2 CSW狀態塊(Command Status Wrapper)
表4 CSW狀態塊
dCSWSignature: 常數0x53425355,標識為CSW狀態塊
dCSWTag: 取相對應的CBW的dCBWTag值。
dCSWDataResidue:實際傳輸的數據個數和期望要傳輸的數據個數之差。
bCSWStatus:命令執行情況,相關值如下:
5.2 SCSI 傳輸協議(或UFI傳輸協議)
很多資料上都是把子類協議設置為0x06,也就是SCSI 傳輸協議,實際測試表明設置為0x04(也就是 UFI傳輸協議)也是可以的。實際看說明書,發現二者很多命令都是相同的,所以這兩種協議對我們來說 都適合,不過我這裡建議最好看UFI傳輸協議手冊,它要比SCSI手冊簡明地多。
無論是SCSI 傳輸協議還是UFI傳輸協議,其命令都是非常多的,不過對於我們的應用,我們僅需實現 如下幾條指令即可。
5.2.1 INQUIRY命令
該命令詢問Mass Storage設備的基本信息,如生產廠家,產品名稱,產品版本等等。
詳細參數說明請參見《UFI Command Specification》,比較有意思的是Peripheral Device Type參數 ,如果設置為0,則表示這是一個可移動的存儲設備(類似U盤),而設置為0x1F,則表示是一個非移動設 備(類似硬盤,圖標在硬盤區出現)。
5.2.2 READ_FORMAT_CAPACITIES命令
該命令獲取Mass Storage設備存儲大小,Block長度(一般為一個扇區大小,默認為512)等信息。
該表僅包括部分反饋信息,詳細說明請參見《UFI Command Specification》。需要注意的是,無論是 塊個數,還是塊長度,其數據格式為大端模式。
5.2.3 READ_CAPACITY命令
該命令返回最後一個塊的索引和塊的長度,其實該命令可以看著是READ_FORMAT_CAPACITIES命令的一 個子集。
注意數據格式為大端模式。
詳細說明請參見《UFI Command Specification》。
5.2.4 READ_10命令
該命令由PC端發出,請求Mass Storage設備發送指定扇區索引、扇區個數的數據。
這是PC機請求的命令,Mass Storage設備直接返回相應的數據即可。
詳細說明請參見《UFI Command Specification》。
5.2.5 WRITE_10命令
該命令由PC端發出,CBW命令塊後面緊跟的就是相應扇區的數據。
Mass Storage設備獲取數據後,寫到相應扇區即可。
這裡需要強調的是,由於要接收的數據量有可能很大,該部分功能又是在時鐘中斷中實現,所以不要 試圖一次獲取所有的扇區數據,否則在實際的TinyCLR環境中運行是不正常的。其實在READ_10中也存在類 似問題,不過實際測試,直接發送所有數據,並沒有什麼問題,這從側面反映PC機的接收數據能力,遠遠 大於MF設備。
5.2.6 REQUEST_SENSE命令
PC機每發送一個命令後,都會檢測設備返回的CSW的狀態值是否為0(Good Status),如果不為0,則 PC機馬上發送REQUEST_SENSE命令,詢問出錯的進一步信息。
我們這裡Sense Key的值直接設為0x05(ILLEGAL REQUEST)即可,主要原因是PC端會詢問各種命令, 由於我們沒有實現,返回的CSW狀態都非Good Status而已。
詳細說明請參見《UFI Command Specification》。
5.2.7 TEST_UNIT_READY命令
在沒有其它命令進行操作時,PC端會每隔一定時間,就會發送該命令,主要是為了探測Mass Storage 設備是否存在(類似心跳信號)。
由於該命令沒有數據交互,我們直接返回狀態Good Status的CSW狀態塊即可。