USB CustomHID设备驱动从USB IP迁移至OTG IP(标准库)

文章介绍了在STM32F105产品中遇到的USB连接错误问题,问题源于使用了不适用于OTG-IP内核的驱动。文章详细阐述了驱动移植过程,包括下载驱动文件、理解项目结构、迁移底层和设备层驱动、修改回调函数以及中断处理,并强调了描述符的修改细节。最后,作者总结了驱动错误可能导致的不稳定性问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

关键词

1.背景

2.查找问题

3.驱动移植过程

3.1 驱动文件下载

3.2 项目结构

3.2.1 底层驱动Low-lever driver

3.2.2 设备端驱动

3.2.3 主机端驱动

3.3 驱动的迁移         

3.3.1 底层驱动

3.3.2 设备层驱动

3.4 回调函数的修改

3.5 中断函数

3.6 描述符

4.总结


关键词

Host 主机

Device 设备

1.背景

        在解决产品的一个老bug的过程中,意外发现产品的usb模块连接有问题,该产品使用STM32F105系列芯片,支持双角色连接,即可以同时作为Host和Device来连接到控制器或上位机。但在使用过程中发现该产品作为Device时的连接经常出现错误,刚插上数据线时连接正常,一段时间后便会无故断开,且断开的时机不定,频繁操作上位机进行读取和写入会提高发生错误的几率,这在实际使用时体验非常不好,并且该问题在其它产品中没有发现。

        本文涉及到的迁移可能仅适合部分项目,可以挑着看,不对的地方欢迎指正。

2.查找问题

        对比其它产品,该产品的特点在于使用了双角色进行连接(其它产品中也存在双角色连接,但分别使用一块独立芯片),在同一块芯片上使用Host-Device双角色是以往没有的。因此对比了该产品上的芯片和以往的芯片,果然不同。以往产品大多使用STM32F103系列芯片,查阅资料,发现该芯片使用的USB模块是基于USB-IP内核设计的(只能作为Device进行连接),而本产品使用STM32F105系列芯片,其USB模块是基于OTG-IP内核设计的(支持双向连接),具体如下图,其中FS表示全速,HS表示高速,另有LS表示低速没有标识出来。

         既然硬件部分就有区分,那就首先检查相关驱动是否正确,果然,该产品中Device部分的驱动仍延续了其它产品的方式,没有根据OTG内核进行相应更改,这肯定是有问题的,接下来的工作就是去更换适配的驱动,并保证不影响原来的产品功能。

3.驱动移植过程

3.1 驱动文件下载

        去ST官网上寻找标准固件库中与USB相关的外设驱动,在这里可以找到OTG内核的驱动:STSW-STM32046 - STM32F105/7、STM32F2和STM32F4 USB on-the-go主机和设备库(UM1021) - 意法半导体STMicroelectronicsSTSW-STM32046 - STM32F105/7、STM32F2和STM32F4 USB on-the-go主机和设备库(UM1021), STSW-STM32046, STMicroelectronicshttps://www.st.com/zh/embedded-software/stsw-stm32046.html        也有USB内核的驱动:

STSW-STM32121 - STM32F10x、STM32L1xx和STM32F3xx USB全速器件库(UM0424) - 意法半导体STMicroelectronicsSTSW-STM32121 - STM32F10x、STM32L1xx和STM32F3xx USB全速器件库(UM0424), STSW-STM32121, STMicroelectronicshttps://www.st.com/zh/embedded-software/stsw-stm32121.html        下载下来后,文件内容如下,其中Libraries里包含了标准库函数和USB驱动,而Project则是st官方给出的USB例程:

         打开Libraries,主要关注以下三个文件夹:

  • STM32_USB_Device_Library,包含设备类的驱动,即不同类型的设备运行所需要的驱动;
  • STM32_USB_HOST_Library,包含主机类的驱动,即不同类型的主机运行所需要的驱动;
  • STM32_USB_OTG_Driver,即最核心的USB驱动,包括底层的通信和初始化以及中断处理。

3.2 项目结构

        从st官方给出的USB驱动组织结构图入手,如下图:最底层(左边框)为主机和设备的底层驱动,官方称为“low-lever driver”;中间层为主机和设备的驱动库,告诉芯片如何调用这些底层驱动进行USB通信;最顶层为主机和设备类的应用层,对不同的使用场景进行了分化。

        

3.2.1 底层驱动Low-lever driver

        我们从主机和设备的底层驱动出发,如下图:最下面的CIL就是核心接口层,对硬件部分进行了抽象(就是数据结构),还包含了USB通信的各种操作。中间层定义的是主机和设备如何调度CIL的资源以及底层的回调函数(HCDDCD,我们不会直接使用),还有中断操作HCD INTDCD INT),这里的HD表示的是主机和设备。最上面则是主机和设备的应用,为下一部分内容。

         这些底层驱动对应的是STM32_USB_OTG_Driver文件夹下的src源文件,是不是一目了然?

3.2.2 设备端驱动

        接着看设备的驱动结构,最底层的仍然是CIL、设备回调和设备中断,如下图:

        重点在中间层,左边是USB library module。告诉USB设备如何进行初始化、在通信过程中的不同状态,以及面对主机的不同请求,如何接收和传输数据。此外,USB设备进行数据传输是基于端点的概念,端点成对存在,每对端点都分为INOUT两个方向,注意,这里的方向都是相对于主机的,即IN表示设备发送给主机,即主机接收;而OUT表示设备接收来自主机的数据,即主机发送

        右边是USB class module,针对不同应用场景对USB设备进行区分,如HID人机交互设备、audio音视频传输设备、MSC大容量存储设备,平时在设备管理器里就能找到这些不同类型的设备(当然,有些设备不是通过USB进行连接的),如下图。区分device class的目的就是为了更好的操作不同用途的USB设备。

        最顶层的是application module,即设备的应用层,这里就比较个性化了,可以直接使用设备库提供给用户的回调函数进行编写;也可以作者自己调用中间层的设备核心函数来编写应用。

3.2.3 主机端驱动

        最后看主机的驱动结构,如下图:

        同样的,其中间层、应用层和设备的驱动结构基本一致,不同的是,由于主机需要主动寻找设备,验证设备,请求设备,整个过程会更加复杂些,这个过程被称为“枚举(Enumeration)”,此外,主机传输数据是建立在通道(Channel)的基础之上,因此需要管理好通信管道。

3.3 驱动的迁移         

3.3.1 底层驱动

        结合上一节的项目结构分析,先来迁移底层核心驱动(low-level driver),即下图中的STM32_USB_OTG_Driver文件夹:

        先看源代码src

  • usb_bsp_template.c, 为USB的外设驱动文件,注意这个文件只是个模板,通常需要拷贝至具体的应用文件夹下并改名为usb_bsp.c,然后再修改,主要使用到几个函数:
void USB_OTG_BSP_Init(USB_OTG_CORE_HANDLE * pdev) //外设的初始化,如USB外设连接的IO口

void USB_OTG_BSP_EnableInterrupt(USB_OTG_CORE_HANDLE * pdev) //使能USB相关中断

void  USB_OTG_BSP_ConfigVBUS(USB_OTG_CORE_HANDLE *pdev) //配置VBUS电源对应的IO口
  • usb_core.c,USB核心文件,包含通信过程,状态机等;
  • usb_dcd.c,USB设备的底层核心,包括设备的初始化,端点通信和端点状态的相关操作;
  • usb_dcd_int.c,USB设备的中断函数集,主入口(全速下)如下函数,在该函数里会通过寄存器进入到具体的中断函数中,如端点接收数据中断(DCD_HandleOutEP_ISR)。
uint32_t USBD_OTG_ISR_Handler (USB_OTG_CORE_HANDLE *pdev) //设备中断函数主入口
  • usb_hcd.c,USB主机的底层核心,需要用到主机模式时再加入;
  • usb_hcd_int.c,USB主机的中断函数集,需要用到主机模式时再加入;
  • usb_otg.c,包含otg模式下的一些驱动配置,在这里不需要用到;

        再看头文件inc,主要看几个特殊的(其它的基本和源文件相对应):

  • usb_conf_template.h,OTG的配置文件,相当于USB的功能开关,根据实际需求选择全速还是高速,并修改FIFO大小,以及是否启用VBUS检测。该这个文件也只是个模板,通常需要拷贝至具体的应用文件夹下并改名为usb_conf.h,然后进行修改
  • usb_core.h,定义了一个重中之重的对象类型,即链接USB所有功能和底层寄存器的USB_OTG_CORE_HANDLE类型,不用修改;
  • usb_defines.h,包括跟速度、端点、通道等相关的宏定义,通常不用修改;
  • usb_regs_h,寄存器的抽象,不用修改;

3.3.2 设备层驱动

        设备层驱动包括通用的设备核心驱动Core,和针对不同设备开发的类驱动Class(主机端也是一样的),先看核心驱动中的源函数Core/src

  • usbd_core.c,设备的核心,包括设备的初始化USBD_Init()函数,设备的三种状态,数据传输的三个阶段,以及其它一些与通信和连接相关的控制函数,通常不需要修改;
  • usbd_ioreq.c,用于处理USB的Transactions结果,不需要修改;
  • usbd_req.c,包含设备端答复主机端不同请求的返回实现,如返回DescriptorConfig等,不需要修改;

        头文件inc中主要看几个特殊的,其它的头文件与源文件相对应:

  • usbd_conf_template.h,设备库的配置文件,定义了不同设备在控制传输中使用的端点,该文件也是一个模板,通常需要复制到应用文件夹下并更名为usbd_conf.h,通常也不需要修改;
  • usbd_usr.h,包含用户回调函数的头文件,这些回调函数在源文件usbd_usr.c中定义(官方通常不会编写,需要自己编写一些应用代码),下面会提到;

        

        再看设备类驱动Class,标准库中给出了五种不同的设备类型(实际上是老版的),目前还加入customHID和hid_cdc_wrapper等复合类型,本文主要使用的是customHID类,仅包含一个源文件和一个头文件:

  • usbd_customhid_core.c,包含customHID设备类的回调函数集USBD_CUSTOMHID_cb,是一个结构体,用在设备驱动初始化时传入(每个设备类都会有一个这样的回调函数集,用于区分不同的设备类),在特定阶段时会调用这些函数,通常在有需要的地方进行修改,如数据传入(DataOut)和接收(DataIn)。
/* usbd_customhid_core.c */
USBD_Class_cb_TypeDef  USBD_CUSTOMHID_cb = 
{
  USBD_CUSTOM_HID_Init,
  USBD_CUSTOM_HID_DeInit,
  USBD_CUSTOM_HID_Setup,
  NULL, /*EP0_TxSent*/  
  USBD_CUSTOM_HID_EP0_RxReady, /*EP0_RxReady*/ /* STATUS STAGE IN */
  USBD_CUSTOM_HID_DataIn, //设备发送数据到主机
  USBD_CUSTOM_HID_DataOut, //设备接收到来自主机的数据
  NULL, /*SOF */
  NULL,
  NULL,      
  USBD_CUSTOM_HID_GetCfgDesc,
#ifdef USB_OTG_HS_CORE  
  USBD_CUSTOM_HID_GetCfgDesc, /* use same config as per FS */
#endif  
};
  • usbd_customhid_core.h,与上面的源文件相对应。

        此外,有一些文件不在官方给的驱动里,而在官方给的例程中,可以直接拷贝相应设备类例程中的文件到自己的应用层文件夹下,有需要再进行修改:

  • usbd_usr.c,包含USB事件回调函数,USB设备进入不同的事件后,会调用这些函数,如设备初始化、连接、挂起、恢复等,根据需要进行修改

/* usbd_usr.c */
USBD_Usr_cb_TypeDef USR_cb = {
  USBD_USR_Init, //初始化
  USBD_USR_DeviceReset, //复位
  USBD_USR_DeviceConfigured, //设备配置完毕
  USBD_USR_DeviceSuspended, //挂起
  USBD_USR_DeviceResumed, //恢复
  USBD_USR_DeviceConnected, //设备连接上
  USBD_USR_DeviceDisconnected, //设备断开后
};
  • usbd_desc.c,设备描述符相关,包括设备的各种描述符以及获取这些描述符的函数;

3.4 回调函数的修改

        需要修改回调函数的地方通常就两处:分别是usbd_customhid_core.c

usbd_usr.c,在上面已经有提到。

3.5 中断函数

        USB的主中断入口通常放在stm32xxx_it.c源文件下,由于在这里使用的是USB全速传输(USE_USB_OTG_FS),中断入口函数便被定义为OTG_FS_IRQHandler()

/* stm32xxx_it.c */
#ifdef USE_USB_OTG_FS
void OTG_FS_IRQHandler(void)
#else
void OTG_HS_IRQHandler(void)
#endif
{
  OS_CPU_SR  cpu_sr;

  OS_ENTER_CRITICAL();                         /* Tell uC/OS-II that we are starting an ISR          */
  OSIntEnter();
  OS_EXIT_CRITICAL();	
  if(USB_HOST_DEVICE >= CONNECT_HOST) //自己定义的变量,用于区分当前是主机还是设备模式
  USBH_OTG_ISR_Handler(&USB_OTG_Core_dev); //主机端USB中断
  else if(USB_HOST_DEVICE == CONNECT_DEVICE)
  USBD_OTG_ISR_Handler(&USB_OTG_Core_dev); //设备端USB中断
  OSIntExit();                                 /* Tell uC/OS-II that we are leaving the ISR          */
}

        为了使用USBH_OTG_ISR_Handler()USBD_OTG_ISR_Handler()两个中断入口,需要包含usb_hcd_int.husb_dcd_int.h两个头文件,具体的,我们进入到USBD_OTG_ISR_Handler()函数中:该函数也是设备中断的总入口,在该函数里,通过对不同寄存器值(不是直接读取,而是通过对象间接获取)进行判断,进入到相应的中断中:

/* usb_dcd_int.c */
uint32_t USBD_OTG_ISR_Handler (USB_OTG_CORE_HANDLE *pdev)
{
  USB_OTG_GINTSTS_TypeDef  gintr_status;
  uint32_t retval = 0;
  
  if (USB_OTG_IsDeviceMode(pdev)) /* ensure that we are in device mode */
  {
    gintr_status.d32 = USB_OTG_ReadCoreItr(pdev);
    if (!gintr_status.d32) /* avoid spurious interrupt */
    {
      return 0;
    }
    
    if (gintr_status.b.outepintr)
    {
      retval |= DCD_HandleOutEP_ISR(pdev); //数据接收中断,即接收FIFO非空
    }    
    
    if (gintr_status.b.inepint)
    {
      retval |= DCD_HandleInEP_ISR(pdev); //数据发送中断,即发送FIFO为空
    }
    
    if (gintr_status.b.modemismatch)
    {
      USB_OTG_GINTSTS_TypeDef  gintsts;
      
      /* Clear interrupt */
      gintsts.d32 = 0;
      gintsts.b.modemismatch = 1;
      USB_OTG_WRITE_REG32(&pdev->regs.GREGS->GINTSTS, gintsts.d32);
    }
    
    if (gintr_status.b.wkupintr)
    {
      retval |= DCD_HandleResume_ISR(pdev);
    }
    
    if (gintr_status.b.usbsuspend)
    {
      retval |= DCD_HandleUSBSuspend_ISR(pdev);
    }
    if (gintr_status.b.sofintr)
    {
      retval |= DCD_HandleSof_ISR(pdev);
      
    }
    
    if (gintr_status.b.rxstsqlvl)
    {
      retval |= DCD_HandleRxStatusQueueLevel_ISR(pdev);
      
    }
    
    if (gintr_status.b.usbreset)
    {
      retval |= DCD_HandleUsbReset_ISR(pdev);
      
    }
    if (gintr_status.b.enumdone)
    {
      retval |= DCD_HandleEnumDone_ISR(pdev);
    }
    
    if (gintr_status.b.incomplisoin)
    {
      retval |= DCD_IsoINIncomplete_ISR(pdev);
    }

    if (gintr_status.b.incomplisoout)
    {
      retval |= DCD_IsoOUTIncomplete_ISR(pdev);
    }    
#ifdef VBUS_SENSING_ENABLED
    if (gintr_status.b.sessreqintr)
    {
      retval |= DCD_SessionRequest_ISR(pdev);
    }

    if (gintr_status.b.otgintr)
    {
      retval |= DCD_OTG_ISR(pdev);
    }   
#endif    
  }
  return retval;
}

     这里面也没有什么需要改的,不同的中断函数内会调用不同的回调函数,如果应用层需要使用某个中断,直接在上一节的回调函数中进行编写即可。

3.6 描述符

        描述符的修改是个细节,105系列USB驱动的描述符文件位置有两处:

  • usbd_desc.c,包含设备描述符和各种字符串描述符(语言、厂家、接口等);
  • usbd_customhid_core.c(其它设备类型找相应的core就行了),包含Report报告和配置描述符,我在迁移最后阶段弄了很久,最后发现是Report不一致;

4.总结

        在发现问题所在时便就有个疑惑:既然驱动都用错了,应该不可能能连接上,而不是先连接上,但会偶发错误!后面在调试的过程中,我反复地对比USB驱动和OTG驱动在运行过程中的数据,发现二者所用的数据结构基本类似,相关寄存器也能对应的上,可以说这两套驱动本是同源(哭笑),但内核不一样终究会出问题。

        本文虽然写了不少,但内容只是个大概,没有针对每个文件进行细说,这里边还是得自己边调试边照着文档一步步来。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值