技术标签: USB学习 usb复合设备模式 USB hid设备添加 stm32 usb hid设备
最近因为项目需要,我们希望单片机既能有hid键盘功能,又能有hid设备的功能。即单片机的一个usb接口插入电脑后,电脑能识别出键盘设备和hid设备,两者同时存在的。
基于项目只是要求实现功能,故本次只是对stm32usb应用方面进行了研究,并未对usb进行更深层次的了解,所以本blog只是记录了,如何基于单片机hid键盘例程,修改成hid键盘+hid设备过程及遇到的问题。
图1单hid设备,在电脑中的显示:
图2单键盘设备在电脑中的显示:
图3 hid键盘+hid设备在电脑中的显示:
本次的目标就是,将stm32 usb插入电脑后,电脑能生成设备键盘+hid设备的节点,即图3示。
在修改移植代码中遇到一些问题,或测试数据需要一些工具(下载请自行百度)。
(1)其中使用bus hound来抓取usb收发的数据内容,通过该工具可以抓到usb设备收发的内容。
(2)使用hid收发工具,就像串口工具一样,可以测试hid设备的收发
上述两个工具,网上都可下载到。
在这部分,我们进入具体的移植详情讲解,以及将所遇到的问题进行说明。
因为底层的usb相关协议,原厂以及做好,作为开发应用的我们,暂时只需要关注应用方面。而对于应用方面,我们要第一个要关注的就是usb_desc.c文件。
当usb插入电脑的时候,usb的host与设备之间就会立即通信,host就会询问usb设备相关信息,入设备类型,支持的协议版本,usb的厂商标识等等。这些内容就在usb_desc.c文件下的设备描述结构体中,用户可根据需要修改这部分的内容。
#define USB_VID 0x5548
#define USB_PID 0x6666
#define DEV_VER 0x0100
/* USB Standard Device Descriptor */
const uint8_t CustomHID_DeviceDescriptor[CUSTOMHID_SIZ_DEVICE_DESC] =
{
0x12, /*bLength */
USB_DEVICE_DESCRIPTOR_TYPE, /*bDescriptorType*/
0x00, /*bcdUSB */
0x02,
0x00, /*bDeviceClass*/
0x00, /*bDeviceSubClass*/
0x00, /*bDeviceProtocol*/
0x40, /*bMaxPacketSize40*/
USB_VID&0xff, /*idVendor*/
USB_VID>>8,
USB_PID&0xff, /*idProduct*/
USB_PID>>8,
DEV_VER&0xff, /*bcdDevice rel. 1.00*/
DEV_VER>>8,
1, /*Index of string descriptor describing
manufacturer */
2, /*Index of string descriptor describing
product*/
3, /*Index of string descriptor describing the
device serial number */
0x01 /*bNumConfigurations*/
}; /* CustomHID_DeviceDescriptor */
上述字段中bMaxPacketSize40字段,是告知usb host本设备能支持传输的最大字节,一般一个标准的hid设备,每次传输的包就是64字节,所以64的十六进制就是0x40。上述结构中,除了关注这个字节数外,还可能常改的就是usb 的VID和PID。
usb设备描述信息之后便是,usb的设备配置描述结构。该设备配置信息结构向host上报本身详细的配置信息以及设备所支持的能力,原本例程只是hid键盘相关的配置,所以我们需要在此结构添加相关信息。
/* USB Configuration Descriptor */
/* All Descriptors (Configuration, Interface, Endpoint, Class, Vendor */
const uint8_t CustomHID_ConfigDescriptor[CUSTOMHID_SIZ_CONFIG_DESC] =
{
0x09, /* bLength: Configuration Descriptor size */
USB_CONFIGURATION_DESCRIPTOR_TYPE, /* bDescriptorType: Configuration */
CUSTOMHID_SIZ_CONFIG_DESC,
/* wTotalLength: Bytes returned */
0x00,
0x02, /* bNumInterfaces: 1 interface */
0x01, /* bConfigurationValue: Configuration value */
0x00, /* iConfiguration: Index of string descriptor describing
the configuration*/
0xC0, /* bmAttributes: Self powered */
0x32, /* MaxPower 100 mA: this current is used for detecting Vbus */
/************** Descriptor of Custom HID interface ****************/
/* 09 */
0x09, /* bLength: Interface Descriptor size */
USB_INTERFACE_DESCRIPTOR_TYPE,/* bDescriptorType: Interface descriptor type */
0x00, /* bInterfaceNumber: Number of Interface */
0x00, /* bAlternateSetting: Alternate setting */
0x02, /* bNumEndpoints */
0x03, /* bInterfaceClass: HID */
0x01, /* bInterfaceSubClass : 1=BOOT, 0=no boot */
0x01, /* nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse */
0, /* iInterface: Index of string descriptor */
/******************** Descriptor of Custom HID HID ********************/
/* 18 */
0x09, /* bLength: HID Descriptor size */
HID_DESCRIPTOR_TYPE, /* bDescriptorType: HID */
0x10, /* bcdHID: HID Class Spec release number */
0x01,
0x00, /* bCountryCode: Hardware target country */
0x01, /* bNumDescriptors: Number of HID class descriptors to follow */
0x22, /* bDescriptorType */
sizeof(CustomHID_ReportDescriptor)&0xFF, /* wItemLength: Total length of Report descriptor :36 bytes*/
(sizeof(CustomHID_ReportDescriptor)>>8)&0xFF,
/******************** Descriptor of Custom HID endpoints ******************/
/* 27 */
0x07, /* bLength: Endpoint Descriptor size */
USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */
0x81, /* bEndpointAddress: Endpoint Address (IN) */
0x03, /* bmAttributes: Interrupt endpoint */
0x08, /* wMaxPacketSize: 64 Bytes max */
0x00,
0x0A, /* bInterval: Polling Interval (32 ms) */
/* 34 */
0x07, /* bLength: Endpoint Descriptor size */
USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */
/* Endpoint descriptor type */
0x01, /* bEndpointAddress: */
/* Endpoint Address (OUT) */
0x03, /* bmAttributes: Interrupt endpoint */
0x08, /* wMaxPacketSize: 64 Bytes max */
0x00,
0x0A, /* bInterval: Polling Interval (10 ms) */
//下面是自己添加的hid设备相关描述//
/************** Descriptor of 2/0 HID interface ****************/
/* 41 */
0x09, /* bLength: Interface Descriptor size */
USB_INTERFACE_DESCRIPTOR_TYPE,/* bDescriptorType: Interface descriptor type */
0x01, /* bInterfaceNumber: Number of Interface这里改成1 */
0x00, /* bAlternateSetting: Alternate setting */
0x02, /* bNumEndpoints这里改成2 */
0x03, /* bInterfaceClass: HID */
0x00, /* bInterfaceSubClass : 1=BOOT, 0=no boot */
0x00, /* nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse */
0x00, /* iInterface: Index of string descriptor */
/******************** Descriptor of 2/0 HID ********************/
/* 50 */
0x09, /* bLength: HID Descriptor size */
HID_DESCRIPTOR_TYPE, /* bDescriptorType: HID */
0x10, /*bcdHID: HID Class Spec release number*/
0x01,
0x00, /* bCountryCode: Hardware target country */
0x01, /* bNumDescriptors: Number of HID class descriptors to follow */
0x22, /* bDescriptorType */
sizeof(HID_driver_ReportDescriptor)&0xFF, /* wItemLength: Total length of Report descriptor :36 bytes*/
(sizeof(HID_driver_ReportDescriptor)>>8)&0xFF,
/******************** Descriptor of 2/0 HID endpoints :3 In, Interrupt, 3 ms******************/
/* 59 */
0x07, /* bLength: Endpoint Descriptor size */
USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */
0x82, /* bEndpointAddress: Endpoint Address (IN) */
0x03, /* bmAttributes: Interrupt endpoint */
0x40, /* wMaxPacketSize: 10 Bytes max */
0x00,
0x22, /* bInterval: Polling Interval (10 ms) */
/******************** Descriptor of 2/0 HID endpoints :3 Out, Interrupt, 3 ms******************/
/* 66 */
0x07, /* bLength: Endpoint Descriptor size */
USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */
0x02, /* bEndpointAddress: Endpoint Address (out) */
0x03, /* bmAttributes: Interrupt endpoint */
0x40, /* wMaxPacketSize: 10 Bytes max */
0x00,
0x0A, /* bInterval: Polling Interval (10 ms) */
/* 73 */
///添加结束//
}; /* CustomHID_ConfigDescriptor */
(1)因为设备usb插入电脑需要被识别成两个设备,所以上述的字段bNumInterfaces,需要改成2。
(2)根据hid设备相关标准,往结构中添加设备配置描述字段,添加完之后记得修改CUSTOMHID_SIZ_CONFIG_DESC数组大小的宏定义。
(3)我们添加了新的一个设备接口,而前面的hid键盘是第一个设备,所以我们添加的是第二个设备,因此我们需要将bInterfaceNumber字段,依次累加,第一个设备是0,所以我们这里就是1.
(4)usb通信的数据收发都是通过端点的,而我们第二个设备使用了两个端点,即一收一发,所以bNumEndpoints 要改成2
(5)然后我这次增加的设备是无类型的,所以nInterfaceProtocol就是0
(6)在hid接口配置的信息结构中,需要填上,该设备的描述信息结构大小,即wItemLength应该要调描述信息结构的
(7)我们增加的hid设备接口,使用的是端口2。端口0是默认的控制交互的端点,端口1是前面hid键盘的通信端点,我们使用的是端点2。
端点2的输入端点bEndpointAddress字段需要配置成0x82,输出端点需要配置为0x02。原因请看下图详解。
(8)对于数据的收发,usb中提供了四种方式:中断传输、批量传输、控制传输、 同步传输。对于数据量不大的业务一般使用中断传输就好,所以上述端点bmAttributes字段配置为0x03。更多详解,请看下图。
(9)我使用的是中断传输数据,但这中断只是usb内部的收发机制吧(具体的没去了解),然后我们收发数据,间隔是通过bInterval字段设置的时间去端点收发数据函数执行代码。故,如果bInterval字段设置间隔太长会影响数据的吞吐量,这个需要看具体需求而定。
修改完上述的hid接口配置信息后,需要给我们新增的hid设备增加接口的设备描述信息,该描述信息具体的字段文档说明,没找到,我是直接复制现成的。
const uint8_t HID_driver_ReportDescriptor[HID_driver_SIZ_REPORT_DESC] =
{
//vendor
0x06, 0x00, 0xFF, //Usage Page (Vendor-Defined 29)
0x09, 0x92, //Usage (Vendor-Defined 146)
0xA1, 0x01, //Collection (Application)
0x19, 0x00, //Usage Minimum (Undefined)
0x2A, 0xFF, 0x00, //Usage Maximum (Vendor-Defined 255)
0x15, 0x00, //Logical Minimum (0)
0x26, 0xFF, 0x00, //Logical Maximum (255)
0x75, 0x08, //Report Size (8)
0x95, 0x40, //Report Count (63)
0x91, 0x00, //Output (Data,Ary,Abs,NWrp,Lin,Pref,NNul,NVol,Bit)
0x19, 0x00, //Usage Minimum (Undefined)
0x2a, 0xFF, 0x00, //Usage Maximum (Vendor-Defined 255)
0x81, 0x00, //Input (Data,Ary,Abs)
0xC0, //End Collection
};
记得按照实际定义HID_driver_SIZ_REPORT_DESC宏的大小。
该步骤实为对步骤1所修改的,在头文件修改宏,或增加定义。
//在头文件修改CUSTOMHID_SIZ_CONFIG_DESC宏定义大小
//自己去算算CustomHID_ConfigDescriptor结构大小就知道是73
#define CUSTOMHID_SIZ_CONFIG_DESC 73
//在头文件新增
#define HID_driver_SIZ_REPORT_DESC 31
#define HID_driver_OFF_HID_DESC 50
extern const uint8_t HID_driver_ReportDescriptor[HID_driver_SIZ_REPORT_DESC];
因为我们新增了一个设备接口,使用端点2进行数据传输,所以我们要在usb_endp.c仿制端点1的数据收发,实现端点2函数。
/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/
/**
* @brief EP1 OUT Callback Routine.
*/
void EP1_OUT_Callback(void)
{
/* Enable the receive of data on EP1 */
SetEPRxStatus(ENDP1, EP_RX_VALID);
}
/**
* @brief EP1 IN Callback Routine.
*/
void EP1_IN_Callback(void)
{
}
void EP2_OUT_Callback(void)
{
USER_USB_HIDEP2_RX_LEN = USB_GetEpRxCnt(ENDP2);
USB_CopyPMAToUserBuf((unsigned char*)USER_USB_HIDEP2_RX_Buf, ENDP2_RXADDR, USER_USB_HIDEP2_RX_LEN);
HtClient_RcvBufDataIn(MODE_HID2, USER_USB_HIDEP2_RX_Buf, USER_USB_HIDEP2_RX_LEN);
/* Enable the receive of data on EP2 */
SetEPRxStatus(ENDP2, EP_RX_VALID);
}
void EP2_IN_Callback(void)
{
}
修改完usb_endp.c文件,我们需要屏蔽usb_conf.h文件中对EP2_IN_Callback和EP2_OUT_Callback函数的定义。
另外,我们需要定义usb收发数据的buf地址。因为usb的工作机制是,数据来了先缓存到usb接收缓存buf,然后按照到了usb数据轮询查看时间,发现有数据后,通过回调EPx_OUT_Callback函数,将数据从该buf拷贝出来,也就是说EPx_OUT_Callback就是usb接收数据的地方。
然后usb有对应的buf用以存放接收的数据,但是需要我们手动设置端点存放数据的起始地址,又因为hid固定是发64Byte一帧数据,所以收发地址依次递增64Byte就行。
以下就是我修改后的usb_conf.h文件。
#ifndef __USB_CONF_H__
#define __USB_CONF_H__
/*-------------------------------------------------------------*/
/* EP_NUM */
/* defines how many endpoints are used by the device */
/*-------------------------------------------------------------*/
#define EP_NUM (3) //这里要修改为3 端点0+端点1+端点2 =3个端点
/*-------------------------------------------------------------*/
/* -------------- Buffer Description Table -----------------*/
/*-------------------------------------------------------------*/
/* buffer table base address */
/* buffer table base address */
#define BTABLE_ADDRESS (0x00)
/* EP0 */
/* rx/tx buffer base address */
#define ENDP0_RXADDR (0x18)//这个起始地址好像可以随意?具体没深入研究
#define ENDP0_TXADDR (0x58)//依次递增64Byte就系,也就是0x40
/* EP1 */
/* tx buffer base address */
#define ENDP1_TXADDR (0x98)
#define ENDP1_RXADDR (0xD8)
/* EP2 */
/* tx buffer base address */
#define ENDP2_TXADDR (0x118) //ENDP2_TXADDR+0x40开始
#define ENDP2_RXADDR (0x158)
/*-------------------------------------------------------------*/
/* ------------------- STS events -------------------------*/
/*-------------------------------------------------------------*/
/* IMR_MSK */
/* mask defining which events has to be handled */
/* by the device application software */
//#define IMR_MSK (CTRL_CTRSM | CTRL_WKUPM | CTRL_SUSPDM | CTRL_ERRORM | CTRL_SOFM \
// | CTRL_ESOFM | CTRL_RSTM )
#define IMR_MSK (CTRL_CTRSM | CTRL_RSTM)
/* CTR service routines */
/* associated to defined endpoints */
//#define EP1_IN_Callback USB_ProcessNop
//#define EP2_IN_Callback USB_ProcessNop //我注释的
#define EP3_IN_Callback USB_ProcessNop
#define EP4_IN_Callback USB_ProcessNop
#define EP5_IN_Callback USB_ProcessNop
#define EP6_IN_Callback USB_ProcessNop
#define EP7_IN_Callback USB_ProcessNop
//#define EP1_OUT_Callback USB_ProcessNop
//#define EP2_OUT_Callback USB_ProcessNop //我注释的
#define EP3_OUT_Callback USB_ProcessNop
#define EP4_OUT_Callback USB_ProcessNop
#define EP5_OUT_Callback USB_ProcessNop
#define EP6_OUT_Callback USB_ProcessNop
#define EP7_OUT_Callback USB_ProcessNop
#endif /*__USB_CONF_H__*/
(1)我们在usb_des.c增加了新的hid接口以及相关的接口配置信息和设备描述信息,这些信息都将会在usb_prop.c调用到。仿制原有的hid键盘设备的代码,在usb_prop.c文件中,我们新增了两个变量。
//hid设备描述信息
USB_OneDescriptor HID_driver_Report_Descriptor = {(uint8_t*)HID_driver_ReportDescriptor, HID_driver_SIZ_REPORT_DESC};
//hid设备接口描述信息
USB_OneDescriptor HID_driver_Hid_Descriptor = {(uint8_t*)CustomHID_ConfigDescriptor + HID_driver_OFF_HID_DESC, CUSTOMHID_SIZ_HID_DESC};
然后增加获取hid设备接口信息和设备描述信息的函数。
uint8_t *HID_driver_GetReportDescriptor(uint16_t Length)
{
return Standard_GetDescriptorData(Length, &HID_driver_Report_Descriptor);
}
uint8_t *HID_driver_GetHIDDescriptor(uint16_t Length)
{
return Standard_GetDescriptorData(Length, &HID_driver_Hid_Descriptor);
}
(2)修改获取设备信息函数。下面通过pInformation->USBwIndex0来区分获取不同的设备信息,而pInformation->USBwIndex0跟我们在usb_desc.c下的CustomHID_ConfigDescriptor结构中的bInterfaceNumber有关。一般bInterfaceNumber从0开始,所以下面的pInformation->USBwIndex0是从0-1遍历。
/**
* @brief Handle the data class specific requests.
* @param Request Nb.
*/
USB_Result CustomHID_Data_Setup(uint8_t RequestNo)
{
uint8_t* (*CopyRoutine)(uint16_t);
// if (pInformation->USBwIndex != 0)
//return UnSupport;
CopyRoutine = NULL;
if ((RequestNo == GET_DESCRIPTOR) && (Type_Recipient == (STANDARD_REQUEST | INTERFACE_RECIPIENT))
&& (pInformation->USBwIndex0 < 3))
{
if (pInformation->USBwValue1 == REPORT_DESCRIPTOR)
{
//原来的hid键盘设备
if (pInformation->USBwIndex0 == 0)
{
CopyRoutine = CustomHID_GetReportDescriptor;
}
//我们新增的hid设备,为什么是1.是我们在hid接口描述bInterfaceNumber中写的是1
else if (pInformation->USBwIndex0 == 1)
{
CopyRoutine = HID_driver_GetReportDescriptor;
}
}
else if (pInformation->USBwValue1 == HID_DESCRIPTOR_TYPE)
{
//原来的hid键盘设备
if (pInformation->USBwIndex0 == 0)
{
CopyRoutine = CustomHID_GetHIDDescriptor;
}
//我们新增的hid设备,为什么是1.是我们在hid接口描述bInterfaceNumber中写的是1
else if (pInformation->USBwIndex0 == 1)
{
CopyRoutine = HID_driver_GetHIDDescriptor;
}
}
} /* End of GET_DESCRIPTOR */
/*** GET_PROTOCOL, GET_REPORT, SET_REPORT ***/
else if ((Type_Recipient == (CLASS_REQUEST | INTERFACE_RECIPIENT)))
{
switch (RequestNo)
{
case GET_PROTOCOL:
CopyRoutine = CustomHID_GetProtocolValue;
break;
case SET_REPORT:
CopyRoutine = CustomHID_SetReport_Feature;
Request = SET_REPORT;
break;
default:
break;
}
}
if (CopyRoutine == NULL)
{
return UnSupport;
}
pInformation->Ctrl_Info.CopyData = CopyRoutine;
pInformation->Ctrl_Info.Usb_wOffset = 0;
(*CopyRoutine)(0);
return Success;
}
(3)修改usb的CustomHID_Reset函数。注意USB_SetEpTxCnt函数,一定要设为0,否则usb会定时上报上次发送的数据,这是一个小坑。
/**
* @brief Custom HID reset routine.
*/
void CustomHID_Reset(void)
{
/* Set CustomHID_DEVICE as not configured */
pInformation->CurrentConfiguration = 0;
pInformation->CurrentInterface = 0; /*the default Interface*/
/* Current Feature initialization */
pInformation->CurrentFeature = CustomHID_ConfigDescriptor[7];
USB_SetBuftab(BTABLE_ADDRESS);
/* Initialize Endpoint 0 */
USB_SetEpType(ENDP0, EP_CONTROL);
SetEPTxStatus(ENDP0, EP_TX_STALL);
USB_SetEpRxAddr(ENDP0, ENDP0_RXADDR);
USB_SetEpTxAddr(ENDP0, ENDP0_TXADDR);
USB_ClrStsOut(ENDP0);
USB_SetEpRxCnt(ENDP0, Device_Property.MaxPacketSize);
USB_SetEpRxValid(ENDP0);
/* Initialize Endpoint 1 */
USB_SetEpType(ENDP1, EP_INTERRUPT);
USB_SetEpTxAddr(ENDP1, ENDP1_TXADDR);
USB_SetEpRxAddr(ENDP1, ENDP1_RXADDR);
USB_SetEpTxCnt(ENDP1, 0);//这里设置需要设置为0,不然usb会自己一直发上次发送的数据
USB_SetEpRxCnt(ENDP1, HIDEP1_REPORT_COUNT);
SetEPRxStatus(ENDP1, EP_RX_VALID);
SetEPRxStatus(ENDP1, EP_TX_VALID);
/* Initialize Endpoint 2 */
USB_SetEpType(ENDP2, EP_INTERRUPT);
USB_SetEpTxAddr(ENDP2, ENDP2_TXADDR);
USB_SetEpRxAddr(ENDP2, ENDP2_RXADDR);
USB_SetEpTxCnt(ENDP2, 0);//这里设置需要设置为0,不然usb会自己一直发上次发送的数据
USB_SetEpRxCnt(ENDP2, HIDEP2_REPORT_COUNT);
SetEPTxStatus(ENDP2, EP_TX_VALID);
SetEPRxStatus(ENDP2, EP_RX_VALID);
/* Set this device to response on default address */
USB_SetDeviceAddress(0);
bDeviceState = ATTACHED;
}
在头文件添加一下函数的声明就好了。
//添加上函数的声明
uint8_t *HID_driver_GetReportDescriptor(uint16_t Length);
uint8_t *HID_driver_GetHIDDescriptor(uint16_t Length);
hid的数据发送,可以通过端点的发送函数,发送,也可以自己调用相关接口发送,以下是分享我的发送函数格式。
#define HIDEP1_REPORT_COUNT (8)
#define HIDEP2_REPORT_COUNT (64)
uint8_t USER_USB_HIDEP1_TX_LEN = 0;
uint8_t USER_USB_HIDEP1_RX_LEN = 0;
uint8_t USER_USB_HIDEP1_TX_Buf[HIDEP1_REPORT_COUNT] ={0};
uint8_t USER_USB_HIDEP1_RX_Buf[HIDEP1_REPORT_COUNT] ={0};
uint8_t USER_USB_HIDEP2_TX_LEN = 0;
uint8_t USER_USB_HIDEP2_RX_LEN = 0;
uint8_t USER_USB_HIDEP2_TX_Buf[HIDEP2_REPORT_COUNT] ={0};
uint8_t USER_USB_HIDEP2_RX_Buf[HIDEP2_REPORT_COUNT] ={0};
int USBHID1_SendData(uint8_t *data, int dataSize)
{
uint16_t delayMsCnt = 0;
uint16_t tempSize = 0;
uint16_t sendSize = 0;
if(bDeviceState != CONFIGURED)
return -1;
if(dataSize == 0)
return -2;
do{
memset(USER_USB_HIDEP1_TX_Buf, 0, HIDEP1_REPORT_COUNT);
if(dataSize>HIDEP1_REPORT_COUNT)
tempSize = HIDEP1_REPORT_COUNT;
else
tempSize = dataSize;
memcpy(USER_USB_HIDEP1_TX_Buf, (uint8_t*)(data + sendSize), tempSize);
USER_USB_HIDEP1_TX_LEN = tempSize;
dataSize -= tempSize;
sendSize += tempSize;
USB_SilWrite(ENDP1, USER_USB_HIDEP1_TX_Buf, HIDEP1_REPORT_COUNT);
USB_SetEpTxValid(ENDP1);
}while(dataSize>0);
//数据发完之后清零
delay_ms(20);
memset(USER_USB_HIDEP1_TX_Buf, 0, HIDEP1_REPORT_COUNT);
USB_SilWrite(ENDP1, USER_USB_HIDEP1_TX_Buf, HIDEP1_REPORT_COUNT);
USB_SetEpTxValid(ENDP1);
return sendSize;
}
int USBHID2_SendData(uint8_t *data, int dataSize)
{
uint16_t delayMsCnt = 0;
uint16_t tempSize = 0;
uint16_t sendSize = 0;
if(bDeviceState != CONFIGURED)
return -1;
if(dataSize == 0)
return -2;
do{
memset(USER_USB_HIDEP2_TX_Buf, 0, HIDEP2_REPORT_COUNT);
if(dataSize>HIDEP2_REPORT_COUNT)
tempSize = HIDEP2_REPORT_COUNT;
else
tempSize = dataSize;
memcpy(USER_USB_HIDEP2_TX_Buf, (uint8_t*)(data + sendSize), tempSize);
USER_USB_HIDEP2_TX_LEN = tempSize;
dataSize -= tempSize;
sendSize += tempSize;
USB_SilWrite(ENDP2, USER_USB_HIDEP2_TX_Buf, HIDEP2_REPORT_COUNT);
USB_SetEpTxValid(ENDP2);
}while(dataSize>0);
return sendSize;
}
修改到此结束。
好了,经过上述的修改,基本上就可以完成实验的目标,一个usb接口模拟出多个usb设备。如下图示:
nullA 卷 第 1 页 共 6 页 中南林业科技大学课程考试试卷一、选择题(每题1分,共20分)1、计算机操作系统是一种(B )。A .应用软件B .系统软件C .工具软件D .字表处理软件2、(C )是作业存在的惟一标志。A .作业名B .进程控制块C .作业控制块D .程序名3、在分时操作系统中,进程调度经常用采( C )算法。A .先来先服务B .最高优先权C .时间片轮转D ...._中南林业科技大学计算机基础知识考试题库
#当前ssh窗口被锁定,可按CTRL + C打断程序运行,或直接关闭窗口,程序退出java -jar xxxxx.jar #当前ssh窗口不被锁定,但是当窗口关闭时,程序中止运行。java -jar xxxxx.jar & #不挂断运行命令,当账户退出或终端关闭时,程序仍然运行nohup java -jar xxxxxx.jar & #指定输出日志的文件,不挂断运行命令,当账户退出或终端关闭时,程序仍然运行nohup java -jar xxxxxx.jar &g._某个进程的命令是grep xx.jar 怎么停止
分类与数值预测是预测问题的两种主要类型
文章目录_博弈论拼单
文章目录1.添加依赖包2.代码3.测试1.添加依赖包 <!-- 压缩图片--> <dependency> <groupId>net.coobird</groupId> <artifactId>thumbnailator</artifactId> <version>0.4.8</version> </dependency>_袟9aitpfphxf8袟
原作者:dog250,授权发布重新整理:极客重生hi ,大家好,今天推荐一篇我认为在TCP BBR技术里面分析非常透彻的文章,希望大家可以学习到一些真正的知识,理解其背后的设计原理,才能应..._bbr带宽增益推导
三星高速CHIP贴片机SM431 贴装速度:55000点/小时(实际出产工效) 贴装精度:0.03MM 0.025(BGA) 贴装头数目:16个头 贴装元件:0201~44MM BGA SOP PLCC SOT QFP 品牌 SAMSUNG 型号 SM421_sm421
最近在看一本书,名叫《软技能》,里面有一章叫“十步学习法”,感觉很有启发,作为软件开发从业者,各种技术日新月异,我们要不断的学习才能不被这个行业淘汰,这也就意味着我们得花足够的时间在学习上,但是一天中工作就要占用我们大部分时间,更何况现在互联网公司都推行995甚至996,这大大压缩了我们的学习时间,这样我们的学习方法和效率就显得很重要,我们如何才能在有限的时间内高效学习? ...
基于.NET的数据访问基类的构建黄光芳(湛江师范学院 教育技术部 ,广东 湛江 524048)摘要:为提高数据库应用程序的设计效率,基于VS.NET开发平台,利用ADO.NET的强大数据访问功能,设计了抽象的数据访问基类。主要通过代码的方式阐述了整个基类的创建过程,并重点介绍了创建存储过程参数的操作方法,在调用存储过程中,通过方法重载实现不同的数据库操作。利用该类并结合存储过程的优势...
java获取当前时间前几个小时的时间getBeforeHourTime和单独计算时间戳的输出一致。所以简单的获取,用时间戳计算一下就好。public static void main(String[] args) { Long start = System.currentTimeMillis(); System.out.println(timeStamp2Date(start + "")); System.out.println(getB..._java date 前几个小时
互相学习,欢迎提出更好的方法本题要求实现一个字符串查找的简单函数。函数接口定义:char *search( char *s, char *t );函数search在字符串s中查找子串t,返回子串t在s中的首地址。若未找到,则返回NULL。#include <stdio.h>#define MAXS 30char *search(char *s, char *t);void ReadString( char s[] ); /* 裁判提供,细节不表 */int ma_用指针实现对给定字符串查找给定的子串并输出第一个子串所在的位置
https://blog.csdn.net/qq_23599965/article/details/80910202