Forum: Mikrocontroller und Digitale Elektronik STM32: Sendepuffer für USART


von Walter T. (nicolas)


Angehängte Dateien:

Lesenswert?

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?

von Walter T. (nicolas)


Angehängte Dateien:

Lesenswert?

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
von The A. (the_a343)


Lesenswert?

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.
--

von Walter T. (nicolas)


Lesenswert?

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
von Stefan F. (Gast)


Lesenswert?

Das "Cube F4" Paket kann man ganz normal einzeln downloaden, ohne CubeMx 
zu installieren.
https://www.st.com/en/embedded-software/stm32cubef4.html

von Walter T. (nicolas)


Lesenswert?

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
von W.S. (Gast)


Angehängte Dateien:

Lesenswert?

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.

von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

@WS
Musst du hier andauernd deinen Magic Number Gruselcode posten?
1
USART2_ISR & (1<<7)
Widerlich!

von Zeno (Gast)


Lesenswert?

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.

von Heinz M. (subi)


Lesenswert?

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.

von m.n. (Gast)


Lesenswert?

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.

von W.S. (Gast)


Lesenswert?

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.

von Stefan F. (Gast)


Lesenswert?

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.

von Walter T. (nicolas)


Lesenswert?

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
von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

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)

von Stefan F. (Gast)


Lesenswert?

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.

von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

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,

von Stefan F. (Gast)


Lesenswert?

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.

von The A. (the_a343)


Lesenswert?

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.
--

von m.n. (Gast)


Lesenswert?

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.

von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

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)

von Holm T. (Gast)


Lesenswert?

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

von Walter T. (nicolas)


Angehängte Dateien:

Lesenswert?

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
von m.n. (Gast)


Lesenswert?

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.

von Walter T. (nicolas)


Lesenswert?

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?

von m.n. (Gast)


Lesenswert?

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.

von Walter T. (nicolas)


Lesenswert?

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
von Andreas M. (Gast)


Lesenswert?

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

von Bernd K. (prof7bit)


Angehängte Dateien:

Lesenswert?

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
von Walter T. (nicolas)


Lesenswert?

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.

von W.S. (Gast)


Lesenswert?

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.

von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

@ 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.

von Bernd K. (prof7bit)


Lesenswert?

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.

von W.S. (Gast)


Lesenswert?

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.

von Walter T. (nicolas)


Lesenswert?

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
von Bernd K. (prof7bit)


Lesenswert?

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
von c++ (Gast)


Lesenswert?

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
}

von Stefan F. (Gast)


Lesenswert?

c++ schrieb:
> memcopied

Das klingt wie "gesaugstaubt".Schreibt man das wirklich so?

von W.S. (Gast)


Lesenswert?

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.

von W.S. (Gast)


Lesenswert?

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.

von Bernd K. (prof7bit)


Lesenswert?

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
von Walter T. (nicolas)


Lesenswert?

@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
von Bernd K. (prof7bit)


Lesenswert?

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!

von Walter T. (nicolas)


Lesenswert?

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!

von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

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.

von m.n. (Gast)


Lesenswert?

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.

von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

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.

von W.S. (Gast)


Lesenswert?

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.

von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

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.

von Stefan F. (Gast)


Lesenswert?

Mw E. schrieb:
> Ich seh da kein USARTx? Oder worauf beziehst du dich?

Er meinte, dass ihm USARTx besser gefallen würde.

von S. R. (svenska)


Lesenswert?

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
Noch kein Account? Hier anmelden.