嵌入式进阶——HID协议

🎬 秋野酱:《个人主页》
🔥 个人专栏:《Java专栏》《Python专栏》

⛺️心若有所向往,何惧道阻且长

文章目录

    • USB烧录
    • USB HID协议
      • USB协议组成
      • 通讯流程
    • 官方USB HID范例
      • 文件说明
      • 修改PC端的显示
    • 兼容库函数
    • HID键盘
    • USB调试工具
    • USB 描述符
      • 设备描述符
      • 配置描述符
      • 接口描述符
    • HID描述符
      • 端点描述符
    • 附录
      • USB设备类型定义

USB烧录

  1. 将最小板的开关拨动到HID位置
    在这里插入图片描述
    按住白色按钮不要松开
    在这里插入图片描述

按住蓝色按钮2秒,然后松开
在这里插入图片描述
观察STC-ISP烧录工具,如果在扫描串口部分出现HID接口,说明成功,否则,按照2、3步骤重复尝试
在这里插入图片描述

  1. 接下来,就可以进行程序烧录,点击下载,无需再安蓝色按钮就可以烧录了

USB HID协议

USB HID(Human Interface Device)是一种USB协议,用于连接人机界面设备(如键盘、鼠标、游戏控制器等)到计算机系统。HID协议提供了一种标准的、规范化的接口,使得这些设备可以在不同的操作系统和平台上运行,并且无需安装任何驱动程序。
HID设备与主机之间的通信是通过数据报文来实现的,通信报文的格式由HID协议规定,主要包括报文头、报文数据和报文状态等信息。HID设备需要向主机发送报文,以响应主机的命令,同时也可以通过发送报文来向主机汇报设备的状态和事件。
在HID协议中,设备可以有多个端点(Endpoint)来支持不同的数据传输方式,其中,输入端点(IN Endpoint)用于从设备向主机传输数据,输出端点(OUT Endpoint)用于从主机向设备传输数据。HID设备通过输入端点向主机发送设备状态信息和事件信息,主机通过输出端点向设备发送控制指令。
HID协议的使用使得人机界面设备的开发变得更加方便和便捷,并且可以实现设备的即插即用。

USB协议组成

在USB HID协议中,有两个主要的对象:主机和设备。主机通常是PC或其他计算机系统,设备可以是任何USB HID设备,例如鼠标、键盘、游戏手柄等。
主机和设备之间的通讯是通过在USB总线上传输数据包来完成的。USB HID设备在被插入主机时,会被主机检测到并分配一个唯一的地址。然后,主机可以向设备发送控制命令,例如获取设备信息、发送数据等。
设备通过向主机发送报告(report)来提供其状态和数据。报告是一组数据字节,其格式由HID协议规定。根据HID规范,设备必须支持三种类型的报告:输入报告(Input Report)、输出报告(Output Report)和特征报告(Feature Report)。
输入报告是设备向主机报告其状态和数据的报告类型。例如,鼠标向主机发送其位置和按键状态。输出报告是主机向设备发送的命令或配置参数等数据的报告类型。例如,主机发送命令让设备关闭或者设置设备参数。特征报告是一种可选的报告类型,可以用于读取或写入设备的某些特性,例如设备的名称或唯一标识符等。
一些专有名词:

  1. 设备描述符(Device Descriptor):描述USB设备的一般特性,例如设备类、子类、协议、供应商ID、产品ID等。
  2. 配置描述符(Configuration Descriptor):描述设备的一个或多个配置,每个配置由多个接口组成,每个接口描述设备的一个功能。
  3. 接口描述符(Interface Descriptor):描述设备的一个功能,包括设备类、子类、协议等信息。
  4. HID描述符(HID Descriptor):描述HID设备的特性,例如报告描述符的数量、报告描述符的长度等。
  5. 报告描述符(Report Descriptor):描述HID设备传输的数据格式和类型,包括输入、输出、特性等信息。
  6. 一般描述符(Generic Descriptor):用于描述除设备、配置、接口、HID和报告之外的其他信息。
  7. 字符串描述符(String Descriptor):描述设备和厂商的字符串信息。

通讯流程

USB HID通讯时序可以大致分为以下几个步骤:

  1. 设备连接和初始化:设备被插入USB端口后,会进行初始化和配置,包括分配USB地址和设置通信端点等。
  2. 主机发送设备描述符:主机会向设备发送请求,要求设备提供自己的描述符信息,包括设备类型、厂商信息、设备功能等。
  3. 设备响应描述符请求:设备接收到主机的请求后,会根据请求提供相应的设备描述符信息,包括设备类型、厂商信息、设备功能等。
  4. 主机发送配置描述符:主机会向设备发送请求,要求设备提供自己的配置描述符信息,包括端点数量、数据传输速率、电源需求等。
  5. 设备响应配置描述符请求:设备接收到主机的请求后,会根据请求提供相应的配置描述符信息,包括端点数量、数据传输速率、电源需求等。
  6. 主机发送数据:主机会向设备发送数据包,数据包中包含了控制信息和数据内容。
  7. 设备接收和处理数据:设备接收到主机发送的数据包后,会进行处理和响应,包括识别控制信息和处理数据内容。
  8. 设备发送数据:设备会向主机发送数据包,数据包中包含了控制信息和数据内容。
  9. 主机接收和处理数据:主机接收到设备发送的数据包后,会进行处理和响应,包括识别控制信息和处理数据内容。
  10. 完成通讯:通讯完成后,设备和主机会进行断开连接和资源释放等操作。
    需要注意的是,USB HID通讯过程中的具体时序和流程可能会因为具体的应用场景和设备而有所不同,上述步骤仅供参考。

在这里插入图片描述

官方USB HID范例

官方提供了HID的示例,我们通过示例来学习一些内容。

  1. 拷贝官方示例,编译,烧录到开发板中

  2. 将开发板的开关拨动到HID

  3. 打开设置中,来到蓝牙和其他设备中,点击进入设备中
    在这里插入图片描述
    查看输入中,多了一个STC HID Demo
    在这里插入图片描述

  4. 将开发板的开关进行切换,观察这个输入中的变化
    官方示例的作用,就是帮助我们构建了一个HID设备,将设备注册到了PC机中。

文件说明

● usb.c和usb.h: USB入口文件,提供USB所有功能,设备信息,配置信息,通讯过程等
● usb_req_std.c和usb_req_std.h:设备信息和配置信息通讯过程中的逻辑实现。
● usb_req_class.c和usb_req_class.h:通讯过程中的逻辑实现
● usb_vendor.c和usb_vendor.h:初始化配置逻辑
● usb_desc.c和usb_desc.h: 协议描述信息,内部是协议的常量信息。

修改PC端的显示

修改usb_desc.c中间的内容即可:

  1. MANUFACTDESC制造商信息。
char code MANUFACTDESC[8] =
{0x08,0x03,'S',0,'T',0,'C',0,
};
char code MANUFACTDESC[16] =
{0x10,0x03,'I',0,'T',0,'H',0,'E',0,'I',0,'M',0,'A',0,
};

修改后长度发生变化,长度由第一个字符描述决定。
需要同时修改头文件长度配置信息
PRODUCTDESC产品信息。

char code PRODUCTDESC[26] =
{0x1a,0x03,'S',0,'T',0,'C',0,' ',0,'H',0,'I',0,'D',0,' ',0,'D',0,'e',0,'m',0,'o',0,
};
char code PRODUCTDESC[34] =
{0x22,0x03,'S',0,'Z',0,' ',0,'I',0,'t',0,'h',0,'e',0,'i',0,'m',0,'a',0,' ',0,0x2e, 0x95, 0xd8, 0x76, '4',0,'1',0,'3',0,
};

修改后长度发生变化,长度由第一个字符描述决定。
需要同时修改头文件长度配置信息
如果是中文,需要把中文转化为ASCII码格式(ASCII在线转换),写入其中
例如:
● 黑马程序员对应ASCII码:\u9ed1\u9a6c\u7a0b\u5e8f\u5458
● 汉字低位在前,添加黑为:0xd1, 0x9e

兼容库函数

由于提供的官方示例中,stc.hSTC8H.hconfig.h,这些文件和我们需要用到的库函数,在变量定义或者是文件命名上,存在重名等冲突,需要进行修改。

  1. 将这三个文件合并成一个文件usb_config.h
#ifndef __USB_CONFIG_H__
#define __USB_CONFIG_H__#include <intrins.h>
#include <stdio.h>#include "config.h"#define FOSC	MAIN_Fosctypedef bit BOOL;
typedef unsigned char BYTE;
typedef unsigned int WORD;
typedef unsigned long DWORD;typedef unsigned char uchar;
typedef unsigned int uint;
typedef unsigned int ushort;
typedef unsigned long ulong;typedef unsigned char uint8_t;
typedef unsigned int uint16_t;
typedef unsigned long uint32_t;#define     CLKSEL                  (*(unsigned char volatile xdata *)0xfe00)
#define     CLKDIV                  (*(unsigned char volatile xdata *)0xfe01)
#define     HIRCCR                  (*(unsigned char volatile xdata *)0xfe02)
#define     XOSCCR                  (*(unsigned char volatile xdata *)0xfe03)
#define     IRC32KCR                (*(unsigned char volatile xdata *)0xfe04)
#define     MCLKOCR                 (*(unsigned char volatile xdata *)0xfe05)
#define     IRCDB                   (*(unsigned char volatile xdata *)0xfe06)
#define     IRC48MCR                (*(unsigned char volatile xdata *)0xfe07)
#define     X32KCR                  (*(unsigned char volatile xdata *)0xfe08)
#define     RSTFLAG                 (*(unsigned char volatile xdata *)0xfe09)#define USB_config() {P_SW2 |= 0x80;P3M0 &= ~0x03;P3M1 |= 0x03;IRC48MCR = 0x80;while (!(IRC48MCR & 0x01));}#define EN_EP1IN
//#define EN_EP2IN
//#define EN_EP3IN
//#define EN_EP4IN
//#define EN_EP5IN
#define EN_EP1OUT
//#define EN_EP2OUT
//#define EN_EP3OUT
//#define EN_EP4OUT
//#define EN_EP5OUT#define EP0_SIZE                64#ifdef EN_EP1IN
#define EP1IN_SIZE              64
#endif
#ifdef EN_EP2IN
#define EP2IN_SIZE              64
#endif
#ifdef EN_EP3IN
#define EP3IN_SIZE              64
#endif
#ifdef EN_EP4IN
#define EP4IN_SIZE              64
#endif
#ifdef EN_EP5IN
#define EP5IN_SIZE              64
#endif
#ifdef EN_EP1OUT
#define EP1OUT_SIZE             64
#endif
#ifdef EN_EP2OUT
#define EP2OUT_SIZE             64
#endif
#ifdef EN_EP3OUT
#define EP3OUT_SIZE             64
#endif
#ifdef EN_EP4OUT
#define EP4OUT_SIZE             64
#endif
#ifdef EN_EP5OUT
#define EP5OUT_SIZE             64
#endif#endif

修改所有include的文件头信息为usb_config.h
修改main.c,进行调试测试

#include "usb_config.h"
#include "usb.h"void sys_init();void main()
{USB_config();usb_init();EA = 1;while (1);
}

HID键盘

官方提供了键盘案例实现,但是嵌套太多,我们需要自己摘出来

#include "usb.h"
#include "usb_req_std.h"
#include "usb_req_class.h"
#include "usb_req_vendor.h"
#include "util.h"BYTE DeviceState;
SETUP Setup;
EPSTATE Ep0State;
BYTE InEpState;
BYTE OutEpState;BOOL UsbInBusy;BYTE xdata UsbBuffer[256];void usb_init()
{USBCLK = 0x00;USBCON = 0x90;usb_write_reg(FADDR, 0x00);usb_write_reg(POWER, 0x09);usb_write_reg(INTRIN1E, 0x3f);usb_write_reg(INTROUT1E, 0x3f);usb_write_reg(INTRUSBE, 0x07);usb_write_reg(POWER, 0x01);DeviceState = DEVSTATE_DEFAULT;Ep0State.bState = EPSTATE_IDLE;InEpState = 0x00;OutEpState = 0x00;UsbInBusy = 0;IE2 |= 0x80;    //EUSB = 1;
}BYTE usb_read_reg(BYTE addr)
{BYTE dat;while (USBADR & 0x80);USBADR = addr | 0x80;while (USBADR & 0x80);dat = USBDAT;return dat;
}void usb_write_reg(BYTE addr, BYTE dat)
{while (USBADR & 0x80);USBADR = addr & 0x7f;USBDAT = dat;
}BYTE usb_read_fifo(BYTE fifo, BYTE *pdat)
{BYTE cnt;BYTE ret;ret = cnt = usb_read_reg(COUNT0);while (cnt--){*pdat++ = usb_read_reg(fifo);}return ret;
}void usb_write_fifo(BYTE fifo, BYTE *pdat, BYTE cnt)
{while (cnt--){usb_write_reg(fifo, *pdat++);}
}void usb_isr() interrupt 25
{BYTE intrusb;BYTE intrin;BYTE introut;BYTE adrTemp;adrTemp = USBADR;     //USBADR 现场保存,避免主循环里写完 USBADR 后产生中断,在中断里修改了 USBADR 内容intrusb = usb_read_reg(INTRUSB);intrin = usb_read_reg(INTRIN1);introut = usb_read_reg(INTROUT1);if (intrusb & RSUIF) usb_resume();if (intrusb & RSTIF) usb_reset();if (intrin & EP0IF) usb_setup();#ifdef EN_EP1INif (intrin & EP1INIF) usb_in_ep1();
#endif
#ifdef EN_EP2INif (intrin & EP2INIF) usb_in_ep2();
#endif
#ifdef EN_EP3INif (intrin & EP3INIF) usb_in_ep3();
#endif
#ifdef EN_EP4INif (intrin & EP4INIF) usb_in_ep4();
#endif
#ifdef EN_EP5INif (intrin & EP5INIF) usb_in_ep5();
#endif#ifdef EN_EP1OUTif (introut & EP1OUTIF) usb_out_ep1();
#endif
#ifdef EN_EP2OUTif (introut & EP2OUTIF) usb_out_ep2();
#endif
#ifdef EN_EP3OUTif (introut & EP3OUTIF) usb_out_ep3();
#endif
#ifdef EN_EP4OUTif (introut & EP4OUTIF) usb_out_ep4();
#endif
#ifdef EN_EP5OUTif (introut & EP5OUTIF) usb_out_ep5();
#endifif (intrusb & SUSIF) usb_suspend();USBADR = adrTemp;    //USBADR 现场恢复
}void usb_resume()
{
}void usb_reset()
{usb_write_reg(FADDR, 0x00);DeviceState = DEVSTATE_DEFAULT;Ep0State.bState = EPSTATE_IDLE;#ifdef EN_EP1INusb_write_reg(INDEX, 1);usb_write_reg(INCSR1, INCLRDT | INFLUSH);
#endif
#ifdef EN_EP2INusb_write_reg(INDEX, 2);usb_write_reg(INCSR1, INCLRDT | INFLUSH);
#endif
#ifdef EN_EP3INusb_write_reg(INDEX, 3);usb_write_reg(INCSR1, INCLRDT | INFLUSH);
#endif
#ifdef EN_EP4INusb_write_reg(INDEX, 4);usb_write_reg(INCSR1, INCLRDT | INFLUSH);
#endif
#ifdef EN_EP5INusb_write_reg(INDEX, 5);usb_write_reg(INCSR1, INCLRDT | INFLUSH);
#endif
#ifdef EN_EP1OUTusb_write_reg(INDEX, 1);usb_write_reg(OUTCSR1, OUTCLRDT | OUTFLUSH);
#endif
#ifdef EN_EP2OUTusb_write_reg(INDEX, 2);usb_write_reg(OUTCSR1, OUTCLRDT | OUTFLUSH);
#endif
#ifdef EN_EP3OUTusb_write_reg(INDEX, 3);usb_write_reg(OUTCSR1, OUTCLRDT | OUTFLUSH);
#endif
#ifdef EN_EP4OUTusb_write_reg(INDEX, 4);usb_write_reg(OUTCSR1, OUTCLRDT | OUTFLUSH);
#endif
#ifdef EN_EP5OUTusb_write_reg(INDEX, 5);usb_write_reg(OUTCSR1, OUTCLRDT | OUTFLUSH);
#endifusb_write_reg(INDEX, 0);
}void usb_suspend()
{
}void usb_setup()
{BYTE csr;usb_write_reg(INDEX, 0);csr = usb_read_reg(CSR0);if (csr & STSTL){usb_write_reg(CSR0, csr & ~SDSTL);Ep0State.bState = EPSTATE_IDLE;}if (csr & SUEND){usb_write_reg(CSR0, csr & ~SSUEND);}switch (Ep0State.bState){case EPSTATE_IDLE:if (csr & OPRDY){usb_read_fifo(FIFO0, (BYTE *)&Setup);Setup.wLength = reverse2(Setup.wLength);switch (Setup.bmRequestType & REQUEST_MASK){case STANDARD_REQUEST:usb_req_std();break;case CLASS_REQUEST:usb_req_class();break;case VENDOR_REQUEST:usb_req_vendor();break;default:usb_setup_stall();return;}}break;case EPSTATE_DATAIN:usb_ctrl_in();break;case EPSTATE_DATAOUT:usb_ctrl_out();break;}
}void usb_setup_stall()
{Ep0State.bState = EPSTATE_STALL;usb_write_reg(CSR0, SOPRDY | SDSTL);
}void usb_setup_in()
{Ep0State.bState = EPSTATE_DATAIN;usb_write_reg(CSR0, SOPRDY);usb_ctrl_in();
}void usb_setup_out()
{Ep0State.bState = EPSTATE_DATAOUT;usb_write_reg(CSR0, SOPRDY);
}void usb_setup_status()
{Ep0State.bState = EPSTATE_IDLE;usb_write_reg(CSR0, SOPRDY | DATEND);
}void usb_ctrl_in()
{BYTE csr;BYTE cnt;usb_write_reg(INDEX, 0);csr = usb_read_reg(CSR0);if (csr & IPRDY) return;cnt = Ep0State.wSize > EP0_SIZE ? EP0_SIZE : Ep0State.wSize;usb_write_fifo(FIFO0, Ep0State.pData, cnt);Ep0State.wSize -= cnt;Ep0State.pData += cnt;if (Ep0State.wSize == 0){usb_write_reg(CSR0, IPRDY | DATEND);Ep0State.bState = EPSTATE_IDLE;}else{usb_write_reg(CSR0, IPRDY);}
}void usb_ctrl_out()
{BYTE csr;BYTE cnt;usb_write_reg(INDEX, 0);csr = usb_read_reg(CSR0);if (!(csr & OPRDY)) return;cnt = usb_read_fifo(FIFO0, Ep0State.pData);Ep0State.wSize -= cnt;Ep0State.pData += cnt;if (Ep0State.wSize == 0){usb_write_reg(CSR0, SOPRDY | DATEND);Ep0State.bState = EPSTATE_IDLE;}else{usb_write_reg(CSR0, SOPRDY);}
}void usb_bulk_intr_in(BYTE *pData, BYTE bSize, BYTE ep)
{usb_write_fifo((BYTE)(FIFO0 + ep), pData, bSize);usb_write_reg(INCSR1, INIPRDY);
}BYTE usb_bulk_intr_out(BYTE *pData, BYTE ep)
{BYTE cnt;cnt = usb_read_fifo((BYTE)(FIFO0 + ep), pData);usb_write_reg(OUTCSR1, 0);return cnt;
}#ifdef EN_EP1IN
void usb_in_ep1()
{BYTE csr;usb_write_reg(INDEX, 1);csr = usb_read_reg(INCSR1);if (csr & INSTSTL){usb_write_reg(INCSR1, INCLRDT);}if (csr & INUNDRUN){usb_write_reg(INCSR1, 0);}UsbInBusy = 0;
}
#endif#ifdef EN_EP2IN
void usb_in_ep2()
{BYTE csr;usb_write_reg(INDEX, 2);csr = usb_read_reg(INCSR1);if (csr & INSTSTL){usb_write_reg(INCSR1, INCLRDT);}if (csr & INUNDRUN){usb_write_reg(INCSR1, 0);}
}
#endif#ifdef EN_EP3IN
void usb_in_ep3()
{BYTE csr;usb_write_reg(INDEX, 3);csr = usb_read_reg(INCSR1);if (csr & INSTSTL){usb_write_reg(INCSR1, INCLRDT);}if (csr & INUNDRUN){usb_write_reg(INCSR1, 0);}
}
#endif#ifdef EN_EP4IN
void usb_in_ep4()
{BYTE csr;usb_write_reg(INDEX, 4);csr = usb_read_reg(INCSR1);if (csr & INSTSTL){usb_write_reg(INCSR1, INCLRDT);}if (csr & INUNDRUN){usb_write_reg(INCSR1, 0);}
}
#endif#ifdef EN_EP5IN
void usb_in_ep5()
{BYTE csr;usb_write_reg(INDEX, 5);csr = usb_read_reg(INCSR1);if (csr & INSTSTL){usb_write_reg(INCSR1, INCLRDT);}if (csr & INUNDRUN){usb_write_reg(INCSR1, 0);}
}
#endif#ifdef EN_EP1OUT
void usb_out_ep1()
{BYTE csr;usb_write_reg(INDEX, 1);csr = usb_read_reg(OUTCSR1);if (csr & OUTSTSTL){usb_write_reg(OUTCSR1, OUTCLRDT);}if (csr & OUTOPRDY){//usb_bulk_intr_in(UsbBuffer, usb_bulk_intr_out(UsbBuffer, 1), 1);    //功能测试,原路返回usb_class_out();}
}
#endif#ifdef EN_EP2OUT
void usb_out_ep2()
{BYTE csr;usb_write_reg(INDEX, 2);csr = usb_read_reg(OUTCSR1);if (csr & OUTSTSTL){usb_write_reg(OUTCSR1, OUTCLRDT);}if (csr & OUTOPRDY){usb_bulk_intr_out(Ep2OutBuffer, 2);}
}
#endif#ifdef EN_EP3OUT
void usb_out_ep3()
{BYTE csr;usb_write_reg(INDEX, 3);csr = usb_read_reg(OUTCSR1);if (csr & OUTSTSTL){usb_write_reg(OUTCSR1, OUTCLRDT);}if (csr & OUTOPRDY){usb_bulk_intr_out(Ep3OutBuffer, 3);}
}
#endif#ifdef EN_EP4OUT
void usb_out_ep4()
{BYTE csr;usb_write_reg(INDEX, 4);csr = usb_read_reg(OUTCSR1);if (csr & OUTSTSTL){usb_write_reg(OUTCSR1, OUTCLRDT);}if (csr & OUTOPRDY){usb_bulk_intr_out(Ep4OutBuffer, 4);}
}
#endif#ifdef EN_EP5OUT
void usb_out_ep5()
{BYTE csr;usb_write_reg(INDEX, 5);csr = usb_read_reg(OUTCSR1);if (csr & OUTSTSTL){usb_write_reg(OUTCSR1, OUTCLRDT);}if (csr & OUTOPRDY){usb_bulk_intr_out(Ep5OutBuffer, 5);}
}
#endif

在usb_req_class.h头中添加声明

void usb_hid_keyboard_send(u8 key[8]);
void usb_class_out();extern BYTE bHidIdle;
// 当PC发送键盘LED信息数据
extern void usb_hid_keyboard_data_in(u8 dat);

usb_req_class.c中添加实现

void usb_hid_keyboard_send(u8 key[8]) {BYTE i;if (DeviceState != DEVSTATE_CONFIGURED)  //如果USB配置没有完成,就直接退出return;if (!UsbInBusy)  //判断USB是否空闲,以及是否有按键按下{// 发送按键操作IE2 &= ~0x80;   //EUSB = 0;UsbInBusy = 1;usb_write_reg(INDEX, 1);for (i=0; i<8; i++){usb_write_reg(FIFO1, key[i]);  //发送按键码}usb_write_reg(INCSR1, INIPRDY);IE2 |= 0x80;    //EUSB = 1;}
}void usb_class_out() {if(usb_bulk_intr_out(UsbBuffer, 1) == 1) {//printf("out: %d\r\n", (int)UsbBuffer[0]);usb_hid_keyboard_data_in(UsbBuffer[0]);}
}

修改usb_desc.c中的配置

#include "usb_config.h"
#include "usb_desc.h"char code DEVICEDESC[18] =
{0x12,                   //bLength(18);0x01,                   //bDescriptorType(Device);0x00,0x02,              //bcdUSB(2.00);0x00,                   //bDeviceClass(0);0x00,                   //bDeviceSubClass0);0x00,                   //bDeviceProtocol(0);0x40,                   //bMaxPacketSize0(64);0xbf,0x34,              //idVendor(34bf);0x01,0xff,              //idProduct(ff01);0x00,0x01,              //bcdDevice(1.00);0x01,                   //iManufacturer(1);0x02,                   //iProduct(2);0x00,                   //iSerialNumber(0);0x01,                   //bNumConfigurations(1);
};char code CONFIGDESC[41] =
{/// 配置描述符 ///0x09,                   //bLength(9);0x02,                   //bDescriptorType(Configuration);0x29,0x00,              //wTotalLength(41);0x01,                   //bNumInterfaces(1);0x01,                   //bConfigurationValue(1);0x00,                   //iConfiguration(0);0x80,                   //bmAttributes(BUSPower);0x32,                   //MaxPower(100mA);/// 接口描述符 ///0x09,                   //bLength(9);0x04,                   //bDescriptorType(Interface);0x00,                   //bInterfaceNumber(0);0x00,                   //bAlternateSetting(0);0x02,                   //bNumEndpoints(2);0x03,                   //bInterfaceClass(HID);0x01,                   //bInterfaceSubClass(0默认 1Boot);0x01,                   //bInterfaceProtocol(1键盘 2鼠标);0x00,                   //iInterface(0); HID接口描述符 (由接口描述符决定) //0x09,                   //bLength(9);0x21,                   //bDescriptorType(HID);0x01,0x01,              //bcdHID(1.01);0x00,                   //bCountryCode(0);0x01,                   //bNumDescriptors(1);0x22,                   //bDescriptorType(HID Report);0x41,0x00,              //wDescriptorLength(65);// 端点描述符(IN) /0x07,                   //bLength(7);0x05,                   //bDescriptorType(Endpoint);0x81,                   //bEndpointAddress(EndPoint1 as IN);0x03,                   //bmAttributes(Interrupt);0x08,0x00,              //wMaxPacketSize(8);0x0a,                   //bInterval(10ms);// 端点描述符(OUT) /0x07,                   //bLength(7);0x05,                   //bDescriptorType(Endpoint);0x01,                   //bEndpointAddress(EndPoint1 as OUT);0x03,                   //bmAttributes(Interrupt);0x01,0x00,              //wMaxPacketSize(1);0x0a,                   //bInterval(10ms);
};/**
char code HIDREPORTDESC[27] =
{0x05,0x0c,              //USAGE_PAGE(Consumer);0x09,0x01,              //USAGE(Consumer Control);0xa1,0x01,              //COLLECTION(Application);0x15,0x00,              //  LOGICAL_MINIMUM(0);0x25,0xff,              //  LOGICAL_MAXIMUM(255);0x75,0x08,              //  REPORT_SIZE(8);0x95,0x40,              //  REPORT_COUNT(64);0x09,0x01,              //  USAGE(Consumer Control);0xb1,0x02,              //  FEATURE(Data,Variable);0x09,0x01,              //  USAGE(Consumer Control);0x81,0x02,              //  INPUT(Data,Variable);0x09,0x01,              //  USAGE(Consumer Control);0x91,0x02,              //  OUTPUT(Data,Variable);0xc0,                   //END_COLLECTION;
};
**//*
Input Report:
0 Modifierkeys (D0:LCtrl D1:LShift D2:LAlt D3:LGui D4:RCtrl D5:RShift D6:RAlt D7:RGui)
1	Reserved
2	Keycode 1
3	Keycode 2
4	Keycode 3
5	Keycode 4
6	Keycode 5
7	Keycode 6
Output Report:
0   LEDs (D0:NumLock D1:CapLock D2:ScrollLock)
*/
char code HIDREPORTDESC[65] =
{0x05,0x01,              //USAGE_PAGE(Generic Desktop);0x09,0x06,              //USAGE(Keyboard);0xa1,0x01,              //COLLECTION(Application);0x05,0x07,              //  USAGE_PAGE(Keyboard);0x19,0xe0,              //  USAGE_MINIMUM(224);0x29,0xe7,              //  USAGE_MAXIMUM(255);0x15,0x00,              //  LOGICAL_MINIMUM(0);0x25,0x01,              //  LOGICAL_MAXIMUM(1);0x75,0x01,              //  REPORT_SIZE(1);0x95,0x08,              //  REPORT_COUNT(8);0x81,0x02,              //  INPUT(Data,Variable,Absolute);0x75,0x08,              //  REPORT_SIZE(8);0x95,0x01,              //  REPORT_COUNT(1);0x81,0x01,              //  INPUT(Constant);0x19,0x00,              //  USAGE_MINIMUM(0);0x29,0x65,              //  USAGE_MAXIMUM(101);0x15,0x00,              //  LOGICAL_MINIMUM(0);0x25,0x65,              //  LOGICAL_MAXIMUM(101);0x75,0x08,              //  REPORT_SIZE(8);0x95,0x06,              //  REPORT_COUNT(6);0x81,0x00,              //  INPUT(Data,Array);0x05,0x08,              //  USAGE_PAGE(LEDs);0x19,0x01,              //  USAGE_MINIMUM(1);0x29,0x03,              //  USAGE_MAXIMUM(3);0x15,0x00,              //  LOGICAL_MINIMUM(0);0x25,0x01,              //  LOGICAL_MAXIMUM(1);0x75,0x01,              //  REPORT_SIZE(1);0x95,0x03,              //  REPORT_COUNT(3);0x91,0x02,              //  OUTPUT(Data,Variable,Absolute);0x75,0x05,              //  REPORT_SIZE(5);0x95,0x01,              //  REPORT_COUNT(1);0x91,0x01,              //  OUTPUT(Constant);0xc0,                   //END_COLLECTION;
};char code LANGIDDESC[4] =
{0x04,0x03,0x09,0x04,
};char code MANUFACTDESC[16] =
{0x10,0x03,'I',0,'T',0,'H',0,'E',0,'I',0,'M',0,'A',0,
};// 第一个是长度:
// 
char code PRODUCTDESC[34] =
{0x22,0x03,'S',0,'Z',0,' ',0,'I',0,'t',0,'h',0,'e',0,'i',0,'m',0,'a',0,' ',0,0x2e, 0x95, 0xd8, 0x76, '4',0,'1',0,'3',0,
};char code PACKET0[2] = 
{0, 0,
};char code PACKET1[2] = 
{1, 0,
};

键盘主逻辑

#include "config.h"
#include "usb.h"
#include "usb_req_class.h"
#include <stdio.h>
#include "delay.h"
#include "GPIO.h"
#include "UART.h"
#include "timer.h"
#include "LOG.h"#define LED1		P27
#define LED2		P26
#define LED3		P15
#define LED4		P14
#define LED5		P23
#define LED6		P22
#define LED7		P21
#define LED8		P20
#define LED_SW	P45#define ROW1	P34
#define ROW2	P35
#define ROW3	P40
#define ROW4	P41#define COL1	P03
#define COL2	P06
#define COL3	P07
#define COL4	P17#define ROW 4
#define COL 4// 记录16个按键状态,0为按下,1为抬起
u16 key_state = 0xFFFF;
u16 last_state = 0xFFFF;#define KEY_UP		1
#define KEY_DOWN	0
// 第n个按键的状态
#define KEY_STATE(r, c)			((key_state & (1 << (r * ROW + c))) >> (r * ROW + c))
#define SET_KEY_UP(r, c)		(key_state |= (1 << (r * ROW + c)))
#define SET_KEY_DOWN(r, c)	(key_state &= ~(1 << (r * ROW + c)))#define IS_KEY_DOWN(n)	KEY_STATE(n / ROW, n % ROW) == KEY_DOWN#define ROW_COL_RESET() {ROW1=1,ROW2=1,ROW3=1,ROW4=1;COL1=1,COL2=1,COL3=1,COL4=1;}
u8 s_count = 0;u8 key_map[] = {0, 0, 0, 0, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, // 4:	 A			0x04 // 5:  B			0x05// 6:  C			0x06// 7:  D			0x07// 8:  E			0x08// 9:  F			0x09// 10: 1			0x1E// 11: 2			0x1F// 12: 3			0x20// 13: 4			0x21// 14: 5			0x22// 15: 6			0x23
};static void ROW_ON(u8 n) {if(n == 0) ROW1 = 0;if(n == 1) ROW2 = 0;if(n == 2) ROW3 = 0;if(n == 3) ROW4 = 0;
}static u8 COL_STATE(u8 n) {if(n == 0) return COL1;if(n == 1) return COL2;if(n == 2) return COL3;if(n == 3) return COL4;
}void GPIO_config(void) {GPIO_InitTypeDef	GPIO_InitStructure;		//结构定义GPIO_InitStructure.Pin  = GPIO_Pin_0 | GPIO_Pin_1;		//指定要初始化的IO,GPIO_InitStructure.Mode = GPIO_PullUp;	//指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PPGPIO_Inilize(GPIO_P3, &GPIO_InitStructure);//初始化GPIO_InitStructure.Pin  = GPIO_Pin_7;	//指定要初始化的IO,GPIO_InitStructure.Mode = GPIO_PullUp;	//指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PPGPIO_Inilize(GPIO_P1, &GPIO_InitStructure);//初始化GPIO_InitStructure.Pin  = GPIO_Pin_4 | GPIO_Pin_5;	//指定要初始化的IO,GPIO_InitStructure.Mode = GPIO_PullUp;	//指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PPGPIO_Inilize(GPIO_P3, &GPIO_InitStructure);//初始化GPIO_InitStructure.Pin  = GPIO_Pin_3 | GPIO_Pin_6 | GPIO_Pin_7;		//指定要初始化的IO,GPIO_InitStructure.Mode = GPIO_PullUp;	//指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PPGPIO_Inilize(GPIO_P0, &GPIO_InitStructure);//初始化GPIO_InitStructure.Pin  = GPIO_Pin_0 | GPIO_Pin_1;	//指定要初始化的IO,GPIO_InitStructure.Mode = GPIO_PullUp;	//指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PPGPIO_Inilize(GPIO_P4, &GPIO_InitStructure);//初始化GPIO_InitStructure.Pin  = GPIO_Pin_5;		//指定要初始化的IO,GPIO_InitStructure.Mode = GPIO_OUT_PP;	//指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PPGPIO_Inilize(GPIO_P4, &GPIO_InitStructure);//初始化GPIO_InitStructure.Pin  = GPIO_Pin_4 | GPIO_Pin_5;		//指定要初始化的IO,GPIO_InitStructure.Mode = GPIO_PullUp;	//指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PPGPIO_Inilize(GPIO_P1, &GPIO_InitStructure);//初始化GPIO_InitStructure.Pin  = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_6 | GPIO_Pin_7;		//指定要初始化的IO,GPIO_InitStructure.Mode = GPIO_PullUp;	//指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PPGPIO_Inilize(GPIO_P2, &GPIO_InitStructure);//初始化
}void UART_config(void)
{COMx_InitDefine		COMx_InitStructure;					//结构定义COMx_InitStructure.UART_Mode      = UART_8bit_BRTx;	//模式, UART_ShiftRight,UART_8bit_BRTx,UART_9bit,UART_9bit_BRTxCOMx_InitStructure.UART_BRT_Use   = BRT_Timer1;			//选择波特率发生器, BRT_Timer1, BRT_Timer2 (注意: 串口2固定使用BRT_Timer2)COMx_InitStructure.UART_BaudRate  = 115200ul;			//波特率, 一般 110 ~ 115200COMx_InitStructure.UART_RxEnable  = ENABLE;				//接收允许,   ENABLE或DISABLECOMx_InitStructure.BaudRateDouble = DISABLE;			//波特率加倍, ENABLE或DISABLECOMx_InitStructure.UART_Interrupt = ENABLE;				//中断允许,   ENABLE或DISABLECOMx_InitStructure.UART_Priority    = Priority_0;			//指定中断优先级(低到高) Priority_0,Priority_1,Priority_2,Priority_3COMx_InitStructure.UART_P_SW      = UART1_SW_P30_P31;	//切换端口,   UART1_SW_P30_P31,UART1_SW_P36_P37,UART1_SW_P16_P17,UART1_SW_P43_P44UART_Configuration(UART1, &COMx_InitStructure);		//初始化串口1 UART1,UART2,UART3,UART4
}void TIMER_config(void) {TIM_InitTypeDef		TIM_InitStructure;						//结构定义TIM_InitStructure.TIM_Mode      = TIM_16BitAutoReload;	//指定工作模式,   TIM_16BitAutoReload,TIM_16Bit,TIM_8BitAutoReload,TIM_16BitAutoReloadNoMaskTIM_InitStructure.TIM_Priority    = Priority_0;			//指定中断优先级(低到高) Priority_0,Priority_1,Priority_2,Priority_3TIM_InitStructure.TIM_Interrupt = ENABLE;					//中断是否允许,   ENABLE或DISABLETIM_InitStructure.TIM_ClkSource = TIM_CLOCK_1T;		//指定时钟源,     TIM_CLOCK_1T,TIM_CLOCK_12T,TIM_CLOCK_ExtTIM_InitStructure.TIM_ClkOut    = DISABLE;				//是否输出高速脉冲, ENABLE或DISABLETIM_InitStructure.TIM_Value     = 65536UL - (MAIN_Fosc / 400UL);		//初值,2.5ms扫描一次键盘状态TIM_InitStructure.TIM_Run       = ENABLE;					//是否初始化后启动定时器, ENABLE或DISABLETimer_Inilize(Timer0,&TIM_InitStructure);					//初始化Timer0	  Timer0,Timer1,Timer2,Timer3,Timer4
}void timer0_call() {u8 key[8];u8 i, j, k0;for(i = 0; i < ROW; i++) {// 初始都是 高电平ROW_COL_RESET();NOP1();ROW_ON(i);for(j = 0; j < COL; j++) {// 当前是UP,当之前是DOWN,则为UP// 当前是DOWN,当之前是UP,则为DOWNif(COL_STATE(j) != KEY_STATE(i, j)) {if(COL_STATE(j)) {// 修改当前状态为UPSET_KEY_UP(i, j);Debug("(%d, %d) Up\r\n", (int)i, (int)j);} else {// 修改当前状态为DOWNSET_KEY_DOWN(i, j);Debug("(%d, %d) Down\r\n", (int)i, (int)j);}}}}if(s_count++ > 10) {// 25ms执行一次s_count = 0;if(last_state == key_state && key_state == 0xFFFF) {// 上一次和这一次都是没有按键,没必要给PC发指令return;}/**			0 Modifierkeys (D0:LCtrl D1:LShift D2:LAlt D3:LGui D4:RCtrl D5:RShift D6:RAlt D7:RGui)1	Reserved2	Keycode 13	Keycode 24	Keycode 35	Keycode 46	Keycode 57	Keycode 6**/// 假设 0-16 key对照表如下// 0: LCtrl// 1: LShift// 2: LAlt// 3: LGui// 4:	 A			0x04 // 5:  B			0x05// 6:  C			0x06// 7:  D			0x07// 8:  E			0x08// 9:  F			0x09// 10: 1			0x1E// 11: 2			0x1F// 12: 3			0x20// 13: 4			0x21// 14: 5			0x22// 15: 6			0x23k0 = 0;if(IS_KEY_DOWN(0)) {k0 |= 1 << 0;} if(IS_KEY_DOWN(1)) {k0 |= 1 << 1;} if(IS_KEY_DOWN(2)) {k0 |= 1 << 2;} if(IS_KEY_DOWN(3)) {k0 |= 1 << 3;} key[0] = k0;key[1] = 0;// 保留key[2] = 0;key[3] = 0;key[4] = 0;key[5] = 0;key[6] = 0;key[7] = 0;j = 2;for(i = 4; i < 16; i++) {if(IS_KEY_DOWN(i)) {key[j++] = key_map[i];}}usb_hid_keyboard_send(key);// 重置上一次的状态last_state = key_state;}		}void usb_hid_keyboard_data_in(u8 dat) {// B7 | B6 | B5 | B4 | B3 | B2 | B1 			| B0// 无 | 无 | 无 | 无 | 无 | XX | 大小写锁 | 数字键盘锁// 数字键盘锁:1为 数字键盘可用// 大小写锁:	1为 大写锁定// LED1 = !((dat >> 7) & 0x01);LED2 = !((dat >> 6) & 0x01);LED3 = !((dat >> 5) & 0x01);LED4 = !((dat >> 4) & 0x01);LED5 = !((dat >> 3) & 0x01);LED6 = !((dat >> 2) & 0x01);LED7 = !((dat >> 1) & 0x01);LED8 = !((dat >> 0) & 0x01);
}void main()
{u8 i;P0M1 = 0;P0M0 = 0;P1M1 = 0;P1M0 = 0;P2M1 = 0;P2M0 = 0;P3M1 = 0;P3M0 = 0;P4M1 = 0;P4M0 = 0;P5M1 = 0;P5M0 = 0;P6M1 = 0;P6M0 = 0;P7M1 = 0;P7M0 = 0;USB_config();usb_init();GPIO_config();//UART_config();TIMER_config();EA = 1;LED_SW = 0;while(1) {if(COM1.RX_TimeOut > 0)		//超时计数{if(--COM1.RX_TimeOut == 0) {if(COM1.RX_Cnt > 0) {for(i=0; i<COM1.RX_Cnt; i++) {// RX1_Buffer[i]接收的字节// TODO:业务逻辑}}COM1.RX_Cnt = 0;}}delay_ms(10);}
}

USB调试工具

USBlyzer是一款调试USB接口的工具

USB 描述符

设备描述符

struct _DEVICE_DESCRIPTOR_STRUCT 
{ BYTE bLength;           //设备描述符的字节数大小,为0x12 BYTE bDescriptorType;   //描述符类型编号,为0x01 WORD bcdUSB;           //USB版本号 BYTE bDeviceClass;   //USB分配的设备类代码,HID必须为0BYTE bDeviceSubClass;   //usb分配的子类代码,HID必须为0BYTE bDeviceProtocl;     //USB分配的设备协议代码,HID必须为0BYTE bMaxPacketSize0;   //端点0的最大包的大小 WORD idVendor;           //厂商编号 WORD idProduct;         //产品编号 WORD bcdDevice;         //设备出厂编号 BYTE iManufacturer;     //描述厂商字符串的索引 BYTE iProduct;           //描述产品字符串的索引 BYTE iSerialNumber;     //描述设备序列号字符串的索引 BYTE bNumConfiguration; //可能的配置数量 
} DEVICE_DESCRIPTOR_STRUCT
char code DEVICEDESC[18] =
{0x12,                   //bLength(18);0x01,                   //bDescriptorType(Device);0x00,0x02,              //bcdUSB(2.00);0x00,                   //bDeviceClass(0);0x00,                   //bDeviceSubClass0);0x00,                   //bDeviceProtocol(0);0x40,                   //bMaxPacketSize0(64);0xbf,0x34,              //idVendor(34bf);0x03,0xff,              //idProduct(ff03);0x00,0x01,              //bcdDevice(1.00);0x01,                   //iManufacturer(1);0x02,                   //iProduct(2);0x00,                   //iSerialNumber(0);0x01,                   //bNumConfigurations(1);
};

● bLength : 描述符大小.固定为0x12.
● bDescriptorType : 设备描述符类型.固定为0x01.
● bcdUSB : USB 规范发布号.表示了本设备能适用于那种协议,如2.0=0200,1.1=0110等.
● bDeviceClass : 类型代码(由USB指定)。当它的值是0时,表示所有接口在配置描述符里,并且所有接口是独立的。当它的值是1到FEH时,表示不同的接口关联的。当它的值是FFH时,它是厂商自己定义的.
● bDeviceSubClass : 子类型代码(由USB分配).如果bDeviceClass值是0,一定要设置为0.其它情况就跟据USB-IF组织定义的编码.
● bDeviceProtocol : 协议代码(由USB分配).如果使用USB-IF组织定义的协议,就需要设置这里的值,否则直接设置为0。如果厂商自己定义的可以设置为FFH.
操作系统使用bDeviceClass、bDeviceSubClass和bDeviceProtocol来查找设备的类驱动程序。通常只有 bDeviceClass 设置在设备级别。大多数类规范选择在接口级别标识自己,因此将 bDeviceClass 设置为 0x00。这允许一个设备支持多个类,即USB复合设备。
● bMaxPacketSize0 : 端点0最大分组大小(只有8,16,32,64有效).
● idVendor : 供应商ID(由USB分配).
● idProduct : 产品ID(由厂商分配).由供应商ID和产品ID,就可以让操作系统加载不同的驱动程序.
● bcdDevice : 设备出产编码.由厂家自行设置.
● iManufacturer : 厂商描述符字符串索引.索引到对应的字符串描述符. 为0则表示没有.
● iProduct : :产品描述符字符串索引.同上.
● iSerialNumber : 设备序列号字符串索引.同上.
● bNumConfigurations : 可能的配置数.定义设备以当前速度支持的配置数量

配置描述符

struct _CONFIGURATION_DESCRIPTOR_STRUCT 
{ BYTE bLength;           //配置描述符的字节数大小,固定为9字节BYTE bDescriptorType;   //描述符类型编号,为0x02 WORD wTotalLength;     //配置所返回的所有数量的大小 BYTE bNumInterface;     //此配置所支持的接口数量 BYTE bConfigurationVale;   //Set_Configuration命令需要的参数值 BYTE iConfiguration;       //描述该配置的字符串的索引值 BYTE bmAttribute;           //供电模式的选择 BYTE MaxPower;             //设备从总线提取的最大电流 
}CONFIGURATION_DESCRIPTOR_STRUCT

● bLength : 描述符大小.固定为0x09.
● bDescriptorType : 配置描述符类型.固定为0x02.
● wTotalLength : 返回整个数据的长度.指此配置返回的配置描述符,接口描述符以及端点描述符的全部大小.
● bNumInterfaces : 配置所支持的接口数.指该配置配备的接口数量,也表示该配置下接口描述符数量.
● bConfigurationValue : 作为Set Configuration的一个参数选择配置值.
● iConfiguration : 用于描述该配置字符串描述符的索引.
● bmAttributes : 供电模式选择.Bit4-0保留,D7:总线供电,D6:自供电,D5:远程唤醒.
● MaxPower : 总线供电的USB设备的最大消耗电流.以2mA为单位.
● 接口描述符:接口描述符说明了接口所提供的配置,一个配置所拥有的接口数量通过配置描述符的bNumInterfaces决定。

接口描述符

struct _INTERFACE_DESCRIPTOR_STRUCT 
{ BYTE bLength;           //设备描述符的字节数大小,为0x09 BYTE bDescriptorType;   //描述符类型编号,为0x04BYTE bInterfaceNumber; //接口的编号 BYTE bAlternateSetting;//备用的接口描述符编号 BYTE bNumEndpoints;     //该接口使用端点数,不包括端点0 BYTE bInterfaceClass;   //接口类型 BYTE bInterfaceSubClass;//接口子类型 BYTE bInterfaceProtocol;//接口所遵循的协议 BYTE iInterface;         //描述该接口的字符串索引值 
}INTERFACE_DESCRIPTOR_STRUCT

● bLength : 描述符大小.固定为0x09.
● bDescriptorType : 接口描述符类型.固定为0x04.
● bInterfaceNumber: 该接口的编号.
● bAlternateSetting : 用于为上一个字段选择可供替换的位置.即备用的接口描述符标号.
● bNumEndpoint : 使用的端点数目.端点0除外.
● bInterfaceClass : 类型代码(由USB分配).
● bInterfaceSubClass : 子类型代码(由USB分配).
● bInterfaceProtocol : 协议代码(由USB分配).
● iInterface : 字符串描述符的索引
HID类型
如果 bInterfaceClass 为 0x03,参考附录中的USB设备定义
则 bInterfaceSubClass 可选为下:
在这里插入图片描述

配置为1,表示:
表示HID设备符是一个启动设备(Boot Device,一般对PC机而言才有意义,意思是BIOS启动时能识别并使用您的HID设备,且只有标准鼠标或键盘类设备才能成为Boot Device,进入bios时不会枚举报告描述符,主机会采用一个默认的标准描述符,所以此时发送的报告要符合这个描述符,这是关键,标准描述符请查询HID协议。
bInterfaceProtocol 表示协议,可选为:
在这里插入图片描述
● 0x01: 表示键盘
● 0x02: 表示鼠标

HID描述符

struct _HID_DESCRIPTOR_STRUCT 
{ BYTE bLength;           //设备描述符的字节数大小, 为0x09BYTE bDescriptorType;   //描述符类型编号,为0x21WORD bcdHID;   			//HID规范版本号(BCD) BYTE bCountryCode;      //硬件设备所在国家的国家代码BYTE bNumDescriptors;   //类别描述符数目(至少有一个报表描述符)BYTE bDescriptorType;   //该类别描述符的类型WORD wDescriptorLength; //该类别描述符的总长度
} ENDPOIN_DESCRIPTOR_STRUCT;

● bLength: 描述符字节数
● bDescriptorType: HID描述符类型,0x21
● bcdHID:设备与其描述符所遵循的HID规范的版本号码,此数值是4个16进位的BCD格式字符。例如版本1.1的bcdHID是0110h
● bCountryCode:国家的识别码。如果不说明,该字段为0
● bNumDescriptors:HID描述符附属的描述符的类型(报表或实体)。每一个 HID都必须至少支持一个报表描述符。一个接口可以支持多个报表描述符,以及一个或多个实体描述符。
● bDescriptorType:HID描述符的偏移量为6和7的bDescriptorType和wDescriptorLength可以重复存在多个。
● wDescriptorLength:HID Report的数据长度

端点描述符

struct _ENDPOIN_DESCRIPTOR_STRUCT 
{ BYTE bLength;           //设备描述符的字节数大小,为0x7 BYTE bDescriptorType;   //描述符类型编号,为0x05BYTE bEndpointAddress; //端点地址及输入输出属性 BYTE bmAttribute;       //端点的传输类型属性 WORD wMaxPacketSize;   //端点收、发的最大包的大小 BYTE bInterval;         //主机查询端点的时间间隔 
} ENDPOIN_DESCRIPTOR_STRUCT;

● bLength : 描述符大小.固定为0x07.
● bDescriptorType : 接口描述符类型.固定为0x05.
● bEndpointAddress : USB设备的端点地址.Bit7,方向,对于控制端点可以忽略,1/0:IN/OUT.Bit6-4,保留.BIt3-0:端点号.
● bmAttributes : 端点属性.Bit7-2,保留(同步有定义).BIt1-0:00控制,01同步,02批量,03中断.
当为同步传输时,bEndpointType的bit3-2的值不同代表的含义不同:
00:无同步
01:异步
10:适配
11:同步
BIT5:4
00: 表示数据端点
01:表示反馈端点Feedback endpoint
10:表示隐式反馈数据端点 Implicit feedback Data endpoint
11:保留
● wMaxPacketSize : 本端点接收或发送的最大信息包大小.
USB2.0时:
对于同步端点,此值用于指示主机在调度中保留的总线时间,这是每(微)帧数据有效负载所需的时间,有效负载时间就是发送一帧数据需要占用的总线时间,在实际数据传输过程中,管道实际使用的带宽可能比保留的带宽少,大家想想,如果实际使用的带宽比保留的还多,那就丢数了;
对于其类型的端点,bit10~bit0指定最大数据包大小(以字节为单位);
bit12bit11对于高速传输的同步和中断端点有效:bit12bit11可指定每个微帧的额外通信次数,这里大家一定要知道是在高速传输中,当一个事务超时时,在一个微帧时间内重传的次数,如果设置为00b(None),则表示在一个微帧内只传输一个事务,不进行额外的超时重传,如果设置为01b,则表示在一个微帧内可以传输两次事务,有一次额外的重传机会,从下面可以看出,一个微帧最多可以有两次重传事务的机会,如果微帧结束了还是失败,就需要等到下一个微帧继续发送该事务;
USB3.0时:wMaxPacketSize表示包的大小。对于bulk为1024,而对于同步传输,可以为0~1024或 1024。
● bInterval : 轮询数据传送端点的时间间隔.对于批量传送和控制传送的端点忽略.对于同步传送的端点,必须为1,对于中断传送的端点,范围为1-255.
对于全速/高速同步端点,此值必须在1到16之间。bInterval值用作2的指数,例如bInterval为4,表示周期为8个单位;
对于全速/低速中断端点,该字段的值可以是1到255,也就是主机多少ms给设备发一次数据请求;
对于高速中断端点,使用bInterval值作为2的指数,例如bInterval为4表示周期为8。这个值必须在1到16之间;
对于高速批量/控制输出端点,bInterval必须指定端点的最大NAK速率。值0表示端点永不NAK。其它值表示每个微帧的bInterval*125us时间最多1个NAK。这个值的范围必须在0到255之间;
00 = None (1 transaction per microframe)
01 = 1 additional (2 per microframe)
10 = 2 additional (3 per microframe)
11 = Reserved
其它位默认为0,
对于全速/低速批量/控制输出端点,此值无意义,可以任意指定。

附录

USB设备类型定义

在接口描述符中来定义bInterfaceClass参数对应的值,表示当前设备是何种类型。
在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/844811.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MFC工控项目实例之一主菜单制作

1、本项目用在WIN10下安装的vc6.0兼容版实现。创建项目名为SEAL_PRESSURE的MFC对话框。在项目res文件下添加相关256色ico格式图片。 2、项目名称&#xff1a;密封压力试验机 主菜单名称&#xff1a; 系统参数 SYS_DATA 系统测试 SYS_TEST 选择型号 TYP_CHOICE 开始试验 TES_STA…

SAP_SD模块 物料科目分配/成本简介

SAP系统各模块与财务都有个方面的集成。文本主要说明销售模块中的科目分配和成本的一个对应关系。 1、首先是在物料主数据上销售视图中的物料科目分配组&#xff0c;S1主营、S2材料等字段&#xff0c;物料销售的时候会将这个物料产生的记录到对应的科目中。 首先是物料主数据中…

pip更新网络问题:Exception: Traceback (most recent call last): File

报错&#xff1a;rootdebian01:~# pip3.9 install --upgrade pip Collecting pip Downloading pip-24.0-py3-none-any.whl (2.1 MB) |██████████████████▉ | 1.2 MB 5.5 kB/s eta 0:02:39ERROR: Exception: Traceback (most recent call last): File “/usr…

利用cython将.py文件编译为.pyd文件

文章目录 1. 引言2. py文件编译为pyd文件步骤2.1 环境准备2.2 准备setup.py文件2.3 进行编译 3. 测试代码 1. 引言 在实际的Python开发中&#xff0c;为了防止Python脚本源码暴露&#xff0c;常常需要对python源码文件进行加密保护&#xff0c;Python的原始文件格式为.py&…

在outlook的邮件中插入HTML;HTML模板获取;页面组态手动生成HTML

本文介绍如何在outlook发送邮件时&#xff0c;在邮件中插入HTML&#xff0c;此HTML可以从获取模板自行进行修改。 文章目录 一、下载HTML模板&#xff08;或自己制作好HTML文件&#xff09;二、outlook新增宏三、新建邮件&#xff0c;插入HTML四、通过图像化页面组态手动生成HT…

做场外个股期权怎么询价

做场外个股期权怎么询价&#xff1f;没有具体的哪家做市商是询价是最低的&#xff0c;个人投资者需要通过机构通道方询价进行对比&#xff0c;各券商的报价由询价机构方提供给到投资者&#xff0c;可以参考不同券商的报价进行比对&#xff0c;再决定是否进行投资。本文来自&…

操作系统复习-操作系统概述

操作系统概述 操作系统的基本功能 操作系统统一管理着计算机资源&#xff1a; 处理器资源IO设备资源存储器资源文件资源 操作系统实现了对计算机资源的抽象&#xff1a; 用户无需向硬件接口编程IO设备管理软件&#xff0c;提供读写接口文件管理软件&#xff0c;提供操作文…

关于验证码的那些漏洞

一、短信轰炸 这类漏洞存在的原因是没有对短信验证码的发送时间、用户及其IP作一些限制。 案例1、正常的短信轰炸 burp一直发包即可 案例2、并发绕过 做了限制咋办&#xff1f;可以试试并发(万物皆可并发) 使用turbo intruder插件进行并发。 并发次数越大是不是轰炸就越多。 …

宝塔安装java环境Jdk1.8

1.打开宝塔——选择“终端”——输入SSH的服务器IP和SSH账号&#xff0c;选择密码验证&#xff0c;输入密码 2。登录成功后&#xff0c;输入&#xff1a;yum list java-1.8*&#xff0c;用于列出所有与 “java-1.8” 相关的软件包 yum list java-1.8* 3.安装Jdk1.8: yum insta…

[leetcode hot150]第二百三十六题,二叉树的最近公共祖先

题目&#xff1a; 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个节点 p、q&#xff0c;最近公共祖先表示为一个节点 x&#xff0c;满足 x 是 p、q 的祖先且 x 的深度尽可能大&#xff08;一个…

【C++】前缀和:一维前缀和

1.题目 2.算法思路 如果暴力求解的话&#xff0c;时间复杂度为O(n*q)。一定会超时。 优化的思路也很简单&#xff0c;就是得到一个求和数组arr&#xff0c;使arr[i]a1a2...ai。 然后每次求l到r之间的数时&#xff0c;直接arr[r]-arr[l-1]就可以得出&#xff01; 这样&#…

从GPT-3.5到GPT-4O:探索AI的进化之旅,哪一版更懂你?

如何评价GPT-4o? 最新的GPT-4O&#xff0c;被誉为GPT-4的增强版。它在保持前代产品优秀性能的基础上&#xff0c;大幅降低了使用成本&#xff0c;使得更多的普通用户也能享受到顶尖AI的服务。GPT-4O在非英语语言处理上的强化&#xff0c;更是让其在全球范围内的适用性大大提高…

Linux 编译器gcc/g++使用

gcc/g同理 编译器运行过程 1. 预处理&#xff08;进行宏替换) gcc -E a.c -o a.i 预处理后还是c语言 -E 只激活预处理,这个不生成文件,你需要把它重定向到一个输出文件里面 告诉gcc&#xff0c;从现在开始进行程序的翻译&#xff0c;将预处理工作做完停下 2. 编译&#x…

【RSGIS数据资源】1981-2021年中国陆地生态系统蒸腾蒸散比数据集

文章目录 摘要基本信息数据结构和内容采集方法信息数据处理方法与数据质量 摘要 本数据集涵盖了中国陆地生态系统蒸腾蒸散比&#xff08;T/ET&#xff09;、蒸腾&#xff08;T&#xff09;及蒸散&#xff08;ET&#xff09;三组数据。基于模型-数据融合方法&#xff0c;集成PT…

树与图的深度优先遍历

数和图的存储方式与遍历 数和图的存储方式&#xff1a; 一般有两种 树是一种特殊的图&#xff08;即无环联通图&#xff09;。所以下面只讲图。 图的话分为两种&#xff1a;①有向图&#xff08;边是有方向的&#xff1a;a➡️b&#xff09;和 ②无向图&#xff08;边是无方…

HNU-计算机体系结构-实验2-Tomasulo算法

计算机体系结构 实验2 计科210X 甘晴void 202108010XXX 1 实验目的 熟悉Tomasulo模拟器同时加深对Tomasulo算法的理解&#xff0c;从而理解指令级并行的一种方式-动态指令调度。 掌握Tomasulo算法在指令流出、执行、写结果各阶段对浮点操作指令以及load和store指令进行什么…

网络融合的力量:企业如何通过“一网多用”提升业务效率

随着企业业务的不断扩展&#xff0c;网络需求变得日益复杂。需要的是一种能够统一承载办公、生产、销售和运营等多业务需求的网络架构。这种“一网多用”的架构&#xff0c;不仅简化了网络部署和管理&#xff0c;还提升了效率并降低了成本。 “一网多用”架构的实际应用&#x…

Guns框架:基于主流技术Spring Boot2 + Vue3 + Antd Vue的现代Java应用开发新纪元

Guns框架&#xff1a;基于主流技术Spring Boot2 Vue3 Antd Vue的现代Java应用开发新纪元 摘要&#xff1a;随着信息技术的飞速发展&#xff0c;软件开发框架在提升开发效率、降低成本方面扮演着至关重要的角色。Guns框架&#xff0c;作为一个现代化的Java应用开发框架&#x…

微信图片识别文字怎么弄?介绍三个识别方法

微信图片识别文字怎么弄&#xff1f;在信息爆炸的时代&#xff0c;我们每天都会接触到大量的图片信息&#xff0c;其中包含的文字内容往往是我们获取信息的重要途径。然而&#xff0c;手动输入图片中的文字既费时又费力&#xff0c;这时&#xff0c;一款能够准确识别微信图片中…

学习笔记——动态路由协议——OSPF(OSPF基本术语)

OSPF基本术语 1、链路状态(LS)与链路状态通告(LSA) 链路(LINK)&#xff1a;路由器上的一个接口。 状态(State)&#xff1a;描述接口以及其与邻居路由器之间的关系。 (1)链路状态(LS) OSPF是一种链路状态协议&#xff0c;所谓的链路状态&#xff0c;其实就是路由器的接口状态…