Hallo zusammen, ich will auf einem STM32-System eine String-Ausgabe für die Konsole am PC bereitstellen (ist Fortsetzung dieses Threads: Beitrag "Re: ST-Link V2-1 Virtual COM Port" ). Bei der Hardware handelt es sich um ein NUCLEO-F446RE bei 168 MHz und gesendet werden soll im 10 kHz-Raster mit 2000000 Baud über USART2. Das funktioniert auch schon. Da die Grundauslastung durch die 10-kHz-ISR ohnehin schon knackig ist, darf sie nicht auch noch auf das Senden der Bytes im UART warten. Deswegen bietet sich ein DMA an. Das senden eines fixen Puffers per DMA funktioniert auch schon (siehe angehängter Quelltext). Und jetzt brauche ich einen Denkanstoß in die richtige Richtung. Adresse und Länge des zu sendenden Byte-Arrays muß ich ja schon bei der Initialisierung des DMAs hinterlegen. Die Anwendung passt aber eher zu einer Art FIFO, wo von der Sende-Funktion eine neues Byte-Array angehängt wird und dann der Start des DMAs manuell ausgelöst wird, der so lange läuft, bis der Ringpuffer wieder leer ist. Allerdings finde ich für den Anwendungsfall nicht den passenden DMA-Modus. Der "double buffer"-Modus wäre wohl eine Notlösung, wenn ich sicherstellen kann, daß der alte Puffer immer schnell genug geleert werden kann. Es gibt einen "Fifo"-Modus, aber der scheint einen fixen Puffer von 4 Wörtern zu benötigen. So kurz sind die zu sendenden Arrays nicht. Was ich noch nicht weiß, ob es möglich ist, innerhalb der DMA-ISR die Adresse und die Länge des Buffers zu ändern, ohne den DMA abzuschalten. Das käme wohl einem klassischen Ringpuffer am nächsten. Und jetzt brauche ich einen Denkanstoß in die richtige Richtung: Wie implementiert man sinnvoll eine Ausgabe-Queue auf einen USART unter der Randbedingung, die Interrupt-Last nicht groß zu erhöhen?
Ich habe mal die Application Note AN4031 durchgearbeitet. Ich verfolge jetzt erst einmal den Ansatz, in der DMA-ISR die Puffer-Adresse und die Länge anzupassen. Irgendwo liegt aber noch der Wurm drin: Die Zeichenkette "Hallo!" wird einmal über den UART gesendet, der DMA1_Stream6_IRQHandler() aber nicht angesprungen. Jetzt habe ich jede Zeile viermal überprüft, deshalb würde ich einen Fehler noch nicht einmal mehr dann sehen, wenn er rot unterkringelt wäre. Hat jemand einen frischeren Blick? Nachtrag: Hab's gefunden. Ein DMA_ITConfig(DMA1_Stream6, DMA_IT_TC, ENABLE); fehlte. Außerdem muß ich das Flag DMA_IT_TCIF6 löschen, und nicht DMA_IT_TCIF4.
:
Bearbeitet durch User
Hallo Walter, ich benutze bei STM die HAL-Libs. Und bei UART den xxx_Transmit_DMA() Aufruf. Der sendet den übergebenen Buffer via DMA. Der Aufrufer darf also den Buffer nach Aufruf der Funktion solange nicht benutzen bis die DMA fertig ist. Im speziellen habe ich ein Circular-Buffer als Zwischenschicht zur Applikation. Die Zwischenschicht prüft, welcher linearer Block aktuell zum Senden bereitsteht und sendet den. Im DMA complete Interrupt werden evtl weitere Bytes gesendet. usw. HTH, Adib. --
Dann schaue ich mir doch mal an, wie die HAL_UART_Transmit_DMA() aufgebaut ist. Mal sehen, ob man die bekommen kann, ohne CubeMX zu installieren und eine ganze Konfiguration durchzuklicken.
:
Bearbeitet durch User
Das "Cube F4" Paket kann man ganz normal einzeln downloaden, ohne CubeMx zu installieren. https://www.st.com/en/embedded-software/stm32cubef4.html
Stefanus F. schrieb: > Das "Cube F4" Paket kann man ganz normal einzeln downloaden Danke für den Tipp. Damit war das ja einfach. Dem DMA läßt sich wirklich einfach in der ISR eine andere Speicherstelle unterschieben. Dann ist die Ausgabe-Queue nachher nur noch eine Fingerübung. So sieht jetzt die ISR aus:
1 | void DMA1_Stream6_IRQHandler(void) |
2 | {
|
3 | /* Clear DMA Stream Transfer Complete interrupt pending bit */
|
4 | DMA_ClearITPendingBit(DMA1_Stream6, DMA_IT_TCIF6); |
5 | |
6 | DMA_Cmd(DMA1_Stream6, DISABLE); |
7 | DMA_ITConfig(DMA1_Stream6, DMA_IT_TC, DISABLE); |
8 | |
9 | DMA1_Stream6->M0AR = (uint32_t) &"123456789012345678901234567890123456789\n"; |
10 | DMA_SetCurrDataCounter(DMA1_Stream6, 40); |
11 | |
12 | DMA_ITConfig(DMA1_Stream6, DMA_IT_TC, ENABLE); |
13 | DMA_Cmd(DMA1_Stream6, ENABLE); |
14 | |
15 | /* Wackeln fuer Debug */
|
16 | io_toggleBit(SPARE1_GPIO, SPARE1_Pin); |
17 | }
|
Der Pin SPARE1 toggelt jetzt mit 2,5 kHz, d.h. effektiv komme ich auf eine Baudrate von 1600000. Da kann man nicht meckern. Nachtrag: Eigentlich beträgt die Bitrate sogar 1800000 bps, da 8n1. Also kann ich mit dem Durchsatz hochzufrieden sein.
:
Bearbeitet durch User
Walter T. schrieb: > Adresse und Länge des zu sendenden Byte-Arrays muß ich ja schon bei der > Initialisierung des DMAs hinterlegen. Die Anwendung passt aber eher zu > einer Art FIFO, wo von der Sende-Funktion eine neues Byte-Array > angehängt wird und dann... Siehste, jetzt merkst du es auch, daß DMA eben nur schaufeln, aber nicht denken kann - und schon garnicht intelligent auf eine Peripherie reagieren. Für sowas braucht's eben eine ISR. Ich häng dir mal was dran, das du als Denkanstoß verwenden kannst. Ist zwar nicht exakt für deinen Chip geschrieben, aber sowas im Detail anpassen und die nicht benötigten UART's rausschmeißen wirst du ja wohl können. W.S.
@WS Musst du hier andauernd deinen Magic Number Gruselcode posten?
1 | USART2_ISR & (1<<7) |
Widerlich!
Mw E. schrieb: > @WS > Musst du hier andauernd deinen Magic Number Gruselcode posten?USART2_ISR > & (1<<7)Widerlich! Nullkommanix zum Thema beitragen, aber rum meckern ist auch widerlich.
Mache das auch so mit dem Wechseln zwischen 2 Speicherbereichen. Es gibt auch noch einen Half-Transfer Interrupt. Also wenn die Hälfte der Übertragung abgeschlossen ist kannst du vorn wieder anfangen reinzuschreiben. Habe ich jedoch noch nicht ausprobiert.
Walter T. schrieb: > Da die Grundauslastung durch die 10-kHz-ISR ohnehin schon knackig ist, > darf sie nicht auch noch auf das Senden der Bytes im UART warten. 10 kHz ISR auf einem 446 ist doch recht gemütlich. Da würde ich einfach die Priorität der UART-ISR heraufsetzen. DMA lohnt sich eher für die Ausgabe größerer Blöcke.
Mw E. schrieb: > Widerlich! Wenn dich funktionierende Lösungen anwidern und du sowas wie (1<<7) lieber in irgend einer fernen Include-Datei versenken willst, dann brauchst du ja bloß deine Klappe zu halten, deine unsachlichen und zu nichts nützen Bemerkungen zu unterlassen und deine Sachen eben anders zu machen. Kurzum, niemand zwingt dich, ne Hilfe anzunehmen. W.S.
Ich mag den Programmierstil von W.S. auch nicht, aber alles was ich bisher von ihm gesehen habe funktioniert und ist nachvollziehbar. Außerdem gilt immer noch: Dem geschenkten Gaul schaut man nicht ins Maul. Egal, was man schreibt bzw. veröffentlicht, es stürzen sich immer die gleichen Hyänen darauf, um es nieder zu machen. Bei manchen Personen habe ich das Gefühl, dass sie gar nichts anderes können/wollen. Das ist widerlich.
W.S. schrieb: > Kurzum, niemand zwingt dich, ne Hilfe anzunehmen. Naja, Martin hat nicht nach einem Tipp gefragt. Ich habe das getan, aber ich sehe mich tatsächlich nicht qualifiziert, Deinen Quelltext zu lesen. Das fängt schon bei dem Verständnis an, warum sprechende Namen auf der linken Seite gut sind, auf der rechten Seite jedoch nicht. Konsequent fände ich es, anstelle USART1_ISR & (1<<7) entweder *(0x4001381C) & (1<<7) oder USART_ISR & USART_FLAG_TXE zu verwenden. Aber ich beschwere mich da nicht. Du wirst schon Deine Gründe haben. Wirklich schlimm finde ich das allerdings ja auch nicht. Ich bin jetzt mit meinen DMA-Bursts ganz zufrieden. Das mit einem kleinen Ringpuffer-Fifo zu verheiraten, ist jetzt nur noch eine kleine Fleißübung. Da finde ich es schlimmer, wenn eine Diskussion wieder Richtung Beleidigungen gezogen wird. Gut oder schlecht lesbarer Quelltext hin oder her. m.n. schrieb: > DMA lohnt > sich eher für die Ausgabe größerer Blöcke. Hat es irgendeinen Nachteil, wenn ich einen völlig überqualifizierten DMA auf einen kleinen Puffer loslasse, wenn er anderweitig nicht benötigt wird?
:
Bearbeitet durch User
Nunja W.S. ist da sehr speziell, da gibts dann noch so geile Aussagen wie "C hat (eigentlich) keine 2D Arrays" und sone Scherze ;) So coded er auch und muss es unbedingt immer jedem hinposten wie mans nicht macht. Is ja egal wenn ers im stillen Kämmerlein macht, aber so übernimmt das ja vllt noch der ein oder Andere. Vor allem bringts hier nix, weil eben KEIN DMA in dem Code genutzt wird. Aber warum er ein USART2_ISR in einem "include versteckt", aber die Bitnamen nicht, das erschließt sich mir auch nicht. Dann vor allem das Doppelgemoppel mit USART1_ISR, USART2_ISR, USART3_ISR. ARM gibt da vor structs zu nutzen und per Basisadresse auf die memory mapped IO zu stülpen. Da hat man dann uartxyz->ISR & kekse; Dann hat man eine Funktion die alle UART kann und muss nicht sowas machen: InitSerial1, InitSerial2, InitSerial3. BÄH! Son STM32 kann 8+ UART haben! Sieht dann so aus: InitSerial(UART1); ------------------------------------ So, jetz zum Thema, jetz is Zeit, ich hab noch ne Platine fertig gemacht: Normalerweise baut man sich da ja eine kleine FIFO und im UART_TX_empty IRQ sendet man den nächsten char raus. Bei einem DMA TX bläst man das jetz auf und guckt auf die FIFO Zeiger wie lang der beschriebene Bereich ist und sendet den dann raus. Daher hat die FIFO nun nicht mehr die 2 Zeiger die anzeigen wo gelesen wird und wo geschrieben wird. Es gibt noch einen dritten Pointer: bis wohin hat der DMA schon gesendet. Den Buffer+LÄnge gibt man jetzt den DMA. Im DMA fertig IRQ (TCIE Flag) markiert man das dann in seiner FIFO als gesendet (Pointer woanders hinzeigen lassen) und es kann wieder in diesen Bereich geschrieben werden. Im selben IRQ guckt man jetzt obs schon was neues gibt zwischen Pointer "bis wohin schon gesendet" und "bis wohin schon geschrieben". Danns endet man das raus. Das Hauptprogramm kann die FIFO wie immer mit putc oder puts befüllen. Der DMA Channel deaktiviert sich übrigens immer selbst wenn er fertig ist. Daher ist jeder Neustart quasi ein neuinit, nur die HAL bläßt das zu sehr auf. Bei meinem Eigenbautreiber gibts daher eine Initfunktion welche in den Stream/Channel schreibt wie die FIFOs auszusehen habe, Burstlänge, prio etc. Dann kommt eine Startfunktion welche nurnoch Pointer+Länge bekommt und ab gehts. Leider ist der DMA im STM32 nicht so Schlau wie andere, denn es gibt auch DMA die eine linked list im Speicher abarbeiten können und in den suspend gehen wenn die abgearbeitet ist. (der Ethernet DMA der STM32 kann das dann glücklicherweise)
Mw E. schrieb: > So coded er auch und muss es unbedingt immer jedem hinposten wie mans > nicht macht. > Is ja egal wenn ers im stillen Kämmerlein macht, aber so > übernimmt das ja vllt noch der ein oder Andere. Na und, geht davon die Welt unter? Ich habe eher das Gefühl, dass du dich da in einen persönlichen Feldzug verrannt hast. Lass ihn doch. Wer seine Quelltexte benutzt, der sieht das selber und kann sich seine Meinung dazu bilden. Deine Meinung dazu hast du hier oft genug kund getan, irgendwann muss es auch mal gut sein.
Stefanus F. schrieb: > Deine Meinung dazu hast du hier oft genug kund getan, irgendwann muss es > auch mal gut sein. Er postets ja immer bei neuen, das wissen also du und ich ;) Ein Mod hatte sich ja auch schon zu dem Thema und W.S. seinen Aussagen geäußert,
Mw E. schrieb: > Er postets ja immer bei neuen Mache du das doch auch so. Mit W.S. bist du schon lange durch, jetzt kannst du jemand anderem helfen, seinen Programmierstil zu verbessern.
Stefanus F. schrieb: > Ich mag den Programmierstil von W.S. auch nicht, aber alles was ich > bisher von ihm gesehen habe funktioniert und ist nachvollziehbar. > Außerdem gilt immer noch: Dem geschenkten Gaul schaut man nicht ins > Maul. > > Egal, was man schreibt bzw. veröffentlicht, es stürzen sich immer die > gleichen Hyänen darauf, um es nieder zu machen. Bei manchen Personen > habe ich das Gefühl, dass sie gar nichts anderes können/wollen. Das ist > widerlich. Aber man wird noch vernünftig drüber reden dürfen. Ich finde, dass dieser Code auch eine Reihe von Code-Smells enthält. Und wenn man den so unkommentiert an die Programmierer-Zukunft weitergibt, denken die "guter Code MUSS so aussehen".!? hier mal ein paar Stichpunkte: 1) Code-Duplikate Für die 5 SChnittstellen steht alles 5mal da. Hier hätte jeweils eine Funktion mit der Schnittstelle als Parameter gereicht. 2) mehrere Anweisunge pro Zeile, incl if und bedingtem Rücksprung i = U2Buf.InRP; if (i == U2Buf.InWP) return 0; Code-Formatierer würden alles auf jeweils eine eigene Zeile setzen 3) verwenden der jeweiligen festen ADdressen von Registern in Hardware-Strukturen. Ist ein bisschen wie 1) Man hätte einen Basis-Pointer auf die Struktur und dem eigentlichen Element verwenden können. ST bietet sowas bereits an. 4) API - der read hätte ein "Erfolg" zurückliefern sollen. ähnlich dem fread aus der C-Std Api. Man spart sich ein zwingendes Testen mit Avail. Und der Read Code wäre IMMER safe. 5) die angesprochenen harten Bitpositionen. Hier weiss man nicht, was der Code tut. ST liefert auch hier Bitpositionen als defines oder so. usw. Der Code ist an sich gut kommentiert. Allerdings; der Code - besitzt nur Funktionen zum Übertragen einzelner Bytes und nicht Buffer-blöcke - nutzen die nicht die Funktionalität der DMA, was zu einer unnötigen Last führt. Grüße, Adib. --
Walter T. schrieb: > Hat es irgendeinen Nachteil, wenn ich einen völlig überqualifizierten > DMA auf einen kleinen Puffer loslasse, wenn er anderweitig nicht > benötigt wird? Wenn z.B. noch irgendeine Flußkontrolle stattfinden soll (XOFF, XON, Handshake), kann man nicht mit DMA arbeiten. Auch eine Adressierung mit dem 9. Bit muß "zu Fuß" ablaufen. W.S. schrieb: > lieber in irgend einer fernen Include-Datei versenken willst So fern ist die notwendige .inc doch garnicht. Mir ist aufgefallen, daß bei F4xx, F7xx und H7xx teilweise Bits ihre Position verändert haben. Da wird man dann mit Konstanten schön geärgert.
Stefanus F. schrieb: > Mw E. schrieb: >> Er postets ja immer bei neuen > > Mache du das doch auch so. Mit W.S. bist du schon lange durch, jetzt > kannst du jemand anderem helfen, seinen Programmierstil zu verbessern. Es geht mir doch nicht um W.S. diese Type ist mir sowas von Egal. Es geht darum, dass nicht ein Anfänger, der hier anonym ließt das als brauchbaren Code empfindet. Wie The A sagte: > Und wenn man den so unkommentiert an die Programmierer-Zukunft > weitergibt, denken die "guter Code MUSS so aussehen".!? m.n. schrieb: > Wenn z.B. noch irgendeine Flußkontrolle stattfinden soll (XOFF, XON, > Handshake), kann man nicht mit DMA arbeiten. Auch eine Adressierung mit > dem 9. Bit muß "zu Fuß" ablaufen. XON/XOFF mit DMA wird in der Tat schwierig. Aber CTS/RTS mit DMA geht (aber nicht wirklich beim STM). Bei den Atmel ARM gibts den PDC (Peripheral DMA Controller). Der hört auf übern UARt zu senden wenn die Gegenstelle nicht Clear ist. Beim STM32 muss man darauf hoffen, dass der CTS/RTS IRQ schnell genug kommt und zu Fuß den DMA anhalten. Beim MUltidrop UART muss man eben schnell nen Header mitschieben um dem CLient zus agen wieviel Byte er bekommen wird bis zu einer nächsten Adressierung und schon geht auch das mit DMA. (Bis auf den wakeup IRQ natürlich)
Mw E. schrieb: > @WS > Musst du hier andauernd deinen Magic Number Gruselcode posten? >
1 | USART2_ISR & (1<<7) |
> Widerlich!
Was ist denn daran widerlich?
Gruß,
Holm
Ich bin jetzt ganz zufrieden mit meinem Senden über den UART. Meine Implementierung kann also - je nach Geschmack - gerne als Inspiration oder abschreckendes Beispiel genutzt werden. Oder - noch lieber - als Anlaß für Tipps zur Verbesserung.
:
Bearbeitet durch User
Walter T. schrieb: > abschreckendes Beispiel genutzt werden. Da ist schon was dran! Wozu diese unnötigen Strukturen? Das ringbuffer_append() braucht vermutlich viel mehr Rechenzeit, als die Ausgabe eines Zeichens per ISR. Wenn Dein restliches Programm auch so "akademisch" aufgebaut ist, wird mir allerdings klar, warum eine 10 kHz ISR den µC in die Knie zwingt.
m.n. schrieb: > Das ringbuffer_append() braucht > vermutlich viel mehr Rechenzeit, als die Ausgabe eines Zeichens per ISR. Na, da bist Du doch sicher Profi genug, um nicht vermuten zu müssen, sondern Dir absolut sicher zu sein, dass diese Funktion mehr Rechenzeit benötigt als das Senden eines einzelnen Zeichens per ISR. Damit ist doch auch klar, dass der Aufwand sich nur dann lohnt, wenn man mal ein paar hundert Bytes Ruhe vor einem weiteren Interrupt aus dieser Richtung haben will. Interessanter wäre deshalb doch eher Kritik aus anderer Richtung: Mache ich mir mit dem Anfang und dem Ende des Byte-Arrays das Alignment kaputt, so dass ich bei jedem Aufruf der DMA-ISR einen hohen Preis bezahlen muss?
Ich verstehe nicht, warum Du diesen Aufwand treibst. Ein Ringpuffer und DMA passen doch garnicht zueinander. Wenn man mit DMA ausgeben will, reserviert man einen Speicherbereich, setzt die DMA-Startadresse auf den Anfang, die Anzahl in den entsprechenden Zähler und triggert die Ausgabe. Wenn man während der Ausgabe weitere Zeichen vorbereiten will/muß, dann nimmt man einen alternativen Puffer, der anschließend ausgegeben wird.
m.n. schrieb: > Wenn man während der Ausgabe weitere Zeichen vorbereiten will/muß, dann > nimmt man einen alternativen Puffer, der anschließend ausgegeben wird. Vielleicht ist meine Denkweise zu naiv: Ich sehe gerade keinen prinzipiellen Unterschied zum Ringpuffer. Ich habe zwei Speicherbereiche. Einen, in den ich schreibe. Einen, aus dem ich lese. Beim Doppelpuffer muß das schreiben und lesen irgendwie mit einer Art Semaphor/Flag/sonstwas synchronisiert werden. Beim Ringpuffer wird das nicht benötigt. Da schreibt und liest jeder dann, wann er lustig ist. Anstelle der Synchronisierung tritt eine Über- und/oder Unterlaufanzeige. Für Doppelpuffer mit konstanten Adressen und Längen kann der DMA den Wechsel in Hardware machen. Wenn sich die Längen der Datentelegramme ändern, muß das auch wieder in Software gemacht werden. Ich sehe keinen Vorteil an einem Doppelpuffer. Für mich ist eine Warteschlange der "normale" Weg, eine Ausgabe mit unterschiedlichen Längen zu realisieren.
:
Bearbeitet durch User
Walter T. schrieb: > Vielleicht ist meine Denkweise zu naiv: Ich sehe gerade keinen > prinzipiellen Unterschied zum Ringpuffer ich finde, Ringpuffer und DMA passen ganz gut zusammen, man braucht keine extra Puffer und der Platz wird nicht verschwendet. Hatte vor kurzem auch so was implementiert, mit dem Unterschied, dass höchstens ein Drittel des Puffers in einem Transfer gesendet wird - damit wird ein Teil des Puffers wieder schneller frei, auch wenn ein solcher Fall, dass der ganze Puffer in einem Rutsch übertragen werden kann, wohl eher selten (oder nie) auftreten wird. Den Doppelpuffer braucht man wenn man viele Daten kontinuierlich sendet oder empfängt, wie z.B. Bei Audioaufnahmen, ansonsten sehe ich da keinen Vorteil. MfG Andreas
Walter T. schrieb: > Oder - noch lieber - als > Anlaß für Tipps zur Verbesserung. Du könntest Deine Ringbufferimplementierung noch so aufbohren daß men beliebig viele Instanzen davon erzeugen kann, dafür änderst Du es so daß Du jeder Funktion (==Methode) als ersten Parameter einen Zeiger auf das Ringbuffer-struct übergibst. Die globalen Variablen für Struct und Buffer entfallen dann und der Anwender muß diese bereitstellen und der init-Methode Zeiger darauf übergeben. Es ist nicht mehr viel zu ändern, es scheint fast so als ob Du es schon mit diesem Gedanken im Hinterkopf entworfen hast. Oben hab ich mal meine eigene Implementation angehängt.
:
Bearbeitet durch User
Andreas M. schrieb: > Hatte vor kurzem auch so was implementiert, mit dem Unterschied, dass > höchstens ein Drittel des Puffers in einem Transfer gesendet wird - > damit wird ein Teil des Puffers wieder schneller frei, Danke für Deinen Beitrag! Da muß ich gestehen: Ich hatte gar nicht auf dem Schirm, daß es durchaus Geschwindigkeitsvorteile haben kann, wenn der Puffer kleinteiliger freigegeben wird. Aber wenn man zum ersten Mal darüber nachdenkt, ist es sofort einleuchtend. Bernd K. schrieb: > es scheint fast so als ob Du es schon mit diesem Gedanken im Hinterkopf > entworfen hast. Ja, das ist zur Angewohnheit geworden. Trotz YAGNI. Interessant finde ich die Bezeichnung des Daten-Structs als "self". Das hatte ich bislang nur im Python-Umfeld gesehen.
Walter T. schrieb: > Das fängt schon bei dem Verständnis an, warum sprechende Namen auf der > linken Seite gut sind, auf der rechten Seite jedoch nicht. Konsequent > fände ich es, anstelle USART1_ISR & (1<<7) entweder *(0x4001381C) & > (1<<7) oder USART_ISR & USART_FLAG_TXE zu verwenden. Aber ich beschwere > mich da nicht. Du wirst schon Deine Gründe haben. Ja und das sind ernste Gründe. ich erkläre es dir mal: Solche Dinge wie USART1_ISR sind Hardware-Register. Diese stehen für sich, lassen sich ohne Bezug auf irgend etwas Anderes ansprechen und sind nicht Teil von irgendetwas Anderem. Deshalb ist es völlig OK und leserlich, selbige mit ihrem Namen zu verwenden, den sie auch im Referenzmanual tragen. So etwas jedoch wie USART_FLAG_TXE ist kein selbständiges Etwas. Das ist ein gravierender Unterschied zu USART1_ISR. Man kann darauf eben nicht zugreifen, so wie man das mit einem Register macht. Sondern man muß so etwas IMMER nur in Verbindung mit dem zugehörigen tatsächlichen Register tun, bei Verwechselungen kommt Bockmist dabei heraus. Du kannst - jedenfalls bei den hier besprochenen µC Typen - eben nicht sowas schreiben: USART_FLAG_TXE = 4711; Eben weil USART_FLAG_TXE kein selbständiges Ding ist. Ich habe schon genug an Bugs suchen müssen, bloß weil irgend jemand versehentlich das falsche Register zu so einem Namen oder den falschen Namen zum richtigen Register hingeschrieben hat. Der Compiler kann das nämlich nicht selber merken. Aber selbst, wenn man das richtige Register und den Namen der gewünschten Bitgruppe hinschreibt, kann Bockmist bei herauskommen, nämlich dann, wenn der Name und seine Verwendung woanders eben anders gesehen wird, asl man es selbst grad tut. Beispiel: Irgendwo steht #define ottokar (1<<7) oder es steht dort #define ottokar 7 und nun steht man vor der Frage, schreibe ich nun karlheinz |= ottokar; oder karlheinz |= (1<<ottokar); hin? Verstehe mal, man muß darauf achten, daß Register und ottokar zueinander passen UND daß ottokar auch so definiert ist, wie man sich das selber gedacht hat. Im Zweifelsfall muß man nach den entsprechenden .h suchen und dort nachschauen. Jedenfalls dann, wenn was nicht läuft und man auf Bugsuche ist. Ein Gegenbeispiel: Ich hatte mir vor Urzeiten meinen eigenen PIC16-Assembler geschrieben, eben weil ich das blöde und mühselige Achten auf zusammengehörige Register und deren Bits leid war. Der olle Assembler von Microchip konnte sowas leider nicht. Deshalb hatte ich dort nen passenden Pseudo eingebaut: ottokar: BIT karlheinz, 7 Mit so einer Vereinbarung kann man dann ottokar setzen und löschen und testen, OHNE darauf achten zu müssen, dazu auch noch den korrekten karlheinz hinzuschreiben. Eben weil die Zuordnung an einer zentralen Stelle einmal richtig erledigt worden ist. Im obigen Falle müßte für sowas sinngemäß etwa dieses stehen: #define USART_FLAG_TXE USART1_ISR[7:7] // weil nur 1 Bit groß dann könnte man USART_FLAG_TXE = 1; schreiben. Aber sowas hängt an der jeweiligen Architektur. Bei ARM-Plattformen klappt das nicht wirklich, weil die für derartiges nicht eingerichtet sind. So. Deshalb ist es weitaus sicherer, Hardwareregister mit ihrem Namen anzusprechen und Bits und Bitgruppen in den Hardwareregistern ohne Namen zu vergeben zu behandeln. Im Zweifelsfalle schaut man nur ins Refman und muß nicht noch in irgendwelchen .h suchen. W.S.
@ W.S. Achsooo! Weil du deine defines nicht ordentlich benennen kannst musst du Magic Numbers benutzen? Mein Beileid hast du jetzt! Aber damit es keine Probleme und Verwechslungen gibt hat sich ARM was ausgedacht. Bzw Vorgaben gemacht. Das ist dann dieses ominöse CMSIS: CMSIS enables consistent device support and simple software interfaces to the processor and its peripherals, simplifying software reuse Dort ist ersteinmal das struct definiert zum memory mappen. (Es fehlt noch deine Ausrede wieso du jedem UART eigene Doppelmoppel defines gibst)
1 | typedef struct |
2 | {
|
3 | __IO uint32_t SR; /*!< USART Status register, Address offset: 0x00 */ |
4 | __IO uint32_t DR; /*!< USART Data register, Address offset: 0x04 */ |
5 | __IO uint32_t BRR; /*!< USART Baud rate register, Address offset: 0x08 */ |
6 | __IO uint32_t CR1; /*!< USART Control register 1, Address offset: 0x0C */ |
7 | __IO uint32_t CR2; /*!< USART Control register 2, Address offset: 0x10 */ |
8 | __IO uint32_t CR3; /*!< USART Control register 3, Address offset: 0x14 */ |
9 | __IO uint32_t GTPR; /*!< USART Guard time and prescaler register, Address offset: 0x18 */ |
10 | } USART_TypeDef; |
Weiter hinten dann:
1 | #define USART1_BASE (APB2PERIPH_BASE + 0x1000U)
|
2 | #define USART2_BASE (APB1PERIPH_BASE + 0x4400U)
|
3 | *schnipp* |
4 | #define USART2 ((USART_TypeDef *) USART2_BASE)
|
5 | #define USART3 ((USART_TypeDef *) USART3_BASE)
|
Jetzt kannste auf USART->SR arbeiten. Einer Subfunktion gibts du das USARTx als Paremeter und die kann dann generisch auf variable->SR arbeiten (die putc per Poll Funktion zB). Um auf den Bits zumzutrommeln gibts dann defines, im Namen des defines steht auch zu welchem Register das Bit gehört mit mans eben NICHT ausversehen zu einem anderen Register schreibt. Damit man jetzt nicht verwechseln kann ob das Bit bereits zurechtgeschoben ist oder nur die Schiebenumemr ist, gibts noch eine Endung des define Namen:
1 | #define USART_SR_TXE_Pos (7U)
|
2 | #define USART_SR_TXE_Msk (0x1U << USART_SR_TXE_Pos) /*!< 0x00000080 */ |
3 | #define USART_SR_TXE USART_SR_TXE_Msk /*!<Transmit Data Register Empty */ |
Damit haste die Position (um mal eine Option die aus mehreren Bits besteht reinzuschieben), eine Maskierung und das Bit selber. Als Beispiel ein putc im Pollbetrieb (für den Faulthandler):
1 | void usart_putc(USART_TypeDef *uart, char c){ |
2 | |
3 | while(!(uart->SR & USART_SR_TXE)){ |
4 | uart->DR = c; |
5 | }
|
6 | }
|
Aufruf dann als:
1 | #define FAULT_UART USART1
|
2 | usart_putc(FAULT_UART, 'W'); |
3 | usart_putc(FAULT_UART, '.'); |
4 | usart_putc(FAULT_UART, 'S'); |
5 | usart_putc(FAULT_UART, '.'); |
So kann man dann auch ganz fix einen anderen UART nutzen, wenn man seine SW mal auf eine neue Platine betreiben will. Bei dir müsst man jetzt per replace alle Funktionsaufrufe durchgehen, gruselig.
Walter T. schrieb: > Interessant finde ich die Bezeichnung des Daten-Structs als "self". Naja, vom Namen "this" hab ich mich abbringen lassen im C Umfeld. Aber ja: Es funktioniert im Prinzip genau wie in Python, auch da muss die Referenz auf das Objekt in der Parameterliste der Methode explizit hingeschrieben werden, dort schimmert die Äquivalenz von Funktion und Methode und die eigentliche Bedeutung von "Objekt" noch deutlich durch.
Mw E. schrieb: > @ W.S. > Achsooo! Weil du deine defines nicht ordentlich benennen kannst musst du > Magic Numbers benutzen? nanana, nicht frech werden! Meine eigenen #define's kenne ich zur Genüge. Ich vergebe ja gelegentlich auch Namen an Bits - nämlich dort, wo es tatsächlich sinnvoll ist. Aber dabei achte ich drauf, daß solches im Rahmen der .c Datei bleibt, wo auch die Verwendung stattfindet. Und nicht über irgendwelche .h Dateien, wodurch bloß unnötige Abhängigkeiten erzeugt würden. Mw E. schrieb: > Damit man jetzt nicht verwechseln kann ob das Bit bereits > zurechtgeschoben ist oder nur die Schiebenumemr ist, gibts noch eine > Endung des define Namen Na Klasse! für ein popliges Bit erzeugst du einen ganzen Zirkus an herumkonstruierten ellenlangen Namen. Geht's noch? Wird durch das ganze Gefummel mit _pos und _mask der Quelltext lesbarer? Oder sicherer und zuverlässiger? Nein, natürlich nicht. Irgendwo stolperst auch du über den Urwald deiner eigenen Indirektionen. Mw E. schrieb: > Jetzt kannste auf USART->SR arbeiten. Steht das im RefMan? Nein, natürlich nicht. Dort steht allenfalls USART1_SR und kein Struct. Aber den eigentlichen Sinn meiner Erklärung hast du nicht im Geringsten verstanden. Es geht darum, eben NICHT grundsätzlich irgendwelche Namen zu verwenden für Dinge, die keinen selbständigen Zugriff haben. Wie eben ein Bit oder eine Bitgruppe im Register XYZ. Mw E. schrieb: > Aufruf dann als:#define FAULT_UART USART1 > usart_putc(FAULT_UART, 'W'); > usart_putc(FAULT_UART, '.'); > usart_putc(FAULT_UART, 'S'); > usart_putc(FAULT_UART, '.'); > > So kann man dann auch ganz fix einen anderen UART nutzen, wenn man seine > SW mal auf eine neue Platine betreiben will. Selbst hier stellst du dich an wie der erste Mensch. Bei mir sieht das übrigens so aus: String_Out("Mw E. kanns halt nicht", stdout); W.S.
W.S. schrieb: > Ja und das sind ernste Gründe. > > ich erkläre es dir mal: Ich versuche mal Deine Ausführungen zu paraphrasieren, um sicherzustellen, daß ich sie richtig verstanden habe. Dabei lasse ich auch einfließen, wie ich Aussagen verstehe, die ich an anderer Stelle von Dir gelesen habe. Also bitte nagel mich jetzt nicht darauf fest, aus welchem Beitrag das stammt. Du hast mit Libraries und Beispielimplementierungen, wie sie von Herstellern geliefert werden, schlechte Erfahrungen gemacht. Deine Lösung besteht darin, diese Software möglichst links liegen zu lassen, um das Problem, fehlerbehaftete Hardware in Betrieb zu nehmen, nicht noch durch zusätzliche fehlerhafte Software zu verkomplizieren. Damit sind Datenblatt, Application Note und Errata-Dokument Deine besten Freunde, und um eine beliebige Peripherie in Betrieb zu nehmen, sollte man diese Freunde so innig kennen, daß es weniger fehlerbehaftet ist, sich direkt auf die Registernamen und Bitnummern zu verlassen, anstelle durch Headerdateien und benannte Namen weitere Indirektionen hinzuzufügen. Registernamen sind natürliche Namen, weil sie ja auch Teile des Datenblatts sind. Bitnamen sind nicht so natürliche Namen, weil sie ohne Register keinen Sinn ergeben. Also kann man auch direkt die Nummer hinschreiben. Habe ich Deinen Standpunkt so grob richtig zusammengefasst?
:
Bearbeitet durch User
W.S. schrieb: > eben NICHT grundsätzlich irgendwelche Namen > zu verwenden für Dinge, die keinen selbständigen Zugriff haben. Wie eben > ein Bit oder eine Bitgruppe im Register XYZ. Was ist das für sinnloses Geschwurbel, was ist bitteschön "keinen selbstständigen Zugriff haben"? Hier ist die goldene Regel für lesbaren Code: ALLE (ohne Ausnahme!) Konstanten die nicht 0 oder 1 sind haben gefälligst einen Namen zu haben damit man sofort sieht was sie bedeuten! Alle! jede einzelne! Wenn der Code fertig ist will ich keine Zahlen im Quelltext mehr stehen sehen! Für Bitmasken werden selbstverständlich die Orginalheader aus dem aktuellen DFP verwendet, schon allein um Zeit für unnötige Arbeit zu sparen und um dumme Fehler beim fehlerträchtigen manuellen Abschreiben aus dem Manual zu vermeiden.
:
Bearbeitet durch User
Am einfachsten und für volle Performance nimmst du einen Ringbuffer aus Buffern, nicht aus einem byte array. In insert() wird dann dann der sendepuffer memcopied, in der DMA TXDone interrupt wird gepopped() und wieder dma mit der nächstem buffer gestartet. Wenn deine Nachrichten unterschiedlich lang sind, bringt das natürlich Speicheroverhead.
1 | class Ringbuffer { |
2 | struct Buffer { |
3 | uint8_t data[100]; |
4 | uint32_t length; |
5 | }
|
6 | |
7 | insert(const uint8_t data[], uint32_t length) { |
8 | //memcpy into next empty Buffer
|
9 | }
|
10 | |
11 | bool pop(const uint8_t *data[], uint32_t *length) { |
12 | //get pointer to oldest data
|
13 | }
|
14 | |
15 | Buffer m_txBuffers[10]; |
16 | |
17 | |
18 | }
|
Walter T. schrieb: > Registernamen sind natürliche Namen, weil sie ja auch Teile des > Datenblatts sind. Bitnamen sind nicht so natürliche Namen, weil sie ohne > Register keinen Sinn ergeben. Also kann man auch direkt die Nummer > hinschreiben. > > Habe ich Deinen Standpunkt so grob richtig zusammengefasst? Ja, so etwa. Man kann das wirklich so auf den Punkt bringen: Register sind Dinge, auf die man natürlich zugreifen kann und sie haben Namen entsprechend dem Manual. Bitnamen sind lediglich Textersetzungen, weil sie ja nur dafür verwendet werden können, um per Schieben, Maskieren usw. in Register eingepaßt werden zu können. Also kann man es auch direkt formulieren. Ich bevorzuge, es etwa so zu halten:
1 | RCC_APB1ENR = (1<<23) | // USB |
2 | (1<<21) | // I2C1 |
3 | (1<<17) | // USART2 enable |
4 | (1<<2) | // TIM4 enable |
5 | (1<<1) | // TIM3 enable |
6 | 1; // TIM2 enable |
Damit ist sowohl die Lesbarkeit und Verständlichkeit gegeben, als auch irgendwelche Bandwurm-Namen und Abhängigkeiten von Fremddateien vermieden. Nochwas: Ich predige den Leuten hier aber noch etwas anderes: Nämlich Low-Level-Dinge von High-Level-Dingen zu trennen. Das bedeutet, für die unterste Ebene eben LowLevel-Treiber zu schreiben, die eine Hardware sauber abstrahieren und in sich gekapselt sind. Das bedeutet dann eben auch, daß jedes Gefummel mit Hardware-Registern und deren Bestandteilen in der Ebene der Algorithmen oder gar in main() rein garnichts zu suchen haben, sondern daß so etwas nur innerhalb des zuständigen Treibers stattfindet und davon rein garnichts 'nach oben' exportiert wird. Die Ebene der Algorithmen soll es dediziert NICHTS angehen, wie der LowLevel-Treiber seine Aufgabe erledigt. Und es bedeutet auch, daß so ein LowLevel-Hardwaretreiber möglichst nicht abhängig ist von weiteren Dateien, die womöglich garnicht im Projekt enthalten sind, sondern als Chip-Support-Package in den Untiefen irgend einer IDE leben. So etwas schafft nur unnötige Abhängigkeiten, die im übrigen auch eine Portierung auf eine andere Toolchain erschweren. Viele Leute hier im Forum sind da völlig verkehrt gepolt. Offenbar kommt das aus den Atmel-AVR-Kreisen her. Diese Leute glauben, wenn sie eine Funktion a la void SetPortPin(Port,Pin,Zustand); schreiben, daß dieses eine echte Hardware-Abstraktion sei. Nein, das ist es eben überhaupt nicht. Sowas ist keine HW-Abstrahierung, sondern nur eine Generalisierung, bei der wichtige Dinge einfach verloren gehen. Typisches Beispiel dafür sind die vielfältigen Möglichkeiten bei Arm-Cortex-Chips zum Umgang mit den Portpins: Da kann man auf Input-Datenregister, Output-Datenregister, Setz- und Rücksetz-Register und bitweise Alias-Arrays zurückgreifen. Ein jedes hat seine Meriten, aber wer nur zur Generalisierung greift (SetPortPin(...)), der vergeigt sich damit all die anderen Möglichkeiten. Richtige Hardware-Abstraktion geht ganz anders, eben über dedizierte Treiber. Das typische Beispiel des UART's hatten wir ja schon, wo man also im Wesentlichen nur Char_Out, Char_In, Char_Available und Init(baudrate) braucht. Bei anderen Dingen sieht eine geeignete Abstrahierung etwa so aus: void Lampe_Ein(void); bool Is_Tuer_Offen(void); usw. - wobei es für den Algorithmus keine Rolle spielt, wie die Funktionen ihre Arbeit verrichten. Vielleicht ist die Tür bei den Antipoden und Is_Tuer_Offen muß per Internet auf die dortige Haustechnik zugreifen - oder der Kontakt der Tür ist gleich nebenan und landet auf Port A Pin 1. W.S.
Bernd K. schrieb: > Wenn der Code fertig ist will ich keine Zahlen im > Quelltext mehr stehen sehen! Dann mach die Augen zu. Ich hätte dir jetzt fast geraten, Briefmarkensammler zu werden, aber das geht nicht. Auf jeder Marke ist garantiert ne Zahl drauf. Aber vielleicht geht Friseur oder Tanzlehrer. Was sollen denn deine sinnlosen Behauptungen? Etwa sowas: #define DREI 3 oder was? Offenbar kannst du nicht zwischen Registern und den darin enthaltenen Bitgruppen unterscheiden. Lies am besten meinen vor-vorletzten Beitrag noch mal gründlich und lerne, ihn auch zu verstehen. W.S.
W.S. schrieb: > Bitnamen sind lediglich Textersetzungen, weil sie ja nur dafür verwendet > werden können, um per Schieben, Maskieren usw. in Register eingepaßt > werden zu können. Also kann man es auch direkt formulieren. Ich > bevorzuge, es etwa so zu halten: > > RCC_APB1ENR = (1<<23) | // USB > (1<<21) | // I2C1 > (1<<17) | // USART2 enable > (1<<2) | // TIM4 enable > (1<<1) | // TIM3 enable > 1; // TIM2 enable Wozu dienen die Kommentare am Ende wenn doch die nackten Zahlen angeblich für sich sprechen? So ähnlich wie unten müsste das aussehen und das wäre dann auch weniger fehlerträchtig und 10 mal einfacher zu lesen, zu verstehen und zu warten:
1 | RCC->APB1ENR = RCC_APB1ENR_USB_Msk |
2 | | RCC_APB1ENR_I2C1_Msk |
3 | | RCC_APB1ENR_USART2_Msk |
4 | | RCC_APB1ENR_TIM4_Msk |
5 | | RCC_APB1ENR_TIM3_Msk |
6 | | RCC_APB1ENR_TIM2_Msk; |
> Etwa sowas: > #define DREI 3 Wenn Du das ernst meinst dann hast Du ein schweres Verständnisproblem, wenn nicht dann bist Du ein langweiliger Troll. > Lies am besten meinen vor-vorletzten Beitrag Da steht nur der übliche geballte Nonsens von Dir drin den man schon kennt, warum sollte irgendjemand Lebenszeit daran verschwenden den selben Unsinn mehr als einmal zu lesen? Ich beschränke mich darauf Deine schlechten Codebeispiele zu kommentieren und zu korrigieren und zu schreiben wie man es stattdessen richtig macht.
:
Bearbeitet durch User
@W.S.: Okay, dann können wir ja beide davon ausgehen, daß ich jetzt verstanden habe, warum bei Dir die hardwarenahe Implementierung so aussieht, wie sie es tut, und warum Du darauf bestehst, dass es nur einen "Hardware Abstraction Layer" geben darf. Ich glaube Dir auch, dass Du diese Vorgehensweise Deinen Projekten schon sehr erfolgreich eingesetzt hast. Für meine Arbeitsweise hätte diese Art der Implementierung allerdings mehr nach- als Vorteile. Vielleicht gehe ich wirklich zu "akademisch" vor. Eine andere Vorgehensweise ist mir allerdings auch nicht möglich. Wie sehr ich Laie in diesem Bereich bin, erkennt man ja schon an der einfachen Tatsache, dass ich im echten Leben noch nicht einmal irgendjemanden kennen würde, der mit mir meinen Quelltext -egal ob gegen Entgelt oder für nette Worte- durchgehen würde. Also bleibt nur der akademische Ansatz und das Verlassen auf die Implementierungshilfen des Herstellers. @Martin, Stefan, Bernd: Von euch verstehe ich die Aufregung nicht. Und die Argumentation ist auch sehr fragwürdig. Auch von der Wortwahl (letzteres nicht von allen). Ich paraphrasiere wieder: "Man darf das nicht unkommentiert stehenlassen, weil das sonst die Jugend verderben könnte. Widerlich." Irgendwie erinnert mich diese Argumentation an die Argumente derer, die Internetsperren durchsetzen wollen. Weil sie Angst haben, dass jemand schwul wird, weil er einmal einen nackten Mann im Internet gesehen hat.
:
Bearbeitet durch User
Walter T. schrieb: > @Martin, Stefan, Bernd, Holm Tiffe: > Von euch verstehe ich die Aufregung nicht. Und die Argumentation ist > auch sehr fragwürdig. Auch von der Wortwahl (letzteres nicht von allen). > Ich paraphrasiere wieder: "Man darf das nicht unkommentiert > stehenlassen, weil das sonst die Jugend verderben könnte. Man sieht doch deutlich wie er Dich schon halb auf seine Seite gezogen hat gerade weil Dir noch die Erfahrung fehlt zu erkennen warum das der unwartbarste und fehlerträchtigste Codierstil der Welt ist den er da propagiert. Im Interesse von Leuten die wie Du in dieser Situation sind ist es angebracht (objektiv!) schlechte Beispiele als solche zu bezeichnen und Leuten zu zeigen und zu begründen wie und warum man es richtig macht. Und Leuten wie W.S. die mit voller Inbrunst fehlerhaften und schlechten Code anderen unter die Nase reiben als angeblich vorbildliches Beispiel sollte man zurechtweisen dürfen! Genauso wie man das auch mit Leuten tut die zum Beispiel Kindern beispielhaft demonstieren wie man rote Ampeln mißachtet und das auch noch toll finden und zu begründen versuchen!
Bernd K. schrieb: > Leuten wie W.S. die mit voller Inbrunst fehlerhaften und schlechten > Code anderen unter die Nase reiben als angeblich vorbildliches Beispiel > sollte man zurechtweisen dürfen! Das hast Du einmal gemacht. Das sollte dann doch reichen. Die Gegenüberstellung kann doch für sich sprechen. Bernd K. schrieb: > Im Interesse von Leuten die wie Du in dieser Situation Jetzt würde mich ja durchaus interessieren, wie Du meine Situation einschätzt. === Bernd K. schrieb: > Man sieht doch deutlich wie er Dich schon halb auf seine Seite gezogen > hat Es gibt hier also Seiten. Die helle und die dunkle Seite der Softwareimplementierung. Ich werde demnächst zur dunklen Seite wechseln, und ihr werdet von mir unter dem Namen "Darth Equus Ferus" hören! Muhahahahaahaha!
W.S. schrieb: > Meine eigenen #define's kenne ich zur Genüge. Ich vergebe ja > gelegentlich auch Namen an Bits - nämlich dort, wo es tatsächlich > sinnvoll ist. Aber dabei achte ich drauf, daß solches im Rahmen der .c > Datei bleibt, wo auch die Verwendung stattfindet. Und nicht über > irgendwelche .h Dateien, wodurch bloß unnötige Abhängigkeiten erzeugt > würden. Warum dann überhaupt h Dateien? Sind doch nur unnötige Abhängigkeiten. Kannst ja von mir aus alle Bit Defines in die c/h Datei des UART Treibers schreiben. Zudem includierst du in deiner serial.c eine #include "STM32F302xB.h". dann kommen DA! eben auch die Bitdefines rein und nicht nur die Registerdefines -> keine zusätzliche Datei. Wo ich mit STM32 angefangen hatte nutzte ich den CMSIS noch nicht und baute meine eigenen structs+Bitdefines. Das sind dann wahnsinnige 3 Dateien! -uart.h -uart_regdefs.h -uart.c Also wenn dus nicht schaffst 3 zusammenhangende Dateien im Ordner zu erkennen tuts mir echt Leid. W.S. schrieb: > Mw E. schrieb: >> Damit man jetzt nicht verwechseln kann ob das Bit bereits >> zurechtgeschoben ist oder nur die Schiebenumemr ist, gibts noch eine >> Endung des define Namen > > Na Klasse! für ein popliges Bit erzeugst du einen ganzen Zirkus an > herumkonstruierten ellenlangen Namen. > > Geht's noch? > > Wird durch das ganze Gefummel mit _pos und _mask der Quelltext lesbarer? > Oder sicherer und zuverlässiger? > > Nein, natürlich nicht. > Irgendwo stolperst auch du über den Urwald deiner eigenen Indirektionen. Ich erzeug da garnichts, der CMSIS Header kommt vom Hersteller und wird zum DL angeboten, da muss man nix verstecktes aus einer IDE rauspopeln. Oder ist automatisch aus einer .svd erzeugbar (Registerdefinitionen für den Debugger Registerview). Zudem sind die Namen garnicht länger als deine Kommentare im Code für die Magic Numbers und diese Kommentare können weg wenn das Bit nen ordentlichen Namen hat: (26 Zeichen): (1<<7) | /* TX Int enable */ (16 Zeichen): USART_CR1_TXEIE| --- (21 Zeichen): (1<<3) | /* TX enable */ (13 Zeichen): USART_CR1_TE| Wie du siehst sparste auchnoch Tipparbeit ;) Die pos/mask sind dafür da mit Leute wie du nicht verwechseln obs 7 oder (1<<7) ist und daher eine gute Lösung. Ein Urwald ist das nicht, die defines sind imemr gleich aufgebaut: PERIPHERIE_REGISTER_BIT_POS/MSK Das heißt du musst den Header nichtmal andauernd aufhaben und lesen, weil man "blind" den Bitnamen aus dem Refman eintippen kann. W.S. schrieb: > Mw E. schrieb: >> Jetzt kannste auf USART->SR arbeiten. > > Steht das im RefMan? Nein, natürlich nicht. Dort steht allenfalls > USART1_SR und kein Struct. > > Aber den eigentlichen Sinn meiner Erklärung hast du nicht im Geringsten > verstanden. Es geht darum, eben NICHT grundsätzlich irgendwelche Namen > zu verwenden für Dinge, die keinen selbständigen Zugriff haben. Wie eben > ein Bit oder eine Bitgruppe im Register XYZ. Weil ARM das so vorschlägt: ---- Code efficiency The (Arm) compiler will normally use a base register plus the immediate offset field available in the load or store instruction to compile struct member or specific array element access. In the Arm instruction set, LDR and STR word and byte instructions have a 4KB range, but LDRH and STRH instructions have a smaller immediate offset of 256 bytes. ---- Arm von mir in Klammern gesetzt, der gcc machts ja auch. Bei deinem definegerödel muss er jedesmal 32Bit aus dem Speicher/Literalpool laden. Zudem, du scheinst es ja überlesen zu haben, erspart es auch Tipparbeit und VERTIPPER wenn du nicht 9 UARTs anlegen musst mit UART1_keks, UART2_keks. Sondern nur ein struct anlegst und das auf eine Basiadresse legst. Dank ARM AAPCS ist das struct auch so definiert, dass da nix schiefgehen kann. Selbst die kleinen STM32 haben ja schon 4-6 UART. Aber du schreibst dir ja anscheiennd gerne die Finger Wund ;) Zudem: Deine Erklärung hat eh keinen Sinn, weil Magic Numbers so ziemlich das widerlichste sind was man einem Quellcode antun kann und da gibts keine Ausreden! Im Refman steht USART_CR und nicht USART1_CR ;) Zudem: kein selbstständiger Zugriff? Du bist der erste der das überhaupt sich ausdenkt. Man merkt wieder, dass du einfach nur keine AHnung von C hast und trotzdem leuten dein Code+Geschwafel aufdrückst. in das UART srruct kannste noch Bitfields für die Bitnamen packen. usart->CR1->TE = 1; Haben einige Studenten bei uns im Kurs so gemacht. (funktioniert bei Registern auf dnen 8Bit Teilzugriffe erlaubt sind) W.S. schrieb: > Mw E. schrieb: >> Aufruf dann als:#define FAULT_UART USART1 >> usart_putc(FAULT_UART, 'W'); >> usart_putc(FAULT_UART, '.'); >> usart_putc(FAULT_UART, 'S'); >> usart_putc(FAULT_UART, '.'); >> >> So kann man dann auch ganz fix einen anderen UART nutzen, wenn man seine >> SW mal auf eine neue Platine betreiben will. > > Selbst hier stellst du dich an wie der erste Mensch. > Bei mir sieht das übrigens so aus: > > String_Out("Mw E. kanns halt nicht", stdout); Nette Nebelkerze, wir reden hier über den Lowlevelcode den du hier als zip gepostet hast. Was weiter oben kommt juckt erstmal nicht. (Natürlich kann man das später so machen). Aber selbst da hat die struct Schreibweise Vorteile, weil du dem Filedescriptor nur das Basisadressendefine als Variable hinterlegen musst und keinen Wald an Funktionspointern. Zudem ist stdio auf Embeddetprojekten wie mit ner Kanone auf Spatzen geschossen. Bernd K. schrieb: > W.S. schrieb: >> eben NICHT grundsätzlich irgendwelche Namen >> zu verwenden für Dinge, die keinen selbständigen Zugriff haben. Wie eben >> ein Bit oder eine Bitgruppe im Register XYZ. > > Was ist das für sinnloses Geschwurbel, was ist bitteschön "keinen > selbstständigen Zugriff haben"? Das Frag ich mich auch! Bernd K. schrieb: > ALLE (ohne Ausnahme!) Konstanten die nicht 0 oder 1 sind haben > gefälligst einen Namen zu haben damit man sofort sieht was sie bedeuten! > Alle! jede einzelne! Wenn der Code fertig ist will ich keine Zahlen im > Quelltext mehr stehen sehen! Richtig. W.S. schrieb: > Ja, so etwa. > Man kann das wirklich so auf den Punkt bringen: Register sind Dinge, auf > die man natürlich zugreifen kann und sie haben Namen entsprechend dem > Manual. > > Bitnamen sind lediglich Textersetzungen, weil sie ja nur dafür verwendet > werden können, um per Schieben, Maskieren usw. in Register eingepaßt > werden zu können. Die Bits haben auch Namen im Manual ;) Bei DEINEM Code sind die Register auch nur Textersetzungen, jetzt sei also doch bitte endlich so Konsequent und schreib auch die Registeradressen als Zahl hin oder nimm Bitnamen statt Magic Numbers. W.S. schrieb: > Ich predige den Leuten hier aber noch etwas anderes: Nämlich > Low-Level-Dinge von High-Level-Dingen zu trennen. > > Das bedeutet, für die unterste Ebene eben LowLevel-Treiber zu schreiben, > die eine Hardware sauber abstrahieren und in sich gekapselt sind. > > Das bedeutet dann eben auch, daß jedes Gefummel mit Hardware-Registern > und deren Bestandteilen in der Ebene der Algorithmen oder gar in main() > rein garnichts zu suchen haben, sondern daß so etwas nur innerhalb des > zuständigen Treibers stattfindet und davon rein garnichts 'nach oben' > exportiert wird. Die Ebene der Algorithmen soll es dediziert NICHTS > angehen, wie der LowLevel-Treiber seine Aufgabe erledigt. > > Und es bedeutet auch, daß so ein LowLevel-Hardwaretreiber möglichst > nicht abhängig ist von weiteren Dateien, die womöglich garnicht im > Projekt enthalten sind, sondern als Chip-Support-Package in den Untiefen > irgend einer IDE leben. So etwas schafft nur unnötige Abhängigkeiten, > die im übrigen auch eine Portierung auf eine andere Toolchain > erschweren. Das ist ja auch richtig so mit der Abstraktion. Der CMSIS Header ist aber eine einzige Datei! So schlimm ist das ja nun nicht oder? Deine uart.c includiert ja auch so schon eine STM32 Serie spezifische Headerdatei, die fällt dann weg. Also wo ist der Unterschied? Achja und die Headerdatei hast du nichtmal mitgeliefert in deinem zip! Der Code ist also nicht verwendbar, den CMSIS Header könnt man sich jetzt beim Hersteller runterladen. In einer "IDE Untiefe" leben die nicht. Guckmal: https://www.st.com/content/st_com/en/products/embedded-software/mcu-mpu-embedded-software/stm32-embedded-software/stm32cube-mcu-mpu-packages/stm32cubef4.html Dabei ist CMSIS noch viel mehr wenn man will (DSP Libs und FFT und und und). Du musst ja die STM32HAL nicht nutzen, tuh ich ja auch nicht, die ist wirklich voller Gruselei und Bugs, aber falsche Bits hab ich bisher im CMSIS Header nicht entdeckt. Eher beim eigene defines erstellen aus dem Refman heraus gabs Fehler zu sehen. In der Beschreibung hieß das Bit anders als noch oben im Registerlayout. Wenn du Magic Numbers jedesmal aus dem Refman heraus abtippst musste jedesmal auf diesen Fehler achten. Einmal ins define abgetippt oder CMSIS genutzt? Einmal aufgefallen und für imemr bereichtigt. (Von diesen Bitfehlern gibts etliche in den STM32 Refmans). W.S. schrieb: > Viele Leute hier im Forum sind da völlig verkehrt gepolt. Offenbar kommt > das aus den Atmel-AVR-Kreisen her. Diese Leute glauben, wenn sie eine > Funktion a la > void SetPortPin(Port,Pin,Zustand); > schreiben, daß dieses eine echte Hardware-Abstraktion sei. Du meinst aus den Failduinokreisen, aber dieses Fass wollen wir jetzt mal lieber nicht aufmachen? bei den AVR Codeguidelines ist das so nämlich nicht festgeschrieben. W.S. schrieb: > Was sollen denn deine sinnlosen Behauptungen? > Etwa sowas: > #define DREI 3 > > oder was? Was ein Schwachsinn. Das define brauch natürlich ein sinnvollen Namen. Bit Nummer 3 in register blahkeks hat doch nen Namen im Refman. Wenn die 3 was anderes macht, dann wird einem ein passender name einfallen.
1 | Register = DREI; |
Ist natürlich kompletter Unsinn.
Mw E. schrieb: > (26 Zeichen): (1<<7) | /* TX Int enable */ > (16 Zeichen): USART_CR1_TXEIE| > --- > (21 Zeichen): (1<<3) | /* TX enable */ > (13 Zeichen): USART_CR1_TE| Das ist doch jetzt absolute Erbsenzählerei. Oder paßt der Quelltext nicht mehr in den Kernspeicher Deines ZX81? Ich stimme W.S. in seinem Vorgehen nicht zu, aber wenn er es so machen will und damit klarkommt: soll er machen.
m.n. schrieb: > Das ist doch jetzt absolute Erbsenzählerei. Oder paßt der Quelltext > nicht mehr in den Kernspeicher Deines ZX81? Ja natürlich, aber W.S. meinte ja, dass es mehr Tipparbeit wäre. Da muss man dann leider Erbsen äh Chars Zählen.
Mw E. schrieb: > Zudem: Deine Erklärung hat eh keinen Sinn, weil Magic Numbers so > ziemlich das widerlichste sind was man einem Quellcode antun kann und da > gibts keine Ausreden! > > Im Refman steht USART_CR und nicht USART1_CR ;) Ach nö, wenn schon dann USARTx_CR und in der Überschrift USART1..,USART2.. usw. Bleib bei mir lieber exakt. > > Zudem: kein selbstständiger Zugriff? > Du bist der erste der das überhaupt sich ausdenkt. > Man merkt wieder, dass du einfach nur keine AHnung von C hast und > trotzdem leuten dein Code+Geschwafel aufdrückst. Ich reduziere deinen ausladenden Beitag auf's Wesentliche: 1. Sogenannte "magic numbers", also echte Zahlen sind dir das Widerlichste. Das ist dein Geschmack und sachlich nicht begründbar. Und daraus leitest du ab, daß alles was dir nicht gefällt, keinen Sinn hat. Tolle Logik! 2. Du verstehst noch immer nicht den Unterschied zwischen einem Register in der Hardware und einem Bit oder einer Bitgruppe in so einem Register. Ich sag's dir nochmal: Auf ein Register kann man zugreifen, auf eine Bitgruppe eben NICHT. Denn dazu muß man auf das zugehörige Register zugreifen und dann oder dabei besagte Bitgruppe geeignet isolieren. Das ist eben das Unselbständige an einer Bitgruppe. Hast du das jetzt ENDLICH kapiert? 3. Du pöbelst hier herum. Vielleicht ist es das Beste für dich, dich selbst als den einzigen C-Kenner einzuschätzen und dem Rest der Welt ins Gesicht zu spucken, wie du es hier getan hast. Aber für den Rest der Welt ist das durchaus nicht das Beste, sondern es ist nur Ausdruck deines Fanatismus. So, ihr versammelten Pappnasen. Ich denke, dem TO ist soweit geholfen, daß er seinen String zum PC nun problemlos ausgeben kann. Wie man sowas sinnvoll in einem LL-Treiber puffert und interruptgesteuert ausgibt, habe ich ihm am geposteten Beispiel gezeigt. Er wird das also in seinen Projekten fürderhin selbst mir seinen eigenen Treibern ohne Probleme erledigen können. Ebenso schätze ich, daß andere Mitleser das eine oder andere Detail ebenso als nützliche Information sehen. Und auf all die fanatischen Flames, wo mit "widerlich" und "du hast ja keine Ahnung" usw. herumgeschmissen wird, kann der Rest der Welt gern verzichten, denn das ist das eigentliche Geschwafel. Vielleicht sollte der eine oder andere in eurer Riege mal über sachliche Argumente nachdenken. W.S.
Ah! Dem Herren gehen die Argumente aus, jetzt tritt er um sich. W.S. schrieb: > Vielleicht ist es das Beste für dich, dich > selbst als den einzigen C-Kenner einzuschätzen und dem Rest der Welt ins > Gesicht zu spucken, wie du es hier getan hast. Du hast ja nun schon von mehreren Leuten in diesem Forum Gegenwind bekommen u.a auch von Mods. Das musste jetzt also nicht nur auf mich beziehen. Du bist eben wie ein Geisterfahrer auf der Autobahn, der sich wundert wieso ihm alle entgegen kommen. W.S. schrieb: > Ach nö, wenn schon dann USARTx_CR und in der Überschrift > USART1..,USART2.. usw. Bleib bei mir lieber exakt. Ich bleib gerne exakt, hier Auszüge aus den Refmans von F2xx: 24.6.4 Control register 1 (USART_CR1) F4xx: 30.6.4 Control register 1 (USART_CR1) G071: 32.7.2 USART control register 1 [alternate] (USART_CR1) H750: 47.7.1 USART control register 1 (USART_CR1) F103: 27.6.4 Control register 1 (USART_CR1) Ich seh da kein USARTx? Oder worauf beziehst du dich? In der Überschrift steht immer "USART registers". W.S. schrieb: > 1. Sogenannte "magic numbers", also echte Zahlen sind dir das > Widerlichste. > Das ist dein Geschmack und sachlich nicht begründbar. Und daraus leitest > du ab, daß alles was dir nicht gefällt, keinen Sinn hat. Tolle Logik! Es gibt also keine sachlichen begründungen? Die kamen hier die ganze zeit und sind auch woanders nachlesbar: Aus der Wikipedia: The term magic number also refers to the bad programming practice of using numbers directly in source code without explanation. In most cases this makes programs harder to read, understand, and maintain. Although most guides make an exception for the numbers zero and one, it is a good idea to define all other numbers in code as named constants.
Mw E. schrieb: > Ich seh da kein USARTx? Oder worauf beziehst du dich? Er meinte, dass ihm USARTx besser gefallen würde.
Walter T. schrieb: > Für meine Arbeitsweise hätte diese Art der Implementierung > allerdings mehr nach- als Vorteile. Vielleicht gehe ich > wirklich zu "akademisch" vor. > Eine andere Vorgehensweise ist mir allerdings auch nicht möglich. Man kann sinnvolle Dinge nehmen und für sich selbst benutzen und andere Dinge weglassen. Ich finde zum Beispiel den Cast auf Basisadressen äußerst nützlich, wenn es sinnvoll ist. Bei ARM sind Peripherieblöcke immer memory-mapped und Kopien voneinander, daher nutze ich das. Netterweise brauche ich nur eine einzelne Definition des Peripherieblocks, die ich von CMSIS fertig bekomme. (Das heißt im Übrigen nicht, dass die CMSIS-Definition korrekt ist!) Andererseits nutze ich die ganzen Masken- und Bit-Definitionen eher selten, weil sie ohne passendes Datenblatt nur begrenzt nützlich sind, dort aber auch nicht aufgeführt werden. Da ich mit verschiedenen Architekturen und Herstellern arbeite, sind die CMSIS-Bitnamen und -masken für mich nicht sprechend. Wer ständig und viel mit z.B. STM32 hantiert, sieht das sicherlich anders. Ich hantiere dann lieber mit Konstanten und zugehöriger Erklärung:
1 | static void i2c_init(void) |
2 | {
|
3 | SYSCTL->RCGC1 |= (1 << 12); /* enable clock for I2C */ |
4 | SYSCTL->RCGC2 |= (1 << 1); /* enable clock for GPIOB */ |
5 | GPIOB->ODR |= 0x0C; /* PB[2:3] open drain */ |
6 | GPIOB->AFSEL |= 0x0C; /* PB[2:3] alt. function (i2c) */ |
7 | I2C0->MCR = 0x0010; /* enable master mode */ |
8 | I2C0->MTPR = 0x09; /* 100 kHz @ 20 MHz */ |
9 | }
|
Dann weiß ich nämlich auch später noch, was ich eigentlich bezweckt habe, ohne ins Datenblatt zu schauen - und wenn ich irgendwas anpassen will, muss ich das ohnehin tun, dann stört mich das auch nicht weiter. Solchen Code habe ich - eben weil hässlich und schwer verständlich - nur auf der unteren Ebene. Oben drüber liegt immer eine Abstraktionsschicht mit halbwegs ordentlicher Schnittstelle.
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.