mikrocontroller.net

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


Autor: Jürgen Sachs (jsachs)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: mehrfacher STK500-Besitzer (Gast)
Datum:

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

Autor: Jürgen Sachs (jsachs)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Werner B. (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Klaus Falser (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Jürgen Sachs (jsachs)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Klaus Falser (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Peter Dannegger (peda)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht 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

Autor: Jürgen Sachs (jsachs)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: AVRFan (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Jürgen Sachs (jsachs)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: AVRFan (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Torben (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: AVRFan (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>Die zwei Buffermethode war Rechenzeitintensiv und auch sehr
>Speicherintensiv.

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

Autor: Torben (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>Aber wieso rechenzeitintensiv??

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

Autor: AVRFan (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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 -> ...).

Autor: Jürgen Sachs (jsachs)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Jürgen Sachs (jsachs)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Simon K. (simon) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Jürgen Sachs (jsachs)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: AVRFan (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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 :-)

Autor: Simon K. (simon) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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)

Autor: Jürgen Sachs (jsachs)
Datum:

Bewertung
0 lesenswert
nicht 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 
:-)

Autor: Jürgen Sachs (jsachs)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Jürgen Sachs (jsachs)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Jürgen Sachs (jsachs)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Andreas K. (a-k)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht 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:
void rx_clear_cmd( void )               // remove last command
{
  u8 dst, src;

  dst = 0;
  src = 0;
  while( Rx_buff[src++] );              // search end of previous command
  do{
    while( src < rx_in ){
      Rx_buff[dst] = Rx_buff[src];      // copy to Rx_buffer start
      dst++;
      src++;
    }
    if( EA == 0 )                       // if interrupts disabled
      rx_in = dst;
    EA = ~EA;                           // toggle interrupt enable
  }while( EA == 0 );                    // until interrupts enabled again
}

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


Peter

Autor: Andreas K. (a-k)
Datum:

Bewertung
0 lesenswert
nicht 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):
uint8_t      buffer[];
volatile int writer;   // buffer write index

void consume(int n)
{
    bool flag;
    do {
        int wx = writer;
        memmove(buffer+0, buffer+n, wx-n);
        int new_writer = wx - n;
        cli();
        flag = (wx == writer);
        if (flag)
            writer = new_writer;
        sei();
    } while (!flag);
}

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

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Andreas K. (a-k)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Besser:
uint8_t      buffer[];
volatile int writer;   // buffer write index

void consume(int n)
{
    int offset = 0;
    bool flag;
    do {
        int wx = writer;
        int count = wx - (offset+n);
        memmove(buffer+offset, buffer+offset+n, count);
        offset += count;
        cli();
        flag = (wx == writer);
        if (flag)
            writer = offset;
        sei();
    } while (!flag);
}

Autor: Andreas K. (a-k)
Datum:

Bewertung
0 lesenswert
nicht 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.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.