【应用笔记】AN1087_APM32F4xx_OTG应用笔记

1    引言

当前APM32F4xx系列拥有高速和全速两个USB控制器,都支持OTG,其中的USB高速控制器拥有两个接口。此应用笔记基于APM32F4xx_OTG_SDK进行示例讲解,该软件开发包可以从极海官网的APM32F4xx软件支持处下载得到。

2    USB OTG简介

APM32F4xx中的OTG符合USB2.0规范,且符合On-The-Go补充标准。在常规的USB中,从机和主机身份相对固定,数据传输的控制都由主机处理和发起。而OTG则可以让器件在从机和主机中转换,既可以当主机,也可以变换为从机。OTG常应用于设备与设备之间的交互,例如打印机和相机之间,手机和U盘之间都可以利用OTG进行数据交互。 

2.1    OTG主机和从机角色切换的原理

在USB的接线的ID线可以用来区分主机和从机。ID线检测为低电平表示主机,ID线检测为高电平则为从机。MCU的ID线内接上拉电阻,当外部的ID线插入时接地,则会检测为低电平,识别为主机;当外部的ID线悬空插入,则会检测为高电平,识别为从机。

2.1.1    主机协商协议(HNP)

HNP全称为Host Negotiation Protocol。这是主机和从机之间用来进行角色互换的协议。APM32F407可以通过拉高OTG外设的全局寄存器GUSBCFG的HNPEN位开启该功能。

2.1.2    会议请求协议(SRP)

SRP全称为Session Request Protocol。此功能允许A-device在总线未使用时停止Vbus供电以降低功耗。APM32F407可以通过拉高OTG外设的全局寄存器GUSBCFG的SRPEN位开启该功能

2.2    APM32F407的USB从机特性

对于目前的APM32F4系列,APM32F407、F405、417、415系列的USB OTG模块都一样,但未来也可能出现F4系列USB模块变动的情况,所以这里就将F407当作示例。
APM32F407作从机时,可用的IN端点有4个,OUT端点有4个,合计8个端点。端点0比较特殊,仅作为控制端点。作为从机时支持全速和高速,其中,高速控制器拥有两个接口,其中有个内嵌的高速PHY,可以减少外围设备的设计。

2.3    APM32F407的USB主机特性

APM32F407作主机时,可用的主机通道有8个,每个通道在使用的时候需要配置对应的端点编号,传输类型等信息。作为主机时支持低速、全速和高速,其中高速控制器拥有两个接口。

2.4    初学者常见的疑惑解答

2.4.1    USB和OTG之间的关系?

答:USB中包含OTG,OTG可以视为USB中的一种特殊类型,专门用来设计便携式设备,实现主机和从机之间的角色切换。OTG也能当作正常的USB来使用,即忽略ID线的作用,强制MCU为主机或从机即可。

2.4.2    A-device、B-device和标准从机的区别?

答:A device为OTG主机,和标准的主机相比,A device的Vbus可以提供更小的电流以降低功耗,它可以切换为从机。B device为OTG从机,用法和标准从机基本一致,只不过可以通过控制器使其能够切换为主机。标准从机就是我们通常情况下解除的USB从机,它只能当从机,且ID线无效。

2.4.3    Vbus有什么作用?

答:Vbus即总线的电压,由USB的主机向从机提供。从机可以分为自供电设备和总线供电设备,由USB的配置描述符告知主机。从机上的Vbus线主要靠接收,在APM32F4的从机控制器中有Vbus检测的功能,也就是Vbus引脚拉高之后就判断USB插入。从机也可以选择不需要Vbus,关闭Vbus检测,在APM32F4的从机中会默认Vbus有效,且能够使用内部的上拉电阻,并通过软件控制上拉电阻的有效与否。

3    USB的从机使用示例

我们在极海官网的APM32F4xx软件支持中可以找到APM32F4xx_OTG_SDK并下载,此SDK包专门为APM32F4系列的USB提供了驱动代码和相关的主机与从机例程。在SDK包中的Project\Device_Examples文件夹中包含了三个例程,其中HID表示鼠标例程,使用的是HID类;MSC表示U盘例程,使用的是MSC大容量类;VCP表示虚拟串口例程,使用的是CDC传输类。

3.1    初始化USB从机

我们可以通过USBD_Init函数对USB初始化,其需要配置的结构体参数如下:

typedef struct

{

    USBD_Descriptor_T *pDeviceDesc;          //!< 设备描述符

    USBD_Descriptor_T *pConfigurationDesc;    //!< 配置描述符

    USBD_Descriptor_T *pStringDesc;           //!< 字符串描述符

    USBD_Descriptor_T *pQualifierDesc;         //!< 设备限定描述符

    USBD_Descriptor_T *pHidReportDesc;        //!< 报告描述符

    USBD_StdReqCallback_T *pStdReqCallback;  //!< 标准请求回调函数集合

    USBD_ReqHandler_T stdReqExceptionHandler; //!< 标准请求出错回调函数

    USBD_ReqHandler_T classReqHandler;        //!< class 请求回调函数

    USBD_ReqHandler_T vendorReqHandler;      //!< 厂商请求回调函数

    USBD_CtrlTxStatusHandler_T txStatusHandler;//!< 控制过程IN状态回调函数

    USBD_CtrlRxStatusHandler_T rxStatusHandler; //!< 控制过程OUT状态回调函数

    USBD_EPHandler_T     outEpHandler;   //!< 端点0以外的OUT端点回调函数

    USBD_EPHandler_T     inEpHandler;    //!< 端点0以外的IN端点回调函数

    USBD_ResetHandler_T  resetHandler;    //!< USB复位回调函数

    USBD_InterruptHandler_T intHandler;     //!< 中断补充处理回调函数

} USBD_InitParam_T;

图 1 USBD_InitParam_T结构体

结构体成员的含义可以参考上述代码中的注释部分。USB_Init函数的作用是传递形参中的回调函数,并且初始化USB的硬件、全局寄存器、设备寄存器和USB中断等内容。
用户可以通过配置usb_config.h文件的全局宏定义来配置全局中断、端点中断、FIFO大小等内容。关于定义的宏,下列内容作了简要解释:

  1. USB_INT_G_SOURCE:全局中断源配置;
  2. USB_INT_EP_OUT_SOURCE:OUT端点中断源配置;
  3. USB_INT_EP_IN_SOURCE:IN端点中断源配置;
  4. USB_VBUS_SWITCH:为1时,初始化时会开启VBUS感应,为0关闭;
  5. USB_SOF_OUTPUT_SWITCH:为1时,会开启SOF输出,为0关闭;
  6. USBD_CONFIGURATION_NUM:描述符配置数,一般为1;
  7. USB_EP0_PACKET_SIZE:端点0最大包的大小;
  8. USB_FS_RX_FIFO_SIZE:全速接收FIFO的大小配置
  9. USB_FS_TX_FIFO_0_SIZE:全速发送FIFO 0的大小配置
  10. USB_FS_TX_FIFO_1_SIZE:全速发送FIFO 1的大小配置
  11. USB_HS_RX_FIFO_SIZE:高速接收FIFO的大小配置
  12. USB_HS_TX_FIFO_0_SIZE:高速发送FIFO 0的大小配置
  13. USB_HS_TX_FIFO_1_SIZE:高速发送FIFO 1的大小配置
  14. DELAY_SOURCE:当其值为USE_DEFAULT时,使用默认的延时函数,值为USE_USER时,用户需要在usb_user.c文件中自定义延时函数。

3.2    中断处理说明

USB作为从机时,其通信的过程通常使用中断来进行处理。从机的中断处理过程可参考下图:

图 2 程序流程图

实际上,USB从机模式下的枚举和通信基本都依靠中断,即使在轮询中调用了收发USB数据的函数,最终也是会通过中断处理数据传输。

3.3    SETUP事务处理

① 启动Setup包传输
调用drv_usb_device.h文件中的USB_OTG_ReceiveSetupPacket函数来启动Setup包的传输。
② 在RxFIFO非空中断接收Setup包
USB接收到数据时触发RxFIFO非空中断, USBD_RxFifoNoEmptyIsrHandler函数(usbd_interrupt.c文件中)会进行处理。当RxFIFO出栈状态指示该包为Setup包时,保存至全局变量g_usbDev.reqData.pack中。
③ 处理Setup包
处理Setup包由usbd_core.c中的USBD_SetupProcess函数执行。当判断出接收到的Setup包为标准请求,则由usbd_stdReq.c中的USBD_StandardReqeust函数进行处理;类请求由全局变量的回调函数g_usbDev.classReqHandler处理;厂商请求由全局变量的回调函数g_usbDev.vendorReqHandler处理。
上述的两个回调函数是在调用USBD_Init函数初始化时对其赋值,用户在初始化时将函数名作函数指针赋值即可。

3.4    IN事务处理

3.4.1    控制端点IN事务

① 启动IN数据传输
调用usbd_core.c中的USBD_CtrlInData函数来启动IN事务并配置IN数据发送缓冲区。对于控制传输,一次启动最多传输端点0的最大包长,若需要传输的数据长度大于最大包长,则拆分成多次传输。
需要发送设备状态时,即发送0长度IN包,可使用usbd_core.c中的USBD_CtrlTxStatus函数来发送设备的状态阶段。
若IN事务中有多个IN数据包需要发送时,则除第一个包外的IN数据由drv_usb_device.c中的USB_OTG_EnableInEpTransfer来使能传输。
USBD_CtrlInData与USB_OTG_EnableInEpTransfer函数的区别在于前者配置了发送缓冲区,发送时会使用该缓冲区发送数据,后者在缓冲区配置好的情况下,直接操作寄存器使能传输,即前者包含后者
② 在TxFIFO空中断发送IN数据包
OTG SDK使用中断的方式来压栈TxFIFO,当TxFIFO达到空的阈值则会触发TxFIFO空中断,阈值由GAHBCFG寄存器中的TXFEL位决定。在中断里,调用usbd_core.c中的USBD_PushDataToTxFIFO函数来将缓冲区数据写入TxFIFO。
③ IN发送完成
IN发送完成后,会调用usbd_core.c中的USBD_CtrlInProcess函数进行处理,判断是否继续发送数据或者需要接收OUT状态。

3.4.2    其他端点IN事务

① 启动IN数据传输
调用usbd_core.c中的USBD_RxData函数来启动OUT事务。
② 在TxFIFO空中断发送IN数据包
在TxFIFO空中断,调用USBD_PushDataToTxFIFO函数来将缓冲区数据写入TxFIFO。
③ IN发送完成
IN发送完成后,由全局变量的回调函数g_usbDev.inEpHandler进行处理,该函数在初始化时赋值。

3.5    用户发送数据示例

这里的“发送”指的是从机向主机发送数据,在事务上属于IN事务。端点0发送数据调用的是USBD_CtrlInData函数,其他端点发送数据可以调用USBD_TxData 函数。
在我们调用发送函数之后,不管我们是在中断还是轮询,这只是“预”发送,真正的发送时刻在USB的发送FIFO空中断触发之后。我们调用发送函数时输入的数据指针和长度信息将会传递到g_usbDev. inBuf[ep]中,其中ep表示不同的端点。我们如果需要判断需要发送的数据是否成功地执行,我们可以去判断g_usbDev. inBuf[ep]. bufLen的时,因为在每次写入数据至发送FIFO之后,g_usbDev. inBuf[ep]. bufLen会随之递减,直到为0表示所有数据发送完成。

uint8_t data[4] = {1, 2, 3, 4};

/** 端点0默认进行的是控制传输,这里演示采用端点0发送数据 */

USBD_CtrlInData(data, 4);

/** 这里演示采用端点1发送数据,不论中断、批量还是同步端点都适用此函数 */

USBD_TxData(USB_EP_1, data, 4);

图 3 发送数据的代码示例

3.6    用户接收数据示例

这里的“接收”指的是从机向主机接收数据,在事务上属于OUT事务。端点0发送数据调用的是USBD_CtrlOutData函数,其他端点发送数据可以调用USBD_RxData 函数。
在实际的USB从机应用中,我们能够接收什么数据是由主机决定的,而接收函数只能启动一次接收,这时候当数据来临时,我们能够将数据准确地保存。接收数据常用于Class请求的处理,当我们收到主机的请求后,我们就能得知主机将要发送什么数据或者需要接收什么数据,这时候可以调用接收数据的函数来启动一次接收。

uint8_t data[4] = {0, 0, 0, 0};

/** 端点0默认进行的是控制传输,这里演示采用端点0接收数据 */

USBD_CtrlOutData(data, 4);

/** 这里演示采用端点1发送数据,不论中断、批量还是同步端点都适用此函数 */

USBD_RxData(USB_EP_1, data, 4);

图 4 接收数据的代码示例

4    USB的主机使用示例

​​​​​​​ ​​​​​​​ ​​​​​​​ ​​​​​​​ ​​​​​​​ ​​​​​​​

图 5 主机轮询状态机

主机轮询状态可以理解为状态机,需要一直被轮询。主机的状态机处理我们可以理解为是一个回调函数数组,通过访问不同的数组,达到不同的状态。我们可以用USBH_ConfigHostState函数来切换不同的主机状态。上图中的状态机中,程序刚运行时一般是设备断开的状态,若有设备插入,则会进入空闲状态,空闲状态判断是否已经插入设备,插入设备后将执行设备连接状态。前三个状态相对固定,用户不需要做额外的操作。

4.1    枚举状态

 图 6 枚举状态

枚举状态是上电后识别设备的重要状态,实际上,枚举阶段只要达到设置地址、获得设备描述符及获得配置描述符三步就可以实现设备枚举。实际应用中,我们可能还需要查看设备的字符串描述符,驱动中只会默认获取厂商字符串、产品字符串和序列号字符串。

4.2    用户输入状态

static void USBH_UserInputHandler(void)

{

    if (g_userCallback.userInputHandler() == USER_OK)

    {

        g_usbHost.classInitHandler();

    }

}

图 7 用户输入状态处理

上图的函数是用户输入状态的处理函数,处于usbd_core.c文件。我们可以看到,当用户回调函数g_userCallback.userInputHandler的返回值为USER_OK时,将会执行class初始化函数即g_usbHost.classInitHandler。
g_userCallback.userInputHandler回调函数由用户定义,例如可以在按键被按下时,返回USER_OK
class初始化函数是在USB主机初始化(USBH_Init)的时候作为输入参数将其配置完成。通常情况下,建议用户在class初始化函数中,将主机状态改变为USBH_HOST_CLASS_REQ或者USBH_HOST_CLASS状态。

4.3    Class状态

关于Class状态可以分为两个部分,一部分是采用端点0通信的控制请求,另一部分是端点0以外的其他端点传输。例如HID类的获取报告描述符就输入控制请求,而进行中断传输的是另一种class处理。
USBH_Init初始化函数中的classReqHandler结构体成员是class请求处理的回调函数,而另一个成员classCoreHandler是class处理的主要回调函数。用户应当在初始化阶段构建合适的class回调函数。

4.4    Suspend、Wake-up和Error状态

这三个状态分别是挂起状态,唤醒状态和错误状态。错误状态会在一些出错情景下触发,而挂起和唤醒状态需要用户在特点的场合去切换,并在调用USBH_Init初始化函数的时候构建好对应的回调函数。

4.5    用户接口说明

在usb_user.c文件中构建了一个g_userCallback全局结构体变量,其结构体成员都是回调函数。此全局变量可以由用户根据需要而进行修改。

typedef struct

{

  USER_InitHandler_T                initHandler;

  USER_DeInitHandler_T             deInitHandler;

  USER_ResetDevHandler_T          resetDevHandler;

  USER_DevAttachedHandler_T       devAttachedHandler;

  USER_DevDetachedHandler_T       devDetachedHandler;

  USER_DevSpeedDetectedHandler_T  devSpeedDetectedHandler;

  USER_DevDescHandler_T           devDescHandler;

  USER_CfgDescHandler_T           cfgDescHandler;

  USER_ManufacturerStringHandler_T  manufacturerStringHandler;

  USER_ProductStringHandler_T       productStringHandler;

  USER_SerialNumStringHandler_T    serialNumStringHandler;

  USER_EnumDoneHandler_T         enumDoneHandler;

  USER_UserInputHandler_T          userInputHandler;

  USER_ApplicationHandler_T         applicationHandler;

  USER_DeviceNotSupportedHandler_T deviceNotSupportedHandler;

  USER_UnrecoveredErrHandler_T     unrecoveredErrHandler;

  USER_DelayCallBack_T             delay;

} USB_UserCallBack_T;

关于回调函数结构体的成员:
initHandler将在USBH_Init函数执行的时候触发,用户可以进行初始化操作;
deInitHandler在USB设备断开或者发送错误的时候触发,用户可以进行某些恢复操作;
resetDevHandler在设备连接时复位端口之后触发,用户可以进行复位操作;
devAttachedHandler在设备连接之后触发,用户可以进行连接之后的某些操作;
devDetachedHandler在设备断开状态中触发,用户可以进行设备断开的处理;
devSpeedDetectedHandler在设备连接时,检测出设备的速度之后触发,关于设备的速度,即g_usbHost.speed,值为0时为高速,1代表全速,2代表低速;
devDescHandler在获得设备描述符之后触发,用户可以在这里提取设备描述符;
cfgDescHandler在获得配置描述符之后触发,用户可以在这里提取配置描述符;
manufacturerStringHandler在获得厂商字符串之后触发,用户可以在这里提取厂商字符串;
productStringHandler在获得产品字符串之后触发,用户可以在这里提取产品字符串;
serialNumStringHandler在获得序列号字符串之后触发,用户可以在这里提取序列号字符串;
enumDoneHandler在设备枚举完成之后触发,用户可以在这里进行特点的操作;
userInputHandler在主机轮询状态机中触发,其返回值为USER_OK时,会执行初始化时配置的classInitHandler回调函数,意味着处理枚举阶段之后的class阶段;
applicationHandler实际上由用户去构造和安放,通常可以放在应用层空闲时轮询阶段;
deviceNotSupportedHandler属于错误状态的一种,当主机不支持此类设备时触发;
unrecoveredErrHandler属于错误状态的一种,表面此类错误为未知错误;
delay是USB延时函数,用户可以使用定时器延时或者变量计数延时等。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值