MPC8309 USB OTG驱动开发:从寄存器解析到实战避坑指南

MPC8309 USB OTG驱动开发:从寄存器解析到实战避坑指南 1. 项目概述与核心价值如果你正在开发一款基于MPC8309这类嵌入式处理器的便携式设备比如一个既能连接U盘读取数据又能被电脑识别为U盘的工业手持终端那么USB OTGOn-The-Go功能几乎是你绕不开的核心技术。这个功能让设备摆脱了传统USB主从架构的束缚可以灵活地在“主机”如读取U盘和“设备”如被PC识别角色间切换。听起来很酷对吧但当你翻开MPC8309那上千页的参考手册看到密密麻麻的寄存器描述时可能会瞬间感到无从下手。OTG功能的实现其硬件层面的“开关”和“状态指示灯”就藏在OTGSC、USBMODE、PORTSC等一系列非EHCI规范的寄存器里。我经历过这个阶段深知只看手册字面描述是不够的。手册告诉你每个位是干什么的但不会告诉你这些位在真实的OTG协议握手、角色切换、电源管理流程中是如何联动工作的更不会告诉你配置时有哪些“坑”。本文的目的就是结合我过去在嵌入式USB驱动开发中的实践经验带你穿透MPC8309参考手册的文本深入理解OTGSC等关键寄存器的每一个关键位域在实际场景中的作用、配置时机和避坑要点。我们将从OTG的基础原理出发落脚到MPC8309的具体寄存器操作最终让你能胸有成竹地编写出稳定可靠的USB OTG驱动代码。这不仅适用于MPC8309其思路和方法对于理解其他嵌入式处理器如STM32系列带OTG的型号的USB控制器也大有裨益。2. USB OTG基础与MPC8309硬件框架解析2.1 USB OTG的核心机制不止是双角色在深入寄存器之前我们必须先统一对USB OTG核心机制的理解。很多人认为OTG就是设备能当主机也能当从机这没错但太笼统了。OTG的本质是一套让两个设备自动协商出谁是主机A设备、谁是从机B设备的协议其核心依赖三个物理信号ID线最关键这是OTG的“身份标识线”。在Micro-AB插座上ID引脚通常接地0表示A设备初始主机悬空或通过电阻上拉1表示B设备初始设备。MPC8309通过监测OTGSC[ID]位来读取这个状态。这是角色判定的第一依据。VBUS线电源与会话管理在传统USB中主机提供VBUS5V。在OTG中VBUS由A设备提供。但OTG引入了更精细的电源管理通过监测VBUS上的电压来判断会话Session状态。例如OTGSC[AVV]A设备VBUS有效的阈值是4.4V而OTGSC[ASV]/[BSV]A/B会话有效的阈值是0.8V。这意味着即使VBUS没有达到满功率的5V只要超过0.8V就认为一个“会话”开始了。DP/DM线数据与协议除了数据传输DPData线在OTG的SRP会话请求协议中扮演重要角色。B设备可以通过在DP线上发送一段脉冲Data-line Pulsing向A设备请求开启VBUS启动一个会话。OTGSC[DPIS]和OTGSC[DPS]位就用于监测和报告这种脉冲事件。一个常见的误区认为软件可以随意、随时切换主机/设备模式。实际上硬件连接ID线状态是基础软件只能在硬件允许的范围内进行模式配置通过USBMODE[CM]并且要严格遵循OTG协议的状态机。例如一个ID引脚被拉低A设备的端口你无法强行将其配置为纯设备模式去连接另一个主机这会导致通信失败。2.2 MPC8309 USB控制器架构概览MPC8309的USB模块是一个双角色设备控制器DRD它内部包含符合EHCI增强型主机控制器接口规范的主机控制器部分以及一个独立的设备控制器。而那些以“—Non-EHCI”标注的寄存器正是实现OTG功能、设备控制器功能以及一些芯片特定优化如总线优先级控制的关键所在。我们可以把这些寄存器分为几大类来理解模式与全局控制类如USBMODE决定了整个控制器是作为主机、设备还是空闲。端口状态与控制类如PORTSC管理端口使能、连接状态、复位等通用端口操作。OTG专用控制类如OTGSC这是OTG功能的“大脑”所有ID、VBUS、DP的状态监测、中断控制和SRP操作都通过它进行。端点控制类如ENDPTCTRL0~ENDPTCTRLn用于配置设备模式下各个端点的类型控制、批量、中断、同步、使能状态和Stall状态。数据传输管理类如ENDPTPRIME,ENDPTFLUSH,ENDPTSTATUS,ENDPTCOMPLETE用于设备模式下管理端点的缓冲区、启动和完成传输。系统接口优化类如SNOOPn,AGE_CNT_THRESH,PRI_CTRL,SI_CTRL这些寄存器用于优化USB控制器与MPC8309内部系统总线CSB之间的数据交互效率和一致性对于高性能或实时性要求高的应用至关重要。理解这个分类有助于我们在编程时快速定位需要操作的寄存器组而不是在内存映射表中盲目寻找。3. 关键寄存器深度解析与配置实战现在我们进入最核心的部分——逐位解析关键寄存器并说明在驱动中如何配置。我会以OTGSC寄存器为例进行最详细的拆解因为它最复杂也最关键。3.1 OTG状态与控制寄存器OTGSC—— OTG的指挥中心OTGSC寄存器是OTG功能的集大成者其位域可以分为四大功能区如下图所示基于手册图16-2131 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 R - DPIE 1msE BSEIE BSVIE ASVIE AVVIE IDIE - DPIS 1msS BSEIS BSVIS ASVIS AVVIS IDIS W w1c w1c w1c w1c w1c w1c 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 R - DPS 1msT BSE BSV ASV AVV ID - DP OT - VC VD W1OTG中断使能位Bits 24-30与状态位Bits 16-22这是驱动中处理异步事件的核心。使能位*IE和状态位*IS一一对应。IDIE/IDISID引脚变化中断。这是检测设备插拔OTG线缆连接的首要事件。当ID线状态变化例如从B设备变为A设备IDIS置位如果IDIE已使能则会产生中断。注意状态位是“写1清除”w1c你必须在中断服务程序ISR中向IDIS位写1来清除中断标志否则会持续产生中断。AVVIE/AVVISA设备VBUS有效中断。当VBUS电压超过或低于4.4V阈值时触发。用于A设备确认自己提供的电源是否被对方接受。ASVIE/ASVISA设备会话有效中断。阈值0.8V。用于A设备监测会话是否开始或结束。BSVIE/BSVISB设备会话有效中断。阈值0.8V。用于B设备监测A设备是否提供了足以启动会话的电源。BSEIE/BSEISB设备会话结束中断。当VBUS从有效降至结束阈值以下时触发。用于B设备知道会话已终止。1msE/1msS1毫秒定时器中断。用于需要精确时间控制的协议操作例如在SRP中控制DP脉冲或VBUS充电的时间。DPIE/DPIS数据脉冲中断。当检测到DP线上有SRP脉冲时触发。配置心得在初始化时通常不会一次性使能所有中断。例如在设备启动时如果ID引脚状态已知且固定可以先不使能IDIE。更常见的做法是在检测到ID变化后根据新的角色A或B来使能相应的VBUS会话中断AVVIE或BSVIE/BSEIE。1msE在需要软件实现精确时序控制时才启用。2OTG状态输入位Bits 8-14这些是只读位反映了当前的硬件状态。ID直接反映ID引脚的电平。1表示B设备初始设备0表示A设备初始机。这是你决定后续软件流程的最根本依据。AVV,ASV,BSV,BSE分别反映对应电压阈值比较器的实时输出。你可以轮询这些位来判断VBUS状态但更高效的方式是使用中断。DPS数据脉冲状态。为1时表示当前DP线上有脉冲。1msT1毫秒定时器翻转位。这个位以1kHz频率不断翻转0-1-0...你可以用它作为简单的软件定时器基准。3OTG控制位Bits 0-4, 7这些是软件可以写入以发起硬件操作的位。DPBit 4数据线脉冲控制。这是B设备发起SRP的关键。当B设备想请求A设备开启VBUS时需要将DP位设置为1并保持至少5ms具体时间参考USB OTG规范这将使DP线上拉电阻有效产生一个脉冲。完成后需清零。OTBit 3OTG终端电阻控制。在设备模式下必须设置为1。这将使能DM线上的下拉电阻这是USB设备被主机识别所必需的。VCBit 1VBUS充电。设置为1会使能一个电流源对VBUS线进行充电用于A设备在SRP响应中检测B设备的连接。VDBit 0VBUS放电。设置为1会通过一个电阻对VBUS放电用于安全地结束会话。一个完整的B设备发起SRP的软件流程示例上电初始化读取OTGSC[ID]发现为1确认自己是B设备。配置USBMODE[CM]为设备模式10。设置OTGSC[OT] 1使能下拉电阻。使能BSVIE和BSEIE中断准备监测会话。此时VBUS无电BSV0B设备想请求会话设置OTGSC[DP] 1启动DP脉冲。延时至少5ms可以使用轮询1msT或硬件定时器。设置OTGSC[DP] 0停止脉冲。等待A设备响应开启VBUS。当VBUS电压超过0.8VBSVIS中断触发在ISR中清除标志并确认BSV1。此时B设备可以开始进行USB设备枚举了。3.2 USB模式寄存器USBMODE与端口状态控制寄存器PORTSCUSBMODE寄存器非常简单但至关重要。CMBits 1:0字段00: 空闲模式。控制器复位后的默认状态。10:设备控制器模式。当OTGSC[ID]1B设备时应配置为此模式。11:主机控制器模式。当OTGSC[ID]0A设备时应配置为此模式。01: 保留。重要警告手册明确指出此寄存器在复位后只能写入一次。如果你想切换模式例如从设备模式切换到主机模式必须先通过写USBCMD[RST]位来软复位整个USB控制器然后才能重新配置USBMODE。直接重复写入是无效的。PORTSC寄存器在主机和设备模式下含义有部分重叠但侧重点不同。CCSBit 0当前连接状态。在主机模式下1表示有设备连接在设备模式下1表示已成功连接到主机。CSCBit 1连接状态变化。状态改变时置位写1清除。在主机模式下这是检测设备插拔的主要中断源之一。PEBit 2端口使能。在主机模式下这个位不能通过软件直接写1来使能端口。端口使能是硬件在复位和使能流程中自动完成的。软件只能写1来禁用端口或当故障如断开发生时硬件会禁用它。在设备模式下此位始终为1只读。PPBit 12端口电源仅主机模式相关。在MPC8309中这个位控制端口的电源开关。很多新手会忽略这一点在主机模式下即使设备物理连接了CCS1如果PP0端口断电那么CCS、CSC等状态位也是0无法检测到设备。因此在初始化主机模式后需要先写PP1给端口上电然后才能进行复位和枚举设备。3.3 端点控制寄存器ENDPTCTRLn与数据传输管理在设备模式下你需要配置端点。ENDPTCTRL0是控制端点Endpoint 0专用的其TX/RX是固定使能且类型为控制端点。从ENDPTCTRL1开始你需要为每个用到的端点进行配置。以一个用于批量数据传输的Endpoint 1 OUT接收和IN发送为例使能端点设置ENDPTCTRL1[RXE] 1和ENDPTCTRL1[TXE] 1。设置端点类型设置ENDPTCTRL1[RXT] 10批量和ENDPTCTRL1[TXT] 10批量。数据同步当USB主机发送SetConfiguration请求后软件需要写ENDPTCTRL1[RXR] 1和ENDPTCTRL1[TXR] 1来复位数据PID序列确保主机和设备的数据包同步从DATA0开始。Stall处理如果端点需要返回Stall握手信号可以写ENDPTCTRL1[RXS] 1或TXS1。当收到新的Setup包时控制端点的Stall位会被硬件自动清除但非控制端点的Stall需要软件手动清除。数据传输流程以Bulk OUT为例软件准备好一个缓冲区并设置好对应的传输描述符dTD和队列头dQH。软件写ENDPTPRIME[PERB]的对应位例如Bit 0对应EP1 OUT为1通知硬件“缓冲区已就绪可以接收数据”。硬件开始解析dQH和dTD并将ENDPTSTATUS[ERBR]对应位置1表示“端点接收缓冲区已就绪”。当主机发送OUT数据包硬件将数据DMA到缓冲区完成传输后硬件设置ENDPTCOMPLETE[ERCE]对应位为1如果dTD中IOC位被设置还会触发USBINT中断。在中断服务程序中软件读取ENDPTCOMPLETE寄存器发现ERCE位被设置于是知道EP1 OUT传输完成然后处理数据并准备下一个缓冲区回到步骤1。如果需要取消一个已提交但未完成的传输可以写ENDPTFLUSH[FERB]对应位为1来刷新接收缓冲区。3.4 系统性能优化寄存器SNOOP、AGE_CNT_THRESH与PRI_CTRL这部分寄存器常被忽略但在对USB传输带宽或实时性有要求的应用中它们能显著提升性能。SNOOP1/SNOOP2寄存器用于设置“监听”地址范围。当USB控制器的DMA访问落在这些地址范围内时会触发缓存一致性操作Cache Coherent Transaction。这在与带Cache的CPU如MPC8309的e300核心协同工作时至关重要。如果你为USB DMA缓冲区分配的内存是可Cache的例如在Linux中通过kmalloc分配你必须正确配置SNOOP寄存器来包含这些缓冲区的地址范围否则会导致数据一致性问题——CPU看到的是Cache里的旧数据而USB控制器写入的是内存里的新数据。配置时SNOOPn[0:19]设置基地址的高20位SNOOPn[27:31]设置范围从4KB到2GB。例如设置SNOOPn[27:31]0x0BSNOOPn[0:19]0x80000则表示对物理地址0x80000000开始的4KB区域进行监听。AGE_CNT_THRESH与PRI_CTRL寄存器这两个寄存器共同实现了一个“老化计数器”优先级提升机制用于优化USB控制器的系统总线访问延迟。AGE_CNT_THRESH设置一个阈值单位是CSB总线时钟周期。PRI_CTRL设置两个优先级水平pri_lvl0和pri_lvl100最低11最高。工作机制USB控制器发起一个总线请求时内部老化计数器从0开始递增。只要请求未被响应计数器每周期加1。如果计数器值 AGE_CNT_THRESH则使用pri_lvl0优先级请求总线。如果计数器值 AGE_CNT_THRESH则使用pri_lvl1更高优先级请求总线。的防止USB控制器因为系统总线繁忙而被“饿死”。当USB的请求等待时间过长时自动提升其仲裁优先级确保及时获取总线使用权从而维持USB的实时性尤其是对于Isochronous和Interrupt传输。手册给出的调优建议非常实用首先尝试禁用老化机制设置AGE_CNT_THRESH0此时始终使用pri_lvl1测试USB性能是否满足要求。如果不满足尝试一个保守配置PRI_CTRL[pri_lvl0]0低PRI_CTRL[pri_lvl1]3高AGE_CNT_THRESH40即等待40个总线周期后提升优先级。如果性能仍不足尝试逐步减小AGE_CNT_THRESH例如每次减5让USB更早地提升优先级。如果AGE_CNT_THRESH40时性能已足够好可以尝试逐步增大该值例如每次加5这有助于降低USB控制器对总线优先级的影响让其他总线主设备如以太网、另一个USB控制器有更公平的机会。4. 驱动开发实战流程与核心代码逻辑理解了寄存器我们来看如何将它们组织成一个完整的驱动初始化流程。以下是一个简化的、基于裸机或简单RTOS的BSP板级支持包层USB OTG驱动初始化框架重点关注OTG相关部分。4.1 初始化流程步骤时钟与电源初始化确保USB控制器模块的时钟例如csb_clk、usb_clk和电源域已使能。这部分依赖具体的SoC时钟树配置。软复位USB控制器写USBCMD[RST] 1等待USBCMD[RST]和USBSTS[HCHalted]确认复位完成。配置USB模式临时先写USBMODE[CM] 00Idle模式或根据硬件设计预设一个模式。此时先不要最终确定主机/设备模式。配置系统接口优化寄存器根据USB DMA缓冲区地址配置SNOOP1/2。根据性能需求配置AGE_CNT_THRESH和PRI_CTRL。初期可采用手册推荐值。配置SI_CTRL例如根据BURSTSIZE寄存器设置rd_prefetch_val。读取ID引脚确定初始角色// 读取OTGSC寄存器的ID位 uint32_t otgsc readl(USB_OTGSC_BASE); int is_a_device ((otgsc OTGSC_ID_MASK) 0); // ID0 - A设备根据ID配置最终模式并初始化控制器if (is_a_device) { // A设备主机初始化流程 writel(USBMODE_CM_HOST, USB_USBMODE_BASE); // CM11 // 配置主机控制器参数帧列表基地址等 // ... // 使能主机端口电源 uint32_t portsc readl(USB_PORTSC_BASE); portsc | PORTSC_PP_MASK; // 写1开启端口电源 writel(portsc, USB_PORTSC_BASE); // 使能主机控制器运行 // ... } else { // B设备设备初始化流程 writel(USBMODE_CM_DEVICE, USB_USBMODE_BASE); // CM10 // 设置OTG终端电阻 otgsc | OTGSC_OT_MASK; // OT1 writel(otgsc, USB_OTGSC_BASE); // 配置设备控制器参数设备地址默认为0端点0等 // 配置非控制端点如ENDPTCTRL1 // ... // 使能设备控制器运行 // ... }配置OTG中断根据当前角色有选择地使能OTGSC中的中断。例如A设备使能AVVIE和IDIEB设备使能BSVIE、BSEIE和IDIE。同时使能控制器全局中断。启动协议状态机如果是B设备且需要主动请求会话则启动SRP流程操作DP位。如果是A设备则等待连接或处理SRP请求监测DPIS。4.2 中断服务程序ISR处理逻辑USB OTG驱动是典型的事件驱动型驱动ISR是核心。一个健壮的ISR需要处理多种中断源void USB_OTG_IRQHandler(void) { uint32_t usbsts readl(USB_USBSTS_BASE); uint32_t otgsc readl(USB_OTGSC_BASE); // 1. 处理OTGSC中断 if (otgsc OTGSC_INTERRUPT_STATUS_MASK) { // 检查所有OTG状态位 if (otgsc OTGSC_IDIS_MASK) { // ID改变角色可能切换 handle_id_change(otgsc); writel(OTGSC_IDIS_MASK, USB_OTGSC_BASE); // 写1清除IDIS } if (otgsc OTGSC_AVVIS_MASK) { // A设备VBUS有效状态变化 handle_a_vbus_valid(otgsc); writel(OTGSC_AVVIS_MASK, USB_OTGSC_BASE); } if (otgsc OTGSC_BSVIS_MASK) { // B设备会话有效 if (otgsc OTGSC_BSV_MASK) { // 会话开始可以启动设备枚举 start_device_enumeration(); } else { // 会话结束进入低功耗或等待状态 session_ended(); } writel(OTGSC_BSVIS_MASK, USB_OTGSC_BASE); } // ... 处理其他OTG中断 } // 2. 处理USB控制器核心中断USBINT if (usbsts USBSTS_INT_MASK) { uint32_t usbint readl(USB_USBINT_BASE); // 处理USB传输完成、错误等事件 // ... // 处理端点完成事件 uint32_t endptcomplete readl(USB_ENDPTCOMPLETE_BASE); if (endptcomplete ENDPTCOMPLETE_ERCE_MASK) { // 端点接收完成 handle_rx_complete(endptcomplete); writel(endptcomplete ENDPTCOMPLETE_ERCE_MASK, USB_ENDPTCOMPLETE_BASE); // 清除对应位 } if (endptcomplete ENDPTCOMPLETE_ETCE_MASK) { // 端点发送完成 handle_tx_complete(endptcomplete); writel(endptcomplete ENDPTCOMPLETE_ETCE_MASK, USB_ENDPTCOMPLETE_BASE); } } // 3. 处理端口状态变化主机模式重要 if (usbsts USBSTS_PORT_CHANGE_DETECT_MASK) { // 假设有这个状态位实际查看PORTSC的CSC uint32_t portsc readl(USB_PORTSC_BASE); if (portsc PORTSC_CSC_MASK) { if (portsc PORTSC_CCS_MASK) { // 设备连接 usb_device_connected(); } else { // 设备断开 usb_device_disconnected(); } writel(PORTSC_CSC_MASK, USB_PORTSC_BASE); // 写1清除CSC } } }5. 常见问题排查与调试技巧实录在实际开发中你会遇到各种各样的问题。以下是我总结的一些典型问题及其排查思路5.1 问题设备无法被识别主机模式症状MPC8309作为主机插入U盘后无反应PORTSC[CCS]始终为0。排查步骤检查物理连接和电源确保USB插座、线缆完好VBUS有5V输出可以用万用表测量。确认端口电源这是最常见的原因。检查PORTSC[PP]是否已设置为1。主机模式下端口必须上电才能检测设备。确认控制器模式检查USBMODE[CM]是否为11主机模式。检查复位状态主机控制器是否已完成复位USBCMD[RST]0并处于运行状态USBCMD[RS]1检查ID引脚状态如果OTGSC[ID]为1B设备那么硬件上它被配置为设备角色无法作为主机。检查电路板上ID引脚的上拉/下拉电阻配置。使用逻辑分析仪或示波器抓取USB DP/DM线上的信号看是否有主机发出的复位信号SE0状态和低速设备检测脉冲。5.2 问题OTG角色切换失败症状插入OTG线缆设备角色没有按预期切换例如期望作为主机但实际进入了设备模式。排查步骤首要检查ID线读取OTGSC[ID]寄存器的值。这是硬件决定的软件无法更改。如果该值与你的线缆Micro-A插头还是Micro-B插头预期不符问题在硬件电路上。Micro-A插头的ID脚是接地的0Micro-B插头的ID脚是悬空/上拉的1。检查中断处理确保IDIE中断已使能并且ISR正确清除了IDIS标志。在ISR中必须根据新的ID值重新配置USBMODE记得先软复位控制器。检查VBUS状态角色切换可能伴随VBUS的开启/关闭。如果A设备未能提供VBUSB设备可能无法进入会话检查OTGSC[AVV]或BSV的状态。5.3 问题USB传输不稳定或速度慢症状数据传输过程中出现丢包、CRC错误或实际传输速率远低于理论值。排查步骤检查DMA缓冲区对齐和SNOOP配置确保为USB DMA分配的内存缓冲区地址是缓存行对齐的通常是32字节或64字节。重点检查SNOOP寄存器配置是否正确覆盖了DMA缓冲区地址范围。错误的SNOOP配置是导致数据一致性错误、传输静默失败的元凶之一。调整总线优先级如果系统总线负载很重例如同时有高速网络吞吐USB传输可能被阻塞。尝试使用AGE_CNT_THRESH和PRI_CTRL机制给USB控制器更高的优先级或更激进的老化阈值。优化端点缓冲区大小和数量对于批量传输适当增加BURSTSIZE寄存器中的TXPBURST和RXPBURST值如设置为64字节可以提升突发传输效率。同时确保软件能及时提交Prime新的缓冲区避免硬件等待。检查时钟精度USB对时钟精度要求很高特别是全速/高速模式。确保提供给USB控制器的时钟如usb_clk频率稳定且精度在规范要求内通常±500ppm以内。5.4 调试技巧利用寄存器进行状态诊断当问题出现时不要盲目修改代码先系统地读取并记录相关寄存器的状态。制作一个寄存器快照函数在关键节点初始化后、中断发生时、出错时调用一个函数将OTGSC、PORTSC、USBMODE、USBSTS、ENDPTSTATUS、ENDPTCOMPLETE等关键寄存器的值打印出来或保存到日志中。关注关键位USBSTS[UI]USB中断是否有待处理中断USBSTS[UE]USB错误是否发生了错误PORTSC[CCS]/[CSC]连接状态是否正常OTGSC[ID]/[BSV]/[AVV]OTG角色和电源状态是否正确ENDPTSTATUS[ETBR]/[ERBR]端点缓冲区是否就绪ENDPTCOMPLETE[ETCE]/[ERCE]传输是否完成对比正常与异常状态将出错时的寄存器快照与正常工作的快照进行对比差异点往往是问题的突破口。开发MPC8309的USB OTG功能就像在指挥一个精密的交响乐团。OTGSC等寄存器就是乐谱上的音符你需要深刻理解每个音符位域在整首曲子OTG协议状态机中的作用和时机。从硬件连接ID、VBUS的确认到控制器模式USBMODE的设定再到中断驱动的事件处理和数据传输管理每一步都需要严谨细致。希望这篇结合了原理、手册解读和实践经验的解析能为你点亮嵌入式USB开发道路上的灯。在实际项目中最宝贵的经验往往来自于解决那些手册里没有写的、千奇百怪的硬件兼容性问题或时序边界条件耐心调试善用工具你一定能让MPC8309的USB OTG稳定可靠地工作起来。