《圈圈教你玩USB》 第三章 USB鼠标的实现——看书笔记( 3 )
3.9 配置描述符集合的结构
3.9.1 配置描述符的结构
序号 | 偏移量/字节 | 域 | 大小/字节 | 说明 | 备注 |
1 | 0 | bLength | 1 | 该描述符的长度(9字节) | 标准USB配置描述符的长度为9字节 |
2 | 1 | bDescriptorType | 1 | 描述符的类型(配置描述符为0x02) | |
3 | 2 | wTotalLength | 2 | 配置描述符集合总长度 | 包括配置、接口、类特殊(如果有)和端点描述符,低字节在先 |
4 | 4 | bNumInterfaces | 1 | 该配置支持的接口数 | 通常,功能单一的设备只有一个接口(例如鼠标),而复合设备则支持多个接口(例如音频设备) |
5 | 5 | bConfigurationValue | 1 | 该配置的值 | 通常,一个USB设备可以支持多个配置,bConfigurationValue就是每个配置的标识。设置配置请求时会发送一个配置值,如果某个配置的bConfiguratonValue值与它相匹配,就表示该配置被激活,为当前配置。 |
6 | 6 | iConfiguration | 1 | 描述该配置的字符串的索引值 | 如果该值为0,表示没有字符串 |
7 | 7 | bmAttributes | 1 | 该配置的属性 |
Bit7:保留,必须设置为1; Bit6:表示供电方式,为1时,表示设备自供电;为0时,表示总线供电; Bit5:表示十分支持远程唤醒,为1时,表示支持; Bit4~0:保留,设置为0。 |
8 | 8 | bMaxPower | 1 | 设备所需要从总线获取的最大电流(单位是2mA) | 例如,如果需要200mA的最大电流,则该字节的值为100。 |
3.9.2 接口描述符的结构
序号 | 偏移量/字节 | 域 | 大小/字节 | 说明 | 备注 |
1 | 0 | bLength | 1 | 该描述符的长度(9字节) | 标准USB接口 描述符的长度为9字节 |
2 | 1 | bDescriptorType | 1 | 描述符的类型(接口描述符为0x04) | |
3 | 2 | bInterfaceNumber | 1 | 该接口的编号(从0开始) | 当一个配置具有多个接口时,每个接口的编号都不相同。从0开始一次递增对一个配置的接口进行编号。 |
4 | 3 | bAlternateSetting | 1 | 该接口的备用编号 | 编号规则与bInterfaceNumber一样,很少使用,设置为0 |
5 | 4 | bNumEndpoints | 1 | 该接口所使用的端点数 | 不包括0端点。如果该字段为0,说明没有非0端点,只使用默认的控制端点。 |
6 | 5 | bInterfaceClass | 1 | 该接口所使用的类 |
和设备描述符中的意义类似。 通常在接口中定义设备的功能,而在设备描述符中将类、子类和协议字段的值设置为0。 |
7 | 6 | bInterfaceSubClass | 1 | 该接口所使用的子类 | |
8 | 7 | bInterfaceProtocol | 1 | 该接口所使用的协议 | |
9 | 8 | iInterface | 1 | 描述该接口的字符串索引 | 如果该值为0,则表示没有字符串 |
3.9.3 端点描述符的结构
序号 | 偏移量/字节 | 域 | 大小/字节 | 说明 | 备注 |
1 | 0 | bLength | 1 | 该描述符的长度(7字节) | 标准USB端点描述符的长度为7字节 |
2 | 1 | bDescriptorType | 1 | 描述符的类型(端点描述符为0x05) | |
3 | 2 | bEndpointAddress | 1 | 该端点的地址(端点传输方向+端点号) |
Bit7表示该端点传输方向,1为输入;0为输出。 Bit3~0为端点号;Bit6~4保留,为0. |
4 | 3 | bmAttributes | 1 | 该端点的属性 |
Bit1~0为端点的传输类型,0位控制传输;1位等时传输;2为批量传输;3为中断传输。 如果该端点为非等时传输,则Bit7~2为保留值,设为0; 如果该端点为等时传输,则 Bit3~2表示同步的类型,0为无同步;1为异步;2为适配;3为同步。 Bit5~4表示用途,0为数据端点;1为反馈端点;2为暗含反馈的数据端点;3为保留值。 Bit7~6保留。 |
5 | 4 | wMaxPackeSize | 2 | 该端点支持的最大包长度 | 对于全速模式和低速模式,Bit10~0表示端点的最大包长,其他位保留为0。对于高速模式,Bit12~11为每个帧附加的传输次数,具体参看USB2.0协议。 |
6 | 6 | bInterval | 1 | 端点的查询时间 |
对于中断端点,表示查询的帧间隔数。 对于等时传输和高速模式的中断、批量传输,该字段的意义情参看USB2.0协议。 |
3.9.4 HID描述符的结构
序号 | 偏移量/字节 | 域 | 大小/字节 | 说明 | 备注 |
1 | 0 | bLength | 1 | 该描述符的长度 |
是该描述符的总长度。其大小和该描述符中下级描述符的个数有关。 例如,只有一个下级描述符时,总长度为1+1+2+1+1+1+2=9字节。 |
2 | 1 | bDescriptorType | 1 | 描述符类型(HID描述符为0x21) | |
3 | 2 | bcdHID | 2 | HID协议的版本 | 这里参看的是USB HID1.1协议,故此处为0x0110 |
4 | 4 | bCountyCode | 1 | 国家代码 | 通常键盘是美式键盘,代码为33,即0x21 |
5 | 5 | bNumDescriptors | 1 | 下级描述符的数量 |
该值至少为1,即至少要有一个报告描述符。 下级描述符可以是报告描述符或物理描述符。 |
6 | 6 | bDescriptorType | 1 | 下级描述符的类型 | 报告描述符的编号为0x22,物理描述符的编号为0x23 |
7 | 7 | wDescriptorLength | 2 | 下级描述符的长度 | 当有多个下级描述符时,bDescriptorType和bDescriptorLength交替重复下去。 |
8 | 9 | bDescriptorType | 1 | 下级描述符的类型(可选) | |
9 | 10 | wDescriptorLength | 2 | 下级描述符的长度(可选) | |
... | ... | ...(可选) |
3.10 配置描述符集合的实现和返回
1)配置描述符集合:
//USB配置描述符集合的定义
//配置描述符总长度为9+9+9+7字节
code uint8 ConfigurationDescriptor[9+9+9+7]=
{
/***************配置描述符***********************/
//bLength字段。配置描述符的长度为9字节。
0x09,
//bDescriptorType字段。配置描述符编号为0x02。
0x02,
//wTotalLength字段。配置描述符集合的总长度,
//包括配置描述符本身、接口描述符、类描述符、端点描述符等。
sizeof(ConfigurationDescriptor)&0xFF, //低字节
(sizeof(ConfigurationDescriptor)>>8)&0xFF, //高字节
//bNumInterfaces字段。该配置包含的接口数,只有一个接口。
0x01,
//bConfiguration字段。该配置的值为1。
0x01,
//iConfigurationz字段,该配置的字符串索引。这里没有,为0。
0x00,
//bmAttributes字段,该设备的属性。由于我们的板子是总线供电的,
//并且我们不想实现远程唤醒的功能,所以该字段的值为0x80。
0x80,
//bMaxPower字段,该设备需要的最大电流量。由于我们的板子
//需要的电流不到100mA,因此我们这里设置为100mA。由于每单位
//电流为2mA,所以这里设置为50(0x32)。
0x32,
/*******************接口描述符*********************/
//bLength字段。接口描述符的长度为9字节。
0x09,
//bDescriptorType字段。接口描述符的编号为0x04。
0x04,
//bInterfaceNumber字段。该接口的编号,第一个接口,编号为0。
0x00,
//bAlternateSetting字段。该接口的备用编号,为0。
0x00,
//bNumEndpoints字段。非0端点的数目。由于USB鼠标只需要一个
//中断输入端点,因此该值为1。
0x01,
//bInterfaceClass字段。该接口所使用的类。USB鼠标是HID类,
//HID类的编码为0x03。
0x03,
//bInterfaceSubClass字段。该接口所使用的子类。在HID1.1协议中,
//只规定了一种子类:支持BIOS引导启动的子类。
//USB键盘、鼠标属于该子类,子类代码为0x01。
0x01,
//bInterfaceProtocol字段。如果子类为支持引导启动的子类,
//则协议可选择鼠标和键盘。键盘代码为0x01,鼠标代码为0x02。
0x02,
//iConfiguration字段。该接口的字符串索引值。这里没有,为0。
0x00,
/******************HID描述符************************/
//bLength字段。本HID描述符下只有一个下级描述符。所以长度为9字节。
0x09,
//bDescriptorType字段。HID描述符的编号为0x21。
0x21,
//bcdHID字段。本协议使用的HID1.1协议。注意低字节在先。
0x10,
0x01,
//bCountyCode字段。设备适用的国家代码,这里选择为美国,代码0x21。
0x21,
//bNumDescriptors字段。下级描述符的数目。我们只有一个报告描述符。
0x01,
//bDescritporType字段。下级描述符的类型,为报告描述符,编号为0x22。
0x22,
//bDescriptorLength字段。下级描述符的长度。下级描述符为报告描述符。
sizeof(ReportDescriptor)&0xFF,
(sizeof(ReportDescriptor)>>8)&0xFF,
/**********************端点描述符***********************/
//bLength字段。端点描述符长度为7字节。
0x07,
//bDescriptorType字段。端点描述符编号为0x05。
0x05,
//bEndpointAddress字段。端点的地址。我们使用D12的输入端点1。
//D7位表示数据方向,输入端点D7为1。所以输入端点1的地址为0x81。
0x81,
//bmAttributes字段。D1~D0为端点传输类型选择。
//该端点为中断端点。中断端点的编号为3。其它位保留为0。
0x03,
//wMaxPacketSize字段。该端点的最大包长。端点1的最大包长为16字节。
//注意低字节在先。
0x10,
0x00,
//bInterval字段。端点查询的时间,我们设置为10个帧时间,即10ms。
0x0A
};
2) 编造报告描述符
//USB报告描述符
code uint8 ReportDescriport[]=
{
0x00
};
3)对配置描述符返回的代码实现
case CONFIGURATION_DESCRIPTOR: //配置描述符
#ifdef DEBUG0
Prints("配置描述符。\r\n");
#endif
pSendData=ConfigurationDescriptor; //需要发送的数据为配置描述符
//判断请求的字节数是否比实际需要发送的字节数多
//这里请求的是配置描述符集合,因此数据长度就是
//ConfigurationDescriptor[3]*256+ConfigurationDescriptor[2]。
//如果请求的比实际的长,那么只返回实际长度的数据
SendLength=ConfigurationDescriptor[3];
SendLength=SendLength*256+ConfigurationDescriptor[2];
if(wLength>SendLength)
{
if(SendLength%DeviceDescriptor[7]==0) //并且刚好是整数个数据包时
{
NeedZeroPacket=1; //需要返回0长度的数据包
}
}
else
{
SendLength=wLength;
}
//将数据通过EP0返回
UsbEp0SendData();
break;
4)分析调试信息
编译代码,下载运行,串口调试信息如下:
3.11 字符串和语言ID请求的实现
1)获取字符串和语言ID的原理
序号 | 偏移量/字节 | 域 | 大小/字节 | 说明 |
1 | 0 | bLength | 1 | 描述符的长度 |
2 | 1 | bDescriptorType | 1 | 描述符类型(字符串为0x03) |
3 | 2 | wLANGID[0] | 2 | 语言ID号0 |
4 | ... | ... | ... | ... |
3+n | 2*n+2 | wLANGID[n] | 2 | 语言ID号n |
语言ID,这里只使用美式英语一种,即0x0409.
序号 | 偏移量/字节 | 域 | 大小/字节 | 说明 |
1 | 0 | bLength | 1 | 描述符的长度 |
2 | 1 | bDescriptorType | 1 | 描述符类型(字符串为0x03) |
3 | 2 | bString | N | UNICODE编码的字符串 |
字符串描述符中的bString字段是使用UNICODE编码的字符串。UNICODE用2字节来表示一个字符,
2)如何通过代码获取字符串和语言ID
/************************语言ID的定义********************/
code uint8 LanguageId[4]=
{
0x04, //本描述符的长度
0x03, //字符串描述符
//0x0409为美式英语的ID
0x09,
0x04
};
//厂商字符串“电脑圈圈的USB专区 Http://group.ednchina.com/93/”的Unicode编码
//8位小端格式
code uint8 ManufacturerStringDescriptor[82]={
82, //该描述符的长度为82字节
0x03, //字符串描述符的类型编码为0x03
0x35, 0x75, //电
0x11, 0x81, //脑
0x08, 0x57, //圈
0x08, 0x57, //圈
0x84, 0x76, //的
0x55, 0x00, //U
0x53, 0x00, //S
0x42, 0x00, //B
0x13, 0x4e, //专
0x3a, 0x53, //区
0x20, 0x00, //
0x48, 0x00, //H
0x74, 0x00, //t
0x74, 0x00, //t
0x70, 0x00, //p
0x3a, 0x00, //:
0x2f, 0x00, ///
0x2f, 0x00, ///
0x67, 0x00, //g
0x72, 0x00, //r
0x6f, 0x00, //o
0x75, 0x00, //u
0x70, 0x00, //p
0x2e, 0x00, //.
0x65, 0x00, //e
0x64, 0x00, //d
0x6e, 0x00, //n
0x63, 0x00, //c
0x68, 0x00, //h
0x69, 0x00, //i
0x6e, 0x00, //n
0x61, 0x00, //a
0x2e, 0x00, //.
0x63, 0x00, //c
0x6f, 0x00, //o
0x6d, 0x00, //m
0x2f, 0x00, ///
0x39, 0x00, //9
0x33, 0x00, //3
0x2f, 0x00 ///
};
//字符串“《圈圈教你玩USB》之USB鼠标”的Unicode编码
//8位小端格式
code uint8 ProductStringDescriptor[34]={
34, //该描述符的长度为34字节
0x03, //字符串描述符的类型编码为0x03
0x0a, 0x30, //《
0x08, 0x57, //圈
0x08, 0x57, //圈
0x59, 0x65, //教
0x60, 0x4f, //你
0xa9, 0x73, //玩
0x55, 0x00, //U
0x53, 0x00, //S
0x42, 0x00, //B
0x0b, 0x30, //》
0x4b, 0x4e, //之
0x55, 0x00, //U
0x53, 0x00, //S
0x42, 0x00, //B
0x20, 0x9f, //鼠
0x07, 0x68 //标
};
//字符串“2008-07-07”的Unicode编码
//8位小端格式
code uint8 SerialNumberStringDescriptor[22]={
22, //该描述符的长度为22字节
0x03, //字符串描述符的类型编码为0x03
0x32, 0x00, //2
0x30, 0x00, //0
0x30, 0x00, //0
0x38, 0x00, //8
0x2d, 0x00, //-
0x30, 0x00, //0
0x37, 0x00, //7
0x2d, 0x00, //-
0x30, 0x00, //0
0x37, 0x00 //7
};
case STRING_DESCRIPTOR: //字符串描述符
#ifdef DEBUG0
Prints("字符串描述符");
#endif
switch(wValue&0xFF) //根据wValue的低字节(索引值)散转
{
case 0: //获取语言ID
#ifdef DEBUG0
Prints("(语言ID)。\r\n");
#endif
pSendData=LanguageId;
SendLength=LanguageId[0];
break;
case 1: //厂商字符串的索引值为1,所以这里为厂商字符串
#ifdef DEBUG0
Prints("(厂商描述)。\r\n");
#endif
pSendData=ManufacturerStringDescriptor;
SendLength=ManufacturerStringDescriptor[0];
break;
case 2: //产品字符串的索引值为2,所以这里为产品字符串
#ifdef DEBUG0
Prints("(产品描述)。\r\n");
#endif
pSendData=ProductStringDescriptor;
SendLength=ProductStringDescriptor[0];
break;
case 3: //产品序列号的索引值为3,所以这里为序列号
#ifdef DEBUG0
Prints("(产品序列号)。\r\n");
#endif
pSendData=SerialNumberStringDescriptor;
SendLength=SerialNumberStringDescriptor[0];
break;
default :
#ifdef DEBUG0
Prints("(未知的索引值)。\r\n");
#endif
//对于未知索引值的请求,返回一个0长度的包
SendLength=0;
NeedZeroPacket=1;
break;
}
//判断请求的字节数是否比实际需要发送的字节数多
//如果请求的比实际的长,那么只返回实际长度的数据
if(wLength>SendLength)
{
if(SendLength%DeviceDescriptor[7]==0) //并且刚好是整数个数据包时
{
NeedZeroPacket=1; //需要返回0长度的数据包
}
}
else
{
SendLength=wLength;
}
//将数据通过EP0返回
UsbEp0SendData();
break;
3)分析调试信息
3.12 设置配置请求的实现
1)设置配置请求的原理
2)如何在D12中设置配置请求
//函数功能:使能端点函数。
//入口参数:Enable: 是否使能。0值为不使能,非0值为使能。
void D12SetEndpointEnable(uint8 Enable)
{
D12WriteCommand(D12_SET_ENDPOINT_ENABLE);
if(Enable!=0)
{
D12WriteByte(0x01); //D0为1使能端点
}
else
{
D12WriteByte(0x00); //不使能端点
}
}
3)何时何地设置配置
case SET_CONFIGURATION: //设置配置
#ifdef DEBUG0
Prints("设置配置。\r\n");
#endif
//使能非0端点。非0端点只有在设置为非0的配置后才能使能。
//wValue的低字节为配置的值,如果该值为非0,才能使能非0端点。
//保存当前配置值
ConfigValue=wValue&0xFF;
D12SetEndpointEnable(ConfigValue);
//返回一个0长度的状态数据包
SendLength=0;
NeedZeroPacket=1;
//将数据通过EP0返回
UsbEp0SendData();
break;
4)分析调试信息
编译下载,返回如下调试信息:
5)何时何地回应类输出请求
case 1: //类请求
#ifdef DEBUG0
Prints("USB类输出请求:");
#endif
switch(bRequest)
{
case SET_IDLE:
#ifdef DEBUG0
Prints("设置空闲。\r\n");
#endif
//只需要返回一个0长度的数据包即可
SendLength=0;
NeedZeroPacket=1;
//将数据通过EP0返回
UsbEp0SendData();
break;
default:
#ifdef DEBUG0
Prints("未知请求。\r\n");
#endif
break;
}
break;
6)分析调试信息
下面将实现这个报告描述符。
3.13 报告描述符的结构及实现
1)什么是报告
3)什么是报告描述符,有什么作用,怎么传送
4)报告描述符的结构是怎么样的
序号 | 条目分类 | 作用 | 内部分类 | 作用 | 备注 |
1 | 主条目 | 用来定义或者分组报告的数据域 | 输入(Input) | 将输入报告划分为不同的数据域,以及指定该域的属性 |
后面跟的第一字节数据每个位的数据表示一种属性,例如: Bit0表示该数据域是变量还是常量; Bit1表示是数组还是单一变量; Bit2表示是相对值还是绝对值等。 |
输出(Output) | |||||
特性(Feature) | |||||
集合(Collection) | |||||
关集合(End Collection) | |||||
2 | 全局条目 |
用来选择用途页,定义数据域的长度、数量、报告ID等。 全局条目出现后对接下来的所有主条目都有效,除非遇到另一个全局条目来改变它。 |
用途页(Usage Page) | 指定设备的功能,相当于HID的子集 | |
逻辑最小值(Logical Minimum) | 描述数据域的取值范围 | ||||
逻辑最大值(Logical Maximum) | |||||
物理最小值(Physical Minimum) | |||||
物理最大值(Physical Maximum) | |||||
数据域大小(Report Size) | 描述某个数据域有多少个位 | ||||
数据域数量(Report Count) | 描述这样的数据域有多少个 | ||||
报告ID(Report ID) | |||||
3 | 局部条目 |
用来定义控制的特性 只在局部有效,遇到一个主条目后,它的效用就结束了。 |
用途(Usage) | 指定个别报表的功能,相当于Usage Page的子集 | |
用途最小值(Usage Minimum) | |||||
用途最大值(Usage Maximum) |
5)如何生成报告描述符
6)USB鼠标的报告描述符代码实现
//USB报告描述符的定义
code uint8 ReportDescriptor[]=
{
//每行开始的第一字节为该条目的前缀,前缀的格式为:
//D7~D4:bTag。D3~D2:bType;D1~D0:bSize。以下分别对每个条目注释。
//这是一个全局(bType为1)条目,选择用途页为普通桌面Generic Desktop Page(0x01)
//后面跟一字节数据(bSize为1),后面的字节数就不注释了,
//自己根据bSize来判断。
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
//这是一个局部(bType为2)条目,说明接下来的应用集合用途用于鼠标
0x09, 0x02, // USAGE (Mouse)
//这是一个主条目(bType为0)条目,开集合,后面跟的数据0x01表示
//该集合是一个应用集合。它的性质在前面由用途页和用途定义为
//普通桌面用的鼠标。
0xa1, 0x01, // COLLECTION (Application)
//这是一个局部条目。说明用途为指针集合
0x09, 0x01, // USAGE (Pointer)
//这是一个主条目,开集合,后面跟的数据0x00表示该集合是一个
//物理集合,用途由前面的局部条目定义为指针集合。
0xa1, 0x00, // COLLECTION (Physical)
//这是一个全局条目,选择用途页为按键(Button Page(0x09))
0x05, 0x09, // USAGE_PAGE (Button)
//这是一个局部条目,说明用途的最小值为1。实际上是鼠标左键。
0x19, 0x01, // USAGE_MINIMUM (Button 1)
//这是一个局部条目,说明用途的最大值为3。实际上是鼠标中键。
0x29, 0x03, // USAGE_MAXIMUM (Button 3)
//这是一个全局条目,说明返回的数据的逻辑值(就是我们返回的数据域的值啦)
//最小为0。因为我们这里用Bit来表示一个数据域,因此最小为0,最大为1。
0x15, 0x00, // LOGICAL_MINIMUM (0)
//这是一个全局条目,说明逻辑值最大为1。
0x25, 0x01, // LOGICAL_MAXIMUM (1)
//这是一个全局条目,说明数据域的数量为三个。
0x95, 0x03, // REPORT_COUNT (3)
//这是一个全局条目,说明每个数据域的长度为1个bit。
0x75, 0x01, // REPORT_SIZE (1)
//这是一个主条目,说明有3个长度为1bit的数据域(数量和长度
//由前面的两个全局条目所定义)用来做为输入,
//属性为:Data,Var,Abs。Data表示这些数据可以变动,Var表示
//这些数据域是独立的,每个域表示一个意思。Abs表示绝对值。
//这样定义的结果就是,第一个数据域bit0表示按键1(左键)是否按下,
//第二个数据域bit1表示按键2(右键)是否按下,第三个数据域bit2表示
//按键3(中键)是否按下。
0x81, 0x02, // INPUT (Data,Var,Abs)
//这是一个全局条目,说明数据域数量为1个
0x95, 0x01, // REPORT_COUNT (1)
//这是一个全局条目,说明每个数据域的长度为5bit。
0x75, 0x05, // REPORT_SIZE (5)
//这是一个主条目,输入用,由前面两个全局条目可知,长度为5bit,
//数量为1个。它的属性为常量(即返回的数据一直是0)。
//这个只是为了凑齐一个字节(前面用了3个bit)而填充的一些数据
//而已,所以它是没有实际用途的。
0x81, 0x03, // INPUT (Cnst,Var,Abs)
//这是一个全局条目,选择用途页为普通桌面Generic Desktop Page(0x01)
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
//这是一个局部条目,说明用途为X轴
0x09, 0x30, // USAGE (X)
//这是一个局部条目,说明用途为Y轴
0x09, 0x31, // USAGE (Y)
//这是一个局部条目,说明用途为滚轮
0x09, 0x38, // USAGE (Wheel)
//下面两个为全局条目,说明返回的逻辑最小和最大值。
//因为鼠标指针移动时,通常是用相对值来表示的,
//相对值的意思就是,当指针移动时,只发送移动量。
//往右移动时,X值为正;往下移动时,Y值为正。
//对于滚轮,当滚轮往上滚时,值为正。
0x15, 0x81, // LOGICAL_MINIMUM (-127)
0x25, 0x7f, // LOGICAL_MAXIMUM (127)
//这是一个全局条目,说明数据域的长度为8bit。
0x75, 0x08, // REPORT_SIZE (8)
//这是一个全局条目,说明数据域的个数为3个。
0x95, 0x03, // REPORT_COUNT (3)
//这是一个主条目。它说明这三个8bit的数据域是输入用的,
//属性为:Data,Var,Rel。Data说明数据是可以变的,Var说明
//这些数据域是独立的,即第一个8bit表示X轴,第二个8bit表示
//Y轴,第三个8bit表示滚轮。Rel表示这些值是相对值。
0x81, 0x06, // INPUT (Data,Var,Rel)
//下面这两个主条目用来关闭前面的集合用。
//我们开了两个集合,所以要关两次。bSize为0,所以后面没数据。
0xc0, // END_COLLECTION
0xc0 // END_COLLECTION
};
//通过上面的报告描述符的定义,我们知道返回的输入报告具有4字节。
//第一字节的低3位用来表示按键是否按下的,高5位为常数0,无用。
//第二字节表示X轴改的变量,第三字节表示Y轴的改变量,第四字节表示
//滚轮的改变量。我们在中断端点1中应该要按照上面的格式返回实际的
//鼠标数据。
第一字节的低3位用来表示按键是否按下,高5位为常数0,无用;第二字节表示X轴的该变量;第三字节表示Y轴的该变量;第四字节表示滚轮的改变量。
7)如何返回报告描述符
case REPORT_DESCRIPTOR: //报告描述符
#ifdef DEBUG0
Prints("报告描述符。\r\n");
#endif
pSendData=ReportDescriptor; //需要发送的数据为报告描述符
SendLength=sizeof(ReportDescriptor); //需要返回的数据长度
//判断请求的字节数是否比实际需要发送的字节数多
//如果请求的比实际的长,那么只返回实际长度的数据
if(wLength>SendLength)
{
if(SendLength%DeviceDescriptor[7]==0) //并且刚好是整数个数据包时
{
NeedZeroPacket=1; //需要返回0长度的数据包
}
}
else
{
SendLength=wLength;
}
//将数据通过EP0返回
UsbEp0SendData();
break;
8)分析调试信息
3.14 报告的返回
1)何时返回报告
2)如何返回报告(返回报告的原理)
//函数功能:端点0输入中断处理函数。
void UsbEp0In(void)
{
#ifdef DEBUG0
Prints("USB端点0输入中断。\r\n");
#endif
//读最后发送状态,这将清除端点0的中断标志位
D12ReadEndpointLastStatus(1);
//发送剩余的字节数
UsbEp0SendData();
}
//函数功能:总线复位中断处理函数。
void UsbBusReset(void)
{
#ifdef DEBUG0
Prints("USB总线复位。\r\n");
#endif
Ep1InIsBusy=0; //复位后端点1输入缓冲区空闲。
}
3)返回报告的代码实现
if(ConfigValue!=0) //如果已经设置为非0的配置,则可以返回报告数据
{
LEDs=~KeyPress; //利用板上8个LED显示按键状态,按下时亮
if(!Ep1InIsBusy) //如果端点1输入没有处于忙状态,则可以发送数据
{
KeyCanChange=0; //禁止按键扫描
if(KeyUp||KeyDown||KeyPress) //如果有按键事件发生
{
SendReport(); //则返回报告
}
KeyCanChange=1; //允许按键扫描
}
}
4)按键和返回报告的对应关系
//函数功能:根据按键情况返回报告的函数。
void SendReport(void)
{
//需要返回的4字节报告的缓冲
//Buf[0]的D0就是左键,D1就是右键,D2就是中键(这里没有)
//Buf[1]为X轴,Buf[2]为Y轴,Buf[3]为滚轮
uint8 Buf[4]={0,0,0,0};
//我们不需要KEY1~KEY6按键改变的信息,所以先将它们清0
KeyUp &=~(KEY1|KEY2|KEY3|KEY4|KEY5|KEY6);
KeyDown &=~(KEY1|KEY2|KEY3|KEY4|KEY5|KEY6);
//如果有按键按住,并且不是KEY7、KEY8(左、右键)
//或者KEY7、KEY8任何一个键有变动的话,则需要返回报告
if((KeyPress&(~(KEY7|KEY8)))||KeyUp||KeyDown)
{
if(KeyPress & KEY1) //如果KEY1按住,则光标需要左移,即X轴为负值。
{
Buf[1]=-1; //这里一次往左移动一个单位。
}
if(KeyPress & KEY2) //如果KEY2按住,则光标需要右移,即X轴为正值。
{
Buf[1]=1; //这里一次往右移动一个单位。
}
if(KeyPress & KEY3) //如果KEY3按住,则光标需要上移,即Y轴为负值。
{
Buf[2]=-1; //这里一次往上移动一个单位。
}
if(KeyPress & KEY4) //如果KEY4按住,则光标需要下移,即Y轴为正值。
{
Buf[2]=1; //这里一次往下移动一个单位。
}
if(KeyPress & KEY5) //如果KEY5按住,则滚轮下滚,即滚轮值为负。
{
Buf[3]=-1; //这里一次往下滚动一个单位。
}
if(KeyPress & KEY6) //如果KEY6按住,则滚轮上滚,既滚轮值为正
{
Buf[3]=1; //这里一次往上滚动一个单位。
}
if(KeyPress & KEY7) //鼠标左键
{
Buf[0]|=0x01; //D0为鼠标左键
}
if(KeyPress & KEY8) //鼠标右键
{
Buf[0]|=0x02; //D1为鼠标右键
}
//报告准备好了,通过端点1返回,长度为4字节。
D12WriteEndpointBuffer(3,4,Buf);
Ep1InIsBusy=1; //设置端点忙标志。
}
//记得清除KeyUp和KeyDown
KeyUp=0;
KeyDown=0;
}
3.16本章小结
《圈圈教你玩USB》 第三章 USB鼠标的实现——看书笔记( 3 )相关推荐
- 圈圈教你玩USB学习总结
前言 最近想学习一下USB的相关知识,然后看大家都比较推荐<圈圈教你玩USB>这本书作为入门资料,看了一个礼拜看到了USB键盘实现章节.因为USB的内容比较多也比较复杂,特写下此文进行梳理 ...
- 【食品加工技术】第三章 淀粉制糖与糖果加工技术 笔记
[食品加工技术]第三章 淀粉制糖与糖果加工技术 笔记 3.1 淀粉制糖技术 淀粉糖的优点 淀粉糖的种类 液体葡萄糖 葡萄糖 果脯糖浆 淀粉糖的性质 几种糖的相对甜度 溶解度 结晶性质 吸湿性和保湿性 ...
- 圈圈教你玩转USB第三版 光盘资料
下载官网: http://service.buaapress.com.cn/mzs/file/detail/id/2199/key/b2fec916cc9fa216abe6fc836e3f7e35
- XX教你玩 MiniPC (三)MK908之CPU RK3188启动 bootloader分析
上一篇我们已经弄明白了整个MiniPC的软件组成,但是并不是很清楚系统启动的细节,所以这一篇就仔细说明一下rk3188 从上电开始到启动内核为止的过程. 再说明一点,这篇有一部分是翻译的内容,但针对翻 ...
- 吴昊品游戏核心算法 Round 16 ——吴昊教你玩口袋妖怪 第三弹 地洞谜题
这样的场景我们应该经常遇到的吧,哈哈! 口袋妖怪的地洞要算是最令人讨厌的了,因为,有些地洞是全黑的,你即使用了闪光灯(必选道具#03),你有时也只能用GBA外壳的荧光屏作为道具才能将整个地洞看清楚. ...
- 高性能MySQL(第二版)第三章 架构优化和索引(上)——读书笔记
一,选择优化的数据类型 更小通常更好 更小的数据类型使用了更少的磁盘,内存和CPU缓存 但是要确保不要低估需要保存的值,在架构中的多个地方增加数据类型的范围是一件极其费时费力的工作.如果不确定需要什么 ...
- 第三章 货币资金及应收款项 详细笔记
文章目录 货币资金的核算 应收款项的核算 货币资金的核算 货币资金含:库存现金 银行存款 其他货币资金 一.库存现金的核算 (一)现金的概念 ▲ 狭义的现金是指存放于企业财会部门,由出纳人员经管的纸币 ...
- 王道操作系统课代表 - 考研计算机 第三章 内存管理 究极精华总结笔记
本篇博客是考研期间学习王道课程 传送门 的笔记,以及一整年里对 操作系统 知识点的理解的总结.希望对新一届的计算机考研人提供帮助!!! 关于对 内存管理 章节知识点总结的十分全面,涵括了<操 ...
- 专升本高数——第三章 一元函数导数的应用【学习笔记】
参考相关公式请进入:专升本高数--常用公式总结大全[补充扩展] https://blog.csdn.net/liu17234050/article/details/104439092 全部知识点请进入 ...
- 《c语言从入门到精通》看书笔记——第11章 结构体和共用体
1.结构体: "结构体"是一种构造类型,它是由若干"成员"组成的,其中的每一个成员可以是一个基本数据类型或者有事一个构造类型. (1)声明结构体时使用的关键字是 ...
最新文章
- 串口同步异步c语言程序,同步串口spi的c语言编程
- 使用ssh-keygen和ssh-copy-id三步实现SSH无密码登录
- java中的标识符和关键字_浅谈java中的标识符、修饰符和关键字
- 【LeetCode】462. 最少移动次数使数组元素相等 II
- Wireshark安装和基本使用
- 【java8新特性】——lambda表达式与函数式接口详解(一)
- 会动的图解 (二) 怎么让goroutine跑一半就退出?
- configure 包,出现error: no acceptable C compiler found in $PATH 问题
- dq坐标系下无功功率表达式_基于自动发电控制的柔性直流输电恢复电网的控制方法_2017103002337_权利要求书_专利查询_专利网_钻瓜专利网...
- 《老路用得上的商学课6—10》博弈论模型
- html中的背景颜色渐变效果,如何CSS实现网页背景三种颜色渐变效果?
- 骑士游历问题——至少需要多少步
- OA实施分析:OA系统选型警惕哪些陷阱
- HR 必知的 360 评估
- 【142期】List 中 remove() 方法的“陷阱”,被坑惨了!
- 大彩串口屏之LUA使用1
- JPG图片插入到CAD图纸中的2种方法
- 自然语言处理研究方向--文本相似度论文合集(不断更新)
- 回炉夜话 - HTML5
- 直方图匹配(直方图规定化)