Forum: Mikrocontroller und Digitale Elektronik Formeln zur Ringpuffer-Arithmetik?


von Rolf F. (Gast)


Lesenswert?

Wo findet man die Formeln zur Ringpuffer-Arithmetik für einfache
Ringpuffer mit n Kanälen und Ringpuffer mit n Kanälen, in denen die
Daten abgelegt weden, die im Repeat-sequence-of-channels Mode gemessen
werden und die anschließend in jedem Kanal m mal aufaddiert werden,
bevor sie im Ringpuffer abgelegt werden?

Ich will damit in C die mit einem AD-Wandler (im
Repeat-sequence-of-channels Mode) eingelesenen Werte in Ringpuffer
packen und die dann mit Applikationen auslesen.
Das Aufaddieren brauche ich um den Netzbrumm rauszufiltern.
Den Applikationen wird jeweils ein Ringpuffer und der Counter (Anzahl
der bisher verarbeiteten AD-Werte) übergeben.
Damit man die Daten aber sowohl lückenlos als auch ohne
Überschneidungen und zeitlich geordnet bekommt, beispielsweise zur
Fourier-Transformation oder zum Plotten der Daten, muß man aber heftig
rumrechnen und irgendwo sollte es diese Formeln doch fertig geben.

von Peter Dannegger (Gast)


Lesenswert?

Ein Ringpuffer ist ein FIFO und damit völlig ungeeignet für sowas.

Warum denn immer alles so kompliziert sehen ?

Nimm einfach ein Feld der Größe der ADC-Kanäle und dann addier die
neuen Werte darin auf und ziehe den Mittelwert ab, z.B.:

unsigned long sum[ADC_CHANNELS];

sum[i] += new_ADC_val - sum[i] / 16;

Das entspricht dann einem einfachen RC-Glied als Tiefpaß.


Peter

von Rolf F. (Gast)


Lesenswert?

> Ein Ringpuffer ist ein FIFO und damit völlig ungeeignet für sowas.

Unsinn; einen Ringpuffer kann man auch als FIFO, FILO oder auch Queue
benutzen. Ich benutze Ringpuffer meist zum Austausch der periodisch
gemessenen Daten zusammen mit dem lezten Zähler, damit ich die Daten
lückenlos und ohne Überschneidungen zusammensetzen kann.


> Nimm einfach ein Feld der Größe der ADC-Kanäle und dann addier die
> neuen Werte darin auf und ziehe den Mittelwert ab, z.B.:
> ...
> Das entspricht dann einem einfachen RC-Glied als Tiefpaß.

Das ist nicht das, was ich brauche und zudem rechenaufwändiger.
Zum Auswerten reicht ein Wert pro Kanal nicht aus; ich brauch pro Kanal
einen Ringpuffer.
Außerdem ändert das nichts am grundlegenden Problem. Beispielsweise muß
die speichernde ISR wissen, was mit dem Wert von der Messung xyz gemacht
werden muß; nach der Berechnung des aktuellen Indexes im aktuellen
Rinpuffer muß ja entweder der erste der m Werte nur geschrieben werden
oder die anderen der m addiert werden. Zudem muß nach einem Auslesen
der Werte von einer Applikation u. A. aus dem ausgelesenen Zähler-Wert
der letzte Eintrag im ausgelesenen Ringpuffer berechnet werden.
Im Prinzip ist das mit Modulo-Arithmetik nicht schwer, und die fertige
Software sehr performant, aber dafür braucht man ein bischen viel
Rechnerei um auf die Formeln zu kommen und zudem macht man da leicht
Fehler wie "um 1 daneben".

von Peter Dannegger (Gast)


Lesenswert?

"einen Ringpuffer kann man auch als FIFO, FILO oder auch Queue
benutzen."

Sag ich doch !

Ein Ringpuffer kann immer nur der Reihe nach, nicht aber wahlfrei
zugegriffen werden.
Habe ich aber z.B. den Meßwert von Kanal 7, dann muß ich auf das 7.
Element zugreifen, d.h. ein Ringpuffer ist ein Schuß in den Ofen, man
braucht einen Zugriff über einen Index.


"Das ist nicht das, was ich brauche und zudem rechenaufwändiger."

???

Das ist doch wesentlich effektiver als Deine umständlichen "durch die
Brust ins Auge" Herangehensweise.

Was ist denn so aufwendig an einer simplen Addition, Schieben um 4 Bit
und einer Subtraktion ?


Ich hatte Dich so verstanden, daß Du das Brummen unterdrücken willst
und da gibt es nichts einfacheres, effektiveres und Ressorcen
schonenderes als einen RC-Tiefpaß.
Ich benutze diese Routine schon seit Jahren und sie ist sehr
zuverlässig.
Ein Timerinterrupt startet den ADC und macht nach der Wandlung diese 3
simplen Rechenschritte.
Die Applikation kann nun jederzeit (d.h. völlig asynchron) darauf
zugreifen und hat immer einen gültigen gefilterten Wandlerwert in den
Händen (sum[i] / 16).


Es ist mir herzlich egal, womit Du große Mengen Deines SRAM und
Rechenzeit sinnlos verschleuderst. Ich verstehe bloß nicht warum Du so
verschwenderisch sein willst.


Peter

von Rolf F. (Gast)


Lesenswert?

> Ein Ringpuffer kann immer nur der Reihe nach, nicht aber wahlfrei
zugegriffen werden.

Unsinn; auf das Array, in dem der Ringpuffer steht, kann man immer
wahlfrei zugreifen!
Ich nehme dafür meist memmove oder memcpy um die Daten zeitlich zu
ordnen und schon einmal eingelesenes abzuschneiden.


> Was ist denn so aufwendig an einer simplen Addition, Schieben um 4
Bit
> und einer Subtraktion ?

Erstens wird damit immer nur abgerundet, zweitens gibt's bei der
Addition keinen Rundungsfehler und drittens kann es etwas allgemeiner
etwas anderes als eine Addition sein, was mit den m Werten gemacht
wird. Wichtig ist erstmal das Rechnen mit dem Zeit-Index.


> Es ist mir herzlich egal, womit Du große Mengen Deines SRAM und
> Rechenzeit sinnlos verschleuderst. Ich verstehe bloß nicht warum Du
so
> verschwenderisch sein willst.

Unsinn, denn Ringpuffer sind die effektivsten Datenstrukturen zum
Zwischenspeichern von Daten, da sie eine feste Größe haben (kein
malloc/calloc +free nötig) und die Daten so lange drinn bleiben, bis
sie von neueren überschrieben werden müssen.

von Rufus T. Firefly (Gast)


Lesenswert?

.

   "Ich nehme dafür meist memmove oder memcpy um die Daten
   zeitlich zu ordnen und schon einmal eingelesenes abzuschneiden."

Sonderlich effizient ist so eine Vorgehensweise aber nicht, vor allem
wird dadurch der _Ring_puffer irgendwie widersinnig.

Das hast Du ja eigentlich auch erkannt ("Ringpuffer sind die
effektivsten Datenstrukturen zum Zwischenspeichern von Daten"), daher
solltest Du tunlichst auf so Spielereien wie memmove oder memcpy
verzichten.

von Peter Dannegger (Gast)


Lesenswert?

Nun wird mir manches klarer, daß einige Leute einen 3GHz Pentium
brauchen aber andere für die gleiche Sache auf einem 8051 noch 80% Idle
sind.

Ich bin halt Praktiker und mag deshalb schnelle und einfache Routinen,
memmove, malloc usw. haben daher bei mir nichts verloren.

Warum Daten erst umständlich in einen Puffer klauben, kompliziert
umsortieren und wieder ausspucken, statt sie einfach gleich zu
verarbeiten.


Die Abrundung kompensiert sich vollständig, da ja auch bei der
Subtraktion abgerundet wird.

Rein mathematisch gesehen ginge ja auch:

sum[i] = new_ADC_val + sum[i] * 15 / 16;

Aber praktisch ist es schlichtweg falsch, es erreicht nämlich nie den
Endwert. Das sind halt so die Praktiker-Tricks.


Ringpuffer benutze ich nur für den Datenempfang (UART, CAN), da dort
die Befehle erst abgearbeitet werden können, wenn sie komplett sind.
Und da sind sie wirklich sinnvoll, da ja keine Umsortierung notwendig
ist (kein memmove).


Peter

von Rolf F. (Gast)


Lesenswert?

> Warum Daten erst umständlich in einen Puffer klauben, kompliziert
> umsortieren und wieder ausspucken, statt sie einfach gleich zu
> verarbeiten.

Das geht nicht, denn die Messung und Speicherung wird von einem
Kernel-Thread gemacht und die Auswertung und Speicherung von einem oder
mehreren User-Space-Threads. Alles zusammen zu packen ergäbe
Spagetti-Code, der weder orthogonal noch modular wäre.

Außerdem hat man mit den Ringpuffern ja die Felxibilität die Rohdaten
verschicken zu können, um sie woanders auszuwerten, beispielsweise
mittels Fourier-Analyse.

von Peter Dannegger (Gast)


Lesenswert?

@Rolf

"Alles zusammen zu packen ergäbe Spagetti-Code, der weder orthogonal
noch modular wäre."

Was ist denn daran Spaghetti, eine sinnvolle Datenverdichtung schon bei
der Datenaufnahme zu machen ?

Spaghetti ist, wenn man kein Konzept hat und ellenlangen Code
hintereinander klatscht.


Der AD-Wandler Prozeß weiß, wann ein neues Datum hereinkommt und kann
dieses sofort verarbeiten. Ein nachfolgender Prozeß muß dagegen erst
umständlich synchronisiert werden. Neben den reinen Wandlerdaten im
Ringpuffer müssen also noch Synchronisationsmechanismen implementiert
werden. Außerdem müssen Pufferüberläufe abgefangen werden, wenn der
nachfolgende Prozeß mal nicht schnell genug die Daten absaugen kann.

Ringpuffer machen daher den Datenaustausch generell sehr aufwendig und
kompliziert.

In Steuerungen sind aber alte Daten in der Regel kalter Kaffe, d.h.
eine einzige Pufferzelle, die regelmäßig mit neuen Daten überschrieben
wird, unabhängig davon, ob der nachfolgende Prozeß die Daten gelesen hat
oder nicht, ist viel einfacher und effektiver.

Ich finde es außerdem nicht sinnvoll, bei z.B. 8 ADC-Kanälen die 8
nachfolgenden Prozesse 8 Filterroutinen laufen zu lassen, statt einfach
nur einer für alle.



"Außerdem hat man mit den Ringpuffern ja die Felxibilität die
Rohdaten
verschicken zu können"

Vergiß es, die eierlegende Wollmilchsau zu entwickeln, die wird ewig
nicht fertig.
Wenn man sich einfach nur auf die aktuell Aufgabe konzentriert, wird
das Programm mindestens 10-mal schneller fertig.


Ich hab früher auch mal so gedacht und das Ende vom Lied war immer:

Mühsam vorausgeplante Erweiterungen wurden nie realisiert, da
inzwischen schon längst andere Hardware, andere CPUs, andere Konzepte
verwendet wurden.

Man verschleißt sich einfach nur an der Featureritis, aber es bringt
überhaupt nichts, Software ist heutzutage viel zu kurzlebig.


Peter

von Rolf F. (Gast)


Lesenswert?

> Ein nachfolgender Prozeß muß dagegen erst
> umständlich synchronisiert werden.

Eben nicht; gerde deshalb nehme ich Ringpuffer und deshalb fehlen mir
noch die Formeln für die komplizierteren Ringpuffer. Für den einfachen
Ringpuffer habe ich die Formeln schon:

Der letzte Eintrag ist beim Index (Zeitindex % N) und der Puffer
enthält Min( N, Zeitindex) Werte. Die Zeit-Index-Differenz zweier
direkt benachbarter Puffer-Inhalte ist 1 oder n-1; die Differenz zum
vorherigen Wert ist immer 1.
Der Zeitindex wird mit -1 initialisiert, damit der nach dem ersten
Eintrag in den Puffer gleich 0 ist und so Index = Zeitindex % N immer
erfüllt  ist.
Zweckmäßigerweise speichert man den Zeitindex am Ende des auszugebenden
eldes, damit es mit einer Anweisung mit diesem Datenfeld ausgegeben
werden kann, beispielsweise über Shared Memory (RTAI). Beispiel:

int16_t out_ringbuffer[N + 4];
int64_t * ptr_counter = &out_ringbuffer[N];

Und der Ringpuffer-Index wird über Makros verwendet:

// increment for ring buffer index (0..n-1), also works with start
value -1
#   define mc_RING_INC(x, n) {++(x); (x) %= (n);}

// next value at incrementing, preview
#   define mc_RING_NEXT(x, n) (((x)+1)%(n))

// decrement for ring buffer index (0..n-1), also works with start
value < 0
#   define mc_RING_DEC(x, n) {if((x) > 0)  --(x); else (x)=(n)-1;}

// value before last increment, review
#   define mc_RING_LAST(x, n) ( (x) ? ((x)-1) : ((n)-1))

von Rolf F. (Gast)


Lesenswert?

Nachtrag: Der Puffer enthält Min( N, Zeitindex-1) Werte.

von Rolf F. (Gast)


Lesenswert?

Ähm, Verwechselung; es sind natürlich
(Zeitindex > n-1 ? N : Zeitindex +1)
Werte.

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.