HID-basierte USB-RS232-Umsetzer

Derartige Konverter finden sich vor allem bei chinesischen Digitalmultimetern. Vorteile gegenüber „echten“ USB-Seriell-Konvertern: Die Nachteile sind:

HOITEK HE2325U - ein HID-basierter USB-Seriell-Umsetzer (RS232, COM-Port)

Anschlüsse laut Datenblatt

P01 18P1
XTALIN2 17P2
n.c.3 16P3
XTALOUT4 15RxD
Data+ (?)514TxD
Uss (0 V)613USB D+
Upp (0 V)712USB D–
Ureg8 11Ucc (5 V)
Data– (?)910n.c.

Dieser 18-polige SMD-Schaltkreis hat folgende Eigenschaften:

Die von Hoitek gewählte Implementierung als HID-Gerät hat folgende Vor- und Nachteile: Eingesetzt wird dieser Schaltkreis bei der PC-Schnittstelle von Handmultimetern, gesehen bei der chineschen Firma UNI-Trend und ihren Hand- und Tischmultimetern. Angeblich ist er auch bei PeakTech-Geräten verbreitet.

Update (unbekannte Chip-Bezeichnung)

Ein noch unbekannter Schaltkreis hat zwar das gleiche HID-Protokoll, aber andere Nummern, einen anderen Hersteller-String und ist Full-Speed. Damit liegt die maximale Datenrate bei 64 kByte/s.

Eine Nachbildung ließe sich mit den neuen Mikrocontrollern MSP430F55xx von Texas Instruments realisieren. Die Vorteile gegenüber CDC (Communication Device Class) wäre ein stiller Windows-Installationsprozess ohne INF-Datei und das Funktionieren unter Windows 98/Me. (CDC wird nicht von Win98/Me unterstützt.)

Dieser Schaltkreis hat einen Silizium-Fehler! Erst nachdem er ein USB-Reset-Signal abbekommt, funktioniert dieser. Windows gibt beim Enumerieren (Anstecken) immer ein USB-Reset-Signal (Single-Ended-Zero für 50 ms) aus, Linux nicht (enumeriert dadurch schneller). Dadurch scheint dieser Chip unter Linux nicht zu funktionieren. Erst nachdem man diesen in Suspend schickt, klappt es. (Das Aufwecken erfolgt mit einem USB-Reset-Signal.)

Dies hat Ralf Burger dankenswerterweise herausgefunden und dokumentiert.

Recherche

USB-Deskriptoren

Device Bus Speed:     Low
neuerdings            Full

Device Descriptor:
 bcdUSB:            0x0100
 bDeviceClass:        0x00
 bDeviceSubClass:     0x00
 bDeviceProtocol:     0x00
 bMaxPacketSize0:     0x08 (8)
 idVendor:          0x04FA (Dallas Semiconductor)
 neuerdings         0x1A86 (QinHeng Electronics)
 idProduct:         0x2490 (DS1490F - iButton)
 neuerdings         0xE008
 bcdDevice:         0x0000
 neuerdings         0x1100
 iManufacturer:       0x01 "Hoitek Semiconductor"
 neuerdings                "WCH.CN \1"
 iProduct:            0x02 "USB to Serial"
 iSerialNumber:       0x00
 bNumConfigurations:  0x01

Configuration Descriptor:
wTotalLength:       0x0029
bNumInterfaces:       0x01
bConfigurationValue:  0x01
iConfiguration:       0x04 "Sample HID"
bmAttributes:         0x80 (Bus Powered )
MaxPower:             0x32 (100 mA)

Interface Descriptor:
bInterfaceNumber:     0x00
bAlternateSetting:    0x00
bNumEndpoints:        0x02
bInterfaceClass:      0x03 (HID)
bInterfaceSubClass:   0x00
bInterfaceProtocol:   0x00
iInterface:           0x00

HID Descriptor:
bcdHID:             0x0100
bCountryCode:         0x00
bNumDescriptors:      0x01
bDescriptorType:      0x22 (Report)
wDescriptorLength:  0x0025 (37 Bytes)

Endpoint Descriptor:
 bEndpointAddress:    0x81 (EP1IN)
 Transfer Type:  Interrupt
 wMaxPacketSize:    0x0008 (8 Bytes)
 bInterval:           0x0A (10 ms)

Endpoint Descriptor:
 bEndpointAddress:    0x02 (EP2OUT)
 Transfer Type:  Interrupt
 wMaxPacketSize:    0x0008 (8 Bytes)
 bInterval:           0x0A (10 ms)

Auf der Suche nach einer Möglichkeit, diesen Konverter mittels Win32-API selbst anzusprechen, musste so viel wie möglich in Erfahrung gebracht werden.

Die zur Verfügung stehende Software für das Multimeter genügt nicht den Anforderungen. Insbesondere ist es nicht möglich, aus mehreren solchen USB-Seriell-Konvertern auszuwählen.

Megabyte-schwere quietschbunte chinesische Zappel-Software voller Übersetzungsfehler ist ohnehin nicht mein Fall, und ohne Quelltext wird man immer auf dem Trockenen sitzen. Ganz zu schweigen von Linux- und MacOS-Anwendung …

Mittels „USB View“ (usbview.exe) wurden die nebenstehenden Deskriptoren ausgelesen. Die Vendor-ID 0x04FA scheint von Dallas Semiconducter überlassen worden zu sein; entweder ist Hoitek eine Tochterfirma, ein Joint Venture, oder aber die (2500 US$ teure) Vendor-ID wurde einfach gemopst.

Der mittels „USB Monitor Professional“ ausgelesene HID-Report-Deskriptor bringt wenig Erleuchtung, ist jedoch zum Nachprogrammieren mittels V-USB nützlich:
{0x06,0xA0,0xFF,	//G Usage Page: 65440 (Unknown)
 0x09,0x01,		//L Usage: 1 (Unknown)
  0xA1,0x02,		//M Collection
  0x09,0x01,		//L  Usage: 1 (Unknown)
  0x15,0x00,		//G  Logical Minimum: 0
  0x26,0xFF,0x00,	//G  Logical Maximum: 255
  0x75,0x08,		//G  Report Size: 8 bits
  0x95,0x08,		//G  Report Count: 8 elements
  0x81,0x02,		//M  Input: 2 (Data,Variable,Absolute, …)
  0x09,0x02,		//L  Usage: 2 (Unknown)
  0x75,0x08,		//G  Report Size: 8 bits … superfluous here!
  0x95,0x08,		//G  Report Count: 8 elements … superfluous here!
  0x91,0x02,		//M  Output: 2 (Data,Variable,Absolute, …)
  0x09,0x03,		//L  Usage: 3 (Unknown)
  0x75,0x08,		//G  Report Size: 8 bits … superfluous here!
  0x95,0x05,		//G  Report Count: 5 elements
  0xB1,0x02,		//M  Feature: 2 (Data,Variable,Absolute, …)
 0xC0};			//M End Collection

Die Windows-API-Funktion HidD_GetCaps() beförderte folgende Zahlen zu Tage:

Usage1
UsagePage0xFFA0
InputReportByteLength9
OutputReportByteLength9
FeatureReportByteLength6
NumberLinkCollectionNodes1
NumberInputButtonCaps0
NumberInputValueCaps1
NumberInputDataIndices1
NumberOutputButtonCaps0
NumberOutputValueCaps1
NumberOutputDataIndices1
NumberFeatureButtonCaps0
NumberFeatureValueCaps1
NumberFeatureDataIndices1

Da erscheint folgende Zuordnung logisch: Report-IDs werden nicht verwendet.

Feature-Report (6 Byte)

#pragma pack(1)
typedef struct {
 char ReportID;	//nicht benutzt: 0
 long BaudRate;	//little endian
 char unknown;	//ist gleich 3 *
}HE2325U_Feature_t;
#pragma pack
* für 8 Datenbits, ohne Parität, 1 Stoppbit

Durch Ausspähen des Aufrufs von HidD_SetFeature() (mit dem Debugger SoftICE) aus der vorhandenen chinesischen Software heraus wurden die Daten des Feature-Reports erraten.

Die Software schickt fälschlicherweise 10 statt 6 Bytes; der Überhang (nur Nullen) wird vom HID-Treiber klaglos ignoriert.

Das Geheimnis des unbekannten Bytes kann erst gelüftet werden, wenn irgendeine Software ein anderes Byte als 3 schickt und (an der alternativen echten seriellen Schnittstelle) andere Parameter einstellt. Vermutung: Datenwortlänge, 0 = 5 bit, 1 = 6 bit, 2 = 7 bit, 3 = 8 bit.

Wegen der unklaren Gestaltung der Paritätsfehlermeldung ist eine Festlegung auf „keine Parität“ wahrscheinlich. Diese könnte sich gut und gerne in höherwertigen Bits versteckt halten.

Input-Report (9 Byte)

typedef struct {
 BYTE ReportID;	//nicht benutzt: 0
 BYTE DataLen:3;//Bit 2:0, s.u.
 BYTE Data[7];	//0..7 Datenbytes
}HE2325U_Input_t;

Mit der chinesischen Software ist es mir trotz SoftICE nicht gelungen, den Input-Report abzufangen!!

Dieser läuft normalerweise über die schnöde Win32-Funktion ReadFile(). Daher blieb mir nichts anderes übrig, als ein Testprogramm zu schreiben und durch „scharfes Hingucken“ die Bedeutung der Bytes zu erschließen.

Die Richtungsumschaltung des 4-Bit-Parallelports erfolgt vermutlich entweder mit dem Feature-Report, oder — was wahrscheinlicher ist — überhaupt nicht: Offene Kollektor-Ausgänge mit eingebautem (oder gar externem) Pull-Up.

Die Länge des Input- und Output-Reports mit 8 Byte führt zu guter USB-Bandbreitenausnutzung. Wäre er länger, müssten stets weitere USB-Interrupt-Transfers angestoßen werden, auch wenn weniger Daten vorliegen. (HID überträgt immer ganze Reports.) Eine feingliedrige, standardkonforme Beschreibung der Reports (obgleich mit „ausgedachten“ Usages) würde einiges Rätselraten ersparen.

Eine Bibliothek he2325u.dll zum Zugriff

Funktionen

Vorläufige Implementierung!
Letzte Änderung 02/11: Parameter für asynchronen Zugriff o.

Download, Einsicht

Mit diesem Wissen gestaltet sich der Zugriff auf solche HID-basierten USB-Seriell-Umsetzer recht einfach.

Im Beispiel wird dem Problem etwas Aufmerksamkeit geschenkt, dem Benutzer unterscheidbare und an USB-Buchsen gebundene „Nummern“ anzubieten.
Einfach „erster gefundener Adapter“, „zweiter gefundener Adapter“ usw. ist auf Dauer konfus, weil man nie weiß, welcher gerade wo ist.

Der angezeigte Name ist dann beispielsweise "USB1", "USB2" usw. (nicht diese Monster aus SetupDiGetDeviceInterfaceDetail()!)
Die Zuordnung wird in der Registrierung gespeichert und von der DLL automatisch verwaltet. (Dummerweise hakelt die derzeitige Implementierung unter Vistas UAC.)

Die 4-Bit-Parallelport-Bits werden vorläufig ignoriert.

Messgerätedaten auswerten

Das ist ein völlig anderes Kapitel. Entschlüsselt sind zurzeit die Handmultimeter UT60, UT61, UT70, UT71, UT81, Zangenstrommesser UT233, Thermometer UT325 sowie die Tischmultimeter UT803 und UT804. Dies betrifft auch die baugleichen Voltcraft-Geräte VC-820, VC-920, VC-940, VC-960 und VC-1008.

Geplante DLL

Funktionen

Einige neuere Geräte, namentlich UT109, sind mit dem USB-Seriell-Wandler CP210x von Silicon Labs ausgestattet. Allerdings wird da standardmäßig ein Treiber installiert, der kein COM-Port abbildet.

Ähnliche Situationen gibt es auch mit FT232, FT245 usw., aber da ist mir bisher kein derartiges Gerät über den Weg gelaufen.

Die künftige DLL fasst alle derartigen Schnittstellen zusammen und vereinfacht auch den Zugriff auf die konventionelle serielle Schnittstelle in einem Worker-Thread.

Verwendung

Die Funktionen mit HHE sollten im Worker-Thread laufen, die anderen im GUI-Thread. Das ist aber kein Muss; nur hat man dann das Problem, dass die synchronen Funktionen HeRead, HeWrite und HeFlush blockieren können. Der Parameter hCancel ermöglicht das Beenden der Blockade im Worker-Thread vom GUI-Thread aus.

Die übliche Anwendung ist, dass im GUI-Thread die Schnittstelle gewechselt wird. Dann muss im Worker-Thread der Schnittstellen-Zugriff abgebrochen werden, dann die Schnittstelle geschlossen und eine andere geöffnet werden. Der GUI-Thread ruft dazu SetEvent(hCancel) auf, und wartet auf die Reaktion des Worker-Threads mit irgend einem anderen Handle per WaitForSingleObject(). Recht häufig wird man den Worker-Thread einfach beenden, dann kann der GUI-Thread einfach auf das Thread-Ende warten: WaitForSingleObject(hThread).

Tut man das ohne hCancel, muss man mit kurzen TimeOuts arbeiten (das erzeugt unnötige Systemlast und überflüssigen Output im Port Monitor), oder aber man hat es mit klassischen Bugs zu tun (Software braucht „ewig“ aufs Beenden oder Rekonfigurieren).

Intern arbeiten die asynchronen Varianten von ReadFile() usw., d.h. das Datei-Handle hCom ist mit FILE_FLAG_OVERLAPPED geöffnet. hCancel ist vom Typ Manual Reset.

Die Strukturen DCB dcb und COMMTIMEOUTS to werden beim Öffnen mit den vorgefundenen Vorgabe-Werten gefüllt. Anwenderseitige Änderungen müssen mit HeSetConfig() zur DLL gemeldet werden, aber je nach tatsächlicher Schnittstelle können die Daten auch direkt genutzt werden. Das heißt, die Strukturelemente sollten zwischendurch nicht geändert werden.