//=========================By xiaowei
/*
*/
//=========================
1、SGM7227,USB高速切换开关,OE是芯片使能,低电平才能使总线导通;
S脚是切换控制;
https://segmentfault.com/a/1190000015995506
stm32自带USB接口,OTG-FS(全速)和OTG-HS(高速),因为stm32f4只带有高速PHY,想使用高速模式,就需要外扩高速PHY,在此为USB3300。
系统配置一个Device端口,一个Host端口;
Device端口连接主机,在此处为POS机,外接USB3300作为高速PHY;
Host端口连接打印进,使用了STM32内置的全速PHY;
移植时,我们重点要修改的就是 USB_APP 文件夹下面的代码。其他代码(USB_OTG 和USB_DEVICE 文件夹下的代码)一般不用修改。
需要修改的文件:
usb_bsp.c 提供了几个 USB 库需要用到的底层初始化函数,包括: IO 设置、中断设置、VBUS配置以及延时函数等,需要我们自己实现。 USB Device(Slave)和 USB Host 共用这个.c 文件。
usbd_desc.c 提供了 USB 设备类的描述符,直接决定了 USB 设备的类型、断点、接口、字符串、制造商等重要信息。这个里面的内容,我们一般不用修改,直接用官方的即可。注意,这里: usbd_desc.c 里面的: usbd 即 device 类,同样: usbh 即 host 类,所以通过文件名我们可以很容易区分该文件是用在 device 还是 host,而只有 usb 字样的那就是 device 和 host 可以共用的。
usbd_usr.c 提供用户应用层接口函数,即 USB 设备类的一些回调函数,当 USB 状态机处理完不同事务的时候,会调用这些回调函数,我们通过这些回调函数,就可以知道 USB 当前状态,比如:是否枚举成功了?是否连接上了?是否断开了?等,根据这些状态,用户应用程序可以执行不同操作,完成特定功能。
usbd_storage_msd.c 提供一些磁盘操作函数,包括支持的磁盘个数,以及每个磁盘的初始化和读写等函数。本章我们设置了 2 个磁盘: SD 卡和 SPI FLASH。
这些文件都是以回调函数的方式被内核调用,需要用户编写具体的应用程序;
使用时,需要定义 USB_OTG_CORE_HANDLE ,是一个全局结构体类型,用于存储 USB 通信中 USB 内核需要使用的的各种变量、状态和缓存等,任何 USB 通信(不论主机,还是从机),我们都必须定义这么一个结构体以实现 USB 通信。
USB 初始化非常简单,只需要调用 USBD_Init 函数即可,本章的 USB 读卡器属于 USB 设备类,所以使用该函数。该函数初始化了USB 设备类处理的各种回调函数,以便 USB 驱动库调用。执行完该函数以后, USB 就启动了,所有 USB 事务,都是通过 USB 中断触发,并由 USB 驱动库自动处理。
USB设备需要存储介质,需要用户定义存储介质驱动。
USB驱动会自己调用处理函数进行处理,用户需要改动的是底层的驱动,即磁盘的读写;具体在usbd_storage_msd.c里面,
磁盘回调函数结构体:
USBD_STORAGE_cb_TypeDef USBD_MICRO_SDIO_fops =
{
STORAGE_Init,
STORAGE_GetCapacity,
STORAGE_IsReady,
STORAGE_IsWriteProtected,
STORAGE_Read,
STORAGE_Write,
STORAGE_GetMaxLun,
(int8_t *)STORAGE_Inquirydata,
};
读操作:
int8_t STORAGE_Read (uint8_t lun,
uint8_t *buf,
uint32_t blk_addr,
uint16_t blk_len)
{
buf=&U_DISK[blk_addr*512];
return 0;
}
这个博客分析的挺好:
https://www.cnblogs.com/Daniel-G/p/3993883.html
USB_OTG_CORE_HANDLE 是全局变量,
typedef struct USB_OTG_handle
{
USB_OTG_CORE_CFGS cfg;
USB_OTG_CORE_REGS regs;
#ifdef USE_DEVICE_MODE
DCD_DEV dev;
#endif
#ifdef USE_HOST_MODE
HCD_DEV host;
#endifdda
#ifdef USE_OTG_MODE
OTG_DEV otg;
#endif
}
USB_OTG_CORE_HANDLE , *PUSB_OTG_CORE_HANDLE;
USB_OTG_CORE_HANDLE分析:
这是个全局结构体,内部存储内核处理USB设备,主机的变量;
当USB作为Device设备时,USB_OTG_CORE_HANDLE定义为DCD_DEV ;
其参数如下:
typedef struct _DCD
{
uint8_t device_config;
uint8_t device_state;
uint8_t device_status;
uint8_t device_old_status;
uint8_t device_address;
uint8_t connection_status;
uint8_t test_mode;
uint32_t DevRemoteWakeup;
USB_OTG_EP in_ep [USB_OTG_MAX_TX_FIFOS];
USB_OTG_EP out_ep [USB_OTG_MAX_TX_FIFOS];
uint8_t setup_packet [8*3];
USBD_Class_cb_TypeDef *class_cb;
USBD_Usr_cb_TypeDef *usr_cb;
USBD_DEVICE *usr_device;
uint8_t *pConfig_descriptor;
}
DCD_DEV , *DCD_PDEV;
DCD_DEV 结构说明:
DCD_DEV结构包含用于实时保存与设备,控制传输状态机以及端点信息和状态相关的所有信息的所有变量和结构。
USB_OTG_CORE_HANDLE USB_OTG_Core;
用来作为主机结构;__ALIGN_BEGIN USB_OTG_CORE_HANDLE USB_OTG_dev __ALIGN_END ;
用来作为从机设备;On-The-Go,即OTG技术就是实现在没有Host的情况下,实现设备间的数据传送。例如数码相机直接连接到打印机上,通过OTG技术,连接两台设备间的USB口,将拍出的相片立即打印出来;也可以将数码照相机中的数据,通过OTG发送到USB接口的移动硬盘上,野外操作就没有必要携带价格昂贵的存储卡,或者背一个便携电脑。
文件描述:该文件处理USB事务的结果。
这两个文件为USB库提供设备接口层封装;
其中usb_dcd文件包含以下函数:
DCD_EP_PrepareRx
DCD_EP_Tx
DCD_EP_Stall
DCD_EP_ClrStall
DCD_EP_Flush
USB OTG低电平驱动器的DCD层有一个必须通过USB中断调用的功能(高速或全速):
DCD_Handle_ISR (USB_OTG_CORE_HANDLE *pdev)
usb_dcd_int文件说明:
上图是USB设备库构架;
如上图所示,USB设备库由两个主要部分组成:库核心和类驱动程序
库核心包括三个主要部分:
USB device core
USB requests
USB I/O requests
USB设备库处理流程图
上图的分析:
分为4个模块,
上层应用;
设备库和设备类
底层驱动
上层应用调用USBD_init()函数来调用USBD库;同时应用层还为用户提供了回调函数接口,在调用USBD库时调用;
上层应用与设备类的交互是通过USBD_Class_cb来调用的,从上一张图中可知,USB的设备类分为很多种,HID,audio,MSC等等;
上层应用与底层驱动的交互是通过几个中断函数联系的;
MSC设备类包含文件:
usbd_msc_core ----- MSC类的核心处理文件
usbd_msc_bot -------bot协议封装
usbd_msc_scsi----------SCSI指令封装
MSC(Mass storage class)
USB大容量存储类是围绕仅批量传输(BOT)构建的。 它使用SCSI透明命令集。
SCSI指令集:
usbd_desc.c 提供了 USB 设备类的描述符,直接决定了 USB 设备的类型、断点、接口、字
符串、制造商等重要信息。
在这里,需要将单片机伪装成打印机,因此需要封装其USB信息。
需要讲解一个概念:USB设备描述符;
说到USB设备,不得不提到各种描述符(descriptors), 一般来说,描述符有如下几种:
1:设备描述符(Device Descriptors)
2:配置描述符(Configuration Descriptors)
2:接口描述符(Interface Descriptors)
3:端点描述符(Endpoint Descriptors)
图片说明:设备描述符定义了配置数多少,配置描述符又定义了端口多少,一层层向下定义。
USB描述符信息存储在USB设备中,在枚举过程中,USB主机会向USB设备发送GetDescriptor请求,
USB设备在收到这个请求之后,会将USB描述符信息返回给USB主机,USB主机分析返回来的数据,判断出该设备是哪一种USB设备,建立相应的数据链接通道。
设备描述符包含信息:
描述符(Descriptor )是一个完整的数据结构, 存储在USB 设备中, 用于描述一个USB 设备的所有属性。USB主机通过一系列命令要求设备发送这些信息。
USB 设备的属性包括很多内容, 为了便于管理, USB2.0协议将这些信息做了分类, 定义了多种描述符,包括标准和HID类的描述符,下面是标准描述符描述。
标准描述符
标准描述符 | bDescriptorType字段 |
---|---|
设备描述符(Device Descriptor) | 0x01 |
配置描述符(Configuration Descriptor) | 0x02 |
字符串描述符(String Descriptor,可选) | 0x03 |
接口描述符(Interface Descriptor) | 0x04 |
端点描述符(Endpoint Descriptor) | 0x05 |
设备限定描述符(DEVICE_QUALIFIER) | 0x06 |
其他速率配置描述符(OTHER_SPEED_CONFIGURATION) | 0x07 |
接口功率描述符(INTERFACE_POWER) | 0x08 |
一个设备有且只能有一个设备描述符,之后的描述符都允许有多个不同的描述符。
设备描述符(Device Descriptor) |
偏移量 | 字段名称 | 长度(Byte) | 字段值 | 意义 |
---|---|---|---|---|
0 | bLength | 1 | 0x12 | 设备描述符的字节数大小 |
1 | bDescriptorType | 1 | 0x01 | 设备描述符类型编号 |
2 | bcdUSB | 2 | 协议版本2.31其值就是0x0231 | USB版本号 |
4 | bDeviceClass | 1 | 类 | USB分配设备类代码(1-FE)FF为厂商自定义 |
5 | bDeviceSubClass | 1 | 子类 | USB分配子类代码 |
6 | bDeviceProtocol | 2 | 协议 | USB分配设备协议代码 |
7 | bMa xPack et Size0 | 1 | 控制传输端点0包大小 | 端点0最大包大小 |
8 | idVendor | 2 | ID编号 | 厂商编号 |
10 | idProduct | 2 | ID编号 | 产品编号 |
12 | bcdDevice | 2 | BCD码 | 设备出厂编号 |
14 | iManufacturer | 1 | 索引 | 描述厂商字符串索引 |
15 | iProduct | 1 | 索引 | 描述产品字符串的索引 |
16 | iSerialNumber | 1 | 索引 | 描述设备序列号字符串的索引 |
17 | b NumConf igur at ions | 1 | 配置描述符个数 | 可能配置数量 |
配置描述符(Configuration Descriptor) |
偏移量 | 字段名称 | 长度(字节) | 字段值 | 意义 |
---|---|---|---|---|
0 | bLength | 1 | 0x09 | 配置描述符的字节数大小 |
1 | bDescriptorType | 1 | 0x02 | 配置描述符类型编号 |
2 | wTotalLength | 2 | 数字 | 这是一个以2 字节二进制数为内容的字段。该字段表示该配置所返回的所有描述符( 包括配置、接口和端点描述符) 的大小总和。 |
4 | bNumInterfaces | 1 | 接口描述符个数 | 此配置所支持的接口数量 |
5 | bConfigurationValue | 1 | 数字 | Set_Configuration /Get_Configuration命令需要的参数值 |
6 | iConfiguration | 1 | 索引 | 描述该配置的字符串的索引值 |
7 | bmAttributes | 1 | 位图 | 供电模式的选择,第7位D7保留固定为1;D6值为1表示自供电,值为0表示总线供电; D5值为1表示支持远程唤醒,值为0则不支持;D4~D0没有意义固定为0 |
8 | MaxPower | 1 | 字段值*2(mA) | 设备从总线提取的最大电流(<=500mA) |
接口描述符(Interface Descriptor) |
偏移量 | 字段名称 | 长度(字节) | 字段值 | 意义 |
---|---|---|---|---|
0 | bLength | 1 | 0x09 | 接口描述符的字节数大小 |
1 | bDescriptorType | 1 | 0x04 | 接口描述符类型编号 |
2 | bInterfaceNumber | 1 | 数字 | 该接口编号 |
3 | bAlternateSetting | 1 | 数字 | |
4 | bNumEndpoints | 1 | 如果为0则说明只用了端点0 | 该接口使用的端点数 ,不包括端点0 |
5 | bInterfaceClass | 1 | 类 | 接口类型 |
6 | bInterfaceSubClass | 1 | 子类 | 接口子类类型 |
7 | bInterfaceProtocol | 1 | 协议 | 接口遵循的协议 |
8 | iInterface | 1 | 索引 | 描述该接口的字符串索引值 |
端点描述符(Endpoint Descriptor) |
用于接口的每个端点都有自己的描述符。 此描述符包含信息
需要主机确定每个端点的带宽要求。 端点描述符为始终由GetDescriptor(获取配置描述符),作为配置信息的一部分返回。 端点描述符不能直接用GetDescriptor()或SetDescriptor()直接访问。对于端点零,不存在端点描述符。
偏移量 | 字段名称 | 长度(字节) | 字段值 | 意义 | |
---|---|---|---|---|---|
0 | bLength | 1 | 0x07 | 端点描述符的字节数大小 | |
1 | bDescriptorType | 1 | 0x05 | 端点描述符类型编号 | |
2 | bEndpointAddress | 1 | 端点 | 端点地址及输入输出属性 | |
3 | bmAttributes | 1 | 位图 | 端点的传输类型属性 | |
4 | wMaxPacketSize | 2 | 数字 | 端点收、发的最大包的大小 | |
6 | bInterval | 1 | 数字 | 主机查询端点的时间间隔 |
__ALIGN_BEGIN uint8_t USBD_DeviceDesc[USB_SIZ_DEVICE_DESC] __ALIGN_END =
{
0x12, /*bLength */
0x01, /*bDescriptorType*/
0x00, /*bcdUSB */ // usb 版本 2
0x02, //
0x00, /*bDeviceClass*/
0x00, /*bDeviceSubClass*/
0x00, /*bDeviceProtocol*/
USB_OTG_MAX_EP0_SIZE, /*bMaxPacketSize*/ // 传输包大小 7
LOBYTE(USBD_VID), /*idVendor*/ // 厂商编码 8
HIBYTE(USBD_VID), /*idVendor*/
LOBYTE(USBD_PID), /*idVendor*/ // 产品编码 10
HIBYTE(USBD_PID), /*idVendor*/
0x00, /*bcdDevice rel. 2.00*/ // 设备出厂编号 12
0x02,
USBD_IDX_MFC_STR, /*Index of manufacturer string*/ // 厂商字符串索引 14
USBD_IDX_PRODUCT_STR, /*Index of product string*/ // 产品字符串索引 15
0x00, /*Index of serial number string*/ // 设备序列号字符串索引 16
USBD_CFG_MAX_NUM /*bNumConfigurations*/ // 配置描述符个数 17
} ; /* USB_DeviceDescriptor */
__ALIGN_BEGIN uint8_t USBD_MSC_CfgDesc[USB_MSC_CONFIG_DESC_SIZ] __ALIGN_END =
{
//---------- 配置描述符 ----------
0x09, /* bLength: Configuation Descriptor size */
0x02, /* bDescriptorType: Configuration */
0x20, // 返回数值大小
0x00,
0x01, /* bNumInterfaces: 1 interface */
0x01, /* bConfigurationValue: */
0x05, /* iConfiguration: */ // 描述配置的字符索引 USBD_MSC_CfgDesc[6]
0xC0, /* bmAttributes: */
0x32, /* MaxPower 100 mA */
//--------------------------------
//---------- 接口描述符 ----------
0x09, /* bLength: Interface Descriptor size */
0x04, /* bDescriptorType: */
0x00, /* bInterfaceNumber: Number of Interface */
0x00, /* bAlternateSetting: Alternate setting */
0x02, /* bNumEndpoints*/
// jz-80 打印机型号
0x07, /* bInterfaceClass: MSC Class */ // 接口 类型 USBD_MSC_CfgDesc[14]
0x01, /* bInterfaceSubClass : SCSI transparent*/ // 接口 子类型 USBD_MSC_CfgDesc[15]
0x02, /* nInterfaceProtocol */ // 接口 协议 USBD_MSC_CfgDesc[16]
0x04, /* iInterface: */ // 接口描述字符串索引 USBD_MSC_CfgDesc[17]
//--------------------------------
//---------- 端点1描述符 ----------
0x07, /*Endpoint descriptor length = 7*/
0x05, /*Endpoint descriptor type */
MSC_IN_EP, /*Endpoint address (IN, address 1) */ // USBD_MSC_CfgDesc[20]
0x02, /*Bulk endpoint type */
LOBYTE(MSC_MAX_PACKET), // USBD_MSC_CfgDesc[22]
HIBYTE(MSC_MAX_PACKET), // USBD_MSC_CfgDesc[23]
0x00, /*Polling interval in milliseconds */
//---------------------------------
//---------- 端点1描述符 ----------
0x07, /*Endpoint descriptor length = 7 */
0x05, /*Endpoint descriptor type */
MSC_OUT_EP, /*Endpoint address (OUT, address 1) */ // USBD_MSC_CfgDesc[27]
0x02, /*Bulk endpoint type */
LOBYTE(MSC_MAX_PACKET), // USBD_MSC_CfgDesc[29]
HIBYTE(MSC_MAX_PACKET), // USBD_MSC_CfgDesc[30]
0x00 /*Polling interval in milliseconds*/
//---------------------------------
};
概念:
PID/VID唯一标识一个设备,HardwareID是为了给系统识别的 ,他是根据PID/VID而生成的。这个与序列号没什么关系,序列号一般都是厂家固化到芯片中的信息而已。GUID只是为了标志你安装的设备是属于一个什么类当中,这个类可以显示再设备管理器中。比如:你可以定义一个类,当然这个类有与系统中任何类都不同的GUID,然后选择一个图标和类名,就可以同网卡等其他设备一起显示在设备管理器下的根目录中了
根据USB规范的规定,所有的USB设备都有供应商ID(VID)和产品识别码(PID),主机通过不同的VID和PID来区别不同的设备,VID和PID都是两个字节长。
其中,供应商ID(VID)由供应商向USB执行论坛申请,每个供应商的VID是唯一的,PID由供应商自行决定。
所以理论上一个USB存储设备的VID应该是设备生产商的VID,而不是主控生产商的VID,这两个VID应该是不同的(主控生产商自己生产的设备除外)。
USB_Host
存储主机状态结构;
使用 USB 主机的时候,需要两个结构体: USB_OTG_CORE_HANDLE和 USB_HOST。
然后, USB 初始化,使用的是 USBH_Init,用于 USB 主机初始化,包括对 USB 硬件和 USB驱动库的初始化。如果是: USB SLAVE 通信,在只需要调用 USBD_Init 函数即可,不过 USBHOST 则还需要调用另外一个函数
USBH_Process,
该函数用于实现 USB 主机通信的核心状态机处理,该函数必须在主函数里面,被循环调用,而且调用频率得比较快才行(越快越好),以便及时处理各种事务。
注意, USBH_Process 函数仅在 U 盘识别阶段,需要频繁反复调用,但是当 U 盘被识别后,剩下的操作(U 盘读写),都可以由 USB 中断处理。
在usb_dcd_int.c文件中,修改了USBD_OTG_EP1OUT_ISR_Handler
中断,可以看到里面调用函数StorePosData
将数据从xfer_buff中压缩存储到PosData.frompos_buf
中,也就是压缩进DataFromPosBuf
中。
if((PosData.status == 0)||(PosData.status == 1))
{
StorePosData(pdev->dev.out_ep[1].xfer_buff,pdev->dev.out_ep[1].xfer_count);
if(PosData.status == 0)
PosData.status = 1;
}
}
/* Inform upper layer: data ready */
/* RX COMPLETE */
USBD_DCD_INT_fops->DataOutStage(pdev , 1);
在usbh_msc_bot.c文件中,更改了USBH_MSC_HandleBOTXfer
函数,其中检测到USBH_MSC_SEND_CBW
状态,调用USBH_BulkSendData
将数据发送给设备;
//USBH_MSC_HandleBOTXfer函数处理:
switch (USBH_MSC_BOTXferParam.BOTState)
{
case USBH_MSC_SEND_CBW:
USBH_BulkSendData (pdev,
PosData.frompos_buf,
PosData.datalen ,
MSC_Machine.hc_num_out);
USBH_MSC_BOTXferParam.BOTStateBkp = USBH_MSC_SEND_CBW;
USBH_MSC_BOTXferParam.BOTState = USBH_MSC_SENT_CBW;
break;
//
USBH_BulkSendData 函数定义:
pdev ------选中的设备
buff---------发送的数据
length---------发送的数据长度
hc_num------------主机发送通道
分析此部分函数,可以了解到数据从pos到打印机的过程;
首先pos发送的数据被stm32拦截,压缩存储在DataFromPosBuf
中,然后通过中断,将指令发送给打印机。
usbh_usr.c里面定义了作为主机的用户回调函数:
这里讲解几个重要的函数:
1、USBH_USR_Device_DescAvailable
//检测到从机的描述符
//DeviceDesc:设备描述符指针
void USBH_USR_Device_DescAvailable(void *DeviceDesc)
{
USBH_DevDesc_TypeDef *hs;
hs=DeviceDesc;
printf("VID: %04Xh\r\n" , (uint32_t)(*hs).idVendor);
printf("PID: %04Xh\r\n" , (uint32_t)(*hs).idProduct);
}
这里当连接打印机后,检测打印机的设备描述符,再将VID和PID打印出来,因此我们在设置Device设备描述符时,按照这个参数设置;
VID: 0416h
PID: 5011h
2、USBH_USR_Manufacturer_String
,USBH_USR_Product_String
,USBH_USR_SerialNum_String
这三个函数用来从打印机获得其设备描述符信息;
diskio里需要用户自定义接口,对存储磁盘进行读写等;
在此处,将打印机作为一个外接磁盘,提供接口函数:
USBH_UDISK_Status
USBH_UDISK_Read
USBH_UDISK_Write
usbd_storage_msd.c是设备作为Device的数据存储相关函数,在此文件中需要用户自定义存储空间,读写操作等回调函数。
在.c文件中定义了虚拟磁盘,封装了用户读写函数;
自定义了接收pos机数据的状态结构体;
//POS信息;
typedef struct DATA_FROM_pos
{
uint8_t status; // 状态 0: 空闲 1:正在获取数据 2:获取完成,等待打印 3:正在打印 4:打印完成、等待转移
//-----------------------------------------------------------
uint8_t *frompos_buf; // 收到的数据缓冲区
uint32_t buflen; // 压缩后的长度
uint32_t datalen; // 压缩前的长度
uint8_t zerototal; // 正在压缩的 0x00 的数量
//-----------------------------------------------------------
uint8_t needprint; // 需要打印
//-----------------------------------------------------------
uint8_t *upzipPointer; // 解压指针,指向需要继续解压的位置,主缓冲区
uint8_t *sbuf; // 发送数据缓冲区
uint8_t sbuflen; // 发送数据长度
uint8_t *unzipbuf; // 解压缓冲区
uint8_t unziplen; // 解压区现存数据长度
uint8_t cmdtype; // 命令类型
uint8_t delayziplen; // 等待一些数据再开始进行压缩
//-----------------------------------------------------------
}DATA_FROM_POS;
1、status
status是一个标志位,用来标识接收数据的状态;
// 状态
0: 空闲
1:正在获取数据
2:获取完成,等待打印
3:正在打印
4:打印完成、等待转移
其中 status改变为1时,是在USBD_OTG_EP1OUT_ISR_Handler
中改变的状态;
USBD_OTG_EP1OUT_ISR_Handler
如果定义#ifdef USB_OTG_HS_DEDICATED_EP1_ENABLED
才能使用;
找到usb_conf.h文件;
在usb_conf.h文件中,注释掉了原文件的宏定义,自己定义了宏;
因为单片机模拟打印机时,使用的是高速PHY,因此USB OTG HS CONFIGURATION
对应是Device的驱动定义。
/****************** USB OTG HS CONFIGURATION **********************************/
#ifdef USB_OTG_HS_CORE
#define RX_FIFO_HS_SIZE 512
#define TX0_FIFO_HS_SIZE 128
#define TX1_FIFO_HS_SIZE 372
#define TX2_FIFO_HS_SIZE 0
#define TX3_FIFO_HS_SIZE 0
#define TX4_FIFO_HS_SIZE 0
#define TX5_FIFO_HS_SIZE 0
#define TXH_NP_HS_FIFOSIZ 256
#define TXH_P_HS_FIFOSIZ 256
// #define USB_OTG_HS_LOW_PWR_MGMT_SUPPORT // old 注释//
// #define USB_OTG_HS_SOF_OUTPUT_ENABLED // old 注释
#ifdef USE_ULPI_PHY
#define USB_OTG_ULPI_PHY_ENABLED
#endif
#ifdef USE_EMBEDDED_PHY
#define USB_OTG_EMBEDDED_PHY_ENABLED
#endif
#define USB_OTG_HS_INTERNAL_DMA_ENABLED
#define USB_OTG_HS_DEDICATED_EP1_ENABLED
#define USB_OTG_EXTERNAL_VBUS_ENABLED
#endif