作者:milershao 剛好幾天前有個客戶STM32F105的OTG模塊操作U盤,發現個別U盤電腦能正常使用,而STM32F105OTG模塊無法識別。ST官方相關FAE有做相關應用筆記,借花獻佛并致謝,轉過來與大家分享之。 問題回顧: 有客戶使用 STM32F2的 OTG 庫中的 U 盤主機例程在連接U盤時,有些 U 盤不能識別,甚至出現操作死機的情況,F就針對版本ST官方提供的 V2.1.0 的 USB 主機庫中的 MSC Host 例程做一些修改,以能夠兼容更多U 盤。 
問題調研: 1、有些 U 盤在收到 BOT_RESET 這個 MSC 類相關命令時,就不再有反應了:設備一直對后續主機發來的 IN 令牌回復 NAK 。通過對 BOT_RESET 命令的調查得知,USB 規范為大容量設備定義了兩種類型的復位:USB 端口復位和大容量設備BOT 復位。 然而USB 規范并未規定這兩種復位對應到 SCSI 命令中是做什么操作。通常設備把 USB端口復位映射成 SCSI 的硬復位;有些設備把 BOT復位映射到設備的 hard reset,有的僅是映射成邏輯單元復位。該條命令通常用于主機對在 BOT 通信中出錯的設備進行復位恢復。但是需要指出的是,有些 U 盤并沒有完全遵守大容量規范,比如不實現對 BOT_RESET 命令的支持。在這種情況下,設備一般會回復 STALL,那么主機需要發送 Set Port Feature(PORT_RESET)來復位設備所連的 Hub 端口。但是這個 U 盤并沒有回復 STALL,且 U 盤是直接連在 STM32 USB 主機上的,并沒有連接 Hub。于是,參照該 U 盤在 Window 下的連接操作的過程文件(tracer file),發現在 枚舉完成后,Windows 并沒有發送 BOT_RESET 命令,而是直接就發送 GET_Max_Lun 命令來獲取 U 盤的邏輯盤符個數。 于是在STM32 的 USB Host 例程中注釋掉 BOT_RESET 命令,果然可以正確操作了。修改代碼如下:
USBH_MSC_Handle() {...... { switch(USBH_MSC_BOTXferParam.MSCState) { case USBH_MSC_BOT_INIT_STATE: USBH_MSC_Init(pdev); //USBH_MSC_BOTXferParam.MSCState = USBH_MSC_BOT_RESET; USBH_MSC_BOTXferParam.MSCState = USBH_MSC_GET_MAX_LUN; break; case… 2、還有些 U 盤在收到 Get_Max_Lun 命令返回 STALL,但是 U 抓到的 tracer 文件如圖 盤主機就走不下去了。 
經查閱,有些只包含一個邏輯盤符的 U 盤可以對該命令回復數值 0 或者直接回復 STALL。 那么對于 USB 主機來說,在收到了該條命令的 STALL 回復后就應該第一:認為該 U 盤僅包含 一個邏輯盤符;第二:對 STALL 應答進行處理,然后繼續下面的命令流程。 通過 USB2.0 協議規范(章節 9.2.7),我們得知在控制傳輸(Control Transfer)過程中,當設備收到的命令自己不支持或者不適合設備當前的設置,就認為是命令出錯。那么設備通過在接 下來的數據階段或者狀態階段回復 STALL 應答來告知主機這個錯誤。這種“協議 STALL”是 控制傳輸特有的;這樣 STALL 的狀態,會在下一個控制傳輸(Setup 令牌)的到來而解除。 我們看看 Windows 對 U 盤這樣的回復是怎么處理,tracer 文件抓圖如下:主機通過 Clear Feature 命令,參數 EP_Halt(這是一個控制傳輸)來把設備方端點的 Halt feature 清除掉。 
從代碼里可以看到,例程是有做這方面的處理的: 如果主機發送的 Get_Max_Lun 命令不被設 備支持,則將 MSCState 狀態設定成 CTRL_ERROR_STATE,發送Clear Feature 的命令,以及隨后 的 Test_Unit_Ready 命令。
USBH_MSC_Handle() ...... { switch(USBH_MSC_BOTXferParam.MSCState) { case USBH_MSC_GET_MAX_LUN: /* Issue GetMaxLUN request */ status = USBH_MSC_GETMaxLUN(pdev, phost); if(status == USBH_OK ) { MSC_Machine.maxLun = *(MSC_Machine.buff) ; /* If device has more that one logical unit then it is not supported */ if((MSC_Machine.maxLun > 0) && (maxLunExceed == FALSE)) { maxLunExceed = TRUE; pphost->usr_cb->DeviceNotSupported(); break; } USBH_MSC_BOTXferParam.MSCState = USBH_MSC_TEST_UNIT_READY; } if(status == USBH_NOT_SUPPORTED ) { /* If the Command has failed, then we need to move to Next State, after STALL condition is cleared by Control-Transfer */ USBH_MSC_BOTXferParam.MSCStateBkp = USBH_MSC_TEST_UNIT_READY; /* a Clear Feature should be issued here */ USBH_MSC_BOTXferParam.MSCState = USBH_MSC_CTRL_ERROR_STATE; } break; …… 但是從抓到的 tracer 文件卻看不到主機走到了發送 Test_Unit_Ready 的命令。經過調試、代碼跟蹤,終于定位在 USBH_HandleControl()中。對 CTRL_DATA_IN_WAIT 的處理,當收到 URB_STALL 的應答后,沒有給 phost 的 Control.state 賦值成 CTRL_STALLED! 
USBH_HandleControl( ) { ...... switch (phost->Control.state) { case CTRL_DATA_IN_WAIT: URB_Status = HCD_GetURB_State(pdev , phost->Control.hc_num_in); /* check is DATA packet transfered successfully */ if (URB_Status == URB_DONE) { phost->Control.state = CTRL_STATUS_OUT; } /* manage error cases*/ if (URB_Status == URB_STALL) { /* In stall case, return to previous machine state*/ phost->gState = phost->gStateBkp; phost->Control.state = CTRL_STALLED; // } else if ......
3、還有一點需要注意的是:本版的 U 盤主機例程,暫時不支持多盤符 U 盤。 剛好幾天前有個客戶STM32F105的OTG模塊操作U盤,發現個別U盤電腦能正常使用,而STM32F105OTG模塊無法識別。ST官方相關FAE有做相關應用筆記,借花獻佛并致謝,轉過來與大家分享之。 問題回顧: 有客戶使用 STM32F2的 OTG 庫中的 U 盤主機例程在連接U盤時,有些 U 盤不能識別,甚至出現操作死機的情況。現就針對版本ST官方提供的 V2.1.0 的 USB 主機庫中的 MSC Host 例程做一些修改,以能夠兼容更多U 盤。 
問題調研: 1、有些 U 盤在收到 BOT_RESET 這個 MSC 類相關命令時,就不再有反應了:設備一直對后續主機發來的 IN 令牌回復 NAK 。通過對 BOT_RESET 命令的調查得知,USB 規范為大容量設備定義了兩種類型的復位:USB 端口復位和大容量設備BOT 復位。 然而USB 規范并未規定這兩種復位對應到 SCSI 命令中是做什么操作。通常設備把 USB端口復位映射成 SCSI 的硬復位;有些設備把 BOT復位映射到設備的 hard reset,有的僅是映射成邏輯單元復位。該條命令通常用于主機對在 BOT 通信中出錯的設備進行復位恢復。但是需要指出的是,有些 U 盤并沒有完全遵守大容量規范,比如不實現對 BOT_RESET 命令的支持。在這種情況下,設備一般會回復 STALL,那么主機需要發送 Set Port Feature(PORT_RESET)來復位設備所連的 Hub 端口。但是這個 U 盤并沒有回復 STALL,且 U 盤是直接連在 STM32 USB 主機上的,并沒有連接 Hub。于是,參照該 U 盤在 Window 下的連接操作的過程文件(tracer file),發現在 枚舉完成后,Windows 并沒有發送 BOT_RESET 命令,而是直接就發送 GET_Max_Lun 命令來獲取 U 盤的邏輯盤符個數。 于是在STM32 的 USB Host 例程中注釋掉 BOT_RESET 命令,果然可以正確操作了。修改代碼如下:
USBH_MSC_Handle() {...... { switch(USBH_MSC_BOTXferParam.MSCState) { case USBH_MSC_BOT_INIT_STATE: USBH_MSC_Init(pdev); //USBH_MSC_BOTXferParam.MSCState = USBH_MSC_BOT_RESET; USBH_MSC_BOTXferParam.MSCState = USBH_MSC_GET_MAX_LUN; break; case… 2、還有些 U 盤在收到 Get_Max_Lun 命令返回 STALL,但是 U 抓到的 tracer 文件如圖 盤主機就走不下去了。 
經查閱,有些只包含一個邏輯盤符的 U 盤可以對該命令回復數值 0 或者直接回復 STALL。 那么對于 USB 主機來說,在收到了該條命令的 STALL 回復后就應該第一:認為該 U 盤僅包含 一個邏輯盤符;第二:對 STALL 應答進行處理,然后繼續下面的命令流程。 通過 USB2.0 協議規范(章節 9.2.7),我們得知在控制傳輸(Control Transfer)過程中,當設備收到的命令自己不支持或者不適合設備當前的設置,就認為是命令出錯。那么設備通過在接 下來的數據階段或者狀態階段回復 STALL 應答來告知主機這個錯誤。這種“協議 STALL”是 控制傳輸特有的;這樣 STALL 的狀態,會在下一個控制傳輸(Setup 令牌)的到來而解除。 我們看看 Windows 對 U 盤這樣的回復是怎么處理,tracer 文件抓圖如下:主機通過 Clear Feature 命令,參數 EP_Halt(這是一個控制傳輸)來把設備方端點的 Halt feature 清除掉。 
從代碼里可以看到,例程是有做這方面的處理的: 如果主機發送的 Get_Max_Lun 命令不被設 備支持,則將 MSCState 狀態設定成 CTRL_ERROR_STATE,發送Clear Feature 的命令,以及隨后 的 Test_Unit_Ready 命令。
USBH_MSC_Handle() ...... { switch(USBH_MSC_BOTXferParam.MSCState) { case USBH_MSC_GET_MAX_LUN: /* Issue GetMaxLUN request */ status = USBH_MSC_GETMaxLUN(pdev, phost); if(status == USBH_OK ) { MSC_Machine.maxLun = *(MSC_Machine.buff) ; /* If device has more that one logical unit then it is not supported */ if((MSC_Machine.maxLun > 0) && (maxLunExceed == FALSE)) { maxLunExceed = TRUE; pphost->usr_cb->DeviceNotSupported(); break; } USBH_MSC_BOTXferParam.MSCState = USBH_MSC_TEST_UNIT_READY; } if(status == USBH_NOT_SUPPORTED ) { /* If the Command has failed, then we need to move to Next State, after STALL condition is cleared by Control-Transfer */ USBH_MSC_BOTXferParam.MSCStateBkp = USBH_MSC_TEST_UNIT_READY; /* a Clear Feature should be issued here */ USBH_MSC_BOTXferParam.MSCState = USBH_MSC_CTRL_ERROR_STATE; } break; …… 但是從抓到的 tracer 文件卻看不到主機走到了發送 Test_Unit_Ready 的命令。經過調試、代碼跟蹤,終于定位在 USBH_HandleControl()中。對 CTRL_DATA_IN_WAIT 的處理,當收到 URB_STALL 的應答后,沒有給 phost 的 Control.state 賦值成 CTRL_STALLED! 
USBH_HandleControl( ) { ...... switch (phost->Control.state) { case CTRL_DATA_IN_WAIT: URB_Status = HCD_GetURB_State(pdev , phost->Control.hc_num_in); /* check is DATA packet transfered successfully */ if (URB_Status == URB_DONE) { phost->Control.state = CTRL_STATUS_OUT; } /* manage error cases*/ if (URB_Status == URB_STALL) { /* In stall case, return to previous machine state*/ phost->gState = phost->gStateBkp; phost->Control.state = CTRL_STALLED; // } else if ......
3、還有一點需要注意的是:本版的 U 盤主機例程,暫時不支持多盤符 U 盤。 |