1. 程式人生 > >做一個U盤的學習路線

做一個U盤的學習路線

開發 除了 方式 而不是 請求 u盤 hid endif .com

最近想研究一個U盤,然後順便熟悉一下USB協議。因為USB協議比較復雜, 常用的復雜外設除了WiFi,Ethernet,SDIO和USB這些就是USB了,學習USB的時候肯定要拿一個東西下手,所以簡單了解之後準備了下列資料:

前期準備

1.《圈圈教你玩USB》。這本書比較經典,但是拿的芯片比較老了,在淘寶上搜索發現這本書配套的PDIUSBD12有現成的獨立模塊使用。因為手頭上正好有一個STM32開發板,可以用來對接它。STM32之前用來對接紅外線後來被閑置(參考這篇http://www.cnblogs.com/tanhangbo/p/4740702.html), 這時候正好用上。

技術分享圖片

左邊是淘寶買的模塊,中間是圈圈的書本,右邊是買書送的PCB,沒有用上。

2.《USB開發大全》,《linux那些事兒》這兩本書買了備用

3.USB一些相關資料包,和USB的系列spec,算作儲備。前期已經了解了一些USB的基本架構,學習起來可以再回顧下。

4.USB的抓包軟件,抓包硬件。抓包軟件抓到的包可能會漏掉一些東西,而且會加上一些系統調用,使用會模糊不清。而抓包軟件可以抓到真實的包,就像wifi的sniffer一樣,一定要看到真實的包才有標準答案,不然學習起來會迷路。USB的抓包器相對於邏輯分析儀比較昂貴,購買USB1.1協議的即可。

5.準備好你的耐心,因為這裏涉及多個協議,這是最重要的

移植代碼

因為選擇了STM32作為主體而不是51單片機,所以代碼難免需要移植一下,這是一個初期的障礙,但是評估起來問題不大。因為需要移植的是GPIO相關的東西,所以只要註意一些細節就可以了。移植過程中遇到的一個比較大的問題是,D12並口的GPIO不要給它拉高,否則通訊會出錯。我之前使用的時候發現一直沒有通(通過並口讀取D12的硬件ID),後來加入了邏輯分析儀之後竟然可以了,於是想到GPIO的硬件問題,最後把上拉改成懸空就可以了。因為STM32的GPIO的API有點繞,所以要仔細對待。

移植到STM32的HAL代碼:

/**
    Init GPIOA as input or Output
*/
void GPIOA_Init(int input)
{
  GPIO_InitTypeDef GPIO_InitStructure;
    

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE);
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    if (input == 1)
        GPIO_InitStructure.GPIO_Mode 
= GPIO_Mode_IN_FLOATING; //GPIO_Mode_IN_FLOATING else GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //GPIO_Mode_Out_PP GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7; GPIO_Init(GPIOA, &GPIO_InitStructure); } void HAL_GPIO_Data_AS_Input() { //GPIO_DeInit(GPIOA); GPIOA_Init(1); } void HAL_GPIO_Data_AS_Output() { //GPIO_DeInit(GPIOA); GPIOA_Init(0); } u8 HAL_GPIO_Read_Data() { return GPIO_ReadInputData(GPIOA) & 0xFF; } /** GPIOB_6 = RD -- Output GPIOB_7 = WR -- Output GPIOB_8 = INT -- Input GPIOB_9 = A0 -- Output */ void GPIOB_Init() { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB , ENABLE); GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; /** Output */ GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_Init(GPIOB, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7; GPIO_Init(GPIOB, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_Init(GPIOB, &GPIO_InitStructure); /** Input */ GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING ; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; GPIO_Init(GPIOB, &GPIO_InitStructure); } /** Write one byte to GPIOA0~GPIO7 */ void HAL_GPIO_Write_Data(unsigned char byte) { #if 0 GPIO_WriteBit(GPIOA, GPIO_Pin_0, (byte & 0x01)); GPIO_WriteBit(GPIOA, GPIO_Pin_1, (byte & (0x01 << 1))); GPIO_WriteBit(GPIOA, GPIO_Pin_2, (byte & (0x01 << 2))); GPIO_WriteBit(GPIOA, GPIO_Pin_3, (byte & (0x01 << 3))); GPIO_WriteBit(GPIOA, GPIO_Pin_4, (byte & (0x01 << 4))); GPIO_WriteBit(GPIOA, GPIO_Pin_5, (byte & (0x01 << 5))); GPIO_WriteBit(GPIOA, GPIO_Pin_6, (byte & (0x01 << 6))); GPIO_WriteBit(GPIOA, GPIO_Pin_7, (byte & (0x01 << 7))); #else uint16_t data = GPIO_ReadOutputData(GPIOA) & 0xFF00; //read high 8 byte GPIO_Write(GPIOA, (data|byte)); //write high&low byte #endif } void HAL_GPIO_Write_RD(int val) { GPIO_WriteBit(GPIOB, GPIO_Pin_6, val); } void HAL_GPIO_Write_WR(int val) { GPIO_WriteBit(GPIOB, GPIO_Pin_7, val); } void HAL_GPIO_Write_A0(int val) { GPIO_WriteBit(GPIOB, GPIO_Pin_9, val); } int HAL_GPIO_Read_INT() { return GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_8); } void HAL_GPIO_Init() { GPIOA_Init(0); GPIOB_Init(); } /** PA0 ~ PA7 test PASS */ void GPIOA_Test() { int i = 0; GPIOA_Init(0); while (1) { printf("Read = %08x\r\n", GPIOB, HAL_GPIO_Read_Data()); //GPIOA_Write_Byte(0x55); //delay_ms(1000); ///GPIOA_Write_Byte(0xaa); os_sleep(1); } }

圈圈代碼裏面的中文太多了,出於強迫癥給它整理格式、加入自定義的打印並且加上顏色。

技術分享圖片

使用的時候HID和USB-TTL的例子都沒問題,後面的U盤裏面就有一些問題了,這裏有一些SCSI命令的響應是沒有的,手動給它添加上去,試過ubuntu14.04和win10都沒有通,經常卡在文件系統的Read(10)這裏,猜測是速度上不去,後面更換STM32自帶的USB試試看。目前成功的例子是linux2.6內核的板子成功給它掛載上去並且讀取到裏面的TXT文件。

對於U盤來說有包括SCSI指令和FAT文件系統層的東西,這些都需要去了解,鞏固。

心得

1.USB協議本身主要是要實現一些基本的描述符,告訴主機自己是誰,有哪些參數(類似SDIO的CCCR/FBR/CIS),實際的數據傳輸流程是為上層協議做準備的。一開始可能對USB數據包本身比較感興趣,後續更多的問題存在於應用協議。

2.對於U盤來說要在USB基礎上了解SCSI和FAT文件系統協議協議格式,重頭戲都是在這裏的。

3.USB可玩性比較高,可以實現標準的HID或者自定義單各種設備。比如USB網卡和CH340這些模塊都是自定義的vendor specific設備。

展望

目前還是有一些問題需要解決的

1.將圈圈的代碼寫死二進制的方式全部改掉(包括USB的標準請求/SCSI指令/FAT格式),換成結構體的表示,手頭上有linux內核代碼和ecos的代碼,可以移植過來。光跑別人的代碼可能理解不深,自己重寫一遍才能深刻理解。

2.在STM32上跑通U盤的例子。目前STM32有了現成的U盤歷程,可以先移植過來看看效果,如果效果不好就將D12的代碼移植過來。

3.在windows和ubuntu上面調通U盤

4.STM32作為SDIO host,做一個USB讀卡器。

5.總結整理文檔,形成自己的USB代碼庫

6.準備好耐心一步步積累吧

做一個U盤的學習路線