Hallo Leute!
Ich versuche eine schlanke USB Schnittstelle auf einem STM32F042 zu
implementieren.
Über den USB sollen kleine Datenpakete (Messwerte, wenige Bytes) etwa im
Sekundentakt übertragen werden. Zu Konfigurationszwecken gibt es auch
ein paar Parameter die über Terminal eingestellt werden sollen. Ist aber
alles nicht Zeitkritisch.
Hab einen ST-Link V2 und arbeite mit CubeIDE.
Die Variante aus dem Code-Generator von ST (cubeMX) hab ich schon
erfolgreich zum Laufen gebracht, allerdings ist mir die Implementierung
zu groß.
Ich hab daher versucht, die Variante von
http://stefanfrings.de/stm32/stm32l0.html (basierend auf der von W.S.)
zu verwenden, kriege diese aber bei mir nicht zum Laufen.
Der Controller meldet sich am PC nicht an, scheinbar wird einfach
garnichts übertragen.
Da meine C-Kompetenzen nicht gerade berauschend sind und ich mit USB
bisher nichts zu tun hatte, hab ich versucht, die Ursache durch
Vergleichen der cubeMX-Version mit der von W.S. zu finden.
Bisherige Erkenntnis:
Beim Initialisieren der USB-Register / Endpoint-Konfiguration sind die
Schreib-Operationen auf die EPxR-Register nicht erfolgreich.
Die Register sind auch nach dem Schreiben noch leer.
Die Zeile
USB_EP0R =
((3 << 12) | /* STAT_RX = 3, also Empfang enabled */
(2 << 4) | /* STAT_TX = 2, also NAK erstmal */
(1 << 9) | /* EP_TYPE = 1, also Control */
logEpCtrl);
in InitEndpoints(void) ist wirkungslos.
Ich dachte zuerst an ein Taktproblem und hab daher die Clock-Config
vollständig von der cubeMX-Variante übernommen, um das als Ursache
auszuschließen.
Interessanterweise funktioniert aber aucgh die cubeMX-Variante nur dann,
wenn ich beim Initialisieren keine Breakpoints setze.
Mit Breakpoints ist es auch dort so dass ein Schreiben auf die EPxR
wirkungslos ist, der Controller meldet sich garnicht am PC.
Hat jemand eine Idee woran das liegen könnte, bzw einen Ansatz zur
weiteren Fehlersuche?
Vielen Dank,
Alex
Alex schrieb:> Die Zeile ... in InitEndpoints(void) ist wirkungslos.
Was genau meinst du mit "wirkungslos"?
Hast du den Pull-Up Widerstand an D+ hinzugefügt oder USB_BCDR_DPPU=1
gesetzt (nicht beides zusammen!) ?
Stimmt der Name der ISR in usb.c (USB_IRQHandler) bzw. hast du ihn
entsprechen deiner Interrupt-Vektor Tabelle angepasst?
Taktest du den USB Port mit dem 48MHz HSI Oszillator?
Hast du die USB Schnittstelle eingeschaltet?
> // Enable USB with pull-up> SET_BIT(RCC->APB1ENR, RCC_APB1ENR_USBEN);> SET_BIT(USB->BCDR, USB_BCDR_DPPU);
Du solltest dein Projekt in compilierbarer Form anhängen, damit wir
nicht endlos viele Fragen stellen müssen und damit jemand dein Problem
reproduzieren kann.
(Ich habe keinen STM32F0, deswegen kann ich es nicht ausprobieren).
> Interessanterweise funktioniert aber aucgh die cubeMX-Variante nur dann,> wenn ich beim Initialisieren keine Breakpoints setze.
Die USB Schnittstelle muss permanent alle Events binnen weniger ms
beantworten, deswegen fällt sie aus, sobald ein Breakpoint erreicht
wird. Benutze zum Debugging lieber (serielle) Text-Meldungen.
Alex schrieb:> STM32F042
Du darfst nicht annehmen, daß ein Lowlevel-Treiber für den STM32F103
einfach so auch auf einem STM32F042 funktioniert. Deshalb ist das
gründliche Lesen des zum USB-Device gehörigen Kapitels in zutreffenden
RefManual zwingend notwendig.
Stephanus hat meinen Treiber auf den STM32F303 umgeschrieben und im
Prinzip müßtest du ihn auf den STM32F042 umschreiben. Jetzt hab ich das
Manual für diesen µC nicht gelesen, kann dir also nicht sagen, ob da nur
ne winzige oder eine umfängliche Anpassung nötig ist.
Eines ist aber klar: alle Vorbereitungen (Pinzuweisung, Takt richtig
aufsetzen, richtigen Interrupt wählen, NVIC richtig aufsetzen) mußt du
selber machen.
Ich hatte (wenn ich mich recht erinnere) auch ein Codestück zum
Postmortem-Debugging beigefügt, das dazu dient, Meldungen, die man
irgendwo in den Treiber einfügt, im RAM zwischenzuspeichern, so daß man
diese von außerhalb des Treibers dann abfragen kann.
W.S.
Hallo Stefan und W.S.
Schon jetzt Danke für eure Hilfe und vor allem dafür, dass ihr euren
Code für alle bereitstellt !!!!!
> Hast du den Pull-Up Widerstand an D+ hinzugefügt oder USB_BCDR_DPPU=1 gesetzt
(nicht beides zusammen!) ?
ja, DPPU wird gesetzt, der PC reagiert darauf -> typische USB-Gerät
nicht erkannt - Meldung; Der Controller tut sonst nichts mehr -> Enum.
schlägt fehl
> Stimmt der Name der ISR in usb.c (USB_IRQHandler) bzw. hast du ihn entsprechen
deiner Interrupt-Vektor Tabelle angepasst?
ja, ist beim F042 auch "USB_IRQHandler" und auch die Nummer ist 31
> Taktest du den USB Port mit dem 48MHz HSI Oszillator?
Der Takt kommt vom USB. Daher hatte ich auch zuerst ein Taktproblem
vermutet und meine Takt-CFG komplett durch die vom cubeMX (die ja
funktioniert) ersetzt. Da ist zwar jetzt die HAL involviert, aber ich
kann zumindest ausschließen(nach meinem Kenntnisstand/bestem Wissen
ausschließen), dass es da ein Problem mit der CLK-Config gibt. Alle
Timer-Takte usw. hab ich geprüft und stimmen.
> Hast du die USB Schnittstelle eingeschaltet?+++
Die Einschalt-Prozedur ist die von W.S. Code, ich hab begonnen die
einzelnen Schritte mit dem RefManual abzugleichen/zu verstehen und bin
eben bis zu den Schreibzugriffen auf die EPxR-Register gekommen.
>> Die Zeile ... in InitEndpoints(void) ist wirkungslos.>Was genau meinst du mit "wirkungslos"?
Das Register ist nach dieser Programmzeile immer noch 0, die Zeile hat
keine Wirkung.
Das gilt für alle Zugriffe auf die EP-Register, Schreiben auf das
CNTR-Register beispielsweise klappt....
> Die USB Schnittstelle muss permanent alle Events binnen weniger ms> beantworten, deswegen fällt sie aus, sobald ein Breakpoint erreicht> wird. Benutze zum Debugging lieber (serielle) Text-Meldungen.
Ich verstehe, dass es ab dem Zeitpunkt, an dem der Pullup gesetzt wird,
zeitkritisch ist.
Hintergrund meiner Aussage war, dass beim cubeMX-code auch die
EP-Register dann leer bleiben.
Meine Interpretation ist, dass die Konfiguration dieser in den IRQs
durchgeführt wird, und nicht wie bei W.S. davor. Oder lieg ich da
falsch?
Zugegebenerweise ist mir der cubeMX-Code extrem schwer nachzuvollziehen.
Ich stehe mit der ganzen Thematik ziemlich am Anfang, und hab einfach
begonnen den Code und das RefManual Schritt für Schritt durchzuarbeiten.
Bis zu dem Punkt, an den ich gekommen bin, gab es imho keine nötige
Anpassung für den F042. Die Schritte bis dorthin sind auch im Manual so
beschrieben. Dass die EPxR nicht geschrieben werden, ist für mich
einfach nicht nachvollziehbar/verständlich.
Alex
Hallo Alex,
bitte die genaue Funktion der Register nachlesen.
Es ist schon spät..und so aus den alten grauen Zellen herraus:
CNTR löst z.B. Funkionen im USB-Core aus.
EP_R wird durch die USB-Core gesetzt ( z.B. USB-Empfang )und kann nur
gelöscht werden.
Pieter
Alex schrieb:> Der Takt kommt vom USB.
Wiebitte?
Der Takt kommt normalerweise vom angeschlossenen Quarz per
Quarzoszillator im Chip. Der muß angeworfen werden, dann die PLL und das
richtige Teilerverhältnis in der Taktversorgung, so daß der
USB-Peripherie-Core seine genauen 48 MHz kriegt.
Wenn das bei deinem Chip anders läuft, dann stehen mMn bei dir größere
Änderungen am Treiber an, deren Umfang ich hier nicht abschätzen kann.
Ich habe aber ein anderes Negativbeispiel: Bei einem Kinetis (von
Freescale, exakten Typ hab ich vergessen) ist die Taktversorgung per
Quarz so schlecht (jittert), daß dort für den USB auf den internen 48
MHz Takt aus einem RC-Oszillator zurückgegriffen werden muß. Da dieser
jedoch frequenzmäßig zu instabil ist, muß er per Software auf die
Signale vom Host am USB aufsynchronisiert werden, bevor irgend eine
Kommunikation am USB möglich war. Als ich das gelesen hatte, war der
Chip bei mir unten durch.
Also gönne deinem Chip einen passenden Quarz und benutze HSE+PLL. Alles
andere halte ich für Pfusch, der nur mit Klimmzügen zum sauberen Laufen
zu bringen ist. Wenn dein Chip für den USB als Takt einen HSI vorsieht,
dann gibt es im USB-Core auch irgend welche Synchronisier-Tricks, die in
meinem Code nicht vorgesehen sind.
W.S.
Alex schrieb:> Der Takt kommt vom USB
Das ist unklar. Das USB Kabel stellt kein Taktsignal zur Verfügung. Du
musst den HSI Oszillator konfigurieren.
Alex schrieb:> Die Einschalt-Prozedur ist die von W.S. Code
Der Code von W.S. schaltet das USB Interface nicht ein. Das musst du
selber machen. Ich habe die beiden relevanten Zeilen oben zitiert.
Du steckst da viel Vertrauen in die HAL. Aber gerade weil du die HAL
jetzt nur noch zum Teil verwendest, kann dabei durchaus etwas ganz
anderes heraus kommen, als geplant. Das solltest du prüfen, insbesondere
die Konfiguration von Taktversorgung und Interrupts.
Setze einen Unterbrechungspunkt in die Zeile "UsbSetup()" und prüfe den
Inhalt aller relevanten Register.
Bedenke dabei, dass meine main.c von einer ganzen Reihe Default-Settings
ausgeht, die der Chip beim Reset einstellt. Die HAL fummelt aber daran
herum.
Alex schrieb:> Das Register ist nach dieser Programmzeile immer noch 0, die Zeile hat> keine Wirkung.
Klingt danach, als ob die entsprechende Funktionsgruppe im RCC nicht
eingeschaltet ist.
Alex schrieb:> Meine Interpretation ist, dass die Konfiguration dieser in den IRQs> durchgeführt wird, und nicht wie bei W.S. davor. Oder lieg ich da> falsch?
Keine Ahnung, wie das in der HAL umgesetzt wurde. Durch den
Spaghetti-Code blicke ich nicht durch. Eine Initialisierung im Interrupt
ist jedenfalls unwahrscheinlich, weil die ISR zwangsläufig erst nach der
Initialisierung aufgerufen wird.
Hast du einen guten Grund, warum du deinen Quelltext immer noch nicht
vorzeigst? Ich habe keine Lust, mich ohne Quelltext weiter in deinen
Kopf hinein zu denken.
W.S. schrieb:> Wenn dein Chip für den USB als Takt einen HSI vorsieht,> dann gibt es im USB-Core auch irgend welche Synchronisier-Tricks, die in> meinem Code nicht vorgesehen sind.
Korrekt. Das ist im Reference Manual im Clock Recovery System
beschrieben. Sollte man vielleicht mal lesen. Der STM32 synchronisiert
seinen Oszillator ziemlich selbstständig mit den SOF Paketen vom USB
Port, und das ist per Default nach dem Reset aktiv.
Aber die HAL fummelt erfahrungsgemäß gerne an den Default Werten herum,
darüber bin ich bereits mehrfach gestolpert.
Vorweg: Es läuft (noch nicht voll getestet...)
Fehlerursache:
ich hatte im SYSCFG versehentlich das remap der Pins PA11 & PA12
überschrieben.
Der F042 hat die USB-Pins als shared-pins ausgeführt, daher ist das
remap nötig.
Lustigerweise kann man dann zwar beim USB alles mögliche initialisieren,
aber die Schreibzugriffe auf die EPxR-Register gehen nicht.
Nochwas zum USB-Sync Takt:
Der F042 (und vmtl andere STM32 auch) haben ein CRS (clock recovery
system), das den internen RC-Osc mit den SOFs des USB synchronisiert.
Die dafür nötigen Funktionen sind in Hardware implementiert und werden
vor dem USB-Treiber initialisiert.
Ich hab mir diese Teile im Manual genau durchgearbeitet, die
Konfiguration passt und funktioniert.
Mit dem USB-Treiber hat das nichts zu tun, der Takt läuft schon vorher.
Hab mir auch angesehen, wie das Ding auf Temperaturänderungen reagiert
;-), ich würd sogar sagen, dass die CLK auf diese Weise stabiler* ist
als mit einem normalen XO (eben wegen des tempcos).
*: genau genommen ist sie eigentlich nicht stabil, sie ist nur auf den
USB synchronisiert -> optimal für eine stabile USB-Verbindung. Wenn man
eine sehr exakte ReferenzCLK in der Schaltung benötigt (z.B: als
Referenz für Messungen etc), ist das eher suboptimal.... aber darum
gehts hier ja nicht.
Jedenfalls hab ich nirgends einen Hinweis gefunden, dass die
Programmierung des USB-Treibers deshalb irgendwie angepasst werden
müsste.
Zum Thema HAL:
ist mir auch nicht ganz geheuer, vor allem weil ich die vielen
Unterprogramme extrem unübersichtlich finde.Hat aber sicher auch was mit
meiner C-Schwäche zu tun...
Nur würde ich die HAL nicht als Spaghetti-Code bezeichnen, die ist wohl
eher das Gegenteil davon....
Alex
Die HAL ist ziemlicher Spaghetticode und erweckt bei mir den Eindruck,
dass da Leute Angst vor Hardare haben und darum mehrere (sinnlose)
Zwischenschichten eingezogen haben.
Wenn der Rechner sieht, dass ein Gerät versucht sich zu melden, wie weit
ist denn der Protokollstack im Controller lauffähig? Kriegst Du irgend
welche Requests und schickst Antworten raus?
Guido Körber schrieb:> Die HAL ist ziemlicher Spaghetticode und erweckt bei mir den Eindruck,> dass da Leute Angst vor Hardare haben und darum mehrere (sinnlose)> Zwischenschichten eingezogen haben.
Ist sie nicht. Es ist eher so, dass viele Leute bis heute nicht
verstanden haben, dass die Zwischenschichten Abstraktionsschichten sind
die bei richtiger Konfiguration des Compilers keinen (!) Overhead
verursachen. Abstraktion ist sinnvoll und kostet heutzutage bei
richtiger Verwendung weder Laufzeit noch Code. Ein USB-Treiber, von dem
dagegen nichts direkt wiederverwendbar ist, ist nicht zeitgemäß (sorry
W.S., aber so schreibt man moderne Software nicht).
avr schrieb:> Es ist eher so, dass viele Leute bis heute nicht> verstanden haben, dass die Zwischenschichten Abstraktionsschichten sind> die bei richtiger Konfiguration des Compilers keinen (!) Overhead> verursachen.
Nein. Definitiv nicht.
Die HAL Funktionen belegen ein vielfaches des Flash Speichers, den man
ohne HAL brauchen würde. Der USB CDC Treiber ist das beste
Paradebeispiel dafür. Vergleiche den mal mit dem Werk von W.S., dann
siehst du es.
> Ein USB-Treiber, von dem dagegen nichts> direkt wiederverwendbar ist, ist nicht zeitgemäß
Das ist mir zu pauschal. Es gibt nicht nur den einen richtigen Weg.
avr schrieb:> Abstraktion ist sinnvoll und kostet heutzutage bei> richtiger Verwendung weder Laufzeit noch Code. Ein USB-Treiber, von dem> dagegen nichts direkt wiederverwendbar ist, ist nicht zeitgemäß (sorry> W.S., aber so schreibt man moderne Software nicht).
Das ist auch nur die ausgeleierte Argumentation von Leuten die nur
Arduino-Libs zusammen stöpseln. Die selben Argumente kamen als die
STM-Libs noch nicht HAL hießen. Sie StdPerLib ist so schön portabel und
man kann sie immer wieder verwenden. Und dann? Angeschissen wenn STM der
Meinung ist die neuen MCUs bekommen nur noch die HAL. Und in 3 Jahren
vielleicht wieder was neues.
Das einzige was sich nicht ändert sind die Register.
Mir ist so ein kompakter Code jedenfalls wesentlich lieber wie das ganze
HAL-Geraffel. Und von wegen modern und zeitgemäß. Ich will vordergründig
ein Problem lösen und nicht ein schönes modernes Programm schreiben. Wer
ein paar Jahre auf dem Buckel hat, hat viele Säue durchs Dorf rennen
sehen. Nur Idioten rennen jeder hinterher. Und STM hat schon einige Säue
losgelassen...
Nachdem die NOP() Problematik im Code von W.S. eliminiert ist, ist er
jedenfalls 1000mal besser als alle anderen mir bekannten
Implementationen.
an Stefan ⛄ F. noch die Frage, hast du die von mir vorgeschlagene
Änderung bezüglich der DoSetAddress() Funktion und der Eliminierung des
letzten nötigen NOP()'s mal probiert?
Beitrag "Re: STM32F303 CAN und USB gleichzeitig"
temp schrieb:> hast du die von mir vorgeschlagene> Änderung bezüglich der DoSetAddress() Funktion und der Eliminierung des> letzten nötigen NOP()'s mal probiert?
Ja läuft, habe ich in meine Codebeispiele übernommen. Bitte
entschuldige, dass ich vergessen habe, Feedback zu geben.
Stefan ⛄ F. schrieb:> Ja läuft, habe ich in meine Codebeispiele übernommen. Bitte> entschuldige, dass ich vergessen habe, Feedback zu geben.
Ich hab mir gerade das Beispiel in STM32L073_usb_test.zip runtergeladen.
Da steht noch
Korrekt macht man das mit dem Adresse setzen so, dass man nach der
Status Stage - d.h. nach dem Senden des leeren ACK Pakets, beim
USB_ISTR_CTR-Interrupt - die Adresse übernimmt (DADDR Register), z.B.:
https://github.com/Erlkoenig90/f1usb/blob/minimal/src/usb.cc#L499
Dadurch passiert es exakt im richtigen Moment, und man blockiert nicht
den Prozessor durch eine lange Nop-Schleife. Bei einer solchen ist die
Dauer auch kritisch - ist sie zu lang, verpasst man das nächste Paket
vom Host (ok, wird automatisch neu versucht), ist sie zu kurz, kommt das
ACK nicht an.
Siehe auch:
https://www.mikrocontroller.net/articles/USB-Tutorial_mit_STM32#Adresszuweisung
Genau der SetAdressrequest ist die Ausnahme und funktioniert etwas
anders als alle anderen Requests. Üblicherweise wird bei einem Requests
erst dann mit ACK bestätigt wenn der Requests alle Aktionen bearbeitet
hat. Bei SetAddress wird sofort bestätigt (unter der alten Adresse) und
dann umgeschaltet allerdings erst wenn das ACK erfolgreich war. Nop ist
nur ein Workarround für einen Fehler im Framework.
Thomas
temp schrieb:> avr schrieb:>> Abstraktion ist sinnvoll und kostet heutzutage bei>> richtiger Verwendung weder Laufzeit noch Code. Ein USB-Treiber, von dem>> dagegen nichts direkt wiederverwendbar ist, ist nicht zeitgemäß (sorry>> W.S., aber so schreibt man moderne Software nicht).>> Das ist auch nur die ausgeleierte Argumentation von Leuten die nur> Arduino-Libs zusammen stöpseln. Die selben Argumente kamen als die> STM-Libs noch nicht HAL hießen. Sie StdPerLib ist so schön portabel und> man kann sie immer wieder verwenden. Und dann? Angeschissen wenn STM der> Meinung ist die neuen MCUs bekommen nur noch die HAL. Und in 3 Jahren> vielleicht wieder was neues.> Das einzige was sich nicht ändert sind die Register.
- Ein Großteil der Arduino-Libs sind Käse, das Argument ist reine
Polemik
- Die PeripheralLib war alles andere als kompatibel, die war eigentlich
sehr Hardware-nah.
- Die HAL ist schon deutlich besser und wird sich so schnell nicht
ändern.
- man kann sich durchaus seine eigene HAL schreiben, aber bitte nicht
gleich das Applikation-Layer integrieren.
> Mir ist so ein kompakter Code jedenfalls wesentlich lieber wie das ganze> HAL-Geraffel. Und von wegen modern und zeitgemäß. Ich will vordergründig> ein Problem lösen und nicht ein schönes modernes Programm schreiben.
Darum geht es nicht. Es geht um Wartbarkeit, Wiederverwendbarkeit und
Testbarkeit von Code.
Ich könnte den Code von WS in den wenigsten Fällen verwenden, obwohl ich
sehr oft USB einsetze. Nur virtuelle COM-Ports habe ich eher selten aus
USB-Klasse. Und daher setze ich auf Abtraktion, so dass ich nur einen
sehr kleinen Teil austauschen muss, um die Klasse zu ändern. Auf der
anderen Seite kann ich sehr einfach mehrere Klassen gleichzeitig
implementieren.
Es gibt nunmal 2 Extrema in die man sich nicht begeben sollte.
Einerseits dem W.S. seinen Magic Number Code und dem aufgeblasenem HAL
Code.
Der Lagenaufbau von sich des STM32HAL ist garnicht mal so schlecht, aber
der Code selbst ist es.
zB wird in einem IRQ direkt an der Statemachine eines Task rumgefummelt
und DANN wird eine Message geschickt zum Task aufzuwachen. WTF?
Niklas G. schrieb:> Korrekt macht man das mit dem Adresse setzen so, dass man nach der> Status Stage - d.h. nach dem Senden des leeren ACK Pakets, beim> USB_ISTR_CTR-Interrupt - die Adresse übernimmt (DADDR Register), z.B.:
Genau das habe ich am Code von W.S. geändert. Der Codeschnipsel oben ist
der Zustand vor der Änderung. Alles weitere kann im verlinkten Thread
nachgelesen werden.
avr schrieb:> Ich könnte den Code von WS in den wenigsten Fällen verwenden, obwohl ich> sehr oft USB einsetze.
Und ich habe bisher ausschließlich virtuelle COM-Ports verwendet, und
dafür ist dieser schlanke Code ideal. Solange ich nichts anderes brauche
besteht deshalb auch keine Notwendigkeit ein funktionierendes System
(mit Fehlern) aufzuhübschen.