Forum: Compiler & IDEs Gute Pufferlösung zwischen ISR und Mainloop


von Jürgen S. (jsachs)


Lesenswert?

Hallo,

ich zermartere mir hier schon das Hirn, aber ich komme nicht so richtig 
auf die Lösung.

Das Problem:
Ich bekomme über einen Busanbindung Daten. Diese Daten können Strings 
(Text bis max 64 Byte länge), Kommandos (im Prinzip auch Text bis max 64 
Byte länge), Ausgang An/Aus (Ausgang Nr 1-255 und Status An Aus) usw...

Die Busdaten werte ich in einem ISR aus und würde die nun gerne in einen 
Puffer schreiben und dort von der Mainloop abarbeiten lassen, da dies 
etwas dauern kann.
So weit so gut. Für das Handling des Softwareupdate muss ich sowieso 
einen Puffer von ca 130 Byte vorhalten, den würde ich gerne hierfür 
nutzen.

Also dachte ich an einen Ringpuffer. ABER: Dann kann ich meine Strings 
oder Kommandos nicht mehr einfach mit "strcmp" prüfen und auswerten, da 
die Daten ja "über das Ende zum Anfang gehen können".

Auch gefällt mich nicht, das ich im ISR die Max 64 Byte von dem 
Seriellen Bus puffer in den Ringpuffer kopieren muss. Das dauert doch 
schon recht lange ?!

Wenn ich das vor der Verarbeitung erst wieder in einen anderen "in reihe 
liegenden Puffer" kopieren muss, braucht das ja wieder Zeit und Speicher 
für die Max 64 Byte.

Vielleicht denke ich auch nur zu Kompliziert oder sehe das zu kritisch ?

Achja das ganze läuft auf einem Mega16 bzw Mega32, programmiert mit 
avr-gcc.

Vielleicht hat einer eine gute Idee.

Danke
Juergen Sachs

von mehrfacher STK500-Besitzer (Gast)


Lesenswert?

Wie wäre es mit einem Pointer-Array in dem du die Startadressen deiner 
Strings speicherst?!

von Jürgen S. (jsachs)


Lesenswert?

mehrfacher STK500-Besitzer wrote:
> Wie wäre es mit einem Pointer-Array in dem du die Startadressen deiner
> Strings speicherst?!
Wenn ich deinen Vorschlag richtig verstehe, habe ich aber immer noch das 
Problem, das die eintreffenden Daten nicht am Stück in den Ringpuffer 
passen, sondern wieder vorne rann müssen, Bsp:

Ringpuffer ist jetzt nur 20 Byte groß um es zu vereinfachen:
- Kommando 1 mit 12 Byte im ISR empfangen und in Ringpuffer ab Pos 1(0) 
kopiert.
- Hauptprogramm beginnt Auswertung Kommando 1.
- Kommando 2 mit 5 Byte im ISR empfangen und in Ringpuffer ab Pos 13 
(12) kopiert.
- Hauptprogramm beendet Auswertung Kommando 1 und gibt "Speicher frei".
- Hauptprogramm beginnt mit Auswertung Kommando 2
- Kommando 3 mit 7 Byte im ISR empfangen und in Ringpuffer ab Pos 18-20 
und 1-5 kopiert.
- Hauptprogramm beendet Ausführung Kommando 2 und gibt "Speicher frei".
usw.....

So würde ich da im Moment mit einem Ringpuffer verstehen und lösen. Da 
hilft mir auch das Pointer Array nicht weiter ?!?!

Aber die Verarbeitung von Kommando 3 im obigen Beispiel ist schon 
schwieriger, da es ja "Zerlegt" ist. Eben Pos 18-20 und Pos 1-5 durch 
den Ringpuffer.

Eine Möglichkeit wäre natürlich auch, immer alle Daten aufzurücken. Also 
wenn vorne 10 Byte frei werden alle nachfolgenden Bytes um 10 Byte 
vorrücken. So bleibt am Ende immer ein Zusammenhängender Speicher übrig. 
Aber das ist ja "kopier" Wahnsinn :-(

Das muss doch schöner gehen.

von Werner B. (Gast)


Lesenswert?

A) Nach ein paar Mega Zeilen Code siehst du das nicht mehr so eng ;-)

B) Bau dir eine eigene "strcmp" die direkt mit dem Ringbuffer umgehen 
kann.

C) Lass doch die ISR die 130 Byte direkt versorgen. Hänge dann noch die 
64 Bytes dran und du hast sogar einen 194 Bytes Ringbuffer. Alles eine 
Sache der "Verwaltung". Kommuniziert wird von ISR zu main nur Index und 
Länge, und main  gibt den Bereich über ein Flag (zB. NULL setzen der 
beiden Variablen) wieder frei.

von Klaus Falser (Gast)


Lesenswert?

Wenn Du nicht mit jedem Byte sparen mußt, danmn würde ich z.B. ein Feld 
von 10  Strings zu je 64 Byte anlegen. Dieser Strings verwendest Du 
zylkisch, wie einen Ringbuffer.
Ein Pointer zeigt jeweils auf den nächsten freien String in den die 
seriellen Kommandos abgelegt werden und auf den String, den der Mainloop 
als nächstes Abarbeiten soll.

Klaus

von Jürgen S. (jsachs)


Lesenswert?

@Werner B.
A) naja auf dem PC ist mir das auch egal, nur eben beim AVR...
B) Eine komplette "Toolchain" aus Defines, Funktionen usw... Aber so 
richtig gefallen will mir das nicht gefallen.
C) Hauptbahnhof ! Wie soll ich die 64 Byte hinten anhängen ?

@Klaus Falser
Bei nur 1kb Ram des Mega16 und 2kB des Mega32 ist das nicht drin. Sonst 
wäre das die Lösung meiner wahl.

Der Punkt ist, da ich das ganze im Moment als "kernel" über eine API 
Entwickelt habe. Die eigentliche Nutzanwendung kommt später je nach 
Anwendung dazu. Daher will ich so viel Ram wie möglich sparen.
Da hier das Handling so einfach wie Möglich sein soll, hätte ich 
Ringpuffer gerne vermieden.
Natürlich könnte ich im ISR das ganze erst in den Ringpuffer kopieren 
(passt am meisten rein). Max 64 Byte, das dürften so an die 250 Zyklen 
verdonnern (geschätzt) und wenn die App so weit ist, das ganze wieder in 
einen Zweiten 64 Byte Puffer kopieren, nochmal an die 250 Zyklen weg :-(
Dort kann das ganze dann in Ruhe verklaubt werden ohen Rücksicht auf 
Verluste.

von Klaus Falser (Gast)


Lesenswert?

Deine zweite Lösung ist auch nicht soo gut.
Du brauchts  64 Byte zusätzlich in der ISR und wieder 64 in der 
Applikation, zusätzlich zum Ringbuffer (+ die Rechenzeit).
Da kannst Du Dir z.B. ein Feld mit 4 oder 5 Feldern zu 64 Byte nach 
meiner Lösung leisten, sind ca. 300 Byte.
Wieviele Befehl im voraus soll man denn überhaupt speichern können. Wenn 
die befehle zu schnell kommen, muss man sie halt verwerfen oder einen 
negative Antwort an den Sender zurückgeben. So etwas mußt Du Dir sowieso 
überlegen.

von Peter D. (peda)


Lesenswert?

Jürgen Sachs wrote:

> Das Problem:
> Ich bekomme über einen Busanbindung Daten. Diese Daten können Strings
> (Text bis max 64 Byte länge), Kommandos (im Prinzip auch Text bis max 64
> Byte länge), Ausgang An/Aus (Ausgang Nr 1-255 und Status An Aus) usw...

Ich verwende dazu einen Linearpuffer.
Die UART schreibt die Bytes ab dem Anfang hinein und wird das 
Endezeichen (0x0A) erkannt, dann wird es durch 0 ersetzt und ein Bit 
gesetzt.
Das Main testet das Bit und kann nun immer ab Pufferanfang das Kommando 
parsen.

Ist es fertig, dann ruft es eine Löschroutine auf, die das Kommando 
entfernt, d.h. alle zwischenzeitlich empfangenen Bytes wieder an den 
Pufferanfang schreibt.

Der große Vorteil ist, man braucht nur einen einzigen Puffer.
Und man muß viel weniger umkopieren (nur alle zwischenzeitlich 
empfangenen Bytes).


Peter

von Peter D. (peda)


Angehängte Dateien:

Lesenswert?

Anbei mal ein Beispielcode für den Linearpuffer.

Er kann sogar gemischt Text und Binärdaten empfangen.

Mit kbhit stellt man fest, ob ein (binär-)Byte empfangen wurde, mit 
getchar holt man es ab.

Um festzustellen, ob ein komplettes Kommando im Puffer steht, ruft man 
rx_get_cmd auf.

Wenn man das Kommando fertig geparst hat, ruft man rx_clear_cmd auf.

Daß hier der UART-Empfang mit Polling erfolgt, ist egal. Als Interrupt 
geht es genauso gut.


Peter

von Jürgen S. (jsachs)


Lesenswert?

Im Prinzip ist das was Du beschreibst das was ich im Moment tue.
Alles in den Seriellen Puffer im ISR und dann Verarbeiten in Mainline. 
Da kommt mein Timing aber bei vielen Kommandos gehörig durcheinander :-(
Daher muss ein Teil der Auswertung mit in die ISR, der Rest des 
Anwendungsprogramms in Main und hat "alle Zeit der Welt" dazu für 
Auswertung und LCD Beschriftung.
Auf das Protokoll habe ich keinen Einfluss, ist vorgegeben und nicht 
mehr veränderbar.

Ich sehe mir dein Beispiel Morgen mal in Ruhe an.
Danke soweit für das Beispiel.

Peter Dannegger wrote:
> Jürgen Sachs wrote:
>
>> Das Problem:
>> Ich bekomme über einen Busanbindung Daten. Diese Daten können Strings
>> (Text bis max 64 Byte länge), Kommandos (im Prinzip auch Text bis max 64
>> Byte länge), Ausgang An/Aus (Ausgang Nr 1-255 und Status An Aus) usw...
>
> Ich verwende dazu einen Linearpuffer.
> Die UART schreibt die Bytes ab dem Anfang hinein und wird das
> Endezeichen (0x0A) erkannt, dann wird es durch 0 ersetzt und ein Bit
> gesetzt.
> Das Main testet das Bit und kann nun immer ab Pufferanfang das Kommando
> parsen.
>
> Ist es fertig, dann ruft es eine Löschroutine auf, die das Kommando
> entfernt, d.h. alle zwischenzeitlich empfangenen Bytes wieder an den
> Pufferanfang schreibt.
>
> Der große Vorteil ist, man braucht nur einen einzigen Puffer.
> Und man muß viel weniger umkopieren (nur alle zwischenzeitlich
> empfangenen Bytes).
>
>
> Peter

von AVRFan (Gast)


Lesenswert?

Ich verwende einen "Doppelpuffer" und brauche dank diesem überhaupt 
keine Bytes umkopieren.

Ein "ActiveBuffer"-Flag mit den Werten (A, B) gibt jederzeit an, welcher 
der beiden Puffer gerade zum Abspeichern benutzt wird.  Zu Anfang werden 
die eintreffenden Bytes in Puffer A gespeichert, beginnend bei Position 
0.  Ist das Kommando vollständig empfangen, wird ein "CmdComplete"-Flag 
gesetzt.  Außerdem wechselt der Wert von ActiveBuffer.  Weitere 
eintreffende Zeichen werden ab jetzt in Puffer B gespeichert (dabei der 
alte Inhalt überschrieben), wieder beginnend bei Position 0.

In der Main wird das "CmdComplete"-Flag geprüft.  Ist es 1, wird der 
Inhalt des gerade nicht aktiven Puffers ausgewertet, und anschließend 
das "CmdComplete"-Flag wieder gelöscht.  Die Auswertung eines Kommandos 
darf somit sinnvollerweise gerade soviel Zeit in Anspruch nehmen, wie 
das komplette Übermitteln eines Kommandos (genauer: des nächsten) 
dauert.

von Karl H. (kbuchegg)


Lesenswert?

>> B) Bau dir eine eigene "strcmp" die direkt mit dem Ringbuffer
>>    umgehen kann.

Das ist die Variante, die ich favorisieren würde.

> B) Eine komplette "Toolchain" aus Defines, Funktionen usw...
>    Aber so richtig gefallen will mir das nicht gefallen.

Na, ja. Sooo schlimm ist das nun auch wieder nicht.
So viele Funktion benutzt man ja beim Kommando-Parsen
nun auch wieder nicht. Und die die benutzt werden, sind
eigentlich ziemlich trivial selbst zu implementieren.

> Da hier das Handling so einfach wie Möglich sein soll, hätte ich
> Ringpuffer gerne vermieden.

Wovon sprichst du? Zugriffe auf Ringbuffer sind ziemlich simpel.
Du darfst nur nicht den Fehler machen und aus der Anwendung heraus
direkt auf den Buffer zuzugreifen. Schreib dir 2 Zugriffsroutinen
(eine für Schreiben und eine für Lesen) und ab da kann es der
restlichen Applikation völlig egal sein, ob da jetzt ein
Ringbuffer zugrunde liegt oder nicht.

> Natürlich könnte ich im ISR das ganze erst in den Ringpuffer kopieren
> (passt am meisten rein). Max 64 Byte, das dürften so an die 250 Zyklen
> verdonnern (geschätzt)

Flexibilität hat eben ihren Preis

von Jürgen S. (jsachs)


Lesenswert?

Karl heinz Buchegger wrote:
>> B) Eine komplette "Toolchain" aus Defines, Funktionen usw...
>>    Aber so richtig gefallen will mir das nicht gefallen.
>
> Na, ja. Sooo schlimm ist das nun auch wieder nicht.
> So viele Funktion benutzt man ja beim Kommando-Parsen
> nun auch wieder nicht. Und die die benutzt werden, sind
> eigentlich ziemlich trivial selbst zu implementieren.
>
Nein, soo schlimm ist das  nicht, aber man muss es machen :-)

>> Da hier das Handling so einfach wie Möglich sein soll, hätte ich
>> Ringpuffer gerne vermieden.
>
> Wovon sprichst du? Zugriffe auf Ringbuffer sind ziemlich simpel.
> Du darfst nur nicht den Fehler machen und aus der Anwendung heraus
> direkt auf den Buffer zuzugreifen. Schreib dir 2 Zugriffsroutinen
> (eine für Schreiben und eine für Lesen) und ab da kann es der
> restlichen Applikation völlig egal sein, ob da jetzt ein
> Ringbuffer zugrunde liegt oder nicht.
Das ist der Einzige Weg wie ich gehen würde. Alles andere ist zu 
Fehleranfällig !

>> Natürlich könnte ich im ISR das ganze erst in den Ringpuffer kopieren
>> (passt am meisten rein). Max 64 Byte, das dürften so an die 250 Zyklen
>> verdonnern (geschätzt)
>
> Flexibilität hat eben ihren Preis
Das ist Richtig, aber 250 Zyklen (worst case) + Protokollverarbeitung im 
ISR, uff.

Da hätte der Vorschlag von AVRFan schon was für sich. Ich splitte meinen 
130 Byte Puffer in 2 Virtuelle Puffer. Nachteil in 99% der Zeit wird der 
nie richtig genutzt und ich habe nur 2 Puffer statt mehrer, je nach 
Stringgrößen. Hummmmmm. Ich drehe mich im Kreis.

von AVRFan (Gast)


Lesenswert?

>Das ist Richtig, aber 250 Zyklen (worst case) + Protokollverarbeitung im
>ISR, uff.

Aber andererseits: 250 Zyklen sind nun auch nicht so grausam viel.  Ein 
mit 8 MHz getakteter AVR braucht dafür 31.25 µs.  Oder willst Du noch 
zehn extrem zeitkritische Hochgeschwindigkeitstasks parallel auf Deinem 
Controller laufen lassen?  Also mein Vorschlag wäre, einfach mal zu 
prüfen, ob Du mit den 250 (oder auch 500) Zyklen in Deinem System 
vielleicht schlicht leben kannst.  Ich schätze die Wahrscheinlichkeit, 
dass dem so ist, als recht hoch ein.

von Peter D. (peda)


Lesenswert?

Jürgen Sachs wrote:
> Im Prinzip ist das was Du beschreibst das was ich im Moment tue.
> Alles in den Seriellen Puffer im ISR und dann Verarbeiten in Mainline.
> Da kommt mein Timing aber bei vielen Kommandos gehörig durcheinander :-(

Kannst Du mal erläutern, was da durcheinanderkommt?

Die Kommandos werden in genau der gleichen Reihenfolge abgearbeitet, wie 
sie eintreffen und nicht durcheinander.

Ich benutze jedenfalls keine Ringpuffer mehr, die sind mir zu aufwendig.

Ich hatte mal nen Ringpuffer für genau 256 Byte geschrieben und die 
Parserfunktionen so, daß sie nur das Lowbyte des Pointers hochzählen. 
Damit kann man über das Ende hinaus parsen. Ist mir dann aber zu tricky 
gewesen.


Peter

von Torben (Gast)


Lesenswert?

Hallo, für ein Projekt hatte ich damals einen Ringbuffer benutzt und kam 
auch bei den Punkt an, wenn er über die Buffergrösse kommt das Frame 
wieder zusammen zubasteln. Ich hab dann ein Flag gesetzt und die 
Information in einen anderen Zwischenbuffer gespeichert und bei 
Frameende den Rest dazu kopiert.

Die zwei Buffermethode war Rechenzeitintensiv und auch sehr 
Speicherintensiv.


Nachdem ich lese das Peter nur ein Linearenbuffer nutzt werde ich für 
zukünftige Sachen die gleiche Methode verwenden, weil er länger 
Mikrocontroller programmiert als ich.

von AVRFan (Gast)


Lesenswert?

>Die zwei Buffermethode war Rechenzeitintensiv und auch sehr
>Speicherintensiv.

Speicherintensiv ist klar - man braucht ja zwei Puffer.  Aber wieso 
rechenzeitintensiv??

von Torben (Gast)


Lesenswert?

>Aber wieso rechenzeitintensiv??

Die Instructions zum Flag setzen, auswerten und von ersten zum zweiten 
Buffer kopieren.

von AVRFan (Gast)


Lesenswert?

>Die Instructions zum Flag setzen, auswerten

Äh... die Instructions zum Flag setzen und auswerten?  Die müssen doch 
in jeder Lösung gegenwärtig sein, oder seh ich das falsch?

>und von ersten zum zweiten Buffer kopieren.

Ich frage mich, ob Du das Prinzip richtig verstanden hast, denn es wird 
nie etwas vom ersten zum zweiten Puffer kopiert - das ist ja gerade der 
Vorteil dieser Methode.

Beide Puffer werden abwechselnd zum Abspeichern der eintreffenden 
UART-Bytes, sowie zum Lesen plus Auswerten der Daten verwendet.

Während das Programm die UART-Bytes in Puffer A speichert, kann es 
unbehelligt und in aller Ruhe die Daten aus Puffer B lesen, weil die 
sich ja nicht ändern.  Wenn das Kommando vollständig ist, ist auch das 
Programm mit dem Auswerten fertig.  Das Programm macht sich nun an die 
Auswertung der Daten in Puffer A, während es neu eintreffende UART-Bytes 
in Puffer B speichert.  Welchen der beiden Puffer das Programm zu 
irgendeinem Zeitpunkt zum Speichern und welches zum Auslesen benutzen 
soll, weiß es anhand der "ActiveBuffer"-Variable, die nach jedem 
Kommando seinen Zustand wechselt (A -> B -> A -> B -> A -> ...).

von Jürgen S. (jsachs)


Lesenswert?

Hat einer von Euch schon Erfahrungen mit "Rotierenden" Puffern auf dem 
AVR ?
Alle Informationen werden hinten angehängt, sofern noch Platz da ist.
Der Puffer wird von vorne abgearbeitet und bearbeitete Daten gelöscht 
indem die anderen Daten nach vorne kopiert werden (aufrücken).

Der Vorteil hierbei ist, das alle Daten weiterhin am Stück vorliegen.
Nachteil: es muss viel kopiert werden
1. Kopieren der Daten in den Bearbeitungspuffer im ISR
2. Bearbeiten der Daten in Mainloop
3. Löschen der Daten durch aufrücken der nachfolgenden Daten in Mainloop

Grob geschätzt dürfte der Kopieraufwand ungefähr mit dem des Ringpuffers 
gleich kommen IM ISR.

Das was ich mir dabei nicht so gefällt, ist das nachrücken der Daten. 
Während dieser Zeit müsste ich vermutlich die ISR sperren ?!
Das wäre beim Ringpuffer ja auch nicht anders ?!?!
Die Ausnutzung wäre genau so gut wie beim Ringpuffer, der Overhead etwas 
größer... Aber sehr einfaches Handling in Mainloop mit Std. Funktionen.

Was mir nur nicht ganz klar wäre ist ob ich wirklich während des ganzen 
Aufrückvorgangs den ISR sperren müsste. Also worst case um 130 Byte 
aufrücken, wenn am Anfang ein Kommando mit 1 Byte war. Wenn ich dabei 
die ISR sperre dürfte das für ca 130 Byte lesen + 130 Byte schreiben + 
130 Zyklen für loop + 130 Zyklen hochzählen. 520 Zyklen, UPS. wie gesagt 
worst case bei vollem Puffer. Sollte nie der Fall werden.

von Jürgen S. (jsachs)


Lesenswert?

AVRFan wrote:
> Beide Puffer werden abwechselnd zum Abspeichern der eintreffenden
> UART-Bytes, sowie zum Lesen plus Auswerten der Daten verwendet.
>
Die Idee ist gut.
In meinem Fall ist es eben so, das ich Daten im Umfang von 5 Byte bis zu 
70 Byte (durch den Protokoll Overhead) kommen.
Je nach "Wert" geht die Verarbeitung schneller oder langsamer. Ein 
Relais schalten geht eben schneller als die Daten für ein Display 
aufzubereiten.

Der Nachteil der Wechselpuffer wäre hier eben das ich nur 2 Puffer 
hätte. Theoretisch könnte ich aber viel mehr Pakete im Puffer speichern.

Sonst würde ich das so umsetzen !

Beim Ringpuffer oder Rotierenden Puffer habe ich eben maximale 
Ausnutzung des Puffers, aber auch den größten Overhead.

von Simon K. (simon) Benutzerseite


Lesenswert?

Bei einem Ringpuffer kopierst du keine Daten "nach vorne".

Du hast zwei Pointer. (Lese/Schreibpointer) die immer auf eine bestimmte 
Position im Ring zeigen. Wenn du also aus dem Puffer lesen willst, liest 
du immer über den Lese-Pointer und inkrementierst den. Willst du was 
schreiben, benutzt du den Schreib-Pointer.

Die Zeiger müssen zu Programmstart natürlich übereinander liegen.

Da muss nichts kopiert werden. Ein Ringpuffer ist für die meisten 
Anwendung eigentlich die beste Lösung.

von Jürgen S. (jsachs)


Lesenswert?

Simon K. wrote:
> Bei einem Ringpuffer kopierst du keine Daten "nach vorne".
>
Das ist mir klar.

Er hat eben nur den Nachteil das die Daten nicht fortlaufend zur 
Verfügung stehen.
Beim Rotierenden Puffer währe das der Fall. Allerdings ist hier Overhead 
höher.

Im Moment betrachte ich beide Lösungen. Mal sehen was mehr Vorteile 
bringt.

von AVRFan (Gast)


Lesenswert?

>In meinem Fall ist es eben so, das ich Daten im Umfang von 5 Byte bis zu
>70 Byte (durch den Protokoll Overhead) kommen.

Ich versteh Dich schon.  Die Länge Deiner Pakete ist variabel.

>Je nach "Wert" geht die Verarbeitung schneller oder langsamer. Ein
>Relais schalten geht eben schneller als die Daten für ein Display
>aufzubereiten.

OK, Relaisschalten geht unbestreitbar schneller, aber möglicherweise 
geht auch beides noch viel schneller als die Übertragung eines 
einzigen Bytes über den UART!  Also mal rechnen: Bei 9600 Baud dauert 
der Transfer eines Bytes 1/960 s = 1.041666 ms.  Das sind bei 8 MHz 
Taktfrequenz 11333 Zyklen, in denen ein AVR ca. 7000 Instruktionen 
abarbeiten kann.  Right?  Wenn nun die Aufbereitungsroutine für die 
Displaydaten diese 7000 Instruktionen gar nicht ausschöpft, macht Dein 
Ziel, kurze Befehle auch "schnell" abzuarbeiten, keinen Sinn.  Bist Du 
sicher, dass Deine Kommandos nicht vielleicht unabhängig von ihrer Länge 
praktisch alle instantan abgearbeitet werden, aufgrund der schieren 
Leistungsfähigkeit eines AVR?

Das mit dem Ringpuffer bei variabler Paketlänge ist ein Problem, das 
keine "gute" Lösung besitzt (bei fester Paketlänge ist der Dual-Puffer 
eine). Willst Du das Parsen mit den normalen Stringfunktionen erledigen, 
bist Du zum gelegentlichen zeitraubenden Umkopieren gezwungen.  Im worst 
case musst Du viel umkopieren.  Willst Du nichts umkopieren, musst Du 
spezielle Stringfunktionen verwenden, die mit den "misaligneten" Strings 
im Puffer (vorderer Stringteil am Addressbereichsende, hinterer am 
Anfang) durch eine zusätzliche Ebene der Indirektion klarkommen.  Diese 
Funktionen werden dann (etwas) langsamer sein.

So schaut's aus :-)

von Simon K. (simon) Benutzerseite


Lesenswert?

Jürgen Sachs wrote:
> Simon K. wrote:
>> Bei einem Ringpuffer kopierst du keine Daten "nach vorne".
>>
> Das ist mir klar.
>
> Er hat eben nur den Nachteil das die Daten nicht fortlaufend zur
> Verfügung stehen.
> Beim Rotierenden Puffer währe das der Fall. Allerdings ist hier Overhead
> höher.

Achso, das verstehst du unter rotierendem Puffer. Nene, sowas ist Murks 
meiner Meinung nach.

Wofür brauchst du denn unbedingt, dass Daten fortlaufend zur Verfügung 
stehen?

Wenn du beispielsweise einen String über den UART ausgeben willst, 
bastelst du dir halt eine UARTSendStringCircular (o.ä.) Funktion, die 
immer ein Byte aus dem Ringpuffer holt und sendet. Und falls man am Ende 
des Ringpuffers ist, wird vorne wieder angefangen.. Null Problemo.

Btw, Beispielsweise In Karl Heinz "Gedankenimplementierungen" eines 
RingBuffers hat er auch immer eine Schnittstelle vorgesehen um Bytes vom 
Ringpuffer abzuholen. Dementsprechend müsste die UART String Sende 
Funktion immer nur über die passende Funktion ein Byte aus dem 
Ringpuffer holen und rausschieben (Solange, bis man die String-Ende-0 
hat)

von Jürgen S. (jsachs)


Lesenswert?

AVRFan wrote:
>>In meinem Fall ist es eben so, das ich Daten im Umfang von 5 Byte bis zu
>>70 Byte (durch den Protokoll Overhead) kommen.
>
> Ich versteh Dich schon.  Die Länge Deiner Pakete ist variabel.
>
>>Je nach "Wert" geht die Verarbeitung schneller oder langsamer. Ein
>>Relais schalten geht eben schneller als die Daten für ein Display
>>aufzubereiten.
>
> OK, Relaisschalten geht unbestreitbar schneller, aber möglicherweise
> geht auch beides noch viel schneller als die Übertragung eines
> einzigen Bytes über den UART!  Also mal rechnen: Bei 9600 Baud dauert
> der Transfer eines Bytes 1/960 s = 1.041666 ms.  Das sind bei 8 MHz
> Taktfrequenz 11333 Zyklen, in denen ein AVR ca. 7000 Instruktionen
> abarbeiten kann.  Right?  Wenn nun die Aufbereitungsroutine für die
> Displaydaten diese 7000 Instruktionen gar nicht ausschöpft, macht Dein
> Ziel, kurze Befehle auch "schnell" abzuarbeiten, keinen Sinn.  Bist Du
> sicher, dass Deine Kommandos nicht vielleicht unabhängig von ihrer Länge
> praktisch alle instantan abgearbeitet werden, aufgrund der schieren
> Leistungsfähigkeit eines AVR?
Baudrate ca 21kBit (ja ist so krumm)
Der Punkt ist dabei das ich die Datem vom Protokoll her Empfange und 
diese dann weitergebe an die Anwendungsschicht. Bei Relais an aus nie 
Problem, bei Kommandos ist das viel aufwendiger. Im Prinzip ein 
permanenter Stringvergleich bis ein Treffer kommt. Das dauert bei vielen 
Kommandos in Folge zu lange und ich komme aus dem Tritt und ich kann 
nicht mehr schnell genug, also dem Timing Entsprechend, Antworten.

Daher muss jetzt die Komplette Kommandoverarbeitung abgetrennt und die 
Aufwendige Protokollauswertung in den ISR. Bisher hängt diese auch in 
der Mainloop. Nur der Empfangspuffer wird bisher im ISR befüllt.
Theoretisch hier alles so Empfohlen im Forum. Nur Pufferbearbeitung im 
ISR, Rest in Mainloop.

> Das mit dem Ringpuffer bei variabler Paketlänge ist ein Problem, das
> keine "gute" Lösung besitzt (bei fester Paketlänge ist der Dual-Puffer
> eine). Willst Du das Parsen mit den normalen Stringfunktionen erledigen,
> bist Du zum gelegentlichen zeitraubenden Umkopieren gezwungen.  Im worst
> case musst Du viel umkopieren.  Willst Du nichts umkopieren, musst Du
> spezielle Stringfunktionen verwenden, die mit den "misaligneten" Strings
> im Puffer (vorderer Stringteil am Addressbereichsende, hinterer am
> Anfang) durch eine zusätzliche Ebene der Indirektion klarkommen.  Diese
> Funktionen werden dann (etwas) langsamer sein.
>
> So schaut's aus :-)
Ja, keine Vorteile ohne Nachteile. Wieso hat ein AVR auch keine 32k Ram 
:-)

von Jürgen S. (jsachs)


Lesenswert?

Simon K. wrote:
> Jürgen Sachs wrote:
>> Simon K. wrote:
>>> Bei einem Ringpuffer kopierst du keine Daten "nach vorne".
>>>
>> Das ist mir klar.
>>
>> Er hat eben nur den Nachteil das die Daten nicht fortlaufend zur
>> Verfügung stehen.
>> Beim Rotierenden Puffer währe das der Fall. Allerdings ist hier Overhead
>> höher.
>
> Achso, das verstehst du unter rotierendem Puffer. Nene, sowas ist Murks
> meiner Meinung nach.
Das kommt auf den Anwendungsfall an...

> Wofür brauchst du denn unbedingt, dass Daten fortlaufend zur Verfügung
> stehen?
Hauptanwendung:
Ich bekomme Texte die ich als Kommando Interpretieren muss. Also 
Hauptsächlich Text suchen und vergleichen. Eventuell einen Teil des 
Textes aufs Display bringen. Aber was mit dem Kommando passiert ist 
wieder Anwendungssache. Im Prinzip bin ich dabei mir eine saubere API zu 
bauen, wo ich nur noch meine Anwendung aufsetze.
Genau das habe ich jetzt schon, im Prinzip. Nur bekomme ich ab und zu 
Timingprobleme...
Der Grund ist klar, daher muss ich die Zwischenpuffer.

> Wenn du beispielsweise einen String über den UART ausgeben willst,
> bastelst du dir halt eine UARTSendStringCircular (o.ä.) Funktion, die
> immer ein Byte aus dem Ringpuffer holt und sendet. Und falls man am Ende
> des Ringpuffers ist, wird vorne wieder angefangen.. Null Problemo.
>
Nur noch Text parsen und reagieren, aber wie gesagt ist das 
Anwendungssache, darauf habe ich keinen Einfluss.

Deswegen mache ich mir ja die Mühe das Optimale zu finden.
Da ich die Anwendungsschicht nicht im Griff habe, also noch nicht kenne, 
ist es wichtig das "richtig" zu machen.

von Peter D. (peda)


Lesenswert?

Simon K. wrote:

> Achso, das verstehst du unter rotierendem Puffer. Nene, sowas ist Murks
> meiner Meinung nach.

Ist das gleiche, was ich Linearbuffer genannt habe und überhaupt kein 
Murks.

Er braucht auch nur ganz wenig Umkopiererei. Im Mittel müssen ja die 
Kommandos langsamer kommen, als sie ausgewertet werden. Damit können 
nicht
sehr viele neue Bytes kommen, wenn ein Kommando abgearbeitet wird und 
nur diese müssen an den Anfang kopiert werden.
Es steht natürlich der gesamte Puffer für eine höhere Spitzenlast zur 
Verfügung.


> Wofür brauchst du denn unbedingt, dass Daten fortlaufend zur Verfügung
> stehen?

Ich habe keine Lust, mir ein strchr, strcmp, atoi, atof usw. alles 
selber zu basteln.
Wozu sind Bibliotheken da, wenn man sie nicht nutzen kann?


Peter

von Jürgen S. (jsachs)


Lesenswert?

Peter Dannegger wrote:
> Simon K. wrote:
>
>> Achso, das verstehst du unter rotierendem Puffer. Nene, sowas ist Murks
>> meiner Meinung nach.
>
> Ist das gleiche, was ich Linearbuffer genannt habe und überhaupt kein
> Murks.
>
> Er braucht auch nur ganz wenig Umkopiererei. Im Mittel müssen ja die
> Kommandos langsamer kommen, als sie ausgewertet werden. Damit können
> nicht
> sehr viele neue Bytes kommen, wenn ein Kommando abgearbeitet wird und
> nur diese müssen an den Anfang kopiert werden.
> Es steht natürlich der gesamte Puffer für eine höhere Spitzenlast zur
> Verfügung.
>
Ahja, dann hab ich das falsch Verstanden. Das mit den Bytes sehe ich 
eben so.

>> Wofür brauchst du denn unbedingt, dass Daten fortlaufend zur Verfügung
>> stehen?
>
> Ich habe keine Lust, mir ein strchr, strcmp, atoi, atof usw. alles
> selber zu basteln.
> Wozu sind Bibliotheken da, wenn man sie nicht nutzen kann?
>
So geht es mir auch. Warum das Rad neu erfinden. Zudem ist dann jeder in 
der Anwendungsschicht von MEINEN Routinen abhängig für den Ringpuffer.

Wie gesagt ich drehe mich hier im Kreis zwischen Vor- und Nachteilen.

von Jürgen S. (jsachs)


Lesenswert?

Ich werde mich mal den Ring und Rotierenden (Linear) Puffer demnächst 
genauer widmen.
Hab voraussichtlich ab Ende nächster Woche Urlaub, da hab ich mal wieder 
Zeit :-)

Ich kann ja mal Melden was dabei raus kam.

Gruss und Danke soweit

von Andreas K. (a-k)


Lesenswert?

Man kann bei linearem Puffer durchaus vermeiden, die Interrupts für 
erhebliche Zeit sperren zu müssen. Indem man das zweimal macht. Der 
erste Durchlauf schiebt bei offenen Interrupts. Der zweite schiebt bei 
gesperrten Interrupts nur noch jene wenigen Bytes, die zwischenzeitlich 
aufgelaufen sind und aktualisiert den Schreibindex.

Paranoide Gemüter reduzieren die für Interrupts gesperrte Zeit weiter, 
indem sie das ganze solange wiederholen, bis zwischenzeitlich nichts 
mehr auflief, was die gesperrte Zeit auf diesen Test und den Update vom 
Schreibindex reduziert.

von Peter D. (peda)


Lesenswert?

Andreas Kaiser wrote:
> PS: Die kritische Phase beschränkt sich in der verschärften Fassung auf
> einen compare-and-exchange Befehl.

Nein, es sind mehrere Befehle. Das aufgelaufene Byte muß kopiert werden, 
der Index erhöht und dann dem Schreibpointer zugewiesen werden.

Ich kann mir nicht vorstellen, daß es ne CPU gibt, die diese vielen 
Instruktionen in einem Befehl schafft.


Beim 8051 könnte das so aussehen:
1
void rx_clear_cmd( void )               // remove last command
2
{
3
  u8 dst, src;
4
5
  dst = 0;
6
  src = 0;
7
  while( Rx_buff[src++] );              // search end of previous command
8
  do{
9
    while( src < rx_in ){
10
      Rx_buff[dst] = Rx_buff[src];      // copy to Rx_buffer start
11
      dst++;
12
      src++;
13
    }
14
    if( EA == 0 )                       // if interrupts disabled
15
      rx_in = dst;
16
    EA = ~EA;                           // toggle interrupt enable
17
  }while( EA == 0 );                    // until interrupts enabled again
18
}

Beim GCC muß man noch drauf achten, daß rx_in volatile ist.


Peter

von Andreas K. (a-k)


Lesenswert?

Peter Dannegger wrote:

> Nein, es sind mehrere Befehle. Das aufgelaufene Byte muß kopiert werden,
> der Index erhöht und dann dem Schreibpointer zugewiesen werden.

Nein. In C dürfte die verschärfte Variante etwa so aussehen (ad hoc 
Version):
1
uint8_t      buffer[];
2
volatile int writer;   // buffer write index
3
4
void consume(int n)
5
{
6
    bool flag;
7
    do {
8
        int wx = writer;
9
        memmove(buffer+0, buffer+n, wx-n);
10
        int new_writer = wx - n;
11
        cli();
12
        flag = (wx == writer);
13
        if (flag)
14
            writer = new_writer;
15
        sei();
16
    } while (!flag);
17
}

> Ich kann mir nicht vorstellen, daß es ne CPU gibt, die diese vielen
> Instruktionen in einem Befehl schafft.

Was man braucht:
  compare R1 with [memory]
  store R2 to [memory] if equal
Ebendies leistet der CMPXCHG Befehl von x86.

Sowas wird routinemässig benötigt, um in SMP-Betriebssystemen verkettete 
Listen verarbeiten zu können ohne das jedesmal mit Semaphoren absichern 
zu müssen. Und daher gibt es bei x86 auch eine Doppelwortversion davon 
(CMPXCHG8B).

von Peter D. (peda)


Lesenswert?

@Andreas Kaiser

Tja, was soll man dazu noch sagen :-(

Du solltest Dir vielleicht überlegen, daß man ne Diskussion zerstört, 
wenn man seinen Artikel löscht, auf den jemand geantwortet hat.


Peter

von Andreas K. (a-k)


Lesenswert?

Besser:
1
uint8_t      buffer[];
2
volatile int writer;   // buffer write index
3
4
void consume(int n)
5
{
6
    int offset = 0;
7
    bool flag;
8
    do {
9
        int wx = writer;
10
        int count = wx - (offset+n);
11
        memmove(buffer+offset, buffer+offset+n, count);
12
        offset += count;
13
        cli();
14
        flag = (wx == writer);
15
        if (flag)
16
            writer = offset;
17
        sei();
18
    } while (!flag);
19
}

von Andreas K. (a-k)


Lesenswert?

Peter Dannegger wrote:

> Du solltest Dir vielleicht überlegen, daß man ne Diskussion zerstört,
> wenn man seinen Artikel löscht, auf den jemand geantwortet hat.

Versehentlich. Sorry. Lautete:

PS: Die kritische Phase beschränkt sich in der verschärften Fassung auf 
einen compare-and-exchange Befehl. Wenn es den ununterbrechbar gibt 
(x86) oder er äquivalent implementierbar ist (PowerPC), dann ist das 
gleichermassen interruptfest und tauglich für SMP/multithreading ohne 
irgendwas explizit sperren oder blockieren zu müssen.

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.