🎬 秋野酱:《个人主页》
🔥 个人专栏:《Java专栏》《Python专栏》
⛺️心若有所向往,何惧道阻且长
文章目录
- USB烧录
- USB HID协议
- USB协议组成
- 通讯流程
- 官方USB HID范例
- 文件说明
- 修改PC端的显示
- 兼容库函数
- HID键盘
- USB调试工具
- USB 描述符
- 设备描述符
- 配置描述符
- 接口描述符
- HID描述符
- 端点描述符
- 附录
- USB设备类型定义
USB烧录
- 将最小板的开关拨动到HID位置
按住白色按钮不要松开
按住蓝色按钮2秒,然后松开
观察STC-ISP烧录工具,如果在扫描串口部分出现HID接口,说明成功,否则,按照2、3步骤重复尝试
- 接下来,就可以进行程序烧录,点击下载,无需再安蓝色按钮就可以烧录了
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)。
输入报告是设备向主机报告其状态和数据的报告类型。例如,鼠标向主机发送其位置和按键状态。输出报告是主机向设备发送的命令或配置参数等数据的报告类型。例如,主机发送命令让设备关闭或者设置设备参数。特征报告是一种可选的报告类型,可以用于读取或写入设备的某些特性,例如设备的名称或唯一标识符等。
一些专有名词:
- 设备描述符(Device Descriptor):描述USB设备的一般特性,例如设备类、子类、协议、供应商ID、产品ID等。
- 配置描述符(Configuration Descriptor):描述设备的一个或多个配置,每个配置由多个接口组成,每个接口描述设备的一个功能。
- 接口描述符(Interface Descriptor):描述设备的一个功能,包括设备类、子类、协议等信息。
- HID描述符(HID Descriptor):描述HID设备的特性,例如报告描述符的数量、报告描述符的长度等。
- 报告描述符(Report Descriptor):描述HID设备传输的数据格式和类型,包括输入、输出、特性等信息。
- 一般描述符(Generic Descriptor):用于描述除设备、配置、接口、HID和报告之外的其他信息。
- 字符串描述符(String Descriptor):描述设备和厂商的字符串信息。
通讯流程
USB HID通讯时序可以大致分为以下几个步骤:
- 设备连接和初始化:设备被插入USB端口后,会进行初始化和配置,包括分配USB地址和设置通信端点等。
- 主机发送设备描述符:主机会向设备发送请求,要求设备提供自己的描述符信息,包括设备类型、厂商信息、设备功能等。
- 设备响应描述符请求:设备接收到主机的请求后,会根据请求提供相应的设备描述符信息,包括设备类型、厂商信息、设备功能等。
- 主机发送配置描述符:主机会向设备发送请求,要求设备提供自己的配置描述符信息,包括端点数量、数据传输速率、电源需求等。
- 设备响应配置描述符请求:设备接收到主机的请求后,会根据请求提供相应的配置描述符信息,包括端点数量、数据传输速率、电源需求等。
- 主机发送数据:主机会向设备发送数据包,数据包中包含了控制信息和数据内容。
- 设备接收和处理数据:设备接收到主机发送的数据包后,会进行处理和响应,包括识别控制信息和处理数据内容。
- 设备发送数据:设备会向主机发送数据包,数据包中包含了控制信息和数据内容。
- 主机接收和处理数据:主机接收到设备发送的数据包后,会进行处理和响应,包括识别控制信息和处理数据内容。
- 完成通讯:通讯完成后,设备和主机会进行断开连接和资源释放等操作。
需要注意的是,USB HID通讯过程中的具体时序和流程可能会因为具体的应用场景和设备而有所不同,上述步骤仅供参考。
官方USB HID范例
官方提供了HID的示例,我们通过示例来学习一些内容。
-
拷贝官方示例,编译,烧录到开发板中
-
将开发板的开关拨动到HID
-
打开设置中,来到蓝牙和其他设备中,点击进入设备中
查看输入中,多了一个STC HID Demo
-
将开发板的开关进行切换,观察这个输入中的变化
官方示例的作用,就是帮助我们构建了一个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中间的内容即可:
- 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,这些文件和我们需要用到的库函数,在变量定义或者是文件命名上,存在重名等冲突,需要进行修改。
- 将这三个文件合并成一个文件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参数对应的值,表示当前设备是何种类型。