Forum: PC-Programmierung "Driverless" HID, bidirektional, je 64 Byte - möglich?


von Bernd K. (prof7bit)


Lesenswert?

Folgende Frage treibt mich um:

Ist es möglich ein USB-Gerät zu haben das folgende Eigenschaften hat?

* Das Gerät hat Endpoints für 64 Byte in und 64 Byte out
* Es wird unter Windows keine Treiberinstallation vom Nutzer verlangt, 
ein generischer HID-Treiber oder wasauchimmer wird automatisch geladen
* Man kann es mit der Windows API oder libhidapi oder libusb oder 
dergleichen öffnen, Daten senden und empfangen.
* Obiges funktioniert mit allen Windows Versionen ab 7 aufwärts

Also kein Joystick oder Maus mit nur wenigen buttons und Daten in ner 
festen Struktur sondern ich brauche die vollen 64 Byte, und zwar in 
beide Richtungen. Oder muss ich tricksen und einen Joystick mit 512 
Buttons definieren? Geht das prinzipiell mit den mitgelieferten 
Windows-HID-Treibern oder kann ich mir das abschminken?

Und falls es das tatsächlich geben sollte wie sähe der 
Interface-Descriptor aus? Wie sähe der HID-Descriptor aus und welche 
zusätzlichen Descriptoren (Report?) bräuchte ich noch und wann/wo würde 
ich die senden oder wann/wo würden die abgefragt?

: Bearbeitet durch User
von Mark W. (kram) Benutzerseite


Lesenswert?

Ja, geht alles. Schau Dir mal die AN82078 von Cypress an. Geht zwar 
darum, einen PSoC mit dem PC zu verheiraten, aber ist sehr gut erklaert. 
Ich habe dieses Beispiel gerade auf 32 byte aufgebohrt, 64 sind Maximum.

von Volker S. (vloki)


Lesenswert?

Ich denke das geht. Ich bin mir sogar fast sicher, dass ich so eines 
habe. Basiert auf dem USBDevice-HID-Custom Beispiel der 
Microchip_Libraries_for_Applications.

Der Descriptor sähe z.B. so aus:
1
/* Device Descriptor */
2
const USB_DEVICE_DESCRIPTOR device_dsc=
3
{
4
    0x12,    // Size of this descriptor in bytes
5
    USB_DESCRIPTOR_DEVICE,                // DEVICE descriptor type
6
    0x0200,                 // USB Spec Release Number in BCD format
7
    0x00,                   // Class Code
8
    0x00,                   // Subclass code
9
    0x00,                   // Protocol code
10
    USB_EP0_BUFF_SIZE,      // Max packet size for EP0, see usb_config.h
11
    0x04D8,                 // Vendor ID
12
    0x003F,                 // Product ID: Mouse in a circle fw demo
13
    0x0002,                 // Device release number in BCD format
14
    0x01,                   // Manufacturer string index
15
    0x02,                   // Product string index
16
    0x00,                   // Device serial number string index
17
    0x01                    // Number of possible configurations
18
};
19
20
/* Configuration 1 Descriptor */
21
const uint8_t configDescriptor1[]={
22
    /* Configuration Descriptor */
23
    0x09,//sizeof(USB_CFG_DSC),     // Size of this descriptor in bytes
24
    USB_DESCRIPTOR_CONFIGURATION,   // CONFIGURATION descriptor type
25
    0x29,0x00,                      // Total length of data for this cfg
26
    1,                              // Number of interfaces in this cfg
27
    1,                              // Index value of this configuration
28
    0,                              // Configuration string index
29
    _DEFAULT | _SELF,               // Attributes, see usb_device.h
30
    50,                             // Max power consumption (2X mA)
31
32
    /* Interface Descriptor */
33
    0x09,//sizeof(USB_INTF_DSC),    // Size of this descriptor in bytes
34
    USB_DESCRIPTOR_INTERFACE,       // INTERFACE descriptor type
35
    0,                              // Interface Number
36
    0,                              // Alternate Setting Number
37
    2,                              // Number of endpoints in this intf
38
    HID_INTF,                       // Class code
39
    0,                              // Subclass code
40
    0,                              // Protocol code
41
    0,                              // Interface string index
42
43
    /* HID Class-Specific Descriptor */
44
    0x09,//sizeof(USB_HID_DSC)+3,   // Size of this descriptor in bytes
45
    DSC_HID,                        // HID descriptor type
46
    0x11,0x01,                      // HID Spec Release Nr. in BCD format (1.11)
47
    0x00,                           // Country Code (0x00 for Not supported)
48
    HID_NUM_OF_DSC,                 // Number of class descriptors, see usbcfg.h
49
    DSC_RPT,                        // Report descriptor type
50
    HID_RPT01_SIZE,0x00,            //sizeof(hid_rpt01) report descriptor
51
52
    /* Endpoint Descriptor */
53
    0x07,/*sizeof(USB_EP_DSC)*/
54
    USB_DESCRIPTOR_ENDPOINT,        //Endpoint Descriptor
55
    HID_EP | _EP_IN,                //EndpointAddress
56
    _INTERRUPT,                     //Attributes
57
    0x40,0x00,                      //size
58
    0x01,                           //Interval
59
60
    /* Endpoint Descriptor */
61
    0x07,/*sizeof(USB_EP_DSC)*/
62
    USB_DESCRIPTOR_ENDPOINT,        //Endpoint Descriptor
63
    HID_EP | _EP_OUT,               //EndpointAddress
64
    _INTERRUPT,                     //Attributes
65
    0x40,0x00,                      //size
66
    0x01                            //Interval
67
};
68
69
//Language code string descriptor
70
const struct{uint8_t bLength;uint8_t bDscType;uint16_t string[1];}sd000={
71
sizeof(sd000),USB_DESCRIPTOR_STRING,{0x0409
72
}};
73
74
//Manufacturer string descriptor
75
const struct{uint8_t bLength;uint8_t bDscType;uint16_t string[27];}sd001={
76
sizeof(sd001),USB_DESCRIPTOR_STRING,
77
{'A',' ','M','i','c','r','o','c','h','i','p',' ',
78
'T','e','c','h','n','o','l','o','g','y',' ','I','n','c','.'
79
}};
80
81
//Product string descriptor
82
const struct{uint8_t bLength;uint8_t bDscType;uint16_t string[35];}sd002={
83
sizeof(sd002),USB_DESCRIPTOR_STRING,
84
{'S','i','m','p','l','e',' ','H','I','D',
85
 ' ','D','e','v','i','c','e',' ','D','e',
86
 'm','o',' ','(','-','>',
87
#if defined(__18F14K50)       
88
    '1','8','F','1','4','K','5','0',')'
89
#elif defined (__18F4550)
90
    ' ','1','8','F','4','5','5','0',')'
91
#elif defined(__18F27J53)
92
    '1','8','F','2','7','J','5','3',')'
93
#elif defined(__18F25K50)
94
    '1','8','F','2','5','K','5','0',')'
95
#elif defined(__16F1459)
96
    ' ','1','6','F','1','4','5','9',')'
97
#elif defined(__16F1455)
98
    ' ','1','6','F','1','4','5','5',')'
99
#else
100
    #warning "Which PIC ???"
101
#endif
102
}};
103
104
//Class specific descriptor - HID 
105
const struct{uint8_t report[HID_RPT01_SIZE];}hid_rpt01={
106
{
107
    0x06, 0x00, 0xFF,   // Usage Page = 0xFF00 (Vendor Defined Page 1)
108
    0x09, 0x01,         // Usage (Vendor Usage 1)
109
    0xA1, 0x01,         // Collection (Application)
110
    0x19, 0x01,         //      Usage Minimum 
111
    0x29, 0x40,         //      Usage Maximum   //64 input usages total (0x01 to 0x40)
112
    0x15, 0x01,         //      Logical Minimum (data bytes in the report may have minimum value = 0x00)
113
//    0x25, 0x40,        //      Logical Maximum (data bytes in the report may have maximum value = 0x00FF = unsigned 255)
114
    0x26, 0xFF, 0x00,       //      Logical Maximum (data bytes in the report may have maximum value = 0x00FF = unsigned 255)
115
    0x75, 0x08,         //      Report Size: 8-bit field size
116
    0x95, 0x40,         //      Report Count: Make sixty-four 8-bit fields (the next time the parser hits an "Input", "Output", or "Feature" item)
117
    0x81, 0x00,         //      Input (Data, Array, Abs): Instantiates input packet fields based on the above report size, count, logical min/max, and usage.
118
    0x19, 0x01,         //      Usage Minimum 
119
    0x29, 0x40,         //      Usage Maximum   //64 output usages total (0x01 to 0x40)
120
    0x91, 0x00,         //      Output (Data, Array, Abs): Instantiates output packet fields.  Uses same report size and count as "Input" fields, since nothing new/different was specified to the parser since the "Input" item.
121
    0xC0}                // End Collection
122
};                  
123
124
125
//Array of configuration descriptors
126
const uint8_t *const USB_CD_Ptr[]=
127
{
128
    (const uint8_t *const)&configDescriptor1
129
};
130
131
//Array of string descriptors
132
const uint8_t *const USB_SD_Ptr[]=
133
{
134
    (const uint8_t *const)&sd000,
135
    (const uint8_t *const)&sd001,
136
    (const uint8_t *const)&sd002
137
};
Der Transfer geht dann über die beiden definierten Endpoints...

: Bearbeitet durch User
von Bernd K. (prof7bit)


Lesenswert?

Ja, das scheint tatsächlich zu funktionieren, immerhin habe ich es jetzt 
mit Inspiration der oben geposteten Descriptoren und darauf aufbauend 
weiterer Google-Recherche geschafft eine erfolgreiche Enumerierung 
sowohl unter Windows als auch unter Linux hinzubekommen und letzte Nacht 
hat zum ersten Mal der zweite Endpoint in beide Richtungen erste 
Lebenszeichen (unter Linux) von sich gegeben.

Ich ringe nun nur noch ein bisschen mit der praktischen Implementation 
auf meiner Hardware, insbesondere dem Verständnis der Kinetis 
USB-Hardware und ihren Zuständen und auch den Feinheiten des 
USB-Protokolls. Sehr große Hilfe beziehe ich dabei übrigens auch aus der 
bemerkenswerten Arbeit von Kevin Cuzner: 
http://kevincuzner.com/2014/12/12/teensy-3-1-bare-metal-writing-a-usb-driver/ 
der nicht müde geworden ist sich durch die nur schwach dokumentierte 
Kinetis USB-Hardware zu kämpfen und seine gewonnenen Erkenntnisse zu 
teilen.

Wenn ich es fertig habe werde ich es ebenfalls auf Github einstellen.

von Bernd K. (prof7bit)


Lesenswert?

Scheint ich hab mich zu früh gefreut. Wie es aussieht ist unter Windows 
7 der generische HID-Treiber broken und sendet Datenmüll:

Wenn ich an die WriteFile() Funktion keinen Buffer übergebe mit exakt 
der Länge 65 Bytes (also Reportgröße plus 1) kommt 1784 
RROR_INVALID_USER_BUFFER und wenn ich nicht in das erste Byte die 
Report-ID eintrage dann kommt 87 ERROR_INVALID_PARAMETER.

gesendet werden dann jedoch die ersten 64 Byte des Buffers incl. der 
Report-ID und das letzte Byte wird abgeschnitten. Mit dem nächsten Paket 
kommts auch nicht, es fällt einfach unter den Tisch.

Unter Linux funktionierts natürlich korrekt.

Also wieder extra Gefrickel nur für bestimmten Windows-Versionen. Wär ja 
auch zu schön gewesen wenn stattdessen mal was einfach normal 
funktionieren könnte :-(

von Volker S. (vloki)


Lesenswert?

Bernd K. schrieb:
> Scheint ich hab mich zu früh gefreut. Wie es aussieht ist unter Windows
> 7 der generische HID-Treiber broken und sendet Datenmüll:

Also bei mir funktioniert es mit Win7 und Linux gleich.
(Gleiche Qt Source -> 
http://www.hs-ulm.de/wir/Personal/PersonalSaSchr/vschilli/QtProjekte/)

Verwendet wurde die HIDAPI von Alan Ott 
(http://www.signal11.us/oss/hidapi/).

: Bearbeitet durch User
von Clemens L. (c_l)


Lesenswert?

Bernd K. schrieb:
> Wie es aussieht ist unter Windows 7 der generische HID-Treiber broken
> und sendet Datenmüll

Wenn dein Gerät nicht HID-, sondern WinUSB-Deskriptoren hat, dann gibt 
es keinen Protokoll-Treiber, der dir dazwischenfunken könnte, und du 
kannst in allen Betriebssystem ohne Kernel-Treiber darauf zugreifen. 
(Und kannst Bulk-Transfers.)

von Bernd K. (prof7bit)


Lesenswert?

Volker S. schrieb:
> Verwendet wurde die HIDAPI von Alan Ott
> (http://www.signal11.us/oss/hidapi/).

Das ist der Code den ich benutzte um eine zweite Meinung einzuholen, das 
linkt gegen die hidapi. Es verhält sich genauso wie mein erster Versuch 
direkt über das Windows API mit CreateFile()/WriteFile().
1
import hid
2
3
x = [16]*65
4
y = [17]*65
5
6
x[0] = 2  # report ID
7
x[1] = 4  # erstes
8
x[63] = 5 # vorletztes
9
x[64] = 6 # letztes
10
11
y[0] = 2  # report ID
12
y[1] = 7  # erstes
13
y[63] = 8 # vorletztes
14
y[64] = 9 # letztes
15
16
d = hid.device()
17
d.open(0x1234, 0x5694)
18
19
# paar mal senden
20
for i in range(3):
21
    d.write(x);
22
    d.write(y);
23
24
d.close()

Bei allen Paketen wird die Report-ID vorangestellt. Bei den ersten 
beiden Paketen wird das letzte Byte komplett verschluckt, es kommen also 
nur 63 Byte Nutzdaten an. Bei den folgenden wird dann jedesmal das 
fehlende Byte in einer weiteren OUT Transaktion der Länge 1 
nachgereicht. Ob dabei immer noch was verschluckt wird hab ich nicht 
getestet.

Es sieht wohl so aus als sollte ich die Report-Größe auf 63 Byte 
verringern um zu verhindern daß er bei jedem Paket immer noch ein 
einzelnes Byte in einem zweiten fast leeren Paket hinterher schicken 
muss.

von Bernd K. (prof7bit)


Lesenswert?

Clemens L. schrieb:
> Wenn dein Gerät nicht HID-, sondern WinUSB-Deskriptoren hat, dann gibt
> es keinen Protokoll-Treiber, der dir dazwischenfunken könnte, und du
> kannst in allen Betriebssystem ohne Kernel-Treiber darauf zugreifen.

Was sind "WinUSB-Deskriptoren"? Meinst Du ne spezielle Interfaceklasse 
oder meinst Du wirklich irgendwelche MS-proprietären 
Nicht-Standard-Deskriptoren (und welche Klasse ist es dann, und welcher 
Treiber wird dann unter Linux geladen)?

Und was passiert unter Windows wenn man es einstöpselt? Gelbes 
Ausrufezeichen? Ich will unter allen Umständen vermeiden daß der 
Benutzer irgendwelche Aktionen durchführen muss nach erstmaligem 
Einstöpseln auf jungfräulichem Windows.

: Bearbeitet durch User
von Bernd K. (prof7bit)


Lesenswert?

Bernd K. schrieb:

> Wenn ich es fertig habe werde ich es ebenfalls auf Github einstellen.

Ich lasse den Worten mal Taten folgen:

    https://github.com/prof7bit/frdm-kl25z-minimal-usb-hid

Das läuft (stand heute Abend) unter Linux. Unter Windows hab ichs noch 
nicht getestet.

Das Projekt (so wie es ist) erfordert

Hardware:
 * FRDM-KL25Z Board (mit Segger-Firmware gemodded)

Software:
 * J-Link Software
 * arm-none-eabi-gcc
 * python-3.x
 * bash
 * make

Python ist erforderlich um die usb_descriptors.c/.h zu generieren.

Das Projekt kompiliert nach dem Auschecken direkt auf der Konsole mit 
make oder alternatiuv dazu hab ich auch noch Projektdateien für Eclipse 
(mit GNU-ARM und PyDev Plugins) beigelegt.

Ein kleines Python script zum Test hab ich auch noch beigelegt, momentan 
noch 2.x weil ich auf die Schnelle das verwendete hid Modul noch nicht 
für python 3.x gefunden hab auf meiner Ubuntu-Kiste, scheint aber noch 
andere hidapi bindings zu geben, war nur zu faul zum Suchen.

von gnarf (Gast)


Lesenswert?

Zählt die Report-ID nicht zum Report dazu (zählt also eigentlich zu den 
64 Bytes und kommt nicht noch oben drauf)?
Ich hätte vermutet, dass man die Angabe einer Report-ID im Descriptor 
weg lassen müsste um volle 64 Bytes nutzen zu können, oder nur 63 Bytes 
an Nutzdaten bei Angabe einer Report-ID und Report-Count=64 im 
Descriptor hätte?

von Clemens L. (c_l)


Lesenswert?

Bernd K. schrieb:
> Was sind "WinUSB-Deskriptoren"? Meinst Du ne spezielle Interfaceklasse
> oder meinst Du wirklich irgendwelche MS-proprietären
> Nicht-Standard-Deskriptoren

Letzteres: 
https://msdn.microsoft.com/en-us/library/windows/hardware/hh450799.aspx
> A WinUSB device is a Universal Serial Bus (USB) device whose firmware
> defines certain Microsoft operating system (OS) feature descriptors
> that report the compatible ID as "WINUSB".
>
> The purpose of a WinUSB device is to enable Windows to load Winusb.sys
> as the device's function driver without a custom INF file.


> und welche Klasse ist es dann

Alles andere als "vendor specific" wäre sinnlos.

> und welcher Treiber wird dann unter Linux geladen)?

Linux ignoriert diese Deskriptoren. Aber in Linux benötigt libusb auch 
keinen speziellen Kerneltreiber.

> Und was passiert unter Windows wenn man es einstöpselt?

Nichts besonderes.

Dein Programm muss sich dann das Gerät über die WinUSB-API selbst 
heraussuchen.

: Bearbeitet durch User
von Bernd K. (prof7bit)


Lesenswert?

Clemens L. schrieb:
>> Und was passiert unter Windows wenn man es einstöpselt?
>
> Nichts besonderes.

Geht das auch mit Windows 7? Oberflächliches Googeln legt nahe daß ich 
dort noch ein .inf brauche. Dann scheidet das leider aus.

von Clemens L. (c_l)


Lesenswert?

Bernd K. schrieb:
> Geht das auch mit Windows 7?

"For versions of Windows earlier than Windows 8, the updated Winusb.inf 
is available through Windows Update. If your computer is configured to 
get driver update automatically, WinUSB driver will get installed 
without any user intervention by using the new INF package."

von Peter (Gast)


Lesenswert?

gnarf schrieb:
> Zählt die Report-ID nicht zum Report dazu (zählt also eigentlich zu den
> 64 Bytes und kommt nicht noch oben drauf)?
> Ich hätte vermutet, dass man die Angabe einer Report-ID im Descriptor
> weg lassen müsste um volle 64 Bytes nutzen zu können, oder nur 63 Bytes
> an Nutzdaten bei Angabe einer Report-ID und Report-Count=64 im
> Descriptor hätte?

Genau so ist es auch. Aber bei insgesamt 64 Bytes ist Schluss.

Also ohne ID (64 Bytes Nutzdaten) oder mit ID (63 Bytes an Nutzdaten). 
Sonst packt das der Firmware-Treiber nicht. Die machen nämlich meist bei 
64 Bytes (gesamt) zu.

Schau mal hier:
http://ahidlib.com/pages/fundamentals.php?lang=de

von Bernd K. (prof7bit)


Lesenswert?

Peter schrieb:
> Also ohne ID (64 Bytes Nutzdaten) oder mit ID (63 Bytes an Nutzdaten).

Danke für den Hinweis, das funktioniert :-)

Durch das simple Weglassen der beiden report-id Einträge im 
Report-Descriptor wird keine Report-ID mehr vorangestellt und ich kann 
die Reportgröße auf 64 Bytes erweitern.

Allerdings muss man beachten dass der Windows-Treiber jetzt in der 
WriteFile() Funktion einen Puffer von exakt 65 Bytes erwartet bei dem 
das erste Byte zwingend 0 sein muss, gesendet werden jedoch nur die 
hinteren 64 Bytes des Puffers.

Umgekehrt muss auch der Buffer der für die ReadFile() Funktion 
vorgehalten wird exakt 65 Bytes betragen, die Nutzdaten landen in den 
hinteren 64 Byte und das erste Byte kann man ignorieren.

Bei Verwendung von HIDAPI (unter Python) ist es sendeseitig genauso wie 
beim nackten Windows WriteFile(), also 65 Byte, aber empgfangsseitig 
scheinen auch 64 Byte zu reichen, zumindest bei den cython-hidapi 
Bindings ist das so. Zum Glück anscheinend unter Linux und Windows 
gleichermaßen.

von Peter (Gast)


Lesenswert?

Bernd K. schrieb:
> Allerdings muss man beachten dass der Windows-Treiber jetzt in der
> WriteFile() Funktion einen Puffer von exakt 65 Bytes erwartet bei dem
> das erste Byte zwingend 0 sein muss, gesendet werden jedoch nur die
> hinteren 64 Bytes des Puffers.
>
> Umgekehrt muss auch der Buffer der für die ReadFile() Funktion
> vorgehalten wird exakt 65 Bytes betragen, die Nutzdaten landen in den
> hinteren 64 Byte und das erste Byte kann man ignorieren.

Genau so ist das.

Man könnte auch sagen:
Egal ob das HID-Gerät eine ID definiert oder nicht, der Windows Treiber 
(WriteFile(), ReadFile()) sendet / empfängt im ersten Byte immer eine ID 
(entweder die definierte oder eben 0).
Die Puffer-Größe auf Windows-Seite entspricht dabei immer der Größe im 
Report-Deskriptor + 1 (für die ID).

von Bernd K. (prof7bit)


Lesenswert?

Hab mal schnell ein HIDAPI Binding für Free Pascal auf Linux gemacht:

https://github.com/prof7bit/HIDAPI.pas

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.