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.
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.
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
voidDMA1_Stream6_IRQHandler(void)
2
{
3
/* Clear DMA Stream Transfer Complete interrupt pending bit */
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.
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.
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?
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)
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.
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.
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.
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
typedefstruct
2
{
3
__IOuint32_tSR;/*!< USART Status register, Address offset: 0x00 */
4
__IOuint32_tDR;/*!< USART Data register, Address offset: 0x04 */
__IOuint32_tCR1;/*!< USART Control register 1, Address offset: 0x0C */
7
__IOuint32_tCR2;/*!< USART Control register 2, Address offset: 0x10 */
8
__IOuint32_tCR3;/*!< USART Control register 3, Address offset: 0x14 */
9
__IOuint32_tGTPR;/*!< 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:
#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
voidusart_putc(USART_TypeDef*uart,charc){
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?
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.
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.
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.
@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.
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.
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.
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
staticvoidi2c_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.