Hallo, ich bin autodidaktischer Hobby-Programmierer und steige seit einiger Zeit vom AVR auf den STM32 um (ich programmiere in C) und habe seit Tagen Probleme, den UART in den Griff zu bekommen. Obwohl ich viel gelesen habe, habe ich den Eindruck, als ob mir da noch was vom Grundverständnis fehlt. ich möchte bei einer 8-bit-Übertragung ein ganzes Byte empfangen und dann auswerten. mein bisheriges Vorgehen: Ich habe einen Buffer definiert: uint8_t byte; UART init: Das mache ich mit CubeMX und habe den Eindruck, dass ich da alles passend eingestellt habe. In die main.c kommt dann: HAL_Init(); MX_USART2_UART_Init(); Den Interrupt aktivieren: HAL_UART_Receive_IT (&huart2, &byte, 1); Dann muss nach meinem bisherigen Verständnis mein Code für den Interrupt in folgende Funktion: HAL_UART_RxCpltCallback Es gehört sich natürlich nicht, das in einem Interrupt zu tun, aber ich habe zum ersten Testen jetzt nicht mit Flags und Buffern gearbeitet, sondern versuche die eingehenden Daten direkt im Interrupt auszuwerten (nur zum Testen). Am Ende aktiviere ich den Interrupt wieder für den nächsten Durchgang. Sieht im Ergebnis etwa so aus: void HAL_UART_RxCpltCallback (UART_HandleTypeDef *huart){ HAL_GPIO_TogglePin (LED1); if( byte==0x90){ HAL_GPIO_TogglePin (LED2); } HAL_UART_Receive_IT (&huart2, &byte, 1); } Mein bisheriges Ergebis: Immer, wenn Daten beim Uart ankommen, toggelt LED1, LED2 toggelt nie, auch wenn ein "0x90" an den UART gesendet wird. Was mache ich falsch? Würde mich über Anregungen sehr freuen. Vielen dank. Beste Grüsse OG
falsche Bitrate? Gerade beim Testen von Neuer/Fremder Software ist ein Debugger hilfreich. Breakpoint setzen und gucken was im byte (blöder Variablenname) ankommt. Bitrate evtl. falsch wegen falscher Clock Configuration, die sollte man sich in CubeMX genau ansehen.
karadur schrieb: > Sollte das nicht vor die Abfrage? das ist ok, damit wird gleich wieder der nächste Empfang gestartet.
>auch wenn ein "0x90" an den UART gesendet wird.
Dumme Frage: Sendest Du ein Byte mit dem numerischen Wert 0x90 oder
einen String "0x90"?
Warum benutzt Du keinen Debugger, um herauszufinden, was tatsächlich im
UART Datenregister ankommt?
>Bitrate evtl. falsch wegen falscher Clock Configuration,
Zur Klärung dieser und ähnlicher Fragen holt man seinen 5EUR Logic
Analyzer in der Kiste.
Ja, vielleicht viel zu lernen auf einmal, aber früher oder später
braucht man das halt.
Hallo, vielen Dank für die schnellen Antworten. @karadur: Ich aktivieren den Interrupt einmal in der main.c beim Starten und dann kann er das erste mal empfangen und immer, wenn er empfangen hat, kommt die ISR und darin wird er (ganz am Ende) fürs nächste mal neu gestartet. So habe ich das bisher verstanden - keine Ahnung, ob das stimmt. @johannes: Die Clock ist ein guter guter Hinweis. Beim Testen der Timer habe ich zwar alles eingestellt und die Zeiten auch mal gestoppt und es stimmte, aber vielleicht hat sich das was verstellt - werde ich mir nochmal mit Ruhe anschauen. Bisher habe ich nicht mit Debugger gearbeitet - vermutlich ist es jetzt an der Zeit, sich das mal anzuschauen. Nehme gerne weitere Hinweise entgegen ;-) Vielen Dank. Beste Grüsse OG
Hallo, @hochladen: Vielen Dank für die Hinweise. Ich sende den nummerischen Wert 0x90 (es handelt sich genau genommen um MIDI-Daten). Mit dem Debugger habe ich ja eben schon gechrieben: es scheint wohl an der Zeit zu sein, sich das anzuschauen. Beste Grüsse OG
Die Abfrage von if( byte==0x90) bringt nichts, da byte ja nirgends gesetzt wird
An F. schrieb: > immer, wenn er empfangen > hat, kommt die ISR und darin wird er (ganz am Ende) fürs nächste mal neu > gestartet. Normalerweise wird ein Interrupt einmal eingerichtet und tritt dann jedesmal auf, wenn das Ereignis eintritt, sprich ein Byte empfangen wurde. Solange bis man ihn gezielt wieder abschaltet, was oft nicht notwendig ist, bis das Programm beendet wird. Was passiert wenn ein schon gestarteter Interrupt erneut initialisiert wird kann man ohne nähere Analyse nicht sagen. Georg
Unabhängig von dem akuten Problem: Wir hatten das Thema erst vor einigen Wochen. Dein Vorhaben ist mit der HAL nicht ganz unproblematisch. Die Routinen der HAL sind toll, wenn man Datenpakete mit fester Größe empfangen will. Bei einzelnen Bytes läuft es aber auf einen gewaltigen Overhead hinaus. Damals wurden zwei Lösungsvorschläge erarbeitet: a) Die HAL nicht benutzen, eine eigene ISR schreiben. b) Die HAL soll immer abwechselnd zwei Puffer (mit einigen Bytes) via DMA befüllen, die beim Auslesen wie ein zusammenhängender Puffer behandelt werden. Ich möchte Dir empfehlen, darüber nachzudenken, ob die HAL hier für deinen Anwendungsfall überhaupt geeignet ist. Nicht dass du da viele Stunden Zeit versenkst um am Ende zu bemerken, dass du die Zeichen (mit HAL) gar nicht schnell genug empfangen und verarbeiten kannst.
Stefan ⛄ F. schrieb: > Die Routinen der HAL sind toll, wenn man > Datenpakete mit fester Größe empfangen will. Bei einzelnen Bytes läuft > es aber auf einen gewaltigen Overhead hinaus. Ach was! Das ist kein Problem, und der Ansatz des TO ist vollkommen korrekt. Im Anhang einer funktionierender Code inkl. Beispiel, der das genau so macht.
Es ging doch um einen ersten Test und dafür sieht der Code korrekt aus. Auch das im Callback gleich wieder das nächste Lesen angestoßen wird ist ok. Die Funktion ist nicht blockierend und startet einen Empfang für n Byte, danach ist Schluss. Nicht effizient, muss aber laufen. Für effizienten Empfang kann man sich das hier ansehen: https://stm32f4-discovery.net/2017/07/stm32-tutorial-efficiently-receive-uart-data-using-dma/
An F. schrieb: > mein bisheriges Vorgehen: > > Ich habe einen Buffer definiert: > uint8_t byte; > > UART init: Das mache ich mit CubeMX und habe den Eindruck, dass ich da > alles passend eingestellt habe. In die main.c kommt dann: > > HAL_Init(); > MX_USART2_UART_Init(); > > Den Interrupt aktivieren: > HAL_UART_Receive_IT (&huart2, &byte, 1); > > Dann muss nach meinem bisherigen Verständnis mein Code für den Interrupt > in folgende Funktion: HAL_UART_RxCpltCallback Naja, typisch ST eben. Viel Aufwand für wenig Ergebnis, dafür aber sehr ST-spezifisch und unportierbar. Vermutlich weißt du nicht, was im Detail all dieses Cube- und HAL-Zeugs intern veranstaltet - weiß ich auch nicht, aber ich mache da ja auch einen großen Bogen drum herum. Normalerweise muß man so etwa folgendes tun: Pins einrichten, Takt für den UART freigeben, UART aufsetzen, passende ISR im Code vorhalten, im NVIC den Interrupt freigeben. Anschließend sollte man mit den Interface-Funktionen des UART-Treibers den UART nach Belieben benutzen können. Ich hänge dir mal ein Beispiel zum Lesen dran, ist für den STM32F302RBT6 und schon ein paar Jahre alt. Da kannst du dir das Prinzip anschauen: Der UART wird nach seiner Initialisierung nur per ISR bedient und diese schaufelt die Daten von bzw. in einen Ringpuffer. Die Funktionen zum Benutzen fassen den UART nicht an (mit einer Ausnahme: zum Senden wird der Sendeinterrupt eingeschaltet), sondern verkehren nur mit dem Ringpuffer. Ach ja: die Pin-Funktionalität (also kein GPIO, sondern UART) und der generelle Takt müssen vorher woanders erledigt werden. In dem Treiber sind alle UARTS des o.g. Chips drin, also müßtest du alles, was du nicht brauchst, einfach rausschmeißen. Aber man kann daran die Funktionalität erkennen, die zu einem herstellerunabhängigen Interface in der Firmware führt. Nochwas: Ein UART soll 8 Bit Zeichen befördern. Also ist dort ein schlichter char angesagt und kein uint8_t. Es sollen ja Zeichen und keine Rechengrößen sein. W.S.
Mal wieder ein typischer W.S. mit dem 5 fachen an nötigen Code. Wieso schreibt/kopiert man 5 mal den selben Code hin anstatt eine Funktion aufzurufen? Das sind C Grundlagen! Deine Magic Numbers kannste auch behalten. Im Vergleich zu deinem Code wirkt der HAL ja sogar bugfrei und aufgeräumt!
Wenn man mit Interrupt arbeitet sollte man solchen blockierenden Mist vergessen. HAL_UART_Receive_IT (&huart2, &byte, 1); Was soll das ? Man verwendet eine Statusmaschine ausserhalb des Interrupts. var rxcame:boolean; myrxbuffer:byte; // sofern ein byte genuegt. procedure processrx; switch myrxbuffer { 0x01 : .. } interrupt receiveuart; myrxbuffer:=UARTRx; // lesen des UARTs rxcame:=true; end interrupt; im main dann main() loop if rxcame { processrx; } .. endloop;
:
Bearbeitet durch User
Joggel E. schrieb: > Wenn man mit Interrupt arbeitet sollte man solchen blockierenden Mist > vergessen. > HAL_UART_Receive_IT (&huart2, &byte, 1); Wer keine Ahnung hat, sollte einfach mal.... ...und statt dessen besser mal die Doku lesen und verstehen! Da blockiert nix!
:
Bearbeitet durch User
Hallo, vielen Dank für die vielen Antworten! Ich habe jetzt keine Zeit, aber werde mir das bei nächster Gelegenheit mal alles in Ruhe anschauen. Dann werde ich weiter nachfragen oder Erfolg berichten. Beste Grüsse OG
Mw E. schrieb: > Mal wieder ein typischer W.S. mit dem 5 fachen an nötigen Code. > Wieso schreibt/kopiert man 5 mal den selben Code hin anstatt eine > Funktion aufzurufen? > Das sind C Grundlagen! > Deine Magic Numbers kannste auch behalten. Warum muß Du nun schon schon wieder rum motzen, bloß weil Du die 4 Zeichen W.S. liest? Er ist doch hier ganz sachlich geblieben und hat's erklärt, wie er an die Sache ran geht und damit sollte es gut sein. Du kannst darfst es gern anders machen, es zwingt Dich auch keiner Magicnumbers zu verwenden (übrigens lese ich in W.S.'s Post nicht eine Magicnumber). Der Tip von W.S. ist doch nicht ganz verkehrt, wenn er vorschlägt das Ganze soweit es geht hardwareunabhängig, damit unabhängig von irgend welchen Herstellerbibliotheken und damit portierbar zu machen. W.S. hat das ja nicht zum ersten Mal hier gepostet. Auch wenn es hier keiner hören mag, er verwendet diesen Code ja auch in der berühmt berüchtigten Lernbetty und dort funktioniert es. Ich habe diesen Code auf einem MSP430 und auch dort funktioniert er anstandslos. > Im Vergleich zu deinem Code wirkt der HAL ja sogar bugfrei und > aufgeräumt! Tja der Unterschied scheint ja offensichtlich zu sein, das es mit der bugfreien und aufgeräumten HAL nicht so wie gewünscht funktioniert. Wenn's so problemlos wäre würde der TO nicht tagelang mit diesem Problem zu bringen und hier nicht anfragen.
der W.S. Post war sein übliches ST/HAL bashing, schon im ersten Satz. Ohne sich damit näher damit beschäfftigt zu haben wie er selber zugibt. Das der Code vom TO prinzipiell richtig ist wurde hier doch oft genug geschrieben, es gibt aber noch andere Fehlerquellen und die werden die Ursache sein. Im HAL Code werden auch Fehler abgehandelt, darauf verzichten viele andere Beispiele. Man muss nur die entsprechenden Callbacks erstellen und aktivieren, das liefert für erste Tests auch gleich viel mehr Infos. Und Portierbarkeit ist immerhin innerhalb der ST Familien gegeben, und das ist mittlerweile ein ganzes Universum an Controllern. Auch die Umstellung auf DMA ist nicht kompliziert weil das in der HAL immer nach dem gleichen Schema läuft. Man muss sich nur damit beschäfftigen und es verstehen.
Johannes S. schrieb: > der W.S. Post war sein übliches ST/HAL bashing, schon im ersten Satz. Mein Gott bist Du empfindlich. Den ersten Absatz im Post überliest man geflissentlich, obwohl ja dieser Teil "ST-spezifisch und unportierbar" definitiv richtig ist. Johannes S. schrieb: > Und Portierbarkeit ist immerhin innerhalb der ST Familien gegeben, und > das ist mittlerweile ein ganzes Universum an Controllern. Ja für einige ist ST offenbar wirklich das ganze Universum. Und auch wenn es diese Leute nicht wahr haben wollen das Universum ist deutlich größer. Wer gewillt ist ab und an über dieses Universum hinaus zu schauen, der tut eben gut daran, die Herstellerbibliotheken, egal ob von ST oder TI oder ..., so sparsam wie nur irgend möglich einzusetzen und sich seine eigene Codebasis zu schaffen, die halt portierbar und auf möglichst vielen Controllern einsetzbar ist. Ist am Anfang vielleicht etwas steiniger, beschert aber einem am Ende wiederverwendbaren Code ohne das man das Rad jedes mal neu erfinden muß. Das die Bibliotheken des Herstellers eben auch ihre Tücken habe beweist eben dieser Thread. Letztendlich ist es egal wie man zum Ziel gelangt und dem der die Hardware benutzt ist es am Ende auch egal ob da im Code Herstellerbibliotheken, eigener Gehirnschmalz, Magicnumbers oder sonst was verwendet wird - es muß funktionieren. Letztendlich muß sich hier der TO entscheiden, welchen Lösungsweg er am Ende einschlägt und da ist der Weg des W.S. einer von mehreren.
Zeno schrieb: > Mein Gott bist Du empfindlich. ich habe genug Posts von W.S. gelesen. Da gibt es immer nur zwei Lösungen: seine und die Falsche.
Johannes S. schrieb: > ich habe genug Posts von W.S. gelesen. Da gibt es immer nur zwei > Lösungen: seine und die Falsche. Halt nur gelesen.
Also die peripheral libraries von ST sind schon seit Jahren gut und werden immer besser. Ich arbeite gerne damit. Aber es gibt natuerlich immer noch genug Programmierer mit dem "Not invented by me" Syndrom. Wers mag... Davon abgesehen: UARTS muessen natuerlich NICHT per Definition 8bit transportieren, da gibt es exotische Abweichungen, und uint8_t zu benutzen is gar kein Problem und wuerde ich auch empfehlen. Eindeutig, gut lesbar, prima. uint8_t ist unter Wasser sowieso einfach nur wieder ein typedef Richtung char. So what... Ich verstehe die Interrupt Konstruktion allerdings auch nicht so ganz, ich seh nicht wo "byte" gesetzt wird. Du gibst einen Pointer zu "byte" an die Funktion die den Interrupt setzt, was da drin passiert kan ich nicht sehen.
Matthias schrieb: > Du gibst einen Pointer zu "byte" an die Funktion die den Interrupt > setzt, was da drin passiert kan ich nicht sehen. Das ist eine HAL Funktion, die bekommt einen Zeiger auf einen Buffer und die Anzahl Elemente die gelesen werden sollen. Nicht Anzahl Byte, bei einer 9 Bit Schnittstelle würden n 16 Bit Worte gelesen. Das passiert in der ISR die auch im HAL Code liegt. Der gezeigte Callback wird aufgerufen wenn die gewünschte Anzahl komplett ist. Es gibt weitere Callbacks für HalfComplete (bei der DMA Version) und Fehler. @TO: ein billiger Fehler wäre noch wenn die simple Diagnose mit LED2 nicht funktioniert, hast du mal die LED vertauscht oder getestet ob die LED2 überhaupt toggelt?
Johannes S. schrieb: > ich habe genug Posts von W.S. gelesen. Da gibt es immer nur zwei > Lösungen: seine und die Falsche. Ja. Genau. Hier haben wir immer wieder das gleiche Szenario: Jemand macht es auf seine Weise, benutzt Cube und Hal oder sonstwas - und es funktioniert nicht, das ist also die falsche Lösung. Nun wird hier um Hilfe nachgesucht. Tja und das, was ich hier poste, das funktioniert eben. Zumindest sollte es jedem, der hier nachfragt, genug zum Lesen geben, damit er ne Anregung bekommt um es aus eigener Kraft besser zu machen als sein erster Versuch, der nicht funktioniert. Das ist eben meine Lösung. Nochwas: Genau obiger Unterschied macht's: es gibt meine Lösung die geht und es gibt viele andere Lösungsversuche, die eben nicht gehen. Selbstverständlich gibt es auch gefühlte 1000 andere Lösungen, die funktionieren - aber deren Autoren landen hier nicht mit einem Hilferuf, sondern machen einfach ihr Ding. Fazit: Wer keine Hilfe will, soll es gefälligst selber machen. Wer alles besser weiß, soll es gefälligst besser machen und hier posten, damit Andere daraus was lernen können. Wer hier bloß herumlabert, weil er "genug Posts" gelesen hat, soll einfach was Anderes lesen und die Klappe halten. Ich habe jedoch ernste Bedenken, wenn jemand hier sowas schreibt: Johannes S. schrieb: > Portierbarkeit ist immerhin innerhalb der ST Familien gegeben, und > das ist mittlerweile ein ganzes Universum an Controllern... Jaja: "DU SOLLST KEINEN GOTT AUSSER ST HABEN" oder so ähnlich. Mit meinen Worten heißt das Scheuklappen. W.S.
Hätte auch noch eine Frage die passen zu diesem Thema passt. Ich möchte meine Sensor daten die wie folgt definiert sind
1 | typedef struct { |
2 | uint8_t sensor_id; |
3 | int16_t x; |
4 | int16_t y; |
5 | int16_t z; |
6 | int16_t w; |
7 | int16_t estimated_accuracy; |
8 | } bhy_data_quaternion_t; |
diese habe ich versucht so zu übertragen.
1 | HAL_UART_Transmit(&huart2,(uint8_t*)&sensor_data->data_quaternion.x, strlen(sensor_data->data_quaternion.x), HAL_MAX_DELAY); |
2 | HAL_UART_Transmit(&huart2,(uint8_t*)&sensor_data->data_quaternion.y, strlen(sensor_data->data_quaternion.y), HAL_MAX_DELAY); |
3 | HAL_UART_Transmit(&huart2,(uint8_t*)&sensor_data->data_quaternion.z, strlen(sensor_data->data_quaternion.z), HAL_MAX_DELAY); |
4 | HAL_UART_Transmit(&huart2,(uint8_t*)&sensor_data->data_quaternion.w, strlen(sensor_data->data_quaternion.w), HAL_MAX_DELAY); |
mit putty lese ich aus der seriellen schnittstelle aber nur unbekannte zeichen. Stimmt der typecast von uint_t 16 zu uint_t 8 ?
allesKäse schrieb: > W.S., warum nutzt du keine Structübergabe an Funktionen für den UART? > ARM gibt das auch so vor. An welcher Stelle sollten Structs denn da nützlich sein? Ich halte die Funktionen schlichtweg separat und das hat gute Gründe: zum einen hat damit der Linker eine Chance, im konkreten Projekt unbenutzte Funktionen wegzulassen und zum anderen ist das Verzweigen auf verschiedene Datenkanäle mit Structs viel zu eingeengt und umständlich - da arbeitet man besser mit Handles und einer über der Lowlevel-Schicht liegenden Verteilerschicht. Obendrein braucht ein Handler, der nur für genau einen Kanal zuständig ist, auch weniger Logik und damit Platz, als einer der für alle UART's zuständig ist. Sowas hab ich auch, siehe Anhang. Damit kann man vom Prinzip her UARTs, CDC-USB, Textfiles auf SD-Karte und anders unter einen Hut kriegen, ohne daß man dazu in den einzelnen Lowlevel-Treibern herumwühlen müßte. Die Quellen im Anhang enthalten nur die Ansätze (CharToFileStream und CharToCommandWindow) und je nach Controllerfamilie gibt es auch nicht alle angedachten UART's etc. Insofern ist das als generisch anzusehen und muß einmal an den jeweiligen µC angepaßt werden. Aber dieser Ansatz ist eben viel universeller als jeder Ansatz per Struct - und das Ganze ist portabel im Gegensatz zu Structs. Mit meinem Ansatz kann man auf unterschiedliche Weise arbeiten: entweder man benutzt gio.c und schreibt z.B. String_Out("Hallo", toUART1) oder man verzichtet auf gio.c, wenn zu wenig Platz im Flash ist und benutzt den betreffenden LL-Handler direkt. W.S.
Walt N. schrieb: > typedef struct { > ... > } bhy_data_quaternion_t; > diese habe ich versucht so zu übertragen. Das ist ein ausgesprochen schlechter Ansatz, denn das ganze soll ja über eine Kommunikationsstrecke gehen. Bei sowas sollte man jedoch wenigstens irgend etwas zum Synchronisieren dazu tun. Entweder man sendet das alles in Textform, was sich leicht synchronisieren läßt (denke mal an die Zeile, die man bei einem GPS-Empfänger hat) oder man müßte all die Blöcke wenigstens mit UU codieren oder als allererstes im Struct einen Kenner (z.B. const char[4] = "0815"; oder so) anordnen. Möglichst so einen Kenner, daß er garantiert nicht zufällig als Sequenz im restlichen Struckt vorkommen kann. W.S.
W.S. schrieb: > Tja und das, was ich hier poste, das funktioniert eben. Nein, auch nicht immer. Und das der Code falsch sein soll oder HAL nix taugt behaupten die die sich nicht damit beschäftigen. Die müssen es ja wissen.
Hallo, Problem gelöst - Johannes hatte den entscheidenden Tipp: Johannes S. schrieb: > Bitrate evtl. falsch wegen falscher Clock Configuration, die sollte man > sich in CubeMX genau ansehen. Vielen Dank nicht nur an ihn, sondern Euch alle für die Hinweise. Ich werde mir die auch noch alle in Ruhe anschauen, weil da steckt ja mehr drin, als die reine Problemlösung in diesem konkreten Fall. Beste Grüsse OG
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.