作一個U盤的學習路線

 

最近想研究一個U盤,而後順便熟悉一下USB協議。由於USB協議比較複雜, 經常使用的複雜外設除了WiFi,Ethernet,SDIO和USB這些就是USB了,學習USB的時候確定要拿一個東西下手,因此簡單瞭解以後準備了下列資料:html

前期準備

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

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

 

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

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

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

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

 

移植代碼

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

 

移植到STM32的HAL代碼:3d

/**
    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);
    }
}

 

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

 

使用的時候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.準備好耐心一步步積累吧

相關文章
相關標籤/搜索