作者:milershao 刚好几天前有个客户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 盘。 刚好几天前有个客户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 盘。 |