之前一直在使用攀藤 G1 ,前面提到 G1 都坏掉了郁闷无比。然后入手了一个 G3 。之所以还选择攀藤的产品最主要是考虑尽可能的复用之前的代码……
下面图是 G1 和 G3 外观,可以看出 G3 要小一点:
数据上和G1稍微有些差别,主要是送出来的数据短了一些。
下面是串口实际获得的数据
具体解读(来自说明书)
最后修改一下之前给 G1 写的 APP 即可
完整的代码
G3Ver1source
编译后的程序
Project2
之前一直在使用攀藤 G1 ,前面提到 G1 都坏掉了郁闷无比。然后入手了一个 G3 。之所以还选择攀藤的产品最主要是考虑尽可能的复用之前的代码……
下面图是 G1 和 G3 外观,可以看出 G3 要小一点:
数据上和G1稍微有些差别,主要是送出来的数据短了一些。
下面是串口实际获得的数据
具体解读(来自说明书)
最后修改一下之前给 G1 写的 APP 即可
完整的代码
G3Ver1source
编译后的程序
Project2
根据网上搜到的结果,在【参考1】下载了 controlP5 的库,然后使用下面的程序
import controlP5.*;
ControlP5 cp5;
String[] textfieldNames = {"tf1", "tf2", "tf3", "tf4", "tf5"};
void setup() {
size(700,400);
PFont font = createFont("arial",20);
cp5 = new ControlP5(this);
int y = 20;
int spacing = 60;
for(String name: textfieldNames){
cp5.addTextfield(name)
.setPosition(20,y)
.setSize(100,40)
.setFont(font)
.setFocus(true)
.setColor(color(255,0,0))
;
y += spacing;
}
textFont(font);
}
void draw() {
background(0);
}
void controlEvent(ControlEvent theEvent) {
if(theEvent.isAssignableFrom(Textfield.class)) {
println("controlEvent: accessing a string from controller '"
+theEvent.getName()+"': "
+theEvent.getStringValue()
);
}
}
运行结果
1. http://www.sojamo.de/libraries/controlP5/
了解了一下 UUID 和 GUID , 资料上来看,这两个是同一个东西。GUID 是 MS 定义的 UUID 的一种实现方法. 两者在写法上稍微有些差别【参考1】【参考2】:
GUID:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (8-4-4-4-12)
UUID:xxxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx (8-4-4-16)
在 UEFI 中,我们经常看到类似下面的定义
“cf8e16a5-c1e8-4e25-b712-4f54a96702c8”
#define EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID \
{ \
0x964e5b22, 0x6459, 0x11d2, {0x8e, 0x39, 0x0, 0xa0, 0xc9, 0x69, 0x72, 0x3b } \
}
UUID PadFileGuid;
对于 GUID ,可以在 \MdePkg\Include\Base.h 看到下面的定义
///
/// 128 bit buffer containing a unique identifier value.
/// Unless otherwise specified, aligned on a 64 bit boundary.
///
typedef struct {
UINT32 Data1;
UINT16 Data2;
UINT16 Data3;
UINT8 Data4[8];
} GUID;
同时,在 \MdePkg\Include\Uefi\UefiBaseType.h 有下面的定义
/// /// 128-bit buffer containing a unique identifier value. /// typedef GUID EFI_GUID;
在 \BaseTools\Source\C\Common\CommonLib.c 有一个打印输出的例子可以直接拿出来用
EFI_STATUS
PrintGuid (
IN EFI_GUID *Guid
)
/*++
Routine Description:
This function prints a GUID to STDOUT.
Arguments:
Guid Pointer to a GUID to print.
Returns:
EFI_SUCCESS The GUID was printed.
EFI_INVALID_PARAMETER The input was NULL.
--*/
{
if (Guid == NULL) {
Error (NULL, 0, 2000, "Invalid parameter", "PrintGuidToBuffer() called with a NULL value");
return EFI_INVALID_PARAMETER;
}
printf (
"%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x\n",
(unsigned) Guid->Data1,
Guid->Data2,
Guid->Data3,
Guid->Data4[0],
Guid->Data4[1],
Guid->Data4[2],
Guid->Data4[3],
Guid->Data4[4],
Guid->Data4[5],
Guid->Data4[6],
Guid->Data4[7]
);
return EFI_SUCCESS;
}
最后,写一个输出的例子
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/ShellCEntryLib.h>
#include <stdio.h>
#include <stdlib.h>
#include <wchar.h>
#include <Protocol/EfiShell.h>
#include <Library/ShellLib.h>
extern EFI_BOOT_SERVICES *gBS;
extern EFI_SYSTEM_TABLE *gST;
extern EFI_RUNTIME_SERVICES *gRT;
#define GUID1 \
{ \
0x964e5b22, 0x6459, 0x11d2, {0x8e, 0x39, 0x0, 0xa0, 0xc9, 0x69, 0x72, 0x3b } \
}
#define GUID2 \
{ \
0x33221100, 0x5544, 0x7766, {0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF } \
}
EFI_STATUS
PrintGuid (
IN EFI_GUID *Guid
)
/*++
Routine Description:
This function prints a GUID to STDOUT.
Arguments:
Guid Pointer to a GUID to print.
Returns:
EFI_SUCCESS The GUID was printed.
EFI_INVALID_PARAMETER The input was NULL.
--*/
{
if (Guid == NULL) {
printf("Parameter error!\n");
return EFI_INVALID_PARAMETER;
}
printf (
"%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x\n",
(unsigned) Guid->Data1,
Guid->Data2,
Guid->Data3,
Guid->Data4[0],
Guid->Data4[1],
Guid->Data4[2],
Guid->Data4[3],
Guid->Data4[4],
Guid->Data4[5],
Guid->Data4[6],
Guid->Data4[7]
);
return EFI_SUCCESS;
}
int
EFIAPI
main (
IN int Argc,
IN CHAR16 **Argv
)
{
EFI_GUID T1=GUID1;
EFI_GUID T2=GUID2;
PrintGuid(&T1);
PrintGuid(&T2);
return EFI_SUCCESS;
}
运行结果
完整代码下载
参考:
1.http://baike.baidu.com/view/1052579.htm UUID
2.http://blog.csdn.net/forlong401/article/details/7580147 UUID 和 GUID 的区别
3.https://en.wikipedia.org/wiki/Globally_unique_identifier Globally unique identifier
4.https://en.wikipedia.org/wiki/Universally_unique_identifier Universally unique identifier
上次买了攀藤 G1 PM2.5 传感器了,这次有需要又拿了出来使用【参考1】。很不幸,经常不工作,探究原因似乎与接线有关系,于是我又重新做了一个线(上次和卖家多要了一根线),用上之后现象稍微有些改善,但是移动之类的还会导致失灵,具体现象就是风扇不转,碰碰插头之类的就好了。一怒之下,拆开研究:
首先,外面是一层壳子,这个可以直接撬开(蓝色的是外面的塑料膜,我基本上没有用,所以这层薄膜还在的)
然后,需要拧掉周围四个固定外壳的螺丝,此外还要拧下固定风扇的三个螺丝才能继续拆解。螺丝居然生锈了,我很惊奇。基本上没怎么用,也都是一直放在家里不知道为什么会这样,观察另外一只(好吧,其实我是买个2个的),同样也有生锈的情况
最后,用螺丝刀之类的撬开即可
主板上有一颗CY8C 的单片机。更惊奇的是:主板上固定的螺丝竟然也有锈蚀!
背面是关键,可以看出有一个激光头样子的东西,这应该是这个传感器的关键部件,配合风扇和壳子内部形成的风道,可以计量单位时间颗粒物的数量。
研究发现插头不紧的原因是:他选用的那种连接件不是自锁样式的。比如:在拆开上面状态下,无法插入接线然后保持卡住的状态。连接件只是让金属接触上,然后卡住是外壳的功劳。这样,如果外壳出现松动或者接头磨损之类的,就根本无法卡住,就会出现我遇到的问题。
前几个月,我在实验点亮各种 MIPI 屏幕,然后发现接口卡具没多久就坏掉了,我去和硬件工程师说,你们做的时候不能选点好的连接器么,他回答是:这个东西装上基本上不会再拆了,像你这样拆来拆去的属于特殊情况,从成本角度考虑我们不会有选择结实一点的需求,估计这个传感器也是本着这样的思路设计的。
最后的结论:东西不错,但是连接设计太差,如果你要像我这样做 DIY 有经常插拔的需求,不建议选择这款。如果你是做毕业设计之类的展示性工作,最好再多买一个备份。
后面的话我想找个 HW 帮着焊接出来一根永久固定的线吧。
参考:
1.http://www.lab-z.com/g1pm25/ 攀藤 G1 PM2.5传感器
前面介绍了USB键盘的使用,这里介绍一下USB鼠标的调用。根据【参考1】的文章进行实验,这次我们的目标是:获得鼠标移动按键信息,串口输出之。
首先运行一下之前的取得描述符的工具抓取一下描述符:
Descriptor of HP Mouse
Start
Device addressed… Requesting device descriptor.
Device descriptor:
Descriptor Length: 12
USB version: 2.0
Class: 00 Use class information in the Interface Descriptor
Subclass: 00
Protocol: 00
Max.packet size: 08
Vendor ID: 046D
Product ID: C077
Revision ID: 6700
Mfg.string index: 01 Length: 18 Contents: Logitech
Prod.string index: 02 Length: 36 Contents: USB Optical Mouse
Serial number index: 00
Number of conf.: 01
Configuration number 0
Total configuration length: 34 bytes
Configuration descriptor:
Total length: 0022
Number of interfaces: 01
Configuration value: 01
Configuration string: 00
Attributes: A0 Remote Wakeup
Max.power: 31 98ma
Interface descriptor:
Interface number: 00
Alternate setting: 00
Endpoints: 01
Class: 03 HID (Human Interface Device)
Subclass: 01
Protocol: 02
Interface string: 00
HID descriptor:
Descriptor length: 09 9 bytes
HID version: 1.11
Country Code: 0 Not Supported
Class Descriptors: 1
Class Descriptor Type: 22 Report
Class Descriptor Length:67 bytes
HID report descriptor:
Length: 1 Type: Global Tag: Usage Page Generic Desktop Controls Data: 01
Length: 1 Type: Local Tag: Usage Data: 02
Length: 1 Type: Main Tag: Collection Application (mouse, keyboard) Data: 01
Length: 1 Type: Local Tag: Usage Data: 01
Length: 1 Type: Main Tag: Collection Physical (group of axes) Data: 00
Length: 1 Type: Global Tag: Usage Page Button Data: 09
Length: 1 Type: Local Tag: Usage Minimum Data: 01
Length: 1 Type: Local Tag: Usage Maximum Data: 08
Length: 1 Type: Global Tag: Logical Minimum Data: 00
Length: 1 Type: Global Tag: Logical Maximum Data: 01
Length: 1 Type: Global Tag: Report Size Data: 01
Length: 1 Type: Global Tag: Report Count Data: 08
Length: 1 Type: Main Tag: Input Data,Variable,Absolute………
Length: 1 Type: Global Tag: Usage Page Generic Desktop Controls Data: 01
Length: 2 Type: Global Tag: Logical Minimum Data: 01 Data: F8
Length: 2 Type: Global Tag: Logical Maximum Data: FF Data: 07
Length: 1 Type: Global Tag: Report Size Data: 0C
Length: 1 Type: Global Tag: Report Count Data: 02
Length: 1 Type: Local Tag: Usage Data: 30
Length: 1 Type: Local Tag: Usage Data: 31
Length: 1 Type: Main Tag: Input Data,Variable,Relative………
Length: 1 Type: Global Tag: Logical Minimum Data: 81
Length: 1 Type: Global Tag: Logical Maximum Data: 7F
Length: 1 Type: Global Tag: Report Size Data: 08
Length: 1 Type: Global Tag: Report Count Data: 01
Length: 1 Type: Local Tag: Usage Data: 38
Length: 1 Type: Main Tag: Input Data,Variable,Relative………
Length: 1 Type: Global Tag: Usage Page Consumer Data: 0C
Length: 2 Type: Local Tag: Usage Data: 38 Data: 02
Length: 1 Type: Global Tag: Report Count Data: 01
Length: 1 Type: Main Tag: Input Data,Variable,Relative………
Length: 0 Type: Main Tag: End Collection
Length: 0 Type: Main Tag: End Collection
Endpoint descriptor:
Endpoint address: 01 Direction: IN
Attributes: 03 Transfer type: Interrupt
Max.packet size: 0006
Polling interval: 0A 10 ms
根据上面的结果,计算长度应该是1×8+Cx2+8×1=40 bits= 5 bytes ,修改【参考1】的代码如下:
#include "Max3421e.h"
#include "Usb.h"
#define DEVADDR 1
#define CONFVALUE 1
void setup();
void loop();
MAX3421E Max;
USB Usb;
void setup()
{
Serial.begin( 115200 );
Serial.println("Start");
Max.powerOn();
delay( 200 );
}
void loop()
{
byte rcode;
Max.Task();
Usb.Task();
if( Usb.getUsbTaskState() == USB_STATE_CONFIGURING ) {
mouse0_init();
}//if( Usb.getUsbTaskState() == USB_STATE_CONFIGURING...
if( Usb.getUsbTaskState() == USB_STATE_RUNNING ) { //poll the keyboard
rcode = mouse0_poll();
if( rcode ) {
Serial.print("Mouse Poll Error: ");
Serial.println( rcode, HEX );
}//if( rcode...
}//if( Usb.getUsbTaskState() == USB_STATE_RUNNING...
}
/* Initialize mouse */
void mouse0_init( void )
{
byte rcode = 0; //return code
/**/
Usb.setDevTableEntry( 1, Usb.getDevTableEntry( 0,0 ) ); //copy device 0 endpoint information to device 1
/* Configure device */
rcode = Usb.setConf( DEVADDR, 0, CONFVALUE );
if( rcode ) {
Serial.print("Error configuring mouse. Return code : ");
Serial.println( rcode, HEX );
while(1); //stop
}//if( rcode...
Usb.setUsbTaskState( USB_STATE_RUNNING );
return;
}
/* Poll mouse using Get Report and print result */
byte mouse0_poll( void )
{
byte rcode,i;
char buf[ 4 ] = { 0 }; //mouse buffer
static char old_buf[ 4 ] = { 0 }; //last poll
/* poll mouse */
rcode = Usb.getReport( DEVADDR, 0, 4, 0, 1, 0, buf );
if( rcode ) { //error
return( rcode );
}
for( i = 0; i < 4; i++) { //check for new information
if( buf[ i ] != old_buf[ i ] ) { //new info in buffer
break;
}
}
if( i == 4 ) {
return( 0 ); //all bytes are the same
}
/* print buffer */
if( buf[ 0 ] & 0x01 ) {
Serial.print("Button1 pressed ");
}
if( buf[ 0 ] & 0x02 ) {
Serial.print("Button2 pressed ");
}
if( buf[ 0 ] & 0x04 ) {
Serial.print("Button3 pressed ");
}
Serial.println("");
Serial.print("X-axis: ");
Serial.println( buf[ 1 ], DEC);
Serial.print("Y-axis: ");
Serial.println( buf[ 2 ], DEC);
Serial.print("Wheel: ");
Serial.println( buf[ 3 ], DEC);
for( i = 0; i < 4; i++ ) {
old_buf[ i ] = buf[ i ]; //copy buffer
}
Serial.println("");
return( rcode );
}
运行结果
完整代码下载
看起来按键和X Y都已经正确,但是 wheel 并不正常。再仔细研究代码,取出来的信息长度和描述符中的不一致。就是说,程序虽然能够运行也能够取到变化的值,但是解读有误。
再进一步研读 Descriptor
Length: 1 Type: Global Tag: Usage Page Generic Desktop Controls Data: 01
Length: 2 Type: Global Tag: Logical Minimum Data: 01 Data: F8 //最小 F801 = 1111 1000 0000 0001 = -2047
Length: 2 Type: Global Tag: Logical Maximum Data: FF Data: 07//最大 07FF = 0000 0111 1111 1111 = 2047
Length: 1 Type: Global Tag: Report Size Data: 0C //这里指出了上面数值的大小是 12位
Length: 1 Type: Global Tag: Report Count Data: 02
Length: 1 Type: Local Tag: Usage Data: 30 // X
Length: 1 Type: Local Tag: Usage Data: 31 // Y
应该是意味着2个12bits长度的数值,对应X Y 分配如下,其中 X[B] 和Y[B]应该是最高的符号位
根据这个改写程序,按照上面的表格取值再拼接起来,特别需要注意的是,我发现Arduino在不同尺寸的数据转换上似乎有一些bug,我只能把值放在 unsigned long int中然后才能进行拼接和输出的动作。
Serial.print("X-axis: ");
x=((libuf[2] & 0xF)<<8)+(libuf[1] & 0xFF );
if (x & 0x800) {
Serial.print("-");
x = ((~x) & 0x7FF) +1;
}
Serial.println(x, HEX);
Serial.print("Y-axis: ");
y=(((libuf[2]>>4) & 0xF) +((libuf[3] & 0xFF )<<4));
if (y & 0x800) {
Serial.print("-");
y = ((~y) & 0x7FF) +1;
}
Serial.println(y, HEX);
最终,程序可以正常执行。
工作正常的程序如下:
#include "Max3421e.h"
#include "Usb.h"
#define DEVADDR 1
#define CONFVALUE 1
void setup();
void loop();
MAX3421E Max;
USB Usb;
void setup()
{
Serial.begin( 115200 );
Serial.println("Start");
Max.powerOn();
delay( 200 );
}
void loop()
{
byte rcode;
Max.Task();
Usb.Task();
if( Usb.getUsbTaskState() == USB_STATE_CONFIGURING ) {
mouse0_init();
}//if( Usb.getUsbTaskState() == USB_STATE_CONFIGURING...
if( Usb.getUsbTaskState() == USB_STATE_RUNNING ) { //poll the keyboard
rcode = mouse0_poll();
if( rcode ) {
Serial.print("Mouse Poll Error: ");
Serial.println( rcode, HEX );
}//if( rcode...
}//if( Usb.getUsbTaskState() == USB_STATE_RUNNING...
}
/* Initialize mouse */
void mouse0_init( void )
{
byte rcode = 0; //return code
/**/
Usb.setDevTableEntry( 1, Usb.getDevTableEntry( 0,0 ) ); //copy device 0 endpoint information to device 1
/* Configure device */
rcode = Usb.setConf( DEVADDR, 0, CONFVALUE );
if( rcode ) {
Serial.print("Error configuring mouse. Return code : ");
Serial.println( rcode, HEX );
while(1); //stop
}//if( rcode...
Usb.setUsbTaskState( USB_STATE_RUNNING );
return;
}
/* Poll mouse using Get Report and print result */
byte mouse0_poll( void )
{
byte rcode,i;
char buf[ 5 ] = { 0 }; //mouse buffer
static char old_buf[ sizeof(buf) ] = { 0 }; //last poll
unsigned long int libuf[ sizeof(buf) ];
unsigned long int x;
unsigned long int y;
/* poll mouse */
rcode = Usb.getReport( DEVADDR, 0, sizeof(buf), 0, 1, 0, buf );
if( rcode ) { //error
return( rcode );
}
for( i = 0; i < sizeof(buf); i++) { //check for new information
if( buf[ i ] != old_buf[ i ] ) { //new info in buffer
break;
}
}
if( i == sizeof(buf)) {
return( 0 ); //all bytes are the same
}
/* print buffer */
if( buf[ 0 ] & 0x01 ) {
Serial.print("Button1 pressed ");
}
if( buf[ 0 ] & 0x02 ) {
Serial.print("Button2 pressed ");
}
if( buf[ 0 ] & 0x04 ) {
Serial.print("Button3 pressed ");
}
for( i = 0; i < sizeof(buf); i++ ) {
old_buf[ i ] = buf[ i ]; //copy buffer
libuf[ i] = buf[i];
}
//Serial.print(libuf[0],HEX); Serial.print(" ");
//Serial.print(libuf[1],HEX); Serial.print(" ");
//Serial.print(libuf[2],HEX); Serial.print(" ");
//Serial.print(libuf[3],HEX); Serial.print(" ");
//Serial.println("");
Serial.print("X-axis: ");
x=((libuf[2] & 0xF)<<8)+(libuf[1] & 0xFF );
if (x & 0x800) {
Serial.print("-");
x = ((~x) & 0x7FF) +1;
}
Serial.println(x, HEX);
Serial.print("Y-axis: ");
y=(((libuf[2]>>4) & 0xF) +((libuf[3] & 0xFF )<<4));
if (y & 0x800) {
Serial.print("-");
y = ((~y) & 0x7FF) +1;
}
Serial.println(y, HEX);
Serial.print("Wheel: ");
Serial.println(buf [4] & 0xFF, HEX);
return( rcode );
}
完整代码下载:
补遗:在运行中,我还发现了一个奇怪的问题:当使用 Get_Report取得数据时,鼠标向一个方向移动,会出现最大值。意思是:比如,向右一直移动鼠标,输出达到 7FF 之后,继续右移,输出值会维持在 7FF 而不会变化。没有找到直接描述这个问题的资料,不过有一些类似的介绍【参考2】,上面提到USB鼠标在送出数据的时候,会有两种方式,一种是Relative ,一种是Absolute。差别在于,比如前者输出 (1,1),意思是告诉系统鼠标移动了(1,1);而后者如果输出 (1,1)则是告诉系统,鼠标移动到(1,1)。看起来,我这个鼠标描述符中提到的是Relative,但是实际输出是 Absolute。
Length: 1 Type: Global Tag: Report Size Data: 0C
Length: 1 Type: Global Tag: Report Count Data: 02
Length: 1 Type: Local Tag: Usage Data: 30
Length: 1 Type: Local Tag: Usage Data: 31
Length: 1 Type: Main Tag: Input Data,Variable,Relative………
于此可以形成对照的是 Wheel,每次滚动之后,很快会再次输出一个 0 的位置,下次再给出新的滚动值。
至于Windows 下,根据资料,系统会自动根据鼠标信息计算出一个大概范围方便使用(比如,屏幕是 1024×768,鼠标是Relative -100到+100的话,如果不做优化,从左上到右下需要做几次才行)。而我用的这个鼠标,声明自己是Relative,但是实际上发出Absolute,只是因为自己的范围很大(-7FF到+7FF),所以也不会有什么感觉。
上述只是猜测,是目前已知的最合理的解释而已。使用逻辑分析仪分析USB鼠标信息得到的结果相同。
逻辑分析仪抓取的结果,Windows使用 Interrupt 方式来和鼠标通讯
下面展示一下USB逻辑分析仪抓到的启动时的一些信息,首先是 Overview

不清楚为什么VendorID 居然是罗技的,一种可能是因为这个是假的HP鼠标,乱写一个ID;另外的可能是HP 找罗技代工的。
可以看到,MaxPower确实是一个很古怪的数值:98ma
参考:
1.https://www.circuitsathome.com/communicating-arduino-with-hid-devices-part-1
2.http://www.microchip.com/forums/m482197.aspx MOUSE HID / Resolution problem
最近玩玩 Processing , 尝试导入一个 DXF 的模型,OBJ 格式的. 使用 Processing 加载之。具体做法是这样的:
1. 下载 OBJLoader 库 【参考1】
2. 把这个库放在 Processing -> File -> Preferences -> Sketchbook location 指定的位置中,例如:
特别注意,默认目录中可能有空格或者中文,那么请你指定一个不含有中文或者空格的目录,像我一样,放在 D:\ 下面最好。就是下面这个样子
这个库可以直接去【参考1】给出的网站上下载,也可以用 Tools -> Add Tool 调出 Tool Manager,他会自动下载列表,你选择 ObjLoader 进行安装(可能是 GFW 的缘故,这个功能不稳定)
3.安装好之后,可以编译 ObjLoader 中 Examples 下面的例子,确保安装正确 (如果出现错误,并且错误不是 ObjLoader 导致的,那么请换另外的例子,有些例子用到了第三方的库)
4.最后编写自己的程序。从 Sketch -> Add Files 中导入 OBJ 模型,再输入下面的代码
import saito.objloader.*;
OBJModel model;
void setup() {
size(600,600,P3D);
model= new OBJModel(this);
model.load("teapot.obj");
model.setDrawMode(POLYGON);
noStroke();
}
void draw() {
background(0);
lights();
pushMatrix();
translate(width/2,height,-width);
rotateY(map(mouseX,0,width,-PI,PI));
rotateX(PI/4);
scale(6.0);
model.draw();
popMatrix();
}
最终运行结果如下
完整的代码下载
参考:
1.https://processing.org/reference/libraries/ 上提到的 OBJLoader by Tatsuya Saito and Matt Ditton .OBJ 3D model file loader
玩了很久的 arduino 老婆一直抱怨没有做过什么实用的东西,这次就做个方便晚上下床的小夜灯。
从技术角度讲非常简单:一个红外遮挡开关(本来是打算用圆柱形的那种,结果买来不好用【参考1】),当收到被阻挡的信号之后,自动从暗到亮,间隔特定时间之后再从亮到暗的灭掉。灯珠选择的是 WS2811。这种是全色灯,理论上有 2^24种颜色(其实肉眼根本分别不出这么多种类,另外,你再看到什么灯吹嘘自己一千六百万种颜色,就知道是和“奥氏体304不锈钢”一样,听上去牛逼的名字而已)。Arduino搭配专门的库函数,控制这个灯还是很方便的。顺便介绍一下原理:这些灯都是WS2811芯片串联起来的,每个灯珠里面都封装着一颗LED和芯片。对外的线有2组,每组都是三根线:VCC/GND/DATA。VCC是5V,所以用起来比较方便的,直接Arduino供电就可以(前提是不要超过Arduino提供电流的上限200ma,如果你不确定最大电流最好像我一样单独供电)。信号方面,每个灯颜色是8位的R ,8位的G,8位的B,串行给出。当第一个WS2811收到信号之后,他会取下自己的RGB颜色,把剩下的信号继续传下去。这样,一根信号线即可给出全部灯珠的颜色信号。
下面就动手开始做了。买来的灯是连接好的一整条,我把它拆成2个一组了。中间使用“SM2.54 接插件 2.54MM 公母壳+公母簧片 对插SM-3P”进行连接。使用这样的插接线的好处是:容易扩充和调整。缺点是:需要手工压头,感觉上我做的连接可靠性不是很高。后来每个插脚除了压合,我还用电烙铁简单焊接了一下。
电路上比较简单,我选择 Arduino Pro Micro,编程方便,体积适中。下面是连接的示意图,特别注意WS2811灯条是有方向的。一边是输入,一边是输出。
代码如下,其中的颜色可以根据具体情况进行调整,这里只是简单演示:
#include "FastLED.h"
//红外传感器(开关用)
#define IRNear A2
//灯带用的数据Pin
#define DATA_PIN 10
//灯带上灯的数量
#define NUM_LEDS 6
//灯带颜色
CRGB leds[NUM_LEDS];
void setup() {
//初始化灯带
FastLED.addLeds<WS2811, DATA_PIN, RGB>(leds, NUM_LEDS);
//初始化接近开关
pinMode(IRNear, INPUT_PULLUP);
}
void loop() {
Serial.println("Led start working");
//灯带关闭状态
for (int i = 0; i < NUM_LEDS; i++ )
{
leds[i] = CRGB::Black;
FastLED.show();
}
//等待稳定
delay(1000);
if (0==digitalRead(IRNear)) {
delay(200);
if (0==digitalRead(IRNear)) {
for (int i = 0; i < NUM_LEDS; i++ )
{
leds[i] = CRGB(255,83,0);
}
for (int j=0; j<255; j++)
{
FastLED.show(j);
delay(100);
}
delay(15000);
for (int i = 0; i < NUM_LEDS; i++ )
{
leds[i] = CRGB::Red;
}
for (int j=255; j>10; j--)
{
FastLED.show(j);
delay(100);
}
for (int i = 0; i < NUM_LEDS; i++ )
{
leds[i] = CRGB::Black;
FastLED.show();
}
}//if (0==digitalRead(IRNear)) {
}//if (0==digitalRead(IRNear)) {
}
最上面是遮挡开关,最下面是多出来的一根线。如果等不够,随时可以再添加上一些。
为了美观和可靠,后面又用纸盒把遮挡开关包裹了一下,同样在这个开关后面也有个用于固定的磁铁。
床是铁的,直接粘在外面实验一下。
最后安装之后就是这个样子,其实最好是让每个灯垂直照射地面,不过晚上实验表明这个作为照明已经足够了。
当一只脚落在地面上,遮挡开关会触发,然后灯会缓慢亮起,到达最亮之后维持15秒,切换为红色,随后再逐渐暗下。
老婆实验之后,提出了一个问题:如果我把一直腿垂在床边,那他岂不是要一直工作?这不好吧?
我的回复是谢霆锋的 《1999谢谢你的爱》。
参考:
1. 再次提醒,买回来的东西一定要及时测试,否则卖家不认账
2. 作者已经哭死,从他们家买了三样东西,历时一周才拿到手上(熊猫慢递)。反复盘点发现他们居然少发了电线。联系卖家一直没动静。然后选择了天猫的退款。结果点的是“退货退款”……两天后,客服告诉我,要选“只退款”。等我取消上一次的请求之后惊奇的发现竟然没有办法再次申请。联系淘宝客服,对方听完我的陈述之后,告诉我去找天猫,俺们不是一家。我又拨打了天猫的热线,客服小伙听过讲述之后,很快发给我一个无法打开的链接(鬼知道为什么搞错了);没办法,又打了一次,给了一个可以打开的链接,目前正在处理中ing.最后结果是退款了,但是前后差不多用了2周。可以说,如果以后有机会在淘宝店买东西也不要在天猫店买东西,服务差别很大。
网站趴窝了几天,具体原因是2015第13号台风“苏迪罗”间接造成的…….
解释一下就是台风来了,有人在果壳发帖子【参考1】,然后我跟帖,外链贴了一下很早之前做的台风的GIF动画,大小是 20MB 。结果一个晚上,台风还没有来,我的网站就趴窝了。
检查日志发现因为这个帖子访问量很大,每个浏览帖子的人都要从网站走20MB,很快就用光了一个月的配额。
后来联系供应商,他也很吃惊,那么多这个月没几天就用光了。后来他给加了一些,又开启了。
上一次出现这样的情况是因为我在网站上放了2个MP3,结果被一个淘宝店铺引用到,只是那次是在月末,我删除了 MP 3第二个月就没事了。没想到这次这么严重。
参考:
1.http://www.guokr.com/post/694425/?page=3#6650529 【直播】2015年最强台风苏迪罗要来了!8月8日凌晨已登陆台湾
下面是一个获得 USB 键盘数据的例子【参考1】。原理上说,是将键盘切换为 Boot Protocol 这样就避免了需要具体解析HID的麻烦。
/* MAX3421E USB Host controller LCD/keyboard demonstration */
//#include <Spi.h>
#include "Max3421e.h"
#include "Usb.h"
/* keyboard data taken from configuration descriptor */
#define KBD_ADDR 1
#define KBD_EP 1
#define KBD_IF 0
#define EP_MAXPKTSIZE 8
#define EP_POLL 0x0a
/**/
//******************************************************************************
// macros to identify special charaters(other than Digits and Alphabets)
//******************************************************************************
#define BANG (0x1E)
#define AT (0x1F)
#define POUND (0x20)
#define DOLLAR (0x21)
#define PERCENT (0x22)
#define CAP (0x23)
#define AND (0x24)
#define STAR (0x25)
#define OPENBKT (0x26)
#define CLOSEBKT (0x27)
#define RETURN (0x28)
#define ESCAPE (0x29)
#define BACKSPACE (0x2A)
#define TAB (0x2B)
#define SPACE (0x2C)
#define HYPHEN (0x2D)
#define EQUAL (0x2E)
#define SQBKTOPEN (0x2F)
#define SQBKTCLOSE (0x30)
#define BACKSLASH (0x31)
#define SEMICOLON (0x33)
#define INVCOMMA (0x34)
#define TILDE (0x35)
#define COMMA (0x36)
#define PERIOD (0x37)
#define FRONTSLASH (0x38)
#define DELETE (0x4c)
/**/
/* Modifier masks. One for both modifiers */
#define SHIFT 0x22
#define CTRL 0x11
#define ALT 0x44
#define GUI 0x88
/**/
/* "Sticky keys */
#define CAPSLOCK (0x39)
#define NUMLOCK (0x53)
#define SCROLLLOCK (0x47)
/* Sticky keys output report bitmasks */
#define bmNUMLOCK 0x01
#define bmCAPSLOCK 0x02
#define bmSCROLLLOCK 0x04
/**/
EP_RECORD ep_record[ 2 ]; //endpoint record structure for the keyboard
char buf[ 8 ] = { 0 }; //keyboard buffer
char old_buf[ 8 ] = { 0 }; //last poll
/* Sticky key state */
bool numLock = false;
bool capsLock = false;
bool scrollLock = false;
bool line = false;
void setup();
void loop();
MAX3421E Max;
USB Usb;
void setup() {
Serial.begin( 9600 );
Serial.println("Start");
Max.powerOn();
delay( 200 );
}
void loop() {
Max.Task();
Usb.Task();
if( Usb.getUsbTaskState() == USB_STATE_CONFIGURING ) {
//wait for addressing state
kbd_init();
Usb.setUsbTaskState( USB_STATE_RUNNING );
}
if( Usb.getUsbTaskState() == USB_STATE_RUNNING ) {
//poll the keyboard
kbd_poll();
}
}
/* Initialize keyboard */
void kbd_init( void )
{
byte rcode = 0; //return code
/**/
/* Initialize data structures */
ep_record[ 0 ] = *( Usb.getDevTableEntry( 0,0 ));
//copy endpoint 0 parameters
ep_record[ 1 ].MaxPktSize = EP_MAXPKTSIZE;
ep_record[ 1 ].Interval = EP_POLL;
ep_record[ 1 ].sndToggle = bmSNDTOG0;
ep_record[ 1 ].rcvToggle = bmRCVTOG0;
Usb.setDevTableEntry( 1, ep_record );
//plug kbd.endpoint parameters to devtable
/* Configure device */
rcode = Usb.setConf( KBD_ADDR, 0, 1 );
if( rcode ) {
Serial.print("Error attempting to configure keyboard. Return code :");
Serial.println( rcode, HEX );
while(1); //stop
}
/* Set boot protocol */
rcode = Usb.setProto( KBD_ADDR, 0, 0, 0 );
if( rcode ) {
Serial.print("Error attempting to configure boot protocol. Return code :");
Serial.println( rcode, HEX );
while( 1 ); //stop
}
delay(2000);
Serial.println("Keyboard initialized");
}
/* Poll keyboard and print result */
/* buffer starts at position 2, 0 is modifier key state and 1 is irrelevant */
void kbd_poll( void )
{
char i;
static char leds = 0;
byte rcode = 0; //return code
/* poll keyboard */
rcode = Usb.inTransfer( KBD_ADDR, KBD_EP, 8, buf );
if( rcode != 0 ) {
return;
}//if ( rcode..
for( i = 2; i < 8; i++ ) {
if( buf[ i ] == 0 ) { //end of non-empty space
break;
}
if( buf_compare( buf[ i ] ) == false ) { //if new key
switch( buf[ i ] ) {
case CAPSLOCK:
capsLock =! capsLock;
leds = ( capsLock ) ? leds |= bmCAPSLOCK : leds &= ~bmCAPSLOCK;
// set or clear bit 1 of LED report byte
break;
case NUMLOCK:
numLock =! numLock;
leds = ( numLock ) ? leds |= bmNUMLOCK : leds &= ~bmNUMLOCK;
// set or clear bit 0 of LED report byte
break;
case SCROLLLOCK:
scrollLock =! scrollLock;
leds = ( scrollLock ) ? leds |= bmSCROLLLOCK : leds &= ~bmSCROLLLOCK;
// set or clear bit 2 of LED report byte
break;
case DELETE:
line = false;
break;
case RETURN:
line =! line;
break;
default:
//Serial.print(HIDtoA( buf[ i ], buf[ 0 ] ));
break;
}//switch( buf[ i ...
Serial.print(buf[ i ],HEX);
Serial.print(' ');
Serial.println(buf[ 0 ],HEX);
rcode = Usb.setReport( KBD_ADDR, 0, 1, KBD_IF, 0x02, 0, &leds );
if( rcode ) {
Serial.print("Set report error: ");
Serial.println( rcode, HEX );
}//if( rcode ...
}//if( buf_compare( buf[ i ] ) == false ...
}//for( i = 2...
for( i = 2; i < 8; i++ ) { //copy new buffer to old
old_buf[ i ] = buf[ i ];
}
}
/* compare byte against bytes in old buffer */
bool buf_compare( byte data )
{
char i;
for( i = 2; i < 8; i++ ) {
if( old_buf[ i ] == data ) {
return( true );
}
}
return( false );
}
/* HID to ASCII converter. Takes HID keyboard scancode, returns ASCII code */
byte HIDtoA( byte HIDbyte, byte mod )
{
/* upper row of the keyboard, numbers and special symbols */
if( HIDbyte >= 0x1e && HIDbyte <= 0x27 ) {
if(( mod & SHIFT ) || numLock ) { //shift key pressed
switch( HIDbyte ) {
case BANG: return( 0x21 );
case AT: return( 0x40 );
case POUND: return( 0x23 );
case DOLLAR: return( 0x24 );
case PERCENT: return( 0x25 );
case CAP: return( 0x5e );
case AND: return( 0x26 );
case STAR: return( 0x2a );
case OPENBKT: return( 0x28 );
case CLOSEBKT: return( 0x29 );
}//switch( HIDbyte...
}
else { //numbers
if( HIDbyte == 0x27 ) { //zero
return( 0x30 );
}
else {
return( HIDbyte + 0x13 );
}
}//numbers
}//if( HIDbyte >= 0x1e && HIDbyte <= 0x27
/**/
/* number pad. Arrows are not supported */
if(( HIDbyte >= 0x59 && HIDbyte <= 0x61 ) &&
( numLock == true )) { // numbers 1-9
return( HIDbyte - 0x28 );
}
if(( HIDbyte == 0x62 ) && ( numLock == true )) { //zero
return( 0x30 );
}
/* Letters a-z */
if( HIDbyte >= 0x04 && HIDbyte <= 0x1d ) {
if((( capsLock == true ) && ( mod & SHIFT ) == 0 )
|| (( capsLock == false ) && ( mod & SHIFT ))) {
//upper case
return( HIDbyte + 0x3d );
}
else { //lower case
return( HIDbyte + 0x5d );
}
}//if( HIDbyte >= 0x04 && HIDbyte <= 0x1d...
/* Other special symbols */
if( HIDbyte >= 0x2c && HIDbyte <= 0x38 ) {
switch( HIDbyte ) {
case SPACE: return( 0x20 );
case HYPHEN:
if(( mod & SHIFT ) == false ) {
return( 0x2d );
}
else {
return( 0x5f );
}
case EQUAL:
if(( mod & SHIFT ) == false ) {
return( 0x3d );
}
else {
return( 0x2b );
}
case SQBKTOPEN:
if(( mod & SHIFT ) == false ) {
return( 0x5b );
}
else {
return( 0x7b );
}
case SQBKTCLOSE:
if(( mod & SHIFT ) == false ) {
return( 0x5d );
}
else {
return( 0x7d );
}
case BACKSLASH:
if(( mod & SHIFT ) == false ) {
return( 0x5c );
}
else {
return( 0x7c );
}
case SEMICOLON:
if(( mod & SHIFT ) == false ) {
return( 0x3b );
}
else {
return( 0x3a );
}
case INVCOMMA:
if(( mod & SHIFT ) == false ) {
return( 0x27 );
}
else {
return( 0x22 );
}
case TILDE:
if(( mod & SHIFT ) == false ) {
return( 0x60 );
}
else {
return( 0x7e );
}
case COMMA:
if(( mod & SHIFT ) == false ) {
return( 0x2c );
}
else {
return( 0x3c );
}
case PERIOD:
if(( mod & SHIFT ) == false ) {
return( 0x2e );
}
else {
return( 0x3e );
}
case FRONTSLASH:
if(( mod & SHIFT ) == false ) {
return( 0x2f );
}
else {
return( 0x3f );
}
default:
break;
}//switch( HIDbyte..
}//if( HIDbye >= 0x2d && HIDbyte <= 0x38..
return( 0 );
}
实验依然使用上一次的USB小键盘。上面的按键分布如下:
关于键值的介绍可以在【参考1】找到
NumLock OFF的情况下,各输出键值:
NumLock ON的情况下,各输出键值:

*输出三次62外加一个53
运行结果
本文完整代码下载 LCDkbd
参考:
1. https://github.com/felis/USB_Host_Shield/tree/master/examples/descriptor_parser USB_Host_Shield/examples/LCDkbd/ 本文原始代码
2. http://www.quadibloc.com/comp/scan.htm
前面一篇介绍了如何获得USB Descriptor,更麻烦的是这个数据的解读。在【参考1】给出了一个直接解析 Descriptor的例子。美中不足的是,这个例子只能在老版本的Arduino上工作(我估计是 0.22),在新版本 1.6.x 的IDE上会出现很多报错。
经过努力修改,终于可以编译通过(需要选择 Arduno Uno),程序如下:
/* MAX3421E USB Host controller configuration descriptor parser */
//#include "Spi.h"
#include "Max3421e.h"
#include "Usb.h"
#include "descriptor_parser.h"
#define LOBYTE(x) ((char*)(&(x)))[0]
#define HIBYTE(x) ((char*)(&(x)))[1]
#define BUFSIZE 256 //buffer size
#define DEVADDR 1
#define getReportDescr( addr, ep, nbytes, parse_func, nak_limit ) ctrlXfer( addr, ep, bmREQ_HIDREPORT, USB_REQUEST_GET_DESCRIPTOR, 0x00, HID_DESCRIPTOR_REPORT, 0x0000, nbytes, parse_func, nak_limit )
#define getReport( addr, ep, nbytes, interface, report_type, report_id, parse_func, nak_limit ) ctrlXfer( addr, ep, bmREQ_HIDIN, HID_REQUEST_GET_REPORT, report_id, report_type, interface, nbytes, parse_func, nak_limit )
/* Foeward declarations */
void setup();
void loop();
byte ctrlXfer( byte addr, byte ep, byte bmReqType, byte bRequest, byte wValLo, byte wValHi, unsigned int wInd, uint16_t nbytes, PARSE parse_func, uint16_t nak_limit );
void HIDreport_parse( uint8_t* buf, uint8_t* head, uint8_t* tail);
typedef struct {
uint8_t bDescriptorType;
uint16_t wDescriptorLength;
} HID_CLASS_DESCRIPTOR;
//typedef void (*PARSE)( int8_t*, int8_t*, int8_t );
MAX3421E Max;
USB Usb;
void setup()
{
Serial.begin( 115200 );
printProgStr(PSTR("\r\nStart"));
Max.powerOn();
delay( 200 );
}
void loop()
{
uint8_t rcode;
uint8_t tmpbyte = 0;
//PARSE pf = &HIDreport_parse;
/**/
Max.Task();
Usb.Task();
if( Usb.getUsbTaskState() >= USB_STATE_CONFIGURING ) { //state configuring or higher
/* printing device descriptor */
printProgStr(PSTR("\r\nDevice addressed... "));
printProgStr(PSTR("Requesting device descriptor."));
tmpbyte = getdevdescr( DEVADDR ); //number of configurations, 0 if error
if( tmpbyte == 0 ) {
printProgStr(PSTR("\r\nDevice descriptor cannot be retrieved. Program Halted\r\n"));
while( 1 ); //stop
}//if( tmpbyte
/* print configuration descriptors for all configurations */
for( uint8_t i = 0; i < tmpbyte; i++ ) {
getconfdescr( DEVADDR, i );
}
/* Stop */
while( 1 ); //stop
}
}
/* Prints device descriptor. Returns number of configurations or zero if request error occured */
byte getdevdescr( byte addr )
{
USB_DEVICE_DESCRIPTOR buf;
byte rcode;
//Max.toggle( BPNT_0 );
rcode = Usb.getDevDescr( addr, 0, 0x12, ( char *)&buf );
if( rcode ) {
printProgStr( rcode_error_msg );
print_hex( rcode, 8 );
return( 0 );
}
printProgStr(PSTR("\r\nDevice descriptor: \r\n"));
//Descriptor length
printProgStr( descr_len );
print_hex( buf.bLength, 8 );
//Descriptor type
// printProgStr( descr_type );
// print_hex( buf.bDescriptorType, 8 );
// printProgStr( descrtype_parse( buf.bDescriptorType ));
//USB Version
printProgStr(PSTR("\r\nUSB version:\t\t"));
Serial.print(( HIBYTE( buf.bcdUSB )), HEX );
Serial.print(".");
Serial.print(( LOBYTE( buf.bcdUSB )), HEX );
//Device class
printProgStr( class_str );
print_hex( buf.bDeviceClass, 8 );
printProgStr( classname_parse( buf.bDeviceClass ));
//Device Subclass
printProgStr( subclass_str );
print_hex( buf.bDeviceSubClass, 8 );
//Device Protocol
printProgStr( protocol_str );
print_hex( buf.bDeviceProtocol, 8 );
//Max.packet size
printProgStr( maxpktsize_str );
print_hex( buf.bMaxPacketSize0, 8 );
//VID
printProgStr(PSTR("\r\nVendor ID:\t\t"));
print_hex( buf.idVendor, 16 );
//PID
printProgStr(PSTR("\r\nProduct ID:\t\t"));
print_hex( buf.idProduct, 16 );
//Revision
printProgStr(PSTR("\r\nRevision ID:\t\t"));
print_hex( buf.bcdDevice, 16 );
//Mfg.string
printProgStr (PSTR("\r\nMfg.string index:\t"));
print_hex( buf.iManufacturer, 8 );
getstrdescr( addr, buf.iManufacturer );
//Prod.string
printProgStr(PSTR("\r\nProd.string index:\t"));
print_hex( buf.iProduct, 8 );
//printProgStr( str_cont );
getstrdescr( addr, buf.iProduct );
//Serial number string
printProgStr(PSTR("\r\nSerial number index:\t"));
print_hex( buf.iSerialNumber, 8 );
//printProgStr( str_cont );
getstrdescr( addr, buf.iSerialNumber );
//Number of configurations
printProgStr(PSTR("\r\nNumber of conf.:\t"));
print_hex( buf.bNumConfigurations, 8 );
return( buf.bNumConfigurations );
}
/* Get string descriptor. Takes device address and string index */
byte getstrdescr( byte addr, byte idx )
{
char buf[ BUFSIZE ];
byte rcode;
byte length;
byte i;
unsigned int langid;
if( idx == 0 ) { //don't try to get index zero
return( 0 );
}
rcode = Usb.getStrDescr( addr, 0, 1, 0, 0, buf ); //get language table length
if( rcode ) {
printProgStr(PSTR("\r\nError retrieving LangID table length"));
return( rcode );
}
length = buf[ 0 ]; //length is the first byte
rcode = Usb.getStrDescr( addr, 0, length, 0, 0, buf ); //get language table
if( rcode ) {
printProgStr(PSTR("\r\nError retrieving LangID table"));
return( rcode );
}
HIBYTE( langid ) = buf[ 3 ]; //get first langid
LOBYTE( langid ) = buf[ 2 ]; //bytes are swapped to account for endiannes
//printProgStr(PSTR("\r\nLanguage ID: "));
//print_hex( langid, 16 );
rcode = Usb.getStrDescr( addr, 0, 1, idx, langid, buf );
if( rcode ) {
printProgStr(PSTR("\r\nError retrieving string length"));
return( rcode );
}
length = ( buf[ 0 ] < 254 ? buf[ 0 ] : 254 );
printProgStr(PSTR(" Length: "));
Serial.print( length, DEC );
rcode = Usb.getStrDescr( addr, 0, length, idx, langid, buf );
if( rcode ) {
printProgStr(PSTR("\r\nError retrieveing string"));
return( rcode );
}
printProgStr(PSTR(" Contents: "));
for( i = 2; i < length; i+=2 ) {
Serial.print( buf[ i ] );
}
return( idx );
}
/* Returns string to class name */
const char* classname_parse( byte class_number )
{
switch( class_number ) {
case 0x00:
return PSTR(" Use class information in the Interface Descriptor");
case 0x01:
return PSTR(" Audio");
case 0x02:
return PSTR(" Communications and CDC Control");
case 0x03:
return PSTR(" HID (Human Interface Device)");
case 0x05:
return PSTR(" Physical");
case 0x06:
return PSTR(" Image");
case 0x07:
return PSTR(" Printer");
case 0x08:
return PSTR(" Mass Storage");
case 0x09:
return PSTR(" Hub");
case 0x0a:
return PSTR(" CDC-Data");
case 0x0b:
return PSTR(" Smart Card");
case 0x0d:
return PSTR(" Content Security");
case 0x0e:
return PSTR(" Video");
case 0x0f:
return PSTR(" Personal Healthcare");
case 0xdc:
return PSTR("Diagnostic Device");
case 0xe0:
return PSTR(" Wireless Controller");
case 0xef:
return PSTR(" Miscellaneous");
case 0xfe:
return PSTR(" Application Specific");
case 0xff:
return PSTR(" Vendor Specific");
default:
return unk_msg;
}//switch( class_number
}
/* Getting configuration descriptor */
byte getconfdescr( byte addr, byte conf )
{
char buf[ BUFSIZE ];
char* buf_ptr = buf;
byte rcode;
byte descr_length;
byte descr_type;
unsigned int total_length;
printProgStr(PSTR("\r\n\nConfiguration number "));
Serial.print( conf, HEX );
rcode = Usb.getConfDescr( addr, 0, 4, conf, buf ); //get total length
if( rcode ) {
printProgStr(PSTR("Error retrieving configuration length. Error code "));
Serial.println( rcode, HEX );
return( 0 );
}//if( rcode
LOBYTE( total_length ) = buf[ 2 ];
HIBYTE( total_length ) = buf[ 3 ];
printProgStr(PSTR("\r\nTotal configuration length: "));
Serial.print( total_length, DEC );
printProgStr(PSTR(" bytes"));
if( total_length > BUFSIZE ) { //check if total length is larger than buffer
printProgStr(PSTR("Total length truncated to "));
Serial.print( BUFSIZE, DEC);
printProgStr(PSTR("bytes"));
total_length = BUFSIZE;
}
rcode = Usb.getConfDescr( addr, 0, total_length, conf, buf ); //get the whole descriptor
while( buf_ptr < buf + total_length ) { //parsing descriptors
descr_length = *( buf_ptr );
descr_type = *( buf_ptr + 1 );
switch( descr_type ) {
case( USB_DESCRIPTOR_CONFIGURATION ):
printconfdescr( buf_ptr );
break;
case( USB_DESCRIPTOR_INTERFACE ):
printintfdescr( buf_ptr );
break;
case( USB_DESCRIPTOR_ENDPOINT ):
printepdescr( buf_ptr );
break;
case( HID_DESCRIPTOR_HID ):
printhid_descr( buf_ptr );
break;
default:
printunkdescr( buf_ptr );
break;
}//switch( descr_type
Serial.println("");
buf_ptr = ( buf_ptr + descr_length ); //advance buffer pointer
}//while( buf_ptr <=...
return( 0 );
}
/* function to print configuration descriptor */
void printconfdescr( char* descr_ptr )
{
USB_CONFIGURATION_DESCRIPTOR* conf_ptr = ( USB_CONFIGURATION_DESCRIPTOR* )descr_ptr;
uint8_t tmpbyte;
printProgStr(PSTR("\r\n\nConfiguration descriptor:"));
printProgStr(PSTR("\r\nTotal length:\t\t"));
print_hex( conf_ptr->wTotalLength, 16 );
printProgStr(PSTR("\r\nNumber of interfaces:\t"));
print_hex( conf_ptr->bNumInterfaces, 8 );
printProgStr(PSTR("\r\nConfiguration value:\t"));
print_hex( conf_ptr->bConfigurationValue, 8 );
printProgStr(PSTR("\r\nConfiguration string:\t"));
tmpbyte = conf_ptr->iConfiguration;
print_hex( tmpbyte, 8 );
getstrdescr( DEVADDR, tmpbyte );
printProgStr(PSTR("\r\nAttributes:\t\t"));
tmpbyte = conf_ptr->bmAttributes;
print_hex( tmpbyte, 8 );
if( tmpbyte & 0x40 ) { //D6
printProgStr(PSTR(" Self-powered"));
}
if( tmpbyte & 0x20 ) { //D5
printProgStr(PSTR(" Remote Wakeup"));
}
printProgStr(PSTR("\r\nMax.power:\t\t"));
tmpbyte = conf_ptr->bMaxPower;
print_hex( tmpbyte, 8 );
printProgStr(PSTR(" "));
Serial.print(( tmpbyte * 2 ), DEC);
printProgStr(PSTR("ma"));
return;
}
/* function to print interface descriptor */
void printintfdescr( char* descr_ptr )
{
USB_INTERFACE_DESCRIPTOR* intf_ptr = ( USB_INTERFACE_DESCRIPTOR* )descr_ptr;
uint8_t tmpbyte;
printProgStr(PSTR("\r\nInterface descriptor:"));
printProgStr(PSTR("\r\nInterface number:\t"));
print_hex( intf_ptr->bInterfaceNumber, 8 );
printProgStr(PSTR("\r\nAlternate setting:\t"));
print_hex( intf_ptr->bAlternateSetting, 8 );
printProgStr(PSTR("\r\nEndpoints:\t\t"));
print_hex( intf_ptr->bNumEndpoints, 8 );
printProgStr( class_str );
tmpbyte = intf_ptr->bInterfaceClass;
print_hex( tmpbyte, 8 );
printProgStr(classname_parse( tmpbyte ));
printProgStr( subclass_str );
print_hex( intf_ptr->bInterfaceSubClass, 8 );
printProgStr( protocol_str );
print_hex( intf_ptr->bInterfaceProtocol, 8 );
printProgStr(PSTR("\r\nInterface string:\t"));
tmpbyte = intf_ptr->iInterface;
print_hex( tmpbyte, 8 );
getstrdescr( DEVADDR, tmpbyte );
return;
}
/* function to print endpoint descriptor */
void printepdescr( char* descr_ptr )
{
USB_ENDPOINT_DESCRIPTOR* ep_ptr = ( USB_ENDPOINT_DESCRIPTOR* )descr_ptr;
uint8_t tmpbyte;
printProgStr(PSTR("\r\nEndpoint descriptor:"));
printProgStr(PSTR("\r\nEndpoint address:\t"));
tmpbyte = ep_ptr->bEndpointAddress;
print_hex( tmpbyte & 0x0f, 8 );
printProgStr(PSTR(" Direction: "));
( tmpbyte & 0x80 ) ? printProgStr(PSTR("IN")) : printProgStr(PSTR("OUT"));
printProgStr(PSTR("\r\nAttributes:\t\t"));
tmpbyte = ep_ptr->bmAttributes;
print_hex( tmpbyte, 8 );
printProgStr(PSTR(" Transfer type: "));
printProgStr((char*)pgm_read_word(&transfer_types[(tmpbyte & 0x03)]));
if(( tmpbyte & 0x03 ) == 1 ) { //Isochronous Transfer
printProgStr(PSTR(", Sync Type: "));
printProgStr((char*)pgm_read_word(&sync_types[(tmpbyte & 0x0c)]));
printProgStr(PSTR(", Usage Type: "));
printProgStr((char*)pgm_read_word(&usage_types[(tmpbyte & 0x30)]));
}//if( tmpbyte & 0x01
printProgStr( maxpktsize_str );
print_hex( ep_ptr->wMaxPacketSize, 16 );
printProgStr(PSTR("\r\nPolling interval:\t"));
tmpbyte = ep_ptr->bInterval;
print_hex( tmpbyte, 8 );
printProgStr(PSTR(" "));
Serial.print( tmpbyte, DEC );
printProgStr(PSTR(" ms"));
return;
}
/* function to print HID descriptor */
void printhid_descr( char* descr_ptr )
{
PARSE pf = &HIDreport_parse;
USB_HID_DESCRIPTOR* hid_ptr = ( USB_HID_DESCRIPTOR* )descr_ptr;
uint8_t tmpbyte;
/**/
printProgStr(PSTR("\r\nHID descriptor:"));
printProgStr(PSTR("\r\nDescriptor length:\t"));
tmpbyte = hid_ptr->bLength;
print_hex( tmpbyte, 8 );
printProgStr(PSTR(" "));
Serial.print( tmpbyte, DEC );
printProgStr(PSTR(" bytes"));
printProgStr(PSTR("\r\nHID version:\t\t"));
Serial.print(( HIBYTE( hid_ptr->bcdHID )), HEX );
Serial.print(".");
Serial.print(( LOBYTE( hid_ptr->bcdHID )), HEX );
tmpbyte = hid_ptr->bCountryCode;
printProgStr(PSTR("\r\nCountry Code:\t\t"));
Serial.print( tmpbyte, DEC );
printProgStr(PSTR(" "));
( tmpbyte > 35 ) ? printProgStr(PSTR("Reserved")) : printProgStr((char*)pgm_read_word(&HID_Country_Codes[ tmpbyte ]));
tmpbyte = hid_ptr->bNumDescriptors;
printProgStr(PSTR("\r\nClass Descriptors:\t"));
Serial.print( tmpbyte, DEC );
//Printing class descriptors
descr_ptr += 6; //advance buffer pointer
for( uint8_t i = 0; i < tmpbyte; i++ ) {
uint8_t tmpdata;
HID_CLASS_DESCRIPTOR* hidclass_ptr = ( HID_CLASS_DESCRIPTOR* )descr_ptr;
tmpdata = hidclass_ptr->bDescriptorType;
printProgStr(PSTR("\r\nClass Descriptor Type:\t"));
Serial.print( tmpdata, HEX );
if(( tmpdata < 0x21 ) || ( tmpdata > 0x2f )) {
printProgStr(PSTR(" Invalid"));
}
switch( tmpdata ) {
case 0x21:
printProgStr(PSTR(" HID"));
break;
case 0x22:
printProgStr(PSTR(" Report"));
break;
case 0x23:
printProgStr(PSTR(" Physical"));
break;
default:
printProgStr(PSTR(" Reserved"));
break;
}//switch( tmpdata
printProgStr(PSTR("\r\nClass Descriptor Length:"));
Serial.print( hidclass_ptr->wDescriptorLength );
printProgStr(PSTR(" bytes"));
printProgStr(PSTR("\r\n\nHID report descriptor:\r\n"));
getReportDescr( DEVADDR, 0 , hidclass_ptr->wDescriptorLength, pf, USB_NAK_LIMIT );
descr_ptr += 3; //advance to the next record
}//for( uint8_t i=...
return;
}
/*function to print unknown descriptor */
void printunkdescr( char* descr_ptr )
{
byte length = *descr_ptr;
byte i;
printProgStr(PSTR("\r\nUnknown descriptor:"));
printProgStr(PSTR("Length:\t\t"));
print_hex( *descr_ptr, 8 );
printProgStr(PSTR("\r\nType:\t\t"));
print_hex( *(descr_ptr + 1 ), 8 );
printProgStr(PSTR("\r\nContents:\t"));
descr_ptr += 2;
for( i = 0; i < length; i++ ) {
print_hex( *descr_ptr, 8 );
descr_ptr++;
}
}
/* Control-IN transfer with callback. Sets address, endpoint, fills control packet with necessary data, dispatches control packet, and initiates bulk IN transfer */
/* Control, data, and setup stages combined from standard USB library to be able to read large data blocks. Restricted to control-IN transfers with data stage */
/* data read and MAX3421E RECV FIFO buffer release shall be performed by parse_func callback */
/* return codes: */
/* 00 = success */
/* 01-0f = non-zero HRSLT */
byte ctrlXfer( byte addr, byte ep, byte bmReqType, byte bRequest, byte wValLo, byte wValHi, unsigned int wInd, uint16_t nbytes, PARSE parse_func, uint16_t nak_limit = USB_NAK_LIMIT )
{
byte rcode;
SETUP_PKT sp;
EP_RECORD* ep_rec = Usb.getDevTableEntry( addr, ep );
byte pktsize;
byte maxpktsize = ep_rec->MaxPktSize;
unsigned int xfrlen = 0;
/**/
Max.regWr( rPERADDR, addr ); //set peripheral address
/* fill in setup packet */
sp.ReqType_u.bmRequestType = bmReqType;
sp.bRequest = bRequest;
sp.wVal_u.wValueLo = wValLo;
sp.wVal_u.wValueHi = wValHi;
sp.wIndex = wInd;
sp.wLength = nbytes;
Max.bytesWr( rSUDFIFO, 8, ( char *)&sp ); //transfer to setup packet FIFO
rcode = Usb.dispatchPkt( tokSETUP, ep, nak_limit ); //dispatch packet
//Serial.println("Setup packet"); //DEBUG
if( rcode ) { //return HRSLT if not zero
printProgStr(PSTR("\r\nSetup packet error: "));
Serial.print( rcode, HEX );
return( rcode );
}
/* Data stage */
//ep_rec->rcvToggle = bmRCVTOG1;
Max.regWr( rHCTL, bmRCVTOG1 ); //set toggle
while( 1 ) { //exited by break
/* request data */
rcode = Usb.dispatchPkt( tokIN, ep, nak_limit );
if( rcode ) {
printProgStr(PSTR("\r\nData Stage Error: "));
Serial.print( rcode, HEX );
return( rcode );
}
/* check for RCVDAVIRQ and generate error if not present */
/* the only case when absense of RCVDAVIRQ makes sense is when toggle error occured. Need to add handling for that */
if(( Max.regRd( rHIRQ ) & bmRCVDAVIRQ ) == 0 ) {
printProgStr(PSTR("\r\nData Toggle error."));
return ( 0xf0 );
}
pktsize = Max.regRd( rRCVBC ); //get received bytes count
parse_func( pktsize ); //call parse function. Parse is expected to read the FIFO completely
Max.regWr( rHIRQ, bmRCVDAVIRQ ); // Clear the IRQ & free the buffer
xfrlen += pktsize; // add this packet's byte count to total transfer length
/* The transfer is complete under two conditions: */
/* 1. The device sent a short packet (L.T. maxPacketSize) */
/* 2. 'nbytes' have been transferred. */
if (( pktsize < maxpktsize ) || (xfrlen >= nbytes )) { // have we transferred 'nbytes' bytes?
break;
}
}//while( 1 )
rcode = Usb.dispatchPkt( tokOUTHS, ep, nak_limit );
if( rcode ) { //return error
printProgStr(PSTR("Status packet error: "));
Serial.print( rcode, HEX );
}
return( rcode );
}
/* Parses bitfields in main items */
void print_mainbitfield( uint8_t byte_toparse )
{
( byte_toparse & 0x01 ) ? printProgStr(PSTR("Constant,")) : printProgStr(PSTR("Data,")); //bit 0
( byte_toparse & 0x02 ) ? printProgStr(PSTR("Variable,")) : printProgStr(PSTR("Array,")); //bit 1
( byte_toparse & 0x04 ) ? printProgStr(PSTR("Relative,")) : printProgStr(PSTR("Absolute,")); //...
( byte_toparse & 0x08 ) ? printProgStr(PSTR("Wrap,")) : printProgStr(PSTR("No Wrap,"));
( byte_toparse & 0x10 ) ? printProgStr(PSTR("Non Linear,")) : printProgStr(PSTR("Linear,"));
( byte_toparse & 0x20 ) ? printProgStr(PSTR("No preferred,")) : printProgStr(PSTR("Preferred State,"));
( byte_toparse & 0x40 ) ? printProgStr(PSTR("Null State,")) : printProgStr(PSTR("No Null Position,")); //bit 6
( byte_toparse & 0x40 ) ? printProgStr(PSTR("Volatile( ignore for Input),")) : printProgStr(PSTR("Non-volatile(Ignore for Input),")); //bit 7
}
/* HID Report Desriptor Parser Callback */
/* called repeatedly from Control transfer function */
void HIDreport_parse( uint8_t pkt_size )
{
#define B_SIZE 0x03 //bSize bitmask
#define B_TYPE 0x0c //bType bitmask
#define B_TAG 0xf0 //bTag bitmask
/* parser states */
enum STATE { ITEM_START, DATA_PARSE };
static STATE state = ITEM_START;
static uint8_t databytes_left = 0;
static uint8_t prefix; //item prefix - type and tag
uint8_t byte_toparse;
uint8_t bType;
uint8_t tmpbyte;
/**/
while( 1 ) {
if( pkt_size ) {
byte_toparse = Max.regRd( rRCVFIFO ); //read a byte from FIFO
pkt_size--;
}
else {
return; //all bytes read
}
switch( state ) {
case ITEM_START: //start of the record
prefix = byte_toparse >>2; //store prefix for databyte parsing
tmpbyte = byte_toparse & B_SIZE;
/* get item length */
( tmpbyte == 0x03 ) ? databytes_left = 4 : databytes_left = tmpbyte;
if( databytes_left ) {
state = DATA_PARSE; //read bytes after prefix
}
printProgStr(PSTR("\r\nLength: "));
Serial.print( databytes_left, DEC );
/* get item type */
bType = ( byte_toparse & B_TYPE ) >>2;
printProgStr(PSTR(" Type: "));
printProgStr((char*)pgm_read_word(&btypes[ bType ]));
/* get item tag */
printProgStr(PSTR("\t\tTag: "));
tmpbyte = ( byte_toparse & B_TAG ) >>4 ;
switch( bType ) {
case 0: //Main
if( tmpbyte < 0x08 ) {
printProgStr(PSTR("Invalid Tag"));
}
else if( tmpbyte > 0x0c ) {
printProgStr( reserved_msg );
}
else {
printProgStr((char*)pgm_read_word(&maintags[ tmpbyte - 8 /* & 0x03 */]));
//Serial.print("Byte: ");
//Serial.println( tmpbyte, HEX );
}
break;//case 0 Main
case 1: //Global
( tmpbyte > 0x0b ) ? printProgStr( reserved_msg ) : printProgStr((char*)pgm_read_word(&globaltags[ tmpbyte ]));
break;//case 1 Global
case 2: //Local
( tmpbyte > 0x0a ) ? printProgStr( reserved_msg ) : printProgStr((char*)pgm_read_word(&localtags[ tmpbyte ]));
break;//case 2 Local
default:
break;
}//switch( bType...
break;//case ITEM_START
case DATA_PARSE:
switch( prefix ) {
case 0x20: //Main Input
case 0x24: //Main Output
case 0x2c: //Main Feature
/* todo: add parsing 8th bit */
print_mainbitfield( byte_toparse );
break;
case 0x28: //Main Collection
if(( byte_toparse > 0x06 ) && ( byte_toparse < 0x80 )) {
printProgStr( reserved_msg );
}
else if(( byte_toparse > 0x7f ) && ( byte_toparse <= 0xff )) {
printProgStr(PSTR("Vendor-defined"));
}
else {
printProgStr((char*)pgm_read_word(&collections[ byte_toparse ]));
}
break;//case 0x28 Main Collection
//case 0x30: //Main End Collection
case 0x01: //Global Usage Page
switch( byte_toparse ) { //see HID Usage Tables doc v.1.12 page 14
case 0x00:
case 0x01:
case 0x02:
case 0x03:
case 0x04:
case 0x05:
case 0x06:
case 0x07:
case 0x08:
case 0x09:
case 0x0a:
case 0x0b:
case 0x0c:
case 0x0d:
case 0x0e:
case 0x0f:
case 0x10:
printProgStr((char*)pgm_read_word(&usage_pages[ byte_toparse ]));
break;
case 0x14:
printProgStr(PSTR("Alphanumeric Display"));
break;
case 0x40:
printProgStr(PSTR("Medical Instruments"));
break;
case 0x80:
case 0x81:
case 0x82:
case 0x83:
printProgStr(PSTR("Monitor page"));
break;
case 0x84:
case 0x85:
case 0x86:
case 0x87:
printProgStr(PSTR("Power page"));
break;
case 0x8c:
printProgStr(PSTR("Bar Code Scanner page"));
break;
case 0x8d:
printProgStr(PSTR("Scale page"));
break;
case 0x8e:
printProgStr(PSTR("Magnetic Stripe Reading (MSR) Devices"));
break;
case 0x8f:
printProgStr(PSTR("Reserved Point of Sale pages"));
break;
case 0x90:
printProgStr(PSTR("Camera Control Page"));
break;
case 0x91:
printProgStr(PSTR("Arcade Page"));
break;
default:
// printProgStr(PSTR("Data: "));
// print_hex( byte_toparse, 8 );
//databytes_left--;
break;
}//switch case 0x01: //Global Usage Page
}//switch( prefix ...
printProgStr(PSTR(" Data: "));
print_hex( byte_toparse, 8 );
databytes_left--;
if( !databytes_left ) {
state = ITEM_START;
}
break;
}//switch( state...
}//while( 1 ...
}
/* prints hex numbers with leading zeroes */
// copyright, Peter H Anderson, Baltimore, MD, Nov, '07
// source: http://www.phanderson.com/arduino/arduino_display.html
void print_hex(int v, int num_places)
{
int mask=0, n, num_nibbles, digit;
for (n=1; n<=num_places; n++) {
mask = (mask << 1) | 0x0001;
}
v = v & mask; // truncate v to specified number of places
num_nibbles = num_places / 4;
if ((num_places % 4) != 0) {
++num_nibbles;
}
do {
digit = ((v >> (num_nibbles-1) * 4)) & 0x0f;
Serial.print(digit, HEX);
}
while(--num_nibbles);
}
/* given a PROGMEM string, use Serial.print() to send it out */
/* Some non-intuitive casting necessary: */
/* printProgStr(PSTR("Func.Mode:\t0x")); */
/* printProgStr((char*)pgm_read_word(&mtpopNames[(op & 0xFF)])); */
void printProgStr(const char* str)
{
if(!str) {
return;
}
char c;
while((c = pgm_read_byte(str++))) {
Serial.write(c);
}
return;
}
工作的照片:
我用USB Shield挂接了一个USB小键盘,获得的结果如下:
Start
Device addressed… Requesting device descriptor.
Device descriptor:
Descriptor Length: 12
USB version: 1.10
Class: 00 Use class information in the Interface Descriptor
Subclass: 00
Protocol: 00
Max.packet size: 08
Vendor ID: 13BA
Product ID: 0001
Revision ID: 0100
Mfg.string index: 00
Prod.string index: 00
Serial number index: 00
Number of conf.: 01
Configuration number 0
Total configuration length: 34 bytes
Configuration descriptor:
Total length: 0022
Number of interfaces: 01
Configuration value: 01
Configuration string: 00
Attributes: A0 Remote Wakeup
Max.power: 32 100ma
Interface descriptor:
Interface number: 00
Alternate setting: 00
Endpoints: 01
Class: 03 HID (Human Interface Device)
Subclass: 01
Protocol: 01
Interface string: 00
HID descriptor:
Descriptor length: 09 9 bytes
HID version: 1.10
Country Code: 0 Not Supported
Class Descriptors: 1
Class Descriptor Type: 22 Report
Class Descriptor Length:54 bytes
HID report descriptor:
Length: 1 Type: Global Tag: Usage Page Generic Desktop Controls Data: 01
Length: 1 Type: Local Tag: Usage Data: 06
Length: 1 Type: Main Tag: Collection Application (mouse, keyboard) Data: 01
Length: 1 Type: Global Tag: Usage Page LEDs Data: 08
Length: 1 Type: Local Tag: Usage Minimum Data: 01
Length: 1 Type: Local Tag: Usage Maximum Data: 03
Length: 1 Type: Global Tag: Logical Minimum Data: 00
Length: 1 Type: Global Tag: Logical Maximum Data: 01
Length: 1 Type: Global Tag: Report Size Data: 01
Length: 1 Type: Global Tag: Report Count Data: 03
Length: 1 Type: Main Tag: Output Data,Variable,Absolute,No Wrap,Linear,Preferred State,No Null Position,Non-volatile(Ignore for Input), Data: 02
Length: 1 Type: Global Tag: Report Count Data: 05
Length: 1 Type: Main Tag: Output Constant,Array,Absolute,No Wrap,Linear,Preferred State,No Null Position,Non-volatile(Ignore for Input), Data: 01
Length: 1 Type: Global Tag: Usage Page Keyboard/Keypad Data: 07
Length: 1 Type: Local Tag: Usage Minimum Data: E0
Length: 1 Type: Local Tag: Usage Maximum Data: E7
Length: 1 Type: Global Tag: Report Count Data: 08
Length: 1 Type: Main Tag: Input Data,Variable,Absolute,No Wrap,Linear,Preferred State,No Null Position,Non-volatile(Ignore for Input), Data: 02
Length: 1 Type: Global Tag: Report Size Data: 08
Length: 1 Type: Global Tag: Report Count Data: 01
Length: 1 Type: Main Tag: Input Constant,Array,Absolute,No Wrap,Linear,Preferred State,No Null Position,Non-volatile(Ignore for Input), Data: 01
Length: 1 Type: Local Tag: Usage Minimum Data: 00
Length: 1 Type: Local Tag: Usage Maximum Data: 91
Length: 2 Type: Global Tag: Logical Maximum Data: FF Data: 00
Length: 1 Type: Global Tag: Report Count Data: 06
Length: 1 Type: Main Tag: Input Data,Array,Absolute,No Wrap,Linear,Preferred State,No Null Position,Non-volatile(Ignore for Input), Data: 00
Length: 0 Type: Main Tag: End Collection
Endpoint descriptor:
Endpoint address: 01 Direction: IN
Attributes: 03 Transfer type: Interrupt
Max.packet size: 0008
Polling interval: 0A 10 ms
修改后的可以正常编译的代码下载
参考:
1. https://github.com/felis/USB_Host_Shield/tree/master/examples/descriptor_parser USB_Host_Shield/examples/descriptor_parser/