Forum: PC-Programmierung USB HID Device mit C# und Jan Axelsons Generic HID Threading möglich?


von Dominik M. (dom1)


Lesenswert?

Hi,

für meine Abschlussarbeit bin ich dabei einen STM32 Mikrocontroller per 
USB-Schnittstelle zu steuern. Ich habe mich mit der Generic Hid Library 
von Jan Axelson unter C# beschäftigt und damit eine kleine 
WinForm-Anwendung zur Steuerung erstellt. Die Kommunikation per USB 
funktioniert auch, jedoch habe ich nun ein paar Probleme bei denen ich 
zurzeit nicht so richtig weiter weiß und hoffe Ihr könnt mir 
weiterhelfen.

Zur Steuerung des Geräts (STM32) werden mehrere Reports a 4 Bytes an das 
Gerät gesendet. Die Senderoutine hat jedoch nicht immer erfolgreich 
einen Report gesendet, sondern stoppte beim Versuch zu senden 
(Filestream.Write). Dies konnte ich durch ein flush nach dem Schreiben 
eines Reports und einer zusätzlichen kleinen Wartephase 
(thread.sleep(30)) beheben. Richtig gefallen tut mir das Sleep aber auch 
nicht. Ich habe das Gefühl, dass das Senden nicht (richtig) 
abgeschlossen wird bevor das Senden des nächsten Reports beginnt. Sollte 
aber eigentlich nicht passieren, da es sich um eine blockierende 
Funktion handelt oder liege ich falsch?

Zum Empfangen von Status-Informationen vom Gerät (ebenfalls 4 Byte 
Reports) wollte ich einen eigenen Thread starten um die Oberfläche 
weiterhin bedienbar zu halten. Der Thread läuft, jedoch bleibt dieser 
hängen, wegen des blockierenden Aufrufs von Filestream.Read. Gibts eine 
elegante Möglichkeit dies zu umgehen, bzw. in bestimmten Abständen den 
Aufruf zu beenden? Ich möchte innerhalb des Threads abfragen ob dieser 
beendet werden soll, deshalb muss ich eine Variable zwischendurch 
abfragen. Mir ist aufgefallen, dass ich während der Thread läuft, dass 
das Gerät keine gesendeten Reports empfängt. Kann es sein, dass ich den 
geöffneten Stream entweder nur schreiben oder nur lesen kann? Das wäre 
ziemlich besch*, da ab und an auch mal ein Report vom PC gesendet werden 
muss. Wenn das so ist, wie kann ich sowohl vom Gerät lesen als auch 
schreiben (in kurzen Abständen)? Gibt es irgendeine andere Möglichkeit 
den Mikrocontroller abzufragen ohne das Senden von Reports zu 
verhindern.?

Umstellung auf eine andere USB Api/Lib würde ich eigentlich ziemlich 
ungern, da die Zeit so schon sehr knapp ist...

von Dominik M. (dom1)


Lesenswert?

So gerade kommt mir noch was, den zusätzlichen Protokolloverhead von USB 
habe ich natürlich nicht beachtet, evtl. wäre es daher geschickter ein 
Teil der 4 Byte Päckchen vom PC aus zusammenzufassen (Konfiguration) und 
in einem großen ~ 60 Byte Report zu senden?

Trotzdem bäuchte ich eine Möglichkeit den STM32 auf Datenversand 
abzufragen als auch an diesen ein Datenpaket (dann aber nur 4 Byte) zu 
senden. Dies scheint aber durch das Lesen innerhalb des Threads 
blockiert zu sein.

von Dominik M. (dom1)


Lesenswert?

So neuer Ansatz, ich verwende nun zwei HIDHandles und dazu passend zwei 
FileStreams, jeweils einer für das Senden und einen zum Empfangen. 
Desweiteren fragt der Thread mit BeginRead anstelle von Read ab. Da das 
Abfragen auf Datenempfang in jedem Fall blockierend arbeitet muss mein 
Mikrocontroller ein Report senden, damit die aufgerufene Funktion sich 
wieder beendet. Dann kann ich den Thread auch wieder beenden.

Sind meine Ausführungen so bescheuert? Gibt es eine bessere Lösung zum 
Abfragen?

von Potter (Gast)


Lesenswert?

Hallo Dominik,

wie groß sind denn Deine Sende-/Empfangspuffer in den Endpunkten. Wenn 
Du Reports versendest, dann kommt zu den eigentlichen Nutzdaten noch die 
führende Report-ID dazu. Zeig mal Deine Deskriptoren.

Ich vermute, dass die Reports, die Du sendest nicht die richtige Größe 
haben. Beispiel:

Report ID = #0x00 mit 4 Byte Nutzdaten
Report ID = #0x11 mit 4 Byte Nutzdaten

Du sendest nun:
0x01,0x02,0x03,0x04 für Deinen Report mit ID = 0x00 (ohne die ID)
und
0x11,0x12,0x13,0x14 für Deinen Report mit ID = 0x11 (ohne die ID)

Was kommt nun am Controller an?
0x01,0x02,0x03,0x04,0x11 für Deinen ersten Sendevorgang (was ja Käse 
ist)
und für den zweiten kommt an:
0x12,0x13,0x14 (ebenfalls Käse)

Das wird nämlich interpretiert mit:
Report #0x01 hat die Daten 0x02,0x3,0x04 0x11 empfangen
usw.

Und da liegt der Hase im Pfeffer!

Gruß Potter

von Dominik M. (dom1)


Lesenswert?

Also die Buffer sollten schon stimmen, die 4 Byte beziehen sich auf 1 
Byte Report ID und 3 Bytes Daten, da sollte eigentlich alles stimmen, da 
ich die gesendeten Daten auch empfange. Ich überprüfe das aber nochmal 
und setzte auf allen Seiten die Endpoint-Größe etwas höher (zurzeit 
genau 4 Byte groß).

Nochmal zum besseren Verständnis, wenn ich nur ein Handle nutze und nur 
einen Stream kann kommt es zu Deadlocks beim Senden oder Empfangen 
(Read, Write). Nutze ich ein Handle Read mit einem Stream Read und das 
gleiche nochmal für Write, funktioniert das Senden und Empfangen von 
Reports.

Um nun zu überpüfen ob sich etwas in meinem Mikrocontroller getan hat, 
sendet dieser eine Statusmeldung (Report besteht aus 1 Byte ID + 3 Bytes 
Daten) benötige ich eine Abfragemöglichkeit. Dafür hielt/halte ich einen 
eigenen Thread angebracht, dieser ruft nun filestream.BeginRead(...) auf 
(sofern nicht schonmal gestartet) und wartet auf Empfang. Es kann 
passieren, dass die Statusmeldungen häufig vorkommen, oder aber auch nur 
alle Stunde, daher möchte ich dem Benutzer meines Steuertools die 
Möglichkeit geben den Thread zu beenden. Dies schlägt leider fehl, 
sofern BeginRead noch auf Datenempfang wartet. Entweder ich schieße den 
Thread mit abort ab, was mir nicht sooo sehr gefällt, oder ich finde 
eine Möglichkeit BeginRead zu beenden. Den Filestream schließen und 
dispose aufrufen scheint auch nicht zu funktionieren (Thread nur mit 
abort killbar, Exceptions treten vorher beim schließen jedoch nicht 
auf!).
Einzige andere Möglichkeit wäre ein "Dummy"-Report zu senden um auf eine 
"Dummy"-Antwort des µC zu warten und BeginRead nicht zu starten. 
Funktioniert auch, nur was mache ich wenn mein µC warum auch immer nicht 
antwortet. Dafür Suche ich eine Lösung die mit BeginRead gestartete 
Routine zu beenden

Hier mal mein HID Report Descriptor:

const uint8_t CustomHID_ReportDescriptor[CUSTOMHID_SIZ_REPORT_DESC] =
  {
    0x06, 0xFF, 0x00,      /* USAGE_PAGE (Vendor Page: 0xFF00) */
    0x09, 0x01,            /* USAGE (Vendor Usage 01)         */
    0xa1, 0x01,            /* COLLECTION (Application)        */
    /* 7 bytes */

    /* Output */
    0x85, 0x01,             /*    REPORT_ID (1)               */
    0x09, 0x01,             /*    USAGE (Vendor defined) (Out data) */
    0x15, 0x00,             /*    LOGICAL_MINIMUM (0)         */
    0x26, 0xFF, 0x00,       /*    LOGICAL_MAXIMUM (255)       */
    0x75, 0x08,             /*    REPORT_SIZE (8 bit)         */
    0x95, 0x03,             /*    REPORT_COUNT (3)            */
    0x91, 0x82,             /*    OUTPUT (Data, Var, Abs, Vol)*/
    /* 22 bytes */

    /* Input */
    0x85, 0x02,             /*    REPORT_ID (2)               */
    0x09, 0x02,             /*    USAGE (Status data)         */
    0x15, 0x00,             /*    LOGICAL_MINIMUM (0)         */
    0x26, 0xFF, 0x00,       /*    LOGICAL_MAXIMUM (255)       */
    0x75, 0x08,             /*    REPORT_SIZE (8 bit)         */
    0x95, 0x03,             /*    REPORT_COUNT (3 Items)      */
    0x81, 0x82,             /*    INPUT (Data, Var, Abs, Vol) */
    /* 37 bytes */

    /* Input */
    0x85, 0x03,             /*    REPORT_ID (3)               */
    0x09, 0x03,             /*    USAGE (ADC IN)              */
    0x15, 0x00,             /*    LOGICAL_MINIMUM (0)         */
    0x26, 0xFF, 0x00,       /*    LOGICAL_MAXIMUM (255)       */
    0x75, 0x08,             /*    REPORT_SIZE (8 bit)         */
    0x95, 0x03,             /*    REPORT_COUNT (3 Items)      */
    0x81, 0x82,             /*    INPUT (Data, Var, Abs, Vol) */
    /* 52 bytes */

    0xc0                    /*    END_COLLECTION (Application)*/
    /* 53 bytes */
  }; /* CustomHID_ReportDescriptor */

Gruß und danke
Dominik

von Uwe (Gast)


Lesenswert?

Ja USB ist halt besch**. USB kann nicht gleichzeitig Senden und 
empfangen !
Es sind zudem zuviele Layer dazwischen und Puffer 
dazwischen.(FIFOs,Puffer,Kerneltreiber,MessageLoops,Windows buffer, .Net 
Zeug usw.) ein Senden oder empfangen dauert 1ms egal wie viele Bytes. Es 
muß ein flush gemacht werden bzw. es müssen dummy bytes hinten angehängt 
werden. USB Geräte klauen sich gegenseitig Slots (bzw. Bandbreite). 
Deine 4 oder 6 USB anschlüsse sind nur ein Kanal der auf eine HUB geht 
und teilen sich die Bandbreite und Slots in denen gesendet und empfangen 
werden kann. Alle hängen an den selben 2 Drähten D+ un D-, nur durch 
HUBs getrennt
(differentielles Halfduplex Leitungspärchen) es kann immer nur ein Gerät 
bedient werden und zwar entweder lesen oder schreiben dann das nächste 
und das nächste bis alles von vorne losgeht.

von Uwe (Gast)


Lesenswert?

http://www.tech-pro.net/intro_usb.html

Traffic on the USB is regulated using time. The unit of time is the 
frame. The length of each frame is governed by the bus clock, which runs 
at a rate of 1KHz, so there are 1,000 frames per second: one per 
millisecond. At the start of each frame a Start Of Frame (SOF) packet is 
sent over the bus, allowing isochronous devices to synchronise with the 
bus.

The concept of frames is central to how the bus shares out bus bandwidth 
among the various competing devices. The USB designers felt that it 
would not be possible to support several concurrent isochronous 
communication flows with fast sample rates using a system where each 
device must interrupt the host for each sample of data to be 
transferred. Consequently they designed the system so that isochronous 
devices are given guaranteed bandwidth by allocating them a proportion 
of the time in each frame.

Interrupt transfers are also to an extent time critical. When a pipe is 
created for an interrupt endpoint, a desired bus access period of 
between 1 and 255ms (10 and 255ms in the case of low speed devices) is 
specified. The system software polls the interrupt endpoint at an 
interval which ensures that if an interrupt transaction is pending it is 
dealt with within the desired time-frame.

von Dominik M. (dom1)


Lesenswert?

Mir ist schon klar, dass USB nicht exakt zum gleichen Zeitpunkt senden 
und empfangen kann. Auch ist mir bekannt, dass der Host den Report 
anfordern muss und mein Device nicht einfach einen Report raushaut.

Mein Stm32 führt bestimmte Aufgaben/Funktionen aus und soll bei 
Abschluss den PC darüber informieren, daher die Datenabfrage im Thread. 
Ich dachte nur, dass ich vielleicht nur einen Filestream bräuchte und 
den asynchronen Aufruf von BeginRead irgendwie auch ohne Datenempfang 
abbrechen könnte.

von Potter (Gast)


Lesenswert?

>0x06, 0xFF, 0x00,      /* USAGE_PAGE (Vendor Page: 0xFF00) */
>0x26, 0xFF, 0x00,       /*    LOGICAL_MAXIMUM (255)       */
Kann das sein? Wie war das mit little und big endian?

Schau mal hier:
http://www.lvr.com/forum/index.php?PHPSESSID=50eedf11baaa0ef231f87a52da7c1b0f&topic=676.msg2504#msg2504
oder hier:
http://stackoverflow.com/questions/8417830/is-it-possible-to-abort-cancel-a-filestream-beginread-asynchronous-read-operatio
Da hat noch jemand das Problem.

Ich empfehle Dir einen Original-Code für den STM32 aufzuspielen, die 
Routinen von J. Axelson als funktionierend anzusehen und zuerst einmal 
das zum Laufen zu bekommen, bevor Du selber in den Code eingreifst ohne 
zu wissen was Du tust.

von Dominik M. (dom1)


Lesenswert?

Potter schrieb:
>>0x06, 0xFF, 0x00,      /* USAGE_PAGE (Vendor Page: 0xFF00) */

Ich habe meinen Descriptor mit dem kleinen Programm HID Descriptor Tool 
von USB.org erstellt und dann den vorhandenen Descriptor aus einem STM32 
USB-Beispiel angepasst/erweitert. Wären die Werte falsch dürfte doch 
eigentlich gar nichts funktionieren? Da ja dann auch die HID Items 
falsch herum eingetragen wären... Steht so auch im Custom HID Beispiel 
von ST.

Potter schrieb:
> Ich empfehle Dir einen Original-Code für den STM32 aufzuspielen, die
> Routinen von J. Axelson als funktionierend anzusehen und zuerst einmal
> das zum Laufen zu bekommen, bevor Du selber in den Code eingreifst ohne
> zu wissen was Du tust.

Du wirst es kaum glauben aber genau so bin ich vorgegangen und übrigens 
die anderen beiden Threads stammen auch von mir. Ich versuche es einfach 
auf mehreren Wegen... Die Routinen von Axelson funktionieren auch 
grundsätzlich. Meinem Empfinden nach handelt es sich ja nun auch eher um 
ein Problem der C# .Net Funktion BeginRead, die nicht einfach 
abgebrochen werden kann. Evtl. auch nur im Zusammenhang mit dem 
geöffneten USB FileStream, da dieser wahrscheinlich kein End of Stream 
ausgibt und BeginRead daher nicht zurückkommt bzw. die Callback-Funktion 
aufruft. Wie soll das auch gehen, die USB-Kommunikation mit anderen 
Geräten läuft ja auch noch...

Gruß Dominik

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.