最近公司有要求进行温度的记录,经过研究入手“UT325数字测温仪”,这是是优利德(UNI-T) 生产的双路测温仪。 但是对于需要用上位机采集数据的用户来说,它只是单路的。它可以通过USB口上报 T1 温度,T2温度或者 T1-T2温度,但是没有办法同时报告 T1 和 T2温度,并且只能通过正面按键才能切换报告的通道,因此,对于我们来说,这只是单通道的测温仪。 官方没有提供支持,为了获得通讯协议,需要使用 USBlyzer 抓取数据逐步分析。第一步就是抓取 Descriptor。 USB Input Device Connection Status | Device connected | Current Configuration | 1 | Speed | Full (12 Mbit/s) | Device Address | 8 | Number Of Open Pipes | 2 |
Device Descriptor USBto Serial Offset | Field | Size | Value | Description | 0 | bLength | 1 | 12h | | 1 | bDescriptorType | 1 | 01h | Device | 2 | bcdUSB | 2 | 0100h | USB Spec 1.0 | 4 | bDeviceClass | 1 | 00h | Class info in Ifc Descriptors | 5 | bDeviceSubClass | 1 | 00h | | 6 | bDeviceProtocol | 1 | 00h | | 7 | bMaxPacketSize0 | 1 | 08h | 8 bytes | 8 | idVendor | 2 | 1A86h | | 10 | idProduct | 2 | E008h | | 12 | bcdDevice | 2 | 1400h | 14.00 | 14 | iManufacturer | 1 | 01h | "WCH.CN ." | 15 | iProduct | 1 | 02h | "USB to Serial" | 16 | iSerialNumber | 1 | 00h | | 17 | bNumConfigurations | 1 | 01h | |
Configuration Descriptor1 Bus Powered, 100 mA Offset | Field | Size | Value | Description | 0 | bLength | 1 | 09h | | 1 | bDescriptorType | 1 | 02h | Configuration | 2 | wTotalLength | 2 | 0029h | | 4 | bNumInterfaces | 1 | 01h | | 5 | bConfigurationValue | 1 | 01h | | 6 | iConfiguration | 1 | 04h | | 7 | bmAttributes | 1 | 80h | Bus Powered | | 4..0: Reserved | | ...00000 | | | 5: Remote Wakeup | | ..0..... | No | | 6: Self Powered | | .0...... | No, Bus Powered | | 7: Reserved (set to one)
(bus-powered for 1.0) | | 1....... | | 8 | bMaxPower | 1 | 32h | 100 mA |
Interface Descriptor 0/0 HID,2 Endpoints Offset | Field | Size | Value | Description | 0 | bLength | 1 | 09h | | 1 | bDescriptorType | 1 | 04h | Interface | 2 | bInterfaceNumber | 1 | 00h | | 3 | bAlternateSetting | 1 | 00h | | 4 | bNumEndpoints | 1 | 02h | | 5 | bInterfaceClass | 1 | 03h | HID | 6 | bInterfaceSubClass | 1 | 00h | | 7 | bInterfaceProtocol | 1 | 00h | | 8 | iInterface | 1 | 00h | |
HID Descriptor Offset | Field | Size | Value | Description | 0 | bLength | 1 | 09h | | 1 | bDescriptorType | 1 | 21h | HID | 2 | bcdHID | 2 | 0100h | 1.00 | 4 | bCountryCode | 1 | 00h | | 5 | bNumDescriptors | 1 | 01h | | 6 | bDescriptorType | 1 | 22h | Report | 7 | wDescriptorLength | 2 | 0025h | 37 bytes |
Endpoint Descriptor 82 2In, Interrupt, 5 ms Offset | Field | Size | Value | Description | 0 | bLength | 1 | 07h | | 1 | bDescriptorType | 1 | 05h | Endpoint | 2 | bEndpointAddress | 1 | 82h | 2 In | 3 | bmAttributes | 1 | 03h | Interrupt | | 1..0: Transfer Type | | ......11 | Interrupt | | 7..2: Reserved | | 000000.. | | 4 | wMaxPacketSize | 2 | 0008h | 8 bytes | 6 | bInterval | 1 | 05h | 5 ms |
Endpoint Descriptor 02 2Out, Interrupt, 5 ms Offset | Field | Size | Value | Description | 0 | bLength | 1 | 07h | | 1 | bDescriptorType | 1 | 05h | Endpoint | 2 | bEndpointAddress | 1 | 02h | 2 Out | 3 | bmAttributes | 1 | 03h | Interrupt | | 1..0: Transfer Type | | ......11 | Interrupt | | 7..2: Reserved | | 000000.. | | 4 | wMaxPacketSize | 2 | 0008h | 8 bytes | 6 | bInterval | 1 | 05h | 5 ms |
Interface 0 HID ReportDescriptor Vendor-Defined 1 Item Tag (Value) | Raw Data | Usage Page (Vendor-Defined 161) | 06 A0 FF | Usage (Vendor-Defined 1) | 09 01 | Collection (Application) | A1 01 | Usage (Vendor-Defined 1) | 09 01 | Logical Minimum (0) | 15 00 | Logical Maximum (255) | 26 FF 00 | Report Size (8) | 75 08 | Report Count (8) | 95 08 | Input (Data,Var,Abs,NWrp,Lin,Pref,NNul,Bit) | 81 02 | Usage (Vendor-Defined 2) | 09 02 | Report Size (8) | 75 08 | Report Count (8) | 95 08 | Output (Data,Var,Abs,NWrp,Lin,Pref,NNul,NVol,Bit) | 91 02 | Usage (Vendor-Defined 3) | 09 03 | Report Size (8) | 75 08 | Report Count (5) | 95 05 | Feature (Data,Var,Abs,NWrp,Lin,Pref,NNul,NVol,Bit) | B1 02 | End Collection | C0 |
从Descriptor上可以看到,它是通过 HID来进行通讯的(根据【参考1】,使用WCH CH9325 Uart转 HID 芯片),这种通讯的好处是无需使用驱动,缺点是速度不快,这里对于温度这种物理量来说已经足够使用。 第二步,分析具体通讯协议了,好在网上找到了一些资料【参考2】,虽然对不上(国产设备经常发生型号相同,但是细节完全不同的情况),但是根据提示重新捋一下还是能够得到大概的结果。 最终的结论是:每次读取HID数据中第二个字节是有意义的,我们需要的温度信息就在上面数值的第二个字节中。
编写一个 Application,输出读取到的数据,可以看到存在一个以 0x0D,0x0A作为结尾的“循环节”。下面的数据中,第一个是HID的Report ID, 所以会比前面USB Analyzer 抓到的多一个。 针对有意义的数值(就是前面红框中的第三个数值:00 00 00 00 00 00 32 3A…….0D 0A)进行分析。
偏移(Byte) | | | | | | | | | | | | | | | | | | | | | | | | | 当前温度单位,摄氏度=1,华氏度=2,开尔文温度=3 | | | | | | | | | | | | | | | | | | | | | | | | | | |
接下来,发现遇到一个奇怪的问题:第一次插入之后运行数据什么数值都没有,就是说:设备上电之后不会主动发送数据出来,而我抓取的数据都是先运行 UT325D的软件查看设备能否正常工作之后才有的。 猜测是运行配套软件之后,上位机的软件发送了什么命令给设备,设备才会打开输出输出的功能。为此,用 USBlyzer录制启动软件后的通讯。 可以看到,按下上位机软件的 USB Connection 之后,有三条“可疑”的指令(确定肯定是上位机触发的,因此在 OUT 的通讯中查找即可),一个是 Set Report 60 09 0000 03,另外两个是 HID 02 5A 00 00 00 00 00 00 和 01 01 00 00 00 00 0000 。 为了便于实验,使用SimpleHIDWrite3 这个软件,它是专门针对 HID设备的测试软件,能够在无需编程的情况下直接发送命令。 打开工具后选择 UT325D 这个 USB 设备,在 SimpleHIDWrite3 这个软件中,该设备名称是 USB to Serial (这个软件使用 Device Descriptor 中的iProduct字符串作为设备名) 再进一步,我们使用 Info 按钮查看设备信息,可以看到如下内容: 特别注意的是,对于这个设备 HID Report的Size 是9字节(1个 Report ID + 8 个自定义内容),Feature Report 的字节是6个。 经过测试,首先使用这个工具SetFeature 6009 00 00 03给设备数据即可开始发送全0的数据,然后需要再使用工具发送 HID 01 01 00 00 00 0000 00,这个设备即可像自带工具一样发送正常数据了。 整个代码是基于我们之前编写的 HIDSend,不同点在于我们引入了HidD_SetFeature API 针对设备发送 SetFeature命令 //发送报告的缓冲区,1字节报告ID+8字节报告数据。 UCHAR WriteReportBuffer[6]; WriteReportBuffer[0]= 0x00; WriteReportBuffer[1] = 0x60; WriteReportBuffer[2]= 0x09; WriteReportBuffer[3]= 0x00; WriteReportBuffer[4]= 0x00; WriteReportBuffer[5]= 0x03; //调用HidD_SetFeature函数发送数据 Result= HidD_SetFeature( hUsb, WriteReportBuffer, 6); 这步完成之后,再使用WriteFile发送我们的 HID 数据: WriteReportBuffer[0]= 0x00; WriteReportBuffer[1]= 0x01; WriteReportBuffer[2]= 0x01; WriteReportBuffer[3]= 0x00; WriteReportBuffer[4]= 0x00; WriteReportBuffer[5]= 0x00; WriteReportBuffer[6]= 0x00; WriteReportBuffer[7]= 0x00; DWORD lpNumberOfBytesWritten; //调用WriteFile函数发送数据 Result= WriteFile(hUsb, WriteReportBuffer, 9, &lpNumberOfBytesWritten, NULL); 上述完成之后就可以正常接收数据了,使用ReadFile反复读取 //调用ReadFile 接收数据 Result= ReadFile( hUsb, ReadReportBuffer, 9, &lpNumberOfBytesRead, NULL); 之后再对收到的数据进行解析,需要注意的是,每次收到的 HID报文中,只有第三个数值是有效的,因此我还引入counter变量进行解析。也正是因为这样解析的缘故,所以输出的时间格式是:SS“ MM’ //if (counter == 6) { printf("%c", ReadReportBuffer[2]);} if (counter == 7) { printf("%c", ReadReportBuffer[2]); } if (counter == 8) { printf("%c", ReadReportBuffer[2]); } if (counter == 9) { printf("%c", ReadReportBuffer[2]); } if (counter == 10) { printf("%c", ReadReportBuffer[2]); } if ((counter ==11)&&(ReadReportBuffer[2]==0x31)) { printf("C"); } if ((counter == 11) &&(ReadReportBuffer[2] == 0x32)) { printf("F"); } if ((counter == 11) &&(ReadReportBuffer[2] == 0x33)) { printf("K"); } if (counter == 15) { printf(" %c", ReadReportBuffer[2]); } if (counter == 16) { printf("%c\"",ReadReportBuffer[2]); } if (counter == 17) { printf("%c", ReadReportBuffer[2]); } if (counter == 18) { printf("%c'", ReadReportBuffer[2]); } if ((counter == 19) &&(ReadReportBuffer[2] == 0x30)) { printf("T1"); } if ((counter == 19) &&(ReadReportBuffer[2] == 0x31)) { printf("T2"); } if ((counter == 19) &&(ReadReportBuffer[2] == 0x32)) { printf("T1-T2"); } if (ReadReportBuffer[2] == 0xA) { printf("\n"); counter = 0; } 运行结果如下: 参考: 1. https://sigrok.org/wiki/WCH_CH9325 3. http://www.uni-trend.com.cn/productsdetail_1302_460_460.html
|