Nach dem es in diesen Thread:
Beitrag "USB CDC von Stefan Frings und WS"
eine ganze Weile um USB-CDC ging, habe ich am Coronafreiwochenende mal
versucht dem Code von WS so zu adaptieren, dass daraus ein
Midi-Interface mit 1 bis 4 Ports (jeweils MidiIn und MidiOut) wird. Zu
CDC findet man häufig was im Netz, Midi ist dagegen relativ selten oder
in komplexen Umgebungen zu finden. Getestet habe ich das mit einem
bluepill-Board mit stm32f103c8 (und auch mit einem Fake wo ein CKS32F103
drauf war).
Im Anhang ein abgespecktes Projekt für Segger ES.
Es ist nur der reine USB-Teil enthalten. Das Demoprogramm schickt das
was es per USB erhält 1 zu 1 wieder zurück, nicht mehr und nicht
weniger. Für ein richtiges Interface muss natürlich noch einiges mehr
getan werden. Das ganze besteht nur aus den Dateien main.cpp, usbmidi.h
und usbmidi.cpp. Alle anderen Dateien im Projekt hat Segger ES erzeugt
und wurden nicht verändert. Aufpassen muss man in usbmidi.cpp auf den
Namen des Interrupts, hier hat Segger seine eigene Logic. Soll das in
andere Umgebungen übernommen werden, muss der Name angepasst werden.
Wie hier ab Seite 16 beschrieben:
https://www.usb.org/sites/default/files/midi10.pdf
wird immer 4Byte weise gearbeitet. Deshalb habe ich die Ringbuffer auf
uint32_t umgestellt. Der Devicedescriptor ist sehr cryptisch, aber da
verschwende ich keine Lebenszeit damit das lesbarer zu machen. Für mich
und das was ich damit vor habe ist es halt ausreichend.
Vielleicht kann es ja jemand mal gebrauchen.
Falls Stefan oder Niklas das interessiert oder sie es in ihren Artikeln
verlinken oder verwenden oder veröffentlichen wollen, meinen Segen dafür
gibt es.
temp schrieb:> ...habe ich am Coronafreiwochenende mal> versucht dem Code von WS so zu adaptieren, dass daraus ein> Midi-Interface mit 1 bis 4 Ports (jeweils MidiIn und MidiOut) wird.
Ja, ganz nett.
Ich hätte dazu aber noch ne Anmerkung: Das ganze Gefummel mit
"DisableUsbIRQ();" mißfällt mir sehr - und es ist eigentlich auch
überflüssig, solange man ein paar Grundsätze beachtet.
Schauen wir mal das hier an:
1
// liefert true, wenn eine MidiMsg abholbereit ist
2
boolUsbRxAvail(void)
3
{
4
DisableUsbIRQ();
5
boolres=rxr!=rxw;
6
EnableUsbIRQ();
7
returnres;
8
}
Diese Funktion kann ja nur liefern, ob etwas zum Lesen vorhanden ist
oder nicht. Das macht sie, indem sie die Indizes rxr und rxw vergleicht.
Lesen der beiden Indizes darf jeder, aber Schreiben jeweils nur einer,
nämlich der, dem der Index gehört.
Der Index, der zum Füllen des Ringpuffers dient, ist quasi Eigentum des
Interrupthandlers. Der darf ihn schreiben.
Der Index, der zum Entleeren des Ringpuffers dient, gehört dem
UsbGetMidiMsg(void), diese Funktion darf ihn schreiben.
Was also kann passieren, wenn mitten in UsbRxAvail(void) ein
USB-Interrupt hineingrätscht? Dann kann sich nur rxw ändern - und zwar
nur in Richtung "noch mehr abholbereite Daten sind enthalten". Das
Umgekehrte (keine Daten enthalten) kann durch den Interrupt NICHT
passieren.
Nun gibt es prinzipiell für UsbRxAvail(void) nur 2 Möglichkeiten:
a) es gibt nix
b) es gibt was (egal wie viel)
Dabei ist es doch egal, ob ein Interrupt dazwischen dazu führt, daß es
NOCH MEHR Daten gibt, als zuvor schon vorhanden waren. Fall b) "es gibt
was" ändert sich nicht, wenn es noch mehr gibt.
Folglich ist das Verbieten des Interrupts in dieser Funktion schlichtweg
überflüssig und sollte besser aus dieser Funktion beseitigt werden.
Der eherne Grundsatz lautet eigentlich: niemand soll in dem Beritt eines
Anderen herumreiten. Hier wäre das, daß weder der Interrupt-Handler in
den Daten von main() und den Programmteilen, die von main() aus benutzt
werden herumschreiben sollte, noch daß main() oder eine Funktion, die
von main() aus benutzt wird, in den Daten des Interrupthandlers
herumschreiben sollte.
Das führt nämlich immer zu dem grundsätzlichen Problem, daß zwei "Leute"
auf denselben Daten schreibend herumreiten - und als Notnagel müssen
dann Interrupts verboten werden.
Das ist wirklich nur ein Notnagel und niemals ein guter Code. Man sollte
sowas also tatsächlich NUR DANN tun, wenn es sonst überhaupt nicht
anders zu machen geht. Aus meiner Sicht ist das aber eher "programmieren
mit dem Holzhammer".
Ich hatte es bei meinem Ur-Treiber so gemacht, daß keine anderen
Programmteile in den Daten des Interrupthandlers herumschreiben und daß
man beim Benutzen sich auch nicht um inneren Befindlichkeiten wie z.B.
das Senden von restlichen Zeichen zum Host zu kümmern hat. Andere haben
da gemeint, dort hineingrätschen zu müssen, z.B. per UsbTxFlush() - aber
das ist eigentlich eine Unannehmlichkeit, die bei einem virtuellen
COM-Port nicht erforderlich ist - und auch nicht vorhanden sein sollte.
Der VCP sollte das selbst erledigen, ohne daß sich jemand anderes drum
kümmern müßte.
Ob sowas bei MIDI nötig wäre, weiß ich nicht, hab mich um dieses
spezielle Thema noch nicht gekümmert. Aber m.W. basiert MIDI ja wohl
ursprünglich auf einem COM-Port mit galvanischer Trennung und 33K
Bitrate (oder waren es 32K ? - zu lange her).
Ob man da das Forcieren einer Übertragung nötig hat, oder mit dem
USB-internen Tick von 1 ms auskommt, um die aufgelaufenen Daten zu
transferieren? - ich tendiere da zu letzterem. Der interne Tick sollte
ausreichen.
W.S.
W.S. schrieb:>> Aber m.W. basiert MIDI ja wohl ursprünglich auf einem COM-Port> mit galvanischer Trennung und 33K Bitrate (oder waren es 32K ?> - zu lange her).>
USB-MIDI hat keine Bitrate, sondern 4byte-Pakete mit Midi-Event-Daten.
Das ist nicht auf Seriell aufgebaut.
Könnte an ahnen, wenn man schon mal USB-Deskriptoren angeschaut hat.
W.S. schrieb:> Ich hätte dazu aber noch ne Anmerkung: Das ganze Gefummel mit> "DisableUsbIRQ();" mißfällt mir sehr - und es ist eigentlich auch> überflüssig, solange man ein paar Grundsätze beachtet.
Ja, in diesem Punkt gebe ich dir Recht. Ich hatte halt nur den letzten
Stand verwendet den Niklas in github gestellt hat.
W.S. schrieb:> Aber m.W. basiert MIDI ja wohl> ursprünglich auf einem COM-Port mit galvanischer Trennung und 33K> Bitrate (oder waren es 32K ? - zu lange her).
Das würde zum tragen kommen, wenn man wirklich ein reines Interface
bauen will mit In/Out über UART. Für alle anderen Fälle, z.B. wenn man
ein paar Potis oder Schalter in Midi-Daten umsetzt keine Baudrate
relevant.
W.S. schrieb:> Ich hatte es bei meinem Ur-Treiber so gemacht, daß keine anderen> Programmteile in den Daten des Interrupthandlers herumschreiben und daß> man beim Benutzen sich auch nicht um inneren Befindlichkeiten wie z.B.> das Senden von restlichen Zeichen zum Host zu kümmern hat. Andere haben> da gemeint, dort hineingrätschen zu müssen, z.B. per UsbTxFlush() - aber> das ist eigentlich eine Unannehmlichkeit, die bei einem virtuellen> COM-Port nicht erforderlich ist - und auch nicht vorhanden sein sollte.> Der VCP sollte das selbst erledigen, ohne daß sich jemand anderes drum> kümmern müßte.
Ja die anderen haben Bugs in deinem Code gefixt, was doch ganz
freundlich ist.
Mit einem Debugger übrigens ;)
Zunächst vielen Dank für den Treiber. Der Beitrag ist schon einige
Monate alt, ich habe den Treiber jetzt ausprobiert und er funktioniert
für Double- und Quad-, aber nicht für Single-Ports. Eventuell liest das
ja noch der Thread-Ersteller und kann etwas dazu sagen?
Thorsten S. schrieb:> nach:> 0x09, 0x02, 0x65, 0x00, 0x02, 0x01, 0x00, 0x00, 0x32, // Config
Wenn dem wirklich so ist, steckt da aber noch ein dicker Bug im Code.
Beim vorletzten Byte muss bit7 immer 1 sein. Das schreibt die Spec seit
usb1.1 vor.
Thomas Z. schrieb:>> nach:>> 0x09, 0x02, 0x65, 0x00, 0x02, 0x01, 0x00, 0x00, 0x32, // Config>> Wenn dem wirklich so ist, steckt da aber noch ein dicker Bug im Code.> Beim vorletzten Byte muss bit7 immer 1 sein. Das schreibt die Spec seit> usb1.1 vor
Ja das ist korrekt. Leider habe ich das beim Kopieren vermasselt.
Allerdings habe ich auch nur unter W10 64bit getestet. Alte
Windows-Versionen habe ich nicht mehr. Und da ist dieses Bit völlig
bedeutungslos. Ob ich bei allen 3 Varianten 0x80 oder 0x00 angebe ist
egal, es geht immer. 0x80 ist aber sicher richtig.
Bleibt die Frage nach den Unterschieden. Wie und mit was übersetzt du
bzw. wie testest du?
temp schrieb:> Alte Windows-Versionen habe ich nicht mehr. Und da ist dieses Bit völlig> bedeutungslos
Das Bit hatte soweit ich mich erinnere bei der usb1.0 Spec eine
Bedeutung. Irgendwas mit Bus + Selfpowered zusätzlich zu bit6. Es ist
also nicht verwunderlich dass es komplett ignoriert wird.
Bleibt die Frage warum das eine Änderung bei Thorsten bewirkt.
temp schrieb:> Ob ich bei allen 3 Varianten 0x80 oder 0x00 angebe ist> egal, es geht immer. 0x80 ist aber sicher richtig.> Bleibt die Frage nach den Unterschieden. Wie und mit was übersetzt du> bzw. wie testest du?
Bei mir läuft der Single Port nur, wenn ich 0x00 statt 0x80 verwende.
Ich verwende die STM32 Cube IDE, also GCC. Die WS-CDC Version läuft bei
mir anstandslos, also am Compiler wird es nicht liegen.
Ich teste mit Ableton Live. Wenn ich 0x80 verwende, erscheint das MIDI
Device zwar im Gerätemanager, aber Ableton Live zeigt keinen Midi Port
mehr an. Bei 0x00 ist der Single Port da.
Ich habe in UsbTreeView gesehen, dass das Bit nicht gesetzt ist, wird
dort als Fehler ausgegeben. Habe dann das Bit wieder gesetzt, neu
compiliert, und plötzlich sieht Ableton Live den Midi Port. Muss ich das
verstehen? Erdstrahlung oder was? Ich hatte vorher zig-fach probiert,
Ableton x-mal neu gestartet, etc. und der Midi Port wurde nicht erkannt.
Übrigens auch in Studio One nicht (das hatte ich getestet, um GANZ
sicher zu gehen).
Ich verstehe es nicht, aber es ist ja immer schön, wenn es dann läuft.
Die Deskriptoren sind falsch. Die Reihenfolge ist falsch.
wTotalLength im AC header muss die Länge der PinDesc. angeben. Die
PinDesc müssen direkt nach dem AC Header folgen.
Zusätzlich sollten die Bulk EPs 7Bytes lang sein. Mit den 9 Bytes wird's
vermutlich aber auch gehen.
Schau dir das Beispiel in der UsbMidi Spec an.
Thorsten S. schrieb:> Muss ich das verstehen?
Das liegt vermutlich daran weil bei dir alle Versionen die gleichen IDs
und SNs haben. Windows merkt sich solche Sachen. Sobald du die
entsprechenden Einträge aus der Registry schmeißt, oder die pnf Dateien
löschst wird das Problem wieder kommen.
Ich werde morgen Mal einen Satz Deskriptoren posten die ich verwende.
man sollte späd abends keine Behauptungen mehr aufstellen die
Deskriptoren sind vollkommen korrekt.
Der Fehler liegt vermutlich an der Seriennummer. Setze die Id für die
Seriennummer im Device Desc. mal auf 0.
UsbAudio.sys braucht keine SN.
Thomas Z. schrieb:> man sollte späd abends keine Behauptungen mehr aufstellen die> Deskriptoren sind vollkommen korrekt.
Vielen Dank, das freut mich dass ich da erst mal keinen Fehler drin
habe. Das mit der Seriennummer ist interessant. Vielleicht hilft das ja.
Ich habe bei mir nur mit Sonar getestet und hatte keine Probleme.
im Windows Gerätemanager kann man 'ausgeblendete Geräte' anzeigen
lassen. Vielleicht hilft es den alten Geist zu löschen damit Windows
keine gepufferten Daten verwendet.