嵌入式进阶——HID协议

news2024/11/8 15:35:22

🎬 秋野酱:《个人主页》
🔥 个人专栏:《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参数对应的值,表示当前设备是何种类型。
在这里插入图片描述
在这里插入图片描述

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

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

相关文章

【因果推断python】2_因果关系初步2

目录 偏差 关键思想 偏差 偏差是使关联不同于因果关系的原因。幸运的是&#xff0c;我们的直觉很容易理解。让我们在课堂示例中回顾一下我们的平板电脑。当面对声称为孩子提供平板电脑的学校会获得更高考试成绩的说法时&#xff0c;我们可以反驳说&#xff0c;即使没有平板电…

「清新题精讲」Skiers

更好的阅读体验 Skiers Description 给定 n n n 个点的有向无环平面图&#xff0c;求最少多少条从 1 1 1 到 n n n 的路径能覆盖原图的所有边&#xff1f; 1 ≤ n ≤ 5 1 0 3 1\le n\le 5\times10^3 1≤n≤5103 Solution 考虑从 1 1 1 到 n n n 的路径其实是边的链覆…

如何应对Android面试官 -> 玩转 Fragment

前言 本章主要讲解下 Framgent 的核心原理&#xff1b; 基础用法 线上基础用法&#xff0c;其他的可以自行百度 FragmentManager manager getSupportFragmentManager(); FragmentTransaction transaction manager.beginTransaction(); transaction.add(R.id.contentlayout,…

C语言函数复习全解析:参数、无参、嵌套与递归

C语言复习 函数篇 文中包括调用有参函数和无参函数、函数的嵌套和递归。首先,通过一道例题介绍了有参函数的概念和用法,即定义一个函数,接受特定参数并返回结果。接着,讲解了无参函数,即执行函数时不需要返回数值,只执行特定操作。然后,介绍了函数的嵌套,即在一个函数内部调用…

【云原生】Kubernetes----PersistentVolume(PV)与PersistentVolumeClaim(PVC)详解

目录 引言 一、存储卷 &#xff08;一&#xff09;存储卷定义 &#xff08;二&#xff09;存储卷的作用 1.数据持久化 2.数据共享 3.解耦 4.灵活性 &#xff08;三&#xff09;存储卷的分类 1.emptyDir存储卷 1.1 定义 1.2 特点 1.3 示例 2.hostPath存储卷 2.1 …

RabbitMQ详情

一.MQ简介 什么是MQ MQ本质是队列&#xff0c;FIFO先入先出&#xff0c;队列中存放的内容是message&#xff08;消息&#xff09;&#xff0c;还是一种跨进程的通信机制&#xff0c;用于上下游传递消息。在互联网架构中是常见的上下游“逻辑解耦物理解耦”的消息通信服务。 主…

Wpf 使用 Prism 实战开发Day28

首页汇总方块点击导航功能 点击首页汇总方块的时候&#xff0c;跳转到对应的数据页面 step1: 在IndexViewModel 中&#xff0c;给TaskBar 里面Target 属性&#xff0c;赋上要跳转的页面 step2: 创建导航事件命令和方法实现 step3: 实现导航的逻辑。通过取到 IRegionManager 的…

ClickHouse 与其他数仓架构的对比——Clickhouse 架构篇(四)

文章目录 前言ClickHouse与Hive的对比计算引擎的差异ClickHouse比Hive查询速度快的原因 ClickHouse与HBase的对比HBase的存储系统与ClickHouse的异同HBase的适用场景及ClickHouse不适合的原因 ClickHouse与Kylin的对比Kylin的架构Kylin解决性能问题的思路Kylin方案的缺陷ClickH…

信息学奥赛初赛天天练-15-阅读程序-深入解析二进制原码、反码、补码,位运算技巧,以及lowbit的神奇应用

更多资源请关注纽扣编程微信公众号 1 2021 CSP-J 阅读程序1 阅读程序&#xff08;程序输入不超过数组或字符串定义的范围&#xff1b;判断题正确填 √&#xff0c;错误填&#xff1b;除特 殊说明外&#xff0c;判断题 1.5 分&#xff0c;选择题 3 分&#xff09; 源码 #in…

字符串编码转换

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 最早的字符串编码是美国标准信息交换码&#xff0c;即ASCII码。它仅对10个数字、26个大写英文字母、26个小写英文字母及一些其他符号进行了编码。ASC…

贪心(临项交换)+01背包,蓝桥云课 搬砖

一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 0搬砖 - 蓝桥云课 (lanqiao.cn) 二、解题报告 1、思路分析 将物品按照w[i] v[i]升序排序然后跑01背包就是答案 下面证明&#xff1a;&#xff08;不要问怎么想到的&#xff0c;做题多了就能想到&#xff…

总负债20.79亿,银行借款在增加,经营所得现金在减少,累计亏损在增加,易点云披露其风险(四)

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 全文共二十五章&#xff0c;总计6万字。 由于篇幅所限&#xff0c;分为&#xff08;一&#xff09;到&#xff08;五&#xff09;篇发布。 本文为《负债20.79亿,银行借款在增加,经营所得现金在减少,易点云披露风险》&am…

力扣爆刷第146天之贪心算法五连刷

力扣爆刷第146天之贪心算法五连刷 文章目录 力扣爆刷第146天之贪心算法五连刷总结一、455. 分发饼干二、376. 摆动序列三、53. 最大子数组和四、122. 买卖股票的最佳时机 II五、5. 跳跃游戏 总结 贪心算法的本质就是选择每一阶段的局部最优&#xff0c;从而达到全局最优。 一…

使用手机短信恢复软件,完成从新手到专家的进阶之路

由于各种原因&#xff0c;如误删、手机设备损坏等&#xff0c;我们可能会面临重要短信丢失的风险。现在市面上有许多手机短信恢复软件可以帮助我们解决这个问题&#xff0c;但从新手到专家的进阶之路并非一蹴而就的过程&#xff0c;它需要耐心、实践和不断地学习。以下是一篇关…

c++编程(15)——list的模拟实现

欢迎来到博主的专栏——c编程 博主ID&#xff1a;代码小豪 文章目录 前言list的数据结构list的默认构造尾插与尾删iterator插入和删除构造、析构、赋值copy构造initializer_list构造operator 析构函数 前言 受限于博主当前的技术水平&#xff0c;暂时还不能模拟实现出STL当中用…

HTTP报文

HTTP报文 报文流 HTTP报文是在HTTP引用程序之间发送的数据块&#xff0c;这些数据块以一种文本形式的元信息开头&#xff0c;这些信息描述了报文的内容和含义&#xff0c;后面跟着可选的数据部分&#xff0c;这些报文在客户端&#xff0c;服务器和代理之间流动。 报文流入源…

Java事务入门:从基础概念到初步实践

Java事务入门&#xff1a;从基础概念到初步实践 引言1. Java事务基础概念1.1 什么是事务&#xff1f;1.2 为什么需要事务&#xff1f; 2. Java事务管理2.1 JDBC 的事务管理2.2 Spring 事务管理2.2.1 Spring JDBC2.2.1.1 添加 Spring 配置2.2.1.2 添加业务代码并测试验证 2.2.2…

在做题中学习(62):矩阵区域和

1314. 矩阵区域和 - 力扣&#xff08;LeetCode&#xff09; 解法&#xff1a;二维前缀和 思路&#xff1a;读题画图才能理解意思&#xff1a;dun点点的是mat中的一个数&#xff0c;而要求的answer同位置的数 以点为中心上下左右延长 k 个单位所围成长方形的和。 因为最后answ…

拷贝构造、移动构造、拷贝赋值、移动赋值

最近在学习C的拷贝构造函数时发现一个问题&#xff1a;在函数中返回局部的类对象时&#xff0c;并没有调用拷贝构造函数。针对这个问题&#xff0c;查阅了一些资料&#xff0c;这里记录整理一下。 调用拷贝构造函数的三种情况&#xff1a; ① 用一个类去初始化另一个对象时&a…

PLC自动化行业的发展前景好吗?

第一先说plc的薪资&#xff1a; 整体的平均薪资还是非常可观的&#xff0c;在1.3w/月左右。 当然PLC是需要经验积累的&#xff0c;尤其需要拥有大型的系统设计经验&#xff0c;那将会在PLC以至于自动化行业都会吃的开。所以待遇是与自身的经验&#xff0c;能力&#xff0c;所在…