嵌入式进阶——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_Fosc


typedef 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_EP1IN
    if (intrin & EP1INIF) usb_in_ep1();
#endif
#ifdef EN_EP2IN
    if (intrin & EP2INIF) usb_in_ep2();
#endif
#ifdef EN_EP3IN
    if (intrin & EP3INIF) usb_in_ep3();
#endif
#ifdef EN_EP4IN
    if (intrin & EP4INIF) usb_in_ep4();
#endif
#ifdef EN_EP5IN
    if (intrin & EP5INIF) usb_in_ep5();
#endif

#ifdef EN_EP1OUT
    if (introut & EP1OUTIF) usb_out_ep1();
#endif
#ifdef EN_EP2OUT
    if (introut & EP2OUTIF) usb_out_ep2();
#endif
#ifdef EN_EP3OUT
    if (introut & EP3OUTIF) usb_out_ep3();
#endif
#ifdef EN_EP4OUT
    if (introut & EP4OUTIF) usb_out_ep4();
#endif
#ifdef EN_EP5OUT
    if (introut & EP5OUTIF) usb_out_ep5();
#endif

    if (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_EP1IN
    usb_write_reg(INDEX, 1);
    usb_write_reg(INCSR1, INCLRDT | INFLUSH);
#endif
#ifdef EN_EP2IN
    usb_write_reg(INDEX, 2);
    usb_write_reg(INCSR1, INCLRDT | INFLUSH);
#endif
#ifdef EN_EP3IN
    usb_write_reg(INDEX, 3);
    usb_write_reg(INCSR1, INCLRDT | INFLUSH);
#endif
#ifdef EN_EP4IN
    usb_write_reg(INDEX, 4);
    usb_write_reg(INCSR1, INCLRDT | INFLUSH);
#endif
#ifdef EN_EP5IN
    usb_write_reg(INDEX, 5);
    usb_write_reg(INCSR1, INCLRDT | INFLUSH);
#endif
#ifdef EN_EP1OUT
    usb_write_reg(INDEX, 1);
    usb_write_reg(OUTCSR1, OUTCLRDT | OUTFLUSH);
#endif
#ifdef EN_EP2OUT
    usb_write_reg(INDEX, 2);
    usb_write_reg(OUTCSR1, OUTCLRDT | OUTFLUSH);
#endif
#ifdef EN_EP3OUT
    usb_write_reg(INDEX, 3);
    usb_write_reg(OUTCSR1, OUTCLRDT | OUTFLUSH);
#endif
#ifdef EN_EP4OUT
    usb_write_reg(INDEX, 4);
    usb_write_reg(OUTCSR1, OUTCLRDT | OUTFLUSH);
#endif
#ifdef EN_EP5OUT
    usb_write_reg(INDEX, 5);
    usb_write_reg(OUTCSR1, OUTCLRDT | OUTFLUSH);
#endif
    usb_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_PP
    GPIO_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_PP
    GPIO_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_PP
    GPIO_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_PP
    GPIO_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_PP
    GPIO_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_PP
		GPIO_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_PP
		GPIO_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_PP
		GPIO_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_BRTx
    COMx_InitStructure.UART_BRT_Use   = BRT_Timer1;			//选择波特率发生器, BRT_Timer1, BRT_Timer2 (注意: 串口2固定使用BRT_Timer2)
    COMx_InitStructure.UART_BaudRate  = 115200ul;			//波特率, 一般 110 ~ 115200
    COMx_InitStructure.UART_RxEnable  = ENABLE;				//接收允许,   ENABLE或DISABLE
    COMx_InitStructure.BaudRateDouble = DISABLE;			//波特率加倍, ENABLE或DISABLE
    COMx_InitStructure.UART_Interrupt = ENABLE;				//中断允许,   ENABLE或DISABLE
    COMx_InitStructure.UART_Priority    = Priority_0;			//指定中断优先级(低到高) Priority_0,Priority_1,Priority_2,Priority_3
    COMx_InitStructure.UART_P_SW      = UART1_SW_P30_P31;	//切换端口,   UART1_SW_P30_P31,UART1_SW_P36_P37,UART1_SW_P16_P17,UART1_SW_P43_P44
    UART_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_16BitAutoReloadNoMask
    TIM_InitStructure.TIM_Priority    = Priority_0;			//指定中断优先级(低到高) Priority_0,Priority_1,Priority_2,Priority_3
    TIM_InitStructure.TIM_Interrupt = ENABLE;					//中断是否允许,   ENABLE或DISABLE
    TIM_InitStructure.TIM_ClkSource = TIM_CLOCK_1T;		//指定时钟源,     TIM_CLOCK_1T,TIM_CLOCK_12T,TIM_CLOCK_Ext
    TIM_InitStructure.TIM_ClkOut    = DISABLE;				//是否输出高速脉冲, ENABLE或DISABLE
    TIM_InitStructure.TIM_Value     = 65536UL - (MAIN_Fosc / 400UL);		//初值,2.5ms扫描一次键盘状态
    TIM_InitStructure.TIM_Run       = ENABLE;					//是否初始化后启动定时器, ENABLE或DISABLE
    Timer_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,则为DOWN
            if(COL_STATE(j) != KEY_STATE(i, j)) {
                if(COL_STATE(j)) {
                    // 修改当前状态为UP
                    SET_KEY_UP(i, j);
                    Debug("(%d, %d) Up\r\n", (int)i, (int)j);
                } else {
                    // 修改当前状态为DOWN
                    SET_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	Reserved
			2	Keycode 1
			3	Keycode 2
			4	Keycode 3
			5	Keycode 4
			6	Keycode 5
			7	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			0x23
			k0 = 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必须为0
    BYTE bDeviceSubClass;   //usb分配的子类代码,HID必须为0
    BYTE bDeviceProtocl;     //USB分配的设备协议代码,HID必须为0
    BYTE 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;   //描述符类型编号,为0x04
    BYTE 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;           //设备描述符的字节数大小, 为0x09
    BYTE bDescriptorType;   //描述符类型编号,为0x21
    WORD 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;   //描述符类型编号,为0x05
    BYTE 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参数对应的值,表示当前设备是何种类型。
在这里插入图片描述
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

29-ESP32-S3-WIFI篇-00 STA模式扫描全部 AP

ESP32-S3 WIFI_Driver 引言 ESP32-S3是一款集成了Wi-Fi和蓝牙功能的芯片。关于WIFI的部分&#xff0c;其实内容比我想象的要多得多。所以通常来说&#xff0c;如果你想要编写自己的Wi-Fi应用程序&#xff0c;最快捷的方法就是先找一个类似的示例应用&#xff0c;然后将它的相…

输入与输出的魔法:探索Python的内置函数

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、从键盘捕获输入&#xff1a;input()函数的力量 二、打印输出&#xff1a;print()函数的…

简单模拟实现shell(Linux)

目录​​​​​​​ 前言 展示效果 实现代码 前言 该代码模拟了shell的实现&#xff0c;也就是解析类似于“ls -a -l"的命令&#xff0c;当我们启动我们自己写的shell的可执行程序时&#xff0c;我们输入"ls"的命令&#xff0c;也可以展示出在shell中输入&…

50etf期权购是什么意思?

今天带你了解50etf期权购是什么意思&#xff1f;很多刚刚接触50ETF期权的投资者或许不太明白50ETF期权投资是一种什么样的投资&#xff0c;对于50ETF期权投资来说&#xff0c;有认购合约与认沽合约&#xff0c;那么“购”也就是认购的意思。 50etf期权购是什么意思&#xff1f;…

速看!!24上软考【电子商务设计师】真题回顾,含答案解析

2024上半年软考考试已经结束了&#xff0c;为大家整理了网友回忆版的电子商务设计师真题及答案&#xff0c;25-26日两批考试总共60道题。 上半年考试的宝子们可以对答案预估分数&#xff01;准备下半年考的宝子可以提前把握考试知识点和出题方向&#xff0c;说不定会遇到相同考…

当今世界三个最厉害的人物颜廷利:全球最受欢迎的点赞第一人

我们生活在一个历史的拐点&#xff0c;一个要求我们重新构思自我、解读世界以及预见未来的时代。在这个时代&#xff0c;那些能够洞悉社会发展的深层逻辑&#xff0c;并据此提出全新定义的人&#xff0c;将成为引领人类思维、塑造普遍价值观和主导全球发展议程的先锋。因此&…

插件“猫抓”使用方法 - 浏览器下载m3u8视频 - 合并 - 视频检测下载 - 网课下载神器

前言 浏览器下载m3u8视频 - 合并 - 网课下载神器 chrome插件-猫抓 https://chrome.zzzmh.cn/info/jfedfbgedapdagkghmgibemcoggfppbb 步骤&#xff1a; P.s. 推荐大佬的学习视频&#xff01; 《WEB前端大师课》超级棒&#xff01; https://ke.qq.com/course/5892689#term_id…

01_Spring Ioc DI案例,setter方法和构造方法注入(详解) + 思维导图

文章目录 一.概念实操Maven父子工程 二. IOC和DI入门案例【重点】1 IOC入门案例【重点】问题导入1.1 门案例思路分析1.2 实现步骤2.1 DI入门案例思路分析2.2 实现步骤2.3 实现代码2.4 图解演示 三、Bean的基础配置问题导入问题导入1 Bean是如何创建的【理解】2 实例化Bean的三种…

Java导出excel带图片(希望能帮助你们节省时间)

第一天太慌张&#xff0c;下班逃跑&#xff0c;一夜没睡好&#xff0c;第二天决定搞出来。 查了好多博客&#xff0c;感觉都挺繁琐的&#xff0c;好多工具类、引入类找不到。经过一上午的琢磨&#xff0c;终于搞定。记录一下 借鉴了这个博主的文章 需求前端点击导出按钮&#…

HackTheBox-Machines--Lazy

Lazy测试过程 1 信息收集 1.端口扫描 发现 SSH&#xff08;22&#xff09;、HTTP&#xff08;80&#xff09;端口 nmap -sC -sV 10.129.159.512.访问 80 端口 1.页面中存在注册功能&#xff0c;测试注册功能 页面返回登录页面及用户名 使用burpsuite观察注册请求 /register.p…

骑车不戴头盔监测摄像机

骑行是一种健康的出行方式&#xff0c;但是在骑行途中不戴头盔存在安全隐患&#xff0c;容易造成头部受伤。为了规范骑行行为&#xff0c;保障骑行安全&#xff0c;可以考虑使用骑车不戴头盔监测摄像机进行监测和识别。这种摄像机可以通过智能识别技术&#xff0c;实时监测骑自…

Centos7时区设置及手动修改时间

一、修改系统时区 1、查看时区命令 timedatectl 2、设置时区命令 #下面将时区设置为上海时区 timedatectl set-timezone Asia/Shanghai 3、查看时区看一下新时区有没有生效 timedatectl 二、手动修改系统时间 修改系统时间 date -s "2023-12-25 16:05:10" 查…

串口调试助手中文乱码 解决方案

输出乱码 一般&#xff0c;当串口调试助手输出乱码时&#xff0c;可能有以下几个原因&#xff1a; 波特率设置错误&#xff1a;串口通信需要保证发送和接收的设备使用相同的波特率。请检查串口调试助手和目标设备的波特率设置是否一致。 数据位、停止位或校验位设置错误&…

自建视频托管平台:MediaCMS

目录 1 MediaCMS简介1.1 介绍1.2 特性1.3 应用场景 2 安装配置2.1 安装1、安装2、汉化 2.2 一些常见配置 3 简单使用3.1 上传3.2 下载3.3 添加标题或者字幕3.4 通过Tag/Category实现视频/文件分类添加 Tag给任一资源分类 1 MediaCMS简介 1.1 介绍 MediaCMS是一个现代的&#…

Vue项目运行页面禁止缩放【移动端和PC端都禁止缩放】解决方案

Vue项目运行页面禁止缩放【移动端和PC端都禁止缩放】解决方案&#xff0c;有的人手很J,总喜欢放大缩小&#xff0c;从而会导致页面错乱&#xff0c;以下是解决方案&#xff0c;简单有效 效果图PC&#xff1a;滚轮缩放和其他缩放都会禁止 移动端效果图&#xff1a;各种手机平板…

活动会议邀请函制作易企秀源码系统 清爽的画面轻轻滑动自动翻页 带完整的前后端搭建教程

系统概述 在当今数字化时代&#xff0c;活动会议的组织和宣传变得至关重要。为了满足这一需求&#xff0c;活动会议邀请函制作易企秀源码系统应运而生。它不仅为用户提供了一个便捷、高效的工具&#xff0c;还具备一系列令人瞩目的特色功能&#xff0c;为活动会议的成功举办提…

Redis篇 哈希表在redis中的命令

哈希命令 一.哈希表的基本认识二. 哈希表在redis中的命令1.hset,hget2.hdel3.hkeys,hvals4.hexists5.hgetall6.hmget7.hlen8.hincrby和hincrbyfloat 一.哈希表的基本认识 在JAVA数据结构中&#xff0c;我们就已经接触到了哈希表&#xff0c; 在当时&#xff0c;我们主要用到的哈…

香港Web3媒体:Techub News

Techub News&#xff1a;香港领先&#xff0c;世界一流的科技媒体平台 在数字化时代&#xff0c;Web3技术的崛起为媒体行业注入了新的活力。作为香港领先的Web3媒体平台&#xff0c;Techub News凭借其专业的团队、丰富的资源和创新的业务模式&#xff0c;成为了行业内的佼佼者。…

Go开发Prometheus客户端实战步骤

1、项目背景 在当前的IT运维环境中&#xff0c;我们的业务系统日益复杂&#xff0c;特别是针对特定的业务逻辑和定制化需求&#xff0c;传统的通用监控工具往往难以覆盖所有的监控场景。例如&#xff0c;考虑到一个复杂的电商平台&#xff0c;除了基础的服务器性能、网络状况等…

Python+Playwright自动化测试-playwright操作iframe(三)

1.简介 iframe 是web自动化里面一个比较头疼的测试场景&#xff0c;在Selenium中处理 iframe 需要切换来切换去非常麻烦。但是在playwright中&#xff0c;让其变得非常简单&#xff0c;我们在使用中无需切换iframe&#xff0c;直接定位元素即可。 2.iframe是什么 iframe就是我…