admin管理员组

文章数量:1567529


2024年7月2日发(作者:)

STM32 USB-HID复合设备(鼠标+键盘 双接口) 的实现方法

Author :卢晓铭

联系方法:lsj9383@

1.首先更改配置描述符,在usb_desc.c中.这是我使用的配置描述符,第一个接口是键盘功能,

第二个接口是鼠标功能

const u8 Joystick_ConfigDescriptor[JOYSTICK_SIZ_CONFIG_DESC] =

{

/***************配置描述符***********************/

0x09, //bLength字段

USB_CONFIGURATION_DESCRIPTOR_TYPE, //bDescriptorType字段

//wTotalLength字段

JOYSTICK_SIZ_CONFIG_DESC,

/* wTotalLength: Bytes returned */

0x00,

0x02, //bNumInterfaces字段

0x01, //bConfiguration字段

0x00, //iConfigurationz字段

0x80, //bmAttributes字段

0x32, //bMaxPower字段

/*******************第一个接口描述符*********************/

0x09, //bLength字段

0x04, //bDescriptorType字段

0x00, //bInterfaceNumber字段

0x00, //bAlternateSetting字段

0x02, //bNumEndpoints字段

0x03, //bInterfaceClass字段

0x01, //bInterfaceSubClass字段

0x01, //bInterfaceProtocol字段

0x00, //iConfiguration字段

/******************HID描述符************************/

0x09, //bLength字段

0x21, //bDescriptorType字段

0x10, //bcdHID字段

0x01,

0x21, //bCountyCode字段

0x01, //bNumDescriptors字段

0x22, //bDescriptorType字段

//bDescriptorLength字段。

//下级描述符的长度。下级描述符为键盘报告描述符。

sizeof(KeyboardReportDescriptor)&0xFF,

(sizeof(KeyboardReportDescriptor)>>8)&0xFF,

/**********************输入端点描述符***********************/

0x07, //bLength字段

0x05, //bDescriptorType字段

0x81, //bEndpointAddress字段

0x03, //bmAttributes字段

0x10, //wMaxPacketSize字段

0x00,

0x0A, //bInterval字段

/**********************输出端点描述符***********************/

0x07, //bLength字段

0x05, //bDescriptorType字段

0x01, //bEndpointAddress字段

0x03, //bmAttributes字段

0x10, //wMaxPacketSize字段

0x00,

0x0A, //bInterval字段

/*******************第二个接口描述符*********************/

0x09, //bLength字段

0x04, //bDescriptorType字段

0x01, //bInterfaceNumber字段

0x00, //bAlternateSetting字段

0x01, //bNumEndpoints字段

0x03, //bInterfaceClass字段

0x01, //bInterfaceSubClass字段

0x02, //bInterfaceProtocol字段

0x00, //iConfiguration字段

/******************HID描述符************************/

0x09, //bLength字段

0x21, //bDescriptorType字段

0x10, //bcdHID字段

0x01,

0x21, //bCountyCode字段

0x01, //bNumDescriptors字段

0x22, //bDescriptorType字段

sizeof(MouseReportDescriptor)&0xFF, //bDescriptorLength字段

(sizeof(MouseReportDescriptor)>>8)&0xFF,

/**********************输入端点描述符***********************/

0x07, //bLength字段

0x05, //bDescriptorType字段

0x82, //bEndpointAddress字段

0x03, //bmAttributes字段。D1~D0为端点传输类型选择

0x40, //wMaxPacketSize字段

0x00,

0x0A //bInterval字段

};

2.写键盘和鼠标报告描述符

//USB键盘报告描述符的定义

const u8 KeyboardReportDescriptor[KP_ReportDescriptor_Size]=

{

0x05, 0x01, // USAGE_PAGE (Generic Desktop) //63

0x09, 0x06, // USAGE (Keyboard)

0xa1, 0x01, // COLLECTION (Application)

0x05, 0x07, // USAGE_PAGE (Keyboard)

0x19, 0xe0, // USAGE_MINIMUM (Keyboard LeftControl)

0x29, 0xe7, // USAGE_MAXIMUM (Keyboard Right GUI)

0x15, 0x00, // LOGICAL_MINIMUM (0)

0x25, 0x01, // LOGICAL_MAXIMUM (1)

0x75, 0x01, // REPORT_SIZE (1)

0x95, 0x08, // REPORT_COUNT (8)

0x81, 0x02, // INPUT (Data,Var,Abs)

0x95, 0x01, // REPORT_COUNT (1)

0x75, 0x08, // REPORT_SIZE (8)

0x81, 0x03, // INPUT (Cnst,Var,Abs)

0x95, 0x05, // REPORT_COUNT (5)

0x75, 0x01, // REPORT_SIZE (1)

0x05, 0x08, // USAGE_PAGE (LEDs)

0x19, 0x01, // USAGE_MINIMUM (Num Lock)

0x29, 0x05, // USAGE_MAXIMUM (Kana)

0x91, 0x02, // OUTPUT (Data,Var,Abs)

0x95, 0x01, // REPORT_COUNT (1)

0x75, 0x03, // REPORT_SIZE (3)

0x91, 0x03, // OUTPUT (Cnst,Var,Abs)

0x95, 0x06, // REPORT_COUNT (6)

0x75, 0x08, // REPORT_SIZE (8)

0x15, 0x00, // LOGICAL_MINIMUM (0)

0x25, 0x65, // LOGICAL_MAXIMUM (101)

0x05, 0x07, // USAGE_PAGE (Keyboard)

0x19, 0x00, // USAGE_MINIMUM (Reserved (no event

indicated))

0x29, 0x65, // USAGE_MAXIMUM (Keyboard Application)

0x81, 0x00, // INPUT (Data,Ary,Abs)

0xc0, // END_COLLECTION

//0xc0,

}; /* Joystick_ReportDescriptor */

///////////////////////////键盘报告描述符完毕////////////////////////////

//USB鼠标报告描述符的定义

const u8 MouseReportDescriptor[Mouse_ReportDescriptor_Size]=

{

0x05, 0x01, // USAGE_PAGE (Generic Desktop)

0x09, 0x02, // USAGE (Mouse)

0xa1, 0x01, // COLLECTION (Application)

0x85, 0x01, //Report ID (1)

0x09, 0x01, // USAGE (Pointer)

0xa1, 0x00, // COLLECTION (Physical)

0x05, 0x09, // USAGE_PAGE (Button)

0x19, 0x01, // USAGE_MINIMUM (Button 1)

0x29, 0x03, // USAGE_MAXIMUM (Button 3)

0x15, 0x00, // LOGICAL_MINIMUM (0)

0x25, 0x01, // LOGICAL_MAXIMUM (1)

0x95, 0x03, // REPORT_COUNT (3)

0x75, 0x01, // REPORT_SIZE (1)

0x81, 0x02, // INPUT (Data,Var,Abs)

0x95, 0x01, // REPORT_COUNT (1)

0x75, 0x05, // REPORT_SIZE (5)

0x81, 0x03, // INPUT (Cnst,Var,Abs)

0x05, 0x01, // USAGE_PAGE (Generic Desktop)

0x09, 0x30, // USAGE (X)

0x09, 0x31, // USAGE (Y)

0x09, 0x38, // USAGE (Wheel)

0x15, 0x81, // LOGICAL_MINIMUM (-127)

0x25, 0x7f, // LOGICAL_MAXIMUM (127)

0x75, 0x08, // REPORT_SIZE (8)

0x95, 0x03, // REPORT_COUNT (3)

0x81, 0x06, // INPUT (Data,Var,Rel)

0xc0, // END_COLLECTION

0xc0 // END_COLLECTION

};

以上是usb_desc.c中的修改,接着是需要修改usb_desc.h中的宏定义

3.由于我们用了 新的配置描述符,所以,我们需要重新定义配置描述符的大小

#define JOYSTICK_SIZ_CONFIG_DESC 66

4.由于我们用了 键盘鼠标 报告 描述符,所以,我们需要定义键盘报告描述符的大小

#define KP_ReportDescriptor_Size 63

#define Mouse_ReportDescriptor_Size 54

5.报告描述符的大小,但是我们还有增加 键盘鼠标报告描述符 的定义

extern const u8 KeyboardReportDescriptor[KP_ReportDescriptor_Size];

extern const u8 MouseReportDescriptor[Mouse_ReportDescriptor_Size];

(如果有不用的报告描述符,就给删掉吧)

6.好,接着让我们更改usb_prop.c中的代码, 我们可以在usb_prop.c中找到 类似一下的代码

ONE_DESCRIPTOR Joystick_Report_Descriptor = //用于获得报告描述符

{

(u8 *)Joystick_ReportDescriptor,

JOYSTICK_SIZ_REPORT_DESC

};

ONE_DESCRIPTOR Mouse_Hid_Descriptor = //用于获得配置描述符中的Hid描述

{

(u8*)Joystick_ConfigDescriptor + JOYSTICK_OFF_HID_DESC, //

JOYSTICK_SIZ_HID_DESC

};

//JOYSTICK_OFF_HID_DESC是HID描述在配置描述符中的偏移量

由于我们更改了报告描述符 和 配置描述符,所以该处应该修改,并且我们有两个报告描述

符(鼠标+键盘),也有两个HID描述(在配置描述符中),所以,这里一共要有4段代码

ONE_DESCRIPTOR KP_Report_Descriptor = //

{ //

(u8 *)KeyboardReportDescriptor, //

KP_ReportDescriptor_Size //

}; //

//

ONE_DESCRIPTOR KP_Hid_Descriptor = //

{ //

(u8*)Joystick_ConfigDescriptor + KP_OFF_HID_DESC, //

JOYSTICK_SIZ_HID_DESC //

}; //

//

ONE_DESCRIPTOR Mouse_Report_Descriptor = //

{ //

(u8 *)MouseReportDescriptor, //

Mouse_ReportDescriptor_Size //

}; //

//

ONE_DESCRIPTOR Mouse_Hid_Descriptor = //

{ //

(u8*)Joystick_ConfigDescriptor + Mouse_OFF_HID_DESC, //

JOYSTICK_SIZ_HID_DESC //

};

我们上面Mouse_OFF_HID_DESC 和 KP_OFF_HID_DESC分别是鼠标HID描述符,在配

置描述符中的偏移量(也就是在配置描述符中的位置),以及键盘HID描述符在配置描述符中

的偏移量,所以,我们回到usb_desc.h中,定义一下

#define KP_OFF_HID_DESC 18

#define Mouse_OFF_HID_DESC 50

呵呵,可以数来验证一下这个偏移是否正确

7.回到usb_prop.c中继续更改,找到void Joystick_Reset(void)函数

由于配置描述符中,使用到了两个端点,端点1的收发(用于键盘),端点2的输入(用于

鼠标,输入输出 是对于PC机的而言, 输入也就是单片机输入电脑)

将函数更改为一下代码

void Joystick_Reset(void)

{

/* Set Joystick_DEVICE as not configured */

pInformation->Current_Configuration = 0;

pInformation->Current_Interface = 0;/*the default Interface*/

/* Current Feature initialization */

pInformation->Current_Feature = Joystick_ConfigDescriptor[7];

SetBTABLE(BTABLE_ADDRESS);

/* Initialize Endpoint 0 */

SetEPType(ENDP0, EP_CONTROL);

SetEPTxStatus(ENDP0, EP_TX_STALL);

SetEPRxAddr(ENDP0, ENDP0_RXADDR);

SetEPTxAddr(ENDP0, ENDP0_TXADDR);

Clear_Status_Out(ENDP0);

SetEPRxCount(ENDP0, Device_ketSize);

SetEPRxValid(ENDP0);

/* Initialize Endpoint In 1 */

SetEPType(ENDP1, EP_INTERRUPT); //初始化为中断端点类型

SetEPTxAddr(ENDP1, ENDP1_TXADDR); //设置发送数据的地址

SetEPTxCount(ENDP1, 8); //设置发送的长度

SetEPTxStatus(ENDP1, EP_TX_NAK); //设置端点处于忙状态

/* Initialize Endpoint Out 1 */

SetEPRxAddr(ENDP1, ENDP1_RXADDR); //设置接收数据的地址

SetEPRxCount(ENDP1, 2); //设置接收长度

SetEPRxStatus(ENDP1, EP_RX_VALID); //设置端点有效,可以接收数据

/* Initialize Endpoint In 2 */

SetEPType(ENDP2, EP_INTERRUPT); //初始化为中断端点类型

SetEPTxAddr(ENDP2, ENDP2_TXADDR); //设置发送数据的地址

SetEPTxCount(ENDP2, 5); //设置发送的长度

SetEPTxStatus(ENDP2, EP_TX_NAK); //设置端点处于忙状态

bDeviceState = ATTACHED;

/* Set this device to response on default address */

SetDeviceAddress(0);

}

由于使用了端点,我们到usb_conf.h中 定义一下各个端点

/* EP1 */

/* tx buffer base address */

#define ENDP1_TXADDR (0x100)

#define ENDP1_RXADDR (0x110)

/* EP2 */

/* tx buffer base address */

#define ENDP2_TXADDR (0x120)

8.回到usb_prop.c中继续作修改

找到Joystick_SetConfiguration 和 Joystick_SetDeviceAddress

这两个函数不需要,我们把里面的代码给删掉,变成以下

void Joystick_SetConfiguration(void)

{

}

void Joystick_SetDeviceAddress (void)

{

}

9.找到RESULT Joystick_Data_Setup(u8 RequestNo)函数,在这个函数中,便是对不同的接口

进行区分, USBwIndex0反应出了接口数,不同的接口,返回不同的报告描述符,

根据在配置描述符中的定义,第0个接口,返回键盘的相关描述符,第一个接口,返回鼠标

相关描述符。将函数配置为如下

RESULT Joystick_Data_Setup(u8 RequestNo)

{

u8 *(*CopyRoutine)(u16);

CopyRoutine = NULL;

if ((RequestNo == GET_DESCRIPTOR)

&& (Type_Recipient == (STANDARD_REQUEST | INTERFACE_RECIPIENT))

&& (pInformation->USBwIndex0 < 2))

{

if (pInformation->USBwValue1 == REPORT_DESCRIPTOR)

{

if (pInformation->USBwIndex0 == 0)

CopyRoutine = KP_GetReportDescriptor;

else

CopyRoutine = Mouse_GetReportDescriptor;

}

else if (pInformation->USBwValue1 == HID_DESCRIPTOR_TYPE)

{

if (pInformation->USBwIndex0 == 0)

CopyRoutine = KP_GetHIDDescriptor;

else

CopyRoutine = Mouse_GetHIDDescriptor;

}

} /* End of GET_DESCRIPTOR */

/*** GET_PROTOCOL ***/

else if ((Type_Recipient == (CLASS_REQUEST | INTERFACE_RECIPIENT))

&& RequestNo == GET_PROTOCOL)

{

CopyRoutine = Joystick_GetProtocolValue;

}

if (CopyRoutine == NULL)

{

return USB_UNSUPPORT;

}

pInformation->Ctrl_ta = CopyRoutine;

pInformation->Ctrl__wOffset = 0;

(*CopyRoutine)(0);

return USB_SUCCESS;

}

在以上代码中

KP_GetReportDescriptor

Mouse_GetReportDescriptor

KP_GetHIDDescriptor;

Mouse_GetHIDDescriptor

都是函数名,这些函数需要定义

CopyRoutine是指针函数,以指示运行哪个函数

我们在usb_prop.h中,对函数进行定义

/***********************************************/

u8 *Mouse_GetReportDescriptor(u16 Length);

u8 *KP_GetReportDescriptor(u16 Length);

u8 *Mouse_GetHIDDescriptor(u16 Length);

u8 *KP_GetHIDDescriptor(u16 Length);

/***********************************************/

把一些没有用的函数删掉,比如

Joystick_GetReportDescriptor(u16 Length)

u8 *Joystick_GetHIDDescriptor(u16 Length)

10.我们回到usb_prop.c中继续修改

找到u8 *Joystick_GetReportDescriptor(u16 Length) 这种 获得报告描述符的 函数

这个函数没有什么用了,我们有我们自己的,可以删掉

u8 *KP_GetReportDescriptor(u16 Length)

{

return Standard_GetDescriptorData(Length, &KP_Report_Descriptor);

}

u8 *Mouse_GetReportDescriptor(u16 Length)

{

return Standard_GetDescriptorData(Length, &Mouse_Report_Descriptor);

}

由于有两个报告描述符需要获得,所以该处申明两个函数,根据接口号码,会进入不同的函

数,Joystick_Data_Setup函数中,我们可以看到接口不同,进入的函数不同,返回的报告描

述符也就不同了。

u8 *Joystick_GetHIDDescriptor(u16 Length) 也没有用,

我们用自己的,删掉它,更改为

u8 *KP_GetHIDDescriptor(u16 Length)

{

return Standard_GetDescriptorData(Length, &KP_Hid_Descriptor);

}

u8 *Mouse_GetHIDDescriptor(u16 Length)

{

return Standard_GetDescriptorData(Length, &Mouse_Hid_Descriptor);

}

现在编译~,我们就可以成功啦~!!

下载进入开发板

我们可以看到设备识别成功啦!

还不保险?那就看看Bus Hound的监控数据吧

点CapsLock有数据流动,那就OK啦。

端点输入数据,请参考

void Joystick_Send(u8 buf0, u8 buf1)

{

u8 Buffer[8] = {0, 0, 0, 0, 0, 0, 0, 0};

Buffer[0] = buf0;

/* prepare buffer to send */

Buffer[3] = buf1;

if(Buffer[0]==0) //键盘

{

/*copy mouse position info in ENDP1 Tx Packet Memory Area*/

UserToPMABufferCopy(Buffer, GetEPTxAddr(ENDP1), 8);

/* enable endpoint for transmission */

SetEPTxValid(ENDP1);

}

else //鼠标

{

UserToPMABufferCopy(Buffer, GetEPTxAddr(ENDP2), 5);

SetEPTxValid(ENDP2);

}

}

鼠标键盘的具体数据,以及格式,请参考单接口模式下的,这个和那个类似的,用Buffer[0]

来区别键盘和鼠标(虽然发送的端点不一样,但是由于报告描述符中,鼠标键盘数据的

Buffer[0]的数据不一样,所以用这个方法来区别)

另外,还有什么疑问,可以在论坛里面讨论

也可以发送邮箱:lsj9383@

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

aaaaaaaaaaaaaaaaaaaaaaaaaaa


本文标签: 描述符键盘报告