读取USB温度计-Arduino中文社区 - Powered by Discuz!

Arduino中文社区

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 8041|回复: 6

读取USB温度计

[复制链接]
发表于 2017-3-25 22:13 | 显示全部楼层 |阅读模式
笔者手头有一个 USB温度计,使用的是USB HID协议,无需安装驱动程序即可在不同版本的Windows中直接使用。同时厂家还提供了一个应的程序可以直接在电脑上获得当前环境温度,非常方便。本文介绍如何使用Arduino Uno + USB Host Shield来读取这个USB温度计的返回值。
image001.jpg
                              首先,需要在 Arduino  IDE 中安装 USBHost Shield 2.0的库,可以从 Library Manager 中轻松的找到并且安装之。
image002.png
然后,将 USB Host Shield 插在Uno 板子上,并连接好USB温度计。
image003.jpg
对于USB设备来说,最重要莫过于描述符(Descriptor)。它反应了整个设备对外的接口状态,因此,首先要做的是:弄清楚这个设备的全部接口。
前述安装的 USB Host库中自带了一些实例,先运行例子中的 USB_Desc 文件。它的作用是读取当前USB 设备的设备描述符(DeviceDescriptor)。运行结果如下:
image004.png
Start
01
--
Device descriptor:
Descriptor Length:           12
Descriptor type: 01
USB version:                      0200
Device class:                      00
Device Subclass: 00
Device Protocol:               00
Max.packet size:               08
Vendor  ID:                         0C45
Product ID:                         7401
Revision ID:                        0001
Mfg.string index:              01
Prod.string index:             02
Serial number index:        00
Number of conf.:              01
Configuration descriptor:
Total length:                      003B
Num.intf:                            02
Conf.value:                        01
Conf.string:                        00
Attr.:                                    A0
Max.pwr:                            32
Interface descriptor:
Intf.number:                      00
Alt.:                                      00
Endpoints:                          01
Intf. Class:                          03
Intf. Subclass:                    01
Intf. Protocol:                   01
Intf.string:                          00
Unknown descriptor:
Length:                09
Type:                    21
Contents:            100100012241000705
Endpoint descriptor:
Endpoint address:             81
Attr.:                                    03
Max.pkt size:                     0008
Polling interval:  0A
Interface descriptor:
Intf.number:                      01
Alt.:                                      00
Endpoints:                          01
Intf. Class:                          03
Intf. Subclass:                    01
Intf. Protocol:                   02
Intf.string:                          00
Unknown descriptor:
Length:                09
Type:                    21
Contents:            100100012229000705
Endpoint descriptor:
Endpoint address:             82
Attr.:                                    03
Max.pkt size:                     0008
Polling interval:  0A
Addr:1(0.0.1)
对照 USB 协议,我们来解读每一项的含义【参考1】。
1.     设备描述符 (DeviceDescriptor )
  
USB 设备描述符(Device Descriptor
  
  
偏移量
  
  
  
  
大小
  
  
  
  
描述
  
  
0
  
  
bLength
  
  
1
  
  
0x12
  
  
本表的字节数
  
  
1
  
  
bDecriptorType
  
  
1
  
  
0x01
  
  
0x01,即设备描述符(Device Descriptor)
  
  
2
  
  
bcdUSB
  
  
2
  
  
0x0200
  
  
此描述符兼容USB 2.0设备
  
  
4
  
  
bDeviceClass
  
  
1
  
  
   0x00
  
  
设备类码为0表示一个设置下每个接口独立管理类,各个接口各自独立工作。
  
  
5
  
  
bDeviceSubClass
  
  
1
  
  
0x00
  
  
子类码,同上,接口独立,所以这里为0x00
  
  
6
  
  
bDevicePortocol
  
  
1
  
  
   0x00
  
  
协议码 ,同上,接口独立,所以这里为0x00
  
  
7
  
  
bMaxPacketSize0
  
  
1
  
  
0x08
  
  
端点0的最大包大小为8字节
  
  
8
  
  
idVendor
  
  
2
  
  
0x0C45
  
  
厂商标志(USB 组织分配给厂商的固定编号)
  
  
10
  
  
idProduct
  
  
2
  
  
0x7401
  
  
产品标志(厂商自定义编号)
  
  
12
  
  
bcdDevice
  
  
2
  
  
0x001
  
  
设备版本号
  
  
14
  
  
iManufacturer
  
  
1
  
  
0x01
  
  
描述厂商信息的字符串描述符的索引值。
  
  
15
  
  
iProduct
  
  
1
  
  
0x02
  
  
描述产品信息的字串描述符的索引值。
  
  
16
  
  
iSerialNumber
  
  
1
  
  
0x00
  
  
描述设备序列号信息的字串描述符的索引值。这里为0表示没有这个字符串。
  
  
17
  
  
bNumConfigurations
  
  
1
  
  
0x01
  
  
配置描述符(Configuration Descriptor)的数量
  
2.     接下来是配置描述符(Configuration Descriptor)
  
USB配置描述符(Configuration  Descriptor)
  
  
   偏移量
  
  
      域
  
  
大小
  
  
   值
  
  
   描述
  
  
       0
  
  
bLength
  
  
1
  
  
  XX
  
  
配置描述符的长度(代码中没有输出)
  
  
       1
  
  
bDescriptorType
  
  
1
  
  
   XX
  
  
代码中没有输出,实际应该是0x02即配置描述符
  
  
       2
  
  
wTotalLength
  
  
2
  
  
   0x003B
  
  
配置信息的总长(包括配置,接口,端点和设备类及厂商定义的描述符)
  
  
       4
  
  
bNumInterfaces
  
  
1
  
  
   0x02
  
  
此配置所支持的接口个数,这里表示一共有2个接口(接口0,接口1)
  
  
       5
  
  
bCongfigurationValue
  
  
1
  
  
   0x01
  
  
在SetConfiguration()请求中用作参数来选定此配置。
  
  
       6
  
  
iConfiguration
  
  
1
  
  
   0x00
  
  
描述此配置的字串描述符索引,0x00表示不存在
  
  
       7
  
  
bmAttributes
  
  
1
  
  
   0xA0
  
  
0xA0 == 0b1010 0000
  
配置特性:
  D7: 保留(设为一)
  D5: 远程唤醒
  D4..0:保留(设为一)
  
  
  
  
       8
  
  
MaxPower
  
  
1
  
  
    0x32
  
  
在此配置下的总线电源耗费量。以 2mA 为一个单位。0x32=50D 就是100mA。
  
3.     前面已经声明了,本设备有2个接口(Interface),所以下面有2个接口描述符的表格
3.1  接口描述符0
  
USB接口描述符(Interface Descriptor)
  
  
偏移量
  
  
  
  
大小
  
  
  
  
说明
  
  
        0
  
  
bLength
  
  
1
  
  
      XX
  
  
此表的字节数(代码中没有输出)
  
  
        1
  
  
bDescriptorType
  
  
1
  
  
XX
  
  
接口描述表类(代码中没有输出,此处应为0x04)
  
  
        2
  
  
bInterfaceNumber
  
  
1
  
  
0x00
  
  
接口号,当前配置支持的接口数组索引(从零开始)。
  
  
        3
  
  
bAlternateSetting
  
  
1
  
  
0x00
  
  
可选设置的索引值。
  
  
        4
  
  
bNumEndpoints
  
  
1
  
  
0x01
  
  
此接口用的端点数量,如果是零则说明此接口只用缺省控制管道。
  
  
        5
  
  
bInterfaceClass
  
  
        1
  
  
0x03
  
  
接口所属的类值,0x03表示人机接口类(HID)【参考2】
  
  
        6
  
  
bInterfaceSubClass
  
  
        1
  
  
0X01
  
  
子类码 【参考2】
  
  
        7
  
  
bInterfaceProtocol
  
  
        1
  
  
0X01
  
  
协议码【参考2】
  
  
        8
  
  
iInterface
  
  
        1
  
  
0x00
  
  
描述此接口的字串描述表的索引值。
  
  
USB端点描述符(EndPoint Descriptor)
  
  
偏移量
  
  
  
  
大小
  
  
  
  
说明
  
  
0
  
  
bLength
  
  
1
  
  
XX
  
  
此描述表的字节数长度(代码中没有输出)
  
  
1
  
  
bDescriptorType
  
  
1
  
  
XX
  
  
端点描述表类(代码中没有输出,此处应为0x05)
  
  
2
  
  
bEndpointAddress
  
  
1
  
  
0x81
  
  
此描述表所描述的端点的地址、方向:
  端点号为1 , 是输入端点(设备到主机)
  
  
3
  
  
bmAttributes
  
  
1
  
  
0x03
  
  
此域的值描述的是在bConfigurationValue域所指的配置下端点的特性。
  11=中断传送
  
  
4
  
  
wMaxPacketSize
  
  
2
  
  
0x008
  
  
当前配置下此端点能够接收或发送的最大数据包的大小为8字节
  
  
6
  
  
bInterval
  
  
1
  
  
0x0A
  
  
周期数据传输端点的时间间隙为10ms
  
  
  
随后的数据是一个HID Descriptor
很明显,这个接口是一个键盘设备。这个USB温度计有一个特别的功能:当用户在其他USB键盘上长按Scroll Lock之后,可以连续输出温度的数据。从上述接口来看,实现的方法是将自身模拟为键盘,激活功能后,使用模拟按键的方式直接输入温度。
3.2  接口描述符1
  
4.    10USB接口描述符的结构
  
  
偏移量
  
  
  
  
大小
  
  
  
  
说明
  
  
        0
  
  
bLength
  
  
1
  
  
XX
  
  
此表的字节数
  
  
        1
  
  
bDescriptorType
  
  
1
  
  
XX
  
  
接口描述表类(此处应为0x04)
  
  
        2
  
  
bInterfaceNumber
  
  
1
  
  
0x01
  
  
接口号,当前配置支持的接口数组索引(从零开始)。
  
  
        3
  
  
bAlternateSetting
  
  
1
  
  
0x00
  
  
可选设置的索引值。
  
  
        4
  
  
bNumEndpoints
  
  
1
  
  
0x01
  
  
此接口用的端点数量,如果是零则说明此接口只用缺省控制管道。
  
  
        5
  
  
bInterfaceClass
  
  
        1
  
  
0x03
  
  
接口所属的类值,0x03表示人机接口类(HID)【参考2】
  
  
        6
  
  
bInterfaceSubClass
  
  
        1
  
  
0x01
  
  
子类码
  
  
        7
  
  
bInterfaceProtocol
  
  
        1
  
  
0x02
  
  
协议码:
  
  
        8
  
  
iInterface
  
  
        1
  
  
0x00
  
  
描述此接口的字串描述表的索引值。
  
  
12USB端点描述符的结构
  
  
偏移量
  
  
  
  
大小
  
  
  
  
说明
  
  
0
  
  
bLength
  
  
1
  
  
XX
  
  
此描述表的字节数长度
  
  
1
  
  
bDescriptorType
  
  
1
  
  
XX
  
  
端点描述表类(此处应为0x05)
  
  
2
  
  
bEndpointAddress
  
  
1
  
  
0x82
  
  
此描述表所描述的端点的地址、方向:
  端点号为2
  Bit 7:    方向,如果控制端点则略。
  1:输入端点(设备到主机)
  
  
3
  
  
bmAttributes
  
  
1
  
  
0x03
  
  
此域的值描述的是在bConfigurationValue域所指的配置下端点的特性。
  11=中断传送
  
  
  
  
4
  
  
wMaxPacketSize
  
  
2
  
  
0x008
  
  
当前配置下此端点能够接收或发送的最大数据包的大小为8字节
  
  
6
  
  
bInterval
  
  
1
  
  
0x0A
  
  
周期数据传输端点的时间间隙为10ms
  
  
  
同样的,在USB Host Shield 2.0的库中还有一个HID描述符分析的代码USBHID_desc.c。可以用来进行简单的分析。从前面的结果得知,这个设备有2HIDInterface,第一个是键盘设备,所以只要关注第二个设备即可。对应的USBHID_desc.ino中我们需要修改两处GetReportDescr函数的参数,让它取得第二个Interface的信息。
uint8_t HIDUniversal2::OnInitSuccessful()
{
    uint8_t    rcode;
   HexDumper<USBReadParser, uint16_t, uint16_t>    Hex;
   ReportDescParser                                Rpt;
    if ((rcode =GetReportDescr(1, &Hex)))
        gotoFailGetReportDescr1;
    if ((rcode =GetReportDescr(1, &Rpt)))
               gotoFailGetReportDescr2;
    return 0;
FailGetReportDescr1:
   USBTRACE("GetReportDescr1:");
    goto Fail;
FailGetReportDescr2:
   USBTRACE("GetReportDescr2:");
    goto Fail;
Fail:
   Serial.println(rcode, HEX);
    Release();
    return rcode;
}
运行结果如下。
image005.png
上面获得结果中给出了HID Descriptor
0000: 06 00 FF 09 01 A1 01 09 01 15 00 26 FF 00 75 08
0010: 95 08 81 02 09 01 95 08 91 02 05 0C 09 00 15 80
0020: 25 7F 75 08 95 08 B1 02 C0
对于枯燥的表格,可以使用来自【参考3】的工具进行分析,结果如下:
0x06, 0x00, 0xFF,  //Usage Page (用户自定义格式)
0x09, 0x01,        //Usage (0x01)
0xA1, 0x01,        //Collection (Application)
0x09, 0x01,       //   Usage (0x01)
0x15, 0x00,       //   Logical Minimum (0)
0x26, 0xFF, 0x00, //   Logical Maximum (255)
0x75, 0x08,       //   Report Size (8)
0x95, 0x08,       //   Report Count (8)
0x81, 0x02,       //   Input (Data,Var,Abs,NoWrap,Linear,Preferred State,No Null Position)
0x09, 0x01,       //   Usage (0x01)
0x95, 0x08,        //  Report Count (8)
0x91, 0x02,       //   Output (Data,Var,Abs,NoWrap,Linear,Preferred State,No Null Position,Non-volatile)
0x05, 0x0C,       //   Usage Page (Consumer)
0x09, 0x00,       //   Usage (Unassigned)
0x15, 0x80,       //   Logical Minimum (128)
0x25, 0x7F,       //   Logical Maximum (127)
0x75, 0x08,       //   Report Size (8)
0x95, 0x08,       //   Report Count (8)
0xB1, 0x02,       //   Feature (Data,Var,Abs,NoWrap,Linear,Preferred State,No Null Position,Non-volatile)
0xC0,              // End Collection
就是说数据是通过8个字节的数组来进行传输的。接下来需要仔细研究USB温度计二次开发文档了,在给出的资料中,提到需要使用特定的命令来读取温度:
  bCommandReadTemper:array[0..8] of Byte=( 0, 1, $80, $33, 1, 0, 0, 0, 0 );
结合USB逻辑分析仪抓包的结果,可以看到使用了 SET_REPORT Packagecommand直接发送给了设备。
image006.png
  
就是说,要先把这个bCommandReadTemper 命令发送给USB 温度计,然后温度计才能做出响应,送回当前温度值。因此,还要修改库文件中的 hiduniversal.cpp,手工构造这一个过程,同时注意温度计在第二个 Interface 上:
uint8_t HIDUniversal:oll() {
        uint8_t rcode= 0;
       if(!bPollEnable)
                return0;
       if((long)(millis() - qNextPollTime) >= 0L) {
               qNextPollTime = millis() + pollInterval;
               uint8_t buf[constBuffLen];
                                                            //LABZ_Start
// bmRequest = Host todevice (0x00) | Class (0x20) | Interface (0x01) = 0x21, bRequest = Set Report(0x09), Report ID (0xF5), Report Type (Feature 0x03), interface (0x00),datalength, datalength, data                                                            
                                                            buf[0]=0x01;
                                                            buf[1]=0x80;
                                                            buf[2]=0x33;
                                                            buf[3]=0x01;
                                                            buf[4]=0x00;
                                                            buf[5]=0x00;
                                                            buf[6]=0x00;
                                                            buf[7]=0x00;
                                                            pUsb->ctrlReq(bAddress,0, bmREQ_HID_OUT, HID_REQUEST_SET_REPORT, 0x00, 0x02, 0x01, 8, 8, buf, NULL);
                                                            //LABZ_End
                              
                //LABZfor(uint8_t i = 0; i < bNumIface; i++) {
for(uint8_t i = 1; i <bNumIface; i++) { //LABZ
                       uint8_t index = hidInterfaces.epIndex[epInterruptInIndex];
                       uint16_t read = (uint16_t)epInfo[index].maxPktSize;
                       ZeroMemory(constBuffLen, buf);
                       uint8_t rcode = pUsb->inTransfer(bAddress, epInfo[index].epAddr,&read, buf);
                       if(rcode) {
                                if(rcode !=hrNAK)
                                       USBTRACE3("(hiduniversal.h) Poll:", rcode, 0x81);
                                return rcode;
                        }
                       if(read > constBuffLen)
                                read =constBuffLen;
                       bool identical = BuffersIdentical(read, buf, prevBuf);
                       SaveBuffer(read, buf, prevBuf);
                       if(identical)
                                return 0;
#if 0
                       Notify(PSTR("\r\nBuf: "), 0x80);
                       for(uint8_t i = 0; i < read; i++) {
                                D_PrintHex<uint8_t> (buf, 0x80);
                               Notify(PSTR(" "), 0x80);
                       }
                       Notify(PSTR("\r\n"), 0x80);
#endif
                       ParseHIDData(this, bHasReportId, (uint8_t)read, buf);
                        HIDReportParser *prs =GetReportParser(((bHasReportId) ? *buf : 0));
                       if(prs)
                               prs->arse(this, bHasReportId, (uint8_t)read, buf);
                }
        }
        return rcode;
}
再回到USB 逻辑分析仪抓取的结果中(有2次输出的温度结果):
image007.png
                                                                                       
结合这个温度计提供的二次开发的文档,可以得知温度的计算方法:
温度= 第三个字节+第四个字节的低4 x0.0625
USB HOST 库提供了HID解析的框架,我们根据框架创建USB温度计的代码,关键部分是:
1. voidTemperReportParser:arse(HID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf)  在这里取得每次获得的 HID数据,如果两次数据有差别,这意味着温度有变化,会通知temperEvents->OnTemperChanged;
2. voidTemperEvents::OnTemperChanged(const TemperEventData *evt) 用来将收到的HID数据转化为温度值,在这里我们直接使用Println从串口数据温度结果。
#include "temper_rptparser.h"
TemperReportParser::TemperReportParser(TemperEvents*evt) :
               temperEvents(evt)
{
  }
void TemperReportParser:arse(HID *hid,bool is_rpt_id, uint8_t len, uint8_t *buf)
{
               boolmatch = true;
   
               //Checking if there are changes in report since the method was last called
               for(uint8_t i=0; i<TEMPER_LEN; i++) {
                              if(buf != oldPad ) {
                                             match= false;
                                             break;
                              }
  }
               // Calling temper event handler
               if(!match && temperEvents) {
                              temperEvents->OnTemperChanged((constTemperEventData*)buf);
                              for(uint8_t i=0; i<TEMPER_LEN; i++) oldPad = buf;
               }
}
void TemperEvents::OnTemperChanged(constTemperEventData *evt)
{
float f;
char  t[10];
byte i;
//for (i=0;i<8;i++) {
//  Serial.print(evt->data);   
//  Serial.print("  ");
// }
  f=evt->data[2]+(evt->data[3] >>4 & 0xF) * 0.0625;
  dtostrf(f,2,2,t);
Serial.println(t);  
}
上述框架完成后,实际代码非常简单:
#include <hid.h>
#include <hiduniversal.h>
#include <usbhub.h>
#include "temper_rptparser.h"
USB                       Usb;
HIDUniversal      Hid(&Usb);
TemperEvents TemperEvents;
TemperReportParser       Temper(&TemperEvents);
void setup()
{
  Serial.begin( 115200);
Serial.println("Start");
  if (Usb.Init() ==-1)
     Serial.println("OSC did not start.");
  delay( 200 );
  if(!Hid.SetReportParser(0, &Temper))
     ErrorMessage<uint8_t>(PSTR("SetReportParser"), 1  );
}
void loop()
{
    Usb.Task();
}
最终运行结果:
image008.png
对于绝大多数USB 设备来说,USB接口部分不会非常复杂,因为设计USB接口和驱动或者应用程序的不会是同一个人。复杂的接口对于他们来说会造成调试和沟通上的极大困难。因此,可以用多种方法来尝试解析数据通讯从而完成Arduino USB设备的控制。
对于 Arduino来说,有多种多样的温度传感器配件,获取环境温度是非常简单的事情,本文的主要目的是展示如何直接和USB设备进行通讯。随着时代的发展,很多测量设备使用USB 接口作为对外通讯的接口,Arduino如果能直接实现USB通讯,将会大大扩展Arduino 的使用范围。
参考:
1.     http://www.baiheee.com/Documents/090518/090518112619.htmUSB开发基础--USB命令(请求)和USB描述符

发表于 2017-3-25 23:57 | 显示全部楼层
好厉害。先收藏备用,以后慢慢学习。
发表于 2018-9-4 19:23 | 显示全部楼层
USB逻辑分析仪 ,用什么软件
 楼主| 发表于 2018-9-5 08:37 | 显示全部楼层
ming3834436 发表于 2018-9-4 19:23
USB逻辑分析仪 ,用什么软件

usb逻辑分析仪是硬件设备,自带软件的
发表于 2018-9-6 21:43 | 显示全部楼层
本帖最后由 ming3834436 于 2018-9-6 22:27 编辑

我根据你的内容读出USB设备描述符(见附件),但请问一下USB的地址和端点是如何看。


捕获.PNG

USB-DESC.pdf

36.34 KB, 下载次数: 1

USB设备描述符

点评

你抓包里面似乎没有地址,并且usb设备地址不是象 i2c 地址那样固定的。 端点就是 Endpoint address: 81  发表于 2018-9-7 08:26
发表于 2018-9-7 09:04 | 显示全部楼层
本帖最后由 ming3834436 于 2018-9-7 11:07 编辑

Arduino如何抓USB的地址 , 请问一下你用什么USB逻辑分析仪。我用的下面这种。
捕获.PNG
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|Archiver|手机版|Arduino中文社区

GMT+8, 2024-11-28 09:23 , Processed in 0.180770 second(s), 22 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表