www.mikrocontroller.net

Forum: Compiler & IDEs gleitender Mittelwert (ADC)


Autor: markus (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
ich habe einen display, der zeigt 3 Messwerte (Spannung. Strom1, Strom2)
es geht um einen Laderegler (Akku-spannung, vervraucher-strom, 
Ladestrom)
die werte sind nicht Stabil, es gibt immer einen Rauschen, und versuche 
das Softwaremässig zu lösen, ich bin auf die Idee gekommen gleitender 
mittelwert zu realieseren, hat jemand eine Idee wie das geht?
ich habe hier einen Vorschlag aber noch nicht ganz richtig

#difine N 16

uint16_t adc_read(uint8_t mux)
{
  uint8_t i;
  uint16_t result;
  static Messwert [3]={0,0,0};
  ADMUX = mux | (1<<REFS0) ;                      // Kanal waehlen

  ADCSRA = (1<<ADEN) | (1<<ADPS0) | (1<<ADPS1) ;    // Frequenzvorteiler
                               // setzen auf 8 (1) und ADC aktivieren 
(1)

  /* nach Aktivieren des ADC wird ein "Dummy-Readout" empfohlen, man 
liest
     also einen Wert und verwirft diesen, um den ADC "warmlaufen zu 
lassen"*/
  ADCSRA |= (1<<ADSC);              // eine ADC-Wandlung
  while ( ADCSRA & (1<<ADSC) ) {
     ;     // auf Abschluss der Konvertierung warten
  }

  result = ADCW;

  Messwert[mux] = ((N-1)*Messwert[mux]+result)/N;


  ADCSRA &= ~(1<<ADEN);             // ADC deaktivieren (2)


  return (Messwert[mux]);
}

Autor: mcl024 (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
1. Es heisst #Define nicht #Difine

2. Du addierst ja gar nichts auf um es anschließend wieder zu teilen um 
einen Mittelwert zu erhalten.

3. Im AVR-GCC Tutorial gibt es ein gutes Beispiel zum ADC, musst du dir 
mal angucken.

Autor: markus (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
es geht aber hier um einen gleitenden Mittelwert??
hat jemand einen Vorschlag, wie der C-Code aussehen soll?

Autor: STK500-Besitzer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>2. Du addierst ja gar nichts auf um es anschließend wieder zu teilen um
>einen Mittelwert zu erhalten.

>  Messwert[mux] = ((N-1)*Messwert[mux]+result)/N;

Passt doch.

Autor: Johannes M. (johnny-m)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
mcl024 wrote:
> 1. Es heisst #Define nicht #Difine
Weder noch: Es heißt #define (C ist Case-sensitiv!)

Autor: markus (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
mcl024 wrote:
> 1. Es heisst #Define nicht #Difine
Weder noch: Es heißt #define (C ist Case-sensitiv!)


Also leute es geht nicht darum define oder difine!!!!!!!! es geht um den 
gleitenden Mittelwert???
weiss vielleicht jemand was ich noch in mein Programm einfügen?? oder 
ist das richtig so? (siehe oben)

Autor: Hüter (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Also leute es geht nicht darum define oder difine!!!!!!!! [...] ???
Tja, du kannst dir nicht aussuchen worüber die Leute reden.
Du kannst es nur beeinflussen, indem du keine solchen Vorlagen lieferst.
Also an die eigene Nase fassen.

Autor: Tom (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
1.) Was heißt "aber noch nicht ganz richtig"?
2.) Vor der Rechnung ist es sinnvoll, result und messwert ein paar bit 
nach links shiften, um den Wertebereich auszunutzen und Rundungsfehler 
zu verringern. Hier ein Beispiel mit 8bit-Wert, der um 8bit nach links 
geshiftet wird, um die 16bit auszunutzen. Dein 10bit-Wert darf natürlich 
nur um 6 geshiftet werden.

#define LOW_PASS_VAL 3

void low_pass(uint8_t *p_speed)
{
  // tiefpass nach http://www.ibrtses.com/embedded/exponential.html

  // startwert bei programmstart.
  static uint16_t speed_filtered = (127 * 256);

  // in 16bit umwandeln (aufloesung)
  uint16_t speed_local = ((uint16_t) *p_speed) << 8;

  // gleitender mittelwert
  // zuerst subtrahieren, damit nichts ueberlaeuft.
  speed_filtered -= (speed_filtered >> LOW_PASS_VAL);
  speed_filtered += (speed_local >> LOW_PASS_VAL);

  *p_speed = (uint8_t) (speed_filtered >> 8);
}


void main(void)
{
  ...
  foo = get_speed();
  low_pass(&foo);
  do_something(foo);
  ...
}

Autor: Bernhard R. (barnyhh)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@ markus

Die Formel für gleitenden Mittelwert (exponentielle Glättung):

Die Meßwerte einer (Zeit-)Reihe seien M(i)
Für den Glättungsfaktor k gilt: 0 <= k <= 1
Das geglättete Ergebnis heiße Mmittel(i).

Es gilt:

Mmittel(i) = Mmittel(i-1) * (1 - k) + M(i) * k

Bei dieser Formel ist die Gleitkomma-Rechnung für AVR-8-Bitter unschön. 
Das Ganze läßt sich umformen zu

Mmittel(i) = Mmittel(i-1) + (M(i) - Mmittel(i-1)) * k

Wenn k jetzt geschickt gewählt ist - 1/(2**n) - reduziert sich die 
gesamte Rechnung auf ein bißchen Shiften und einmal addieren.

Bernhard

Autor: Blackbird (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>> speed_filtered += (speed_local >> LOW_PASS_VAL);

damit wird die Genauigkeit des Meßwertes herabgesetzt, unötigerweise.

Lieber in die andere Richtung shiften, etwa so (Wertebereich beachten!):

#define FILT_KOEFF        5       // = 32 values to filter
...
avg = ((avg << FILT_KOEFF) - avg + adc_data) >> FILT_KOEFF;

Ist verkürzt so: avg(neu) = (31 * avg(alt) + adc_data) / 32


Blackbird

Autor: Andreas R. (rebirama)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@Blackbird:
Das ist schon der richtige Ansatz, aber Rundungsfehler machen das 
Ergebnis auch wieder unbrauchbar:
Nachachrechnen: avg(alt)=250, adc_data=255 : Dein filter wird nie auf 
die 255 kommen!

pseudocode:

uint8 avg(uint8 neu)
{
   static uint16 speicher=0;

   speicher=speicher - speicher/32 + neu;
   return speicher/32;
}

oder warscheinlich etwas schneller, liefert dafür aber ein um einen tick 
"älteres" sample

uint8 avg(uint8 neu)
{
   static uint16 speicher=0;
   uint8 temp;

   temp=speicher/32;//shiften nur einmal durchführen
   speicher=speicher - temp + neu;
   return temp;
}

Der Trick ist, sich das Zwischenergebnis(bei mir "speicher" bei dir avg 
selbst) mit höherer Auflösung zu merken.

Autor: Blackbird (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@Andreas R. (rebirama)

Ja, Du hast Recht. Rundungsfehler ist bei meinem Ansatz immer 1 Digit, 
um das das Ergebnis kleiner ist.
Bei uint8-Werten doch schon ein deutlicher Meßfehler, den man sich nicht 
auch noch einhandeln muß.

>>Der Trick ist, sich das Zwischenergebnis(bei mir "speicher" bei dir avg
selbst) mit höherer Auflösung zu merken.

Dann bleibt nur noch der Rundungsfehler, hervorgerufen durch die 
Division (shiften) mit 32.


Blackbird

Autor: Blackbird (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Den kann man mit:

    temp=(speicher + 16) / 32;

umgehen. Analog zu Dezimalsystem mit Wert + 0,5 (und dann die 
Nachkommastellen abschneiden).

Blackbird

Autor: Andreas R. (rebirama)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@ Blackbird:
Der Rundungsfehler in deinem Ansatz ist im schlimmsten Fall nicht nur 1 
digit, sondern 15 Digits, Beispiel:
avg=240, adc=255:

da kommt als Ergebniss nach dem Filtern 240,468... wieder selbst bei 
korrekter Rundung 240 ergibt.
Der Filter bleibt praktisch dort stehen.

Aber mit dem Runden hast du Recht. Bringt im Mittel ein halbes Digit und 
lässt sich effizient implementieren. Wieder was dazu gelernt ;-)

Autor: Markus (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
bei mir tritt das Rauschen ab 14V (AKKU-Spannung), also unetr 14V sind 
die Messwerte Stabil.
hat jemand eine Idee woran das liegen kann??

Autor: Stefan B. (stefan) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Welcher Akkutyp ist das und was macht der, wenn du 14,x V Akkuspannung 
drauf gibst? Gast der schon? Gasung bei 12 V Bleiakku: 6 Zellen je 2,4 V 
=> 14,4V http://de.wikipedia.org/wiki/Gasung

Autor: Urs (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Korinthenkackerei: Wenn Du wirklich einen gleitenden Mittelwert und 
keine exponentielle Glättung willst, kommst Du um einen Ringpuffer o.ä. 
nicht herum, über den Du dann ganz normal den arithmetischen Mittelwert 
berechnest.

Der Unterschied ist bei vielen Anwendungen nicht bedeutend, aber z.B. 
ein perfektes Rechtecksignal durch einen gleitenden Mittelwert geschickt 
ergibt ein "Trapezsignal", d.h. symmetrische, linear an und absteigende 
Flanken, bei der exponentiellen Glättung eben die 
"Kondensatorlade-entladekurven" (Haifischflossen), die Flanken sind 
nicht symmetrisch, eine unschöne Verzerrung also.

Dazu kommt noch, beim gleitenden Mittelwert über N Schritte, haben 
Ereignisse, die vor N Schritten passiert sind, garantiert keinen 
Einfluss mehr auf das Ergebnis, bei der Exponentiellen Glättung dagegen 
schon, bei manchen Anwendungen ist dieses lange Nachhinken unerwünscht. 
Beispiel: Wenn man z.B. wie hier mit N=16 rechnet und kriegt nach langer 
0 Phase eine Flanke von 0 auf 255 rein, dann ist nach 16 Messwerten der 
exponentielle geglättete Wert (erster Ordnung) erst auf ~164 geklettert, 
wenn ich mich nicht verrechnet habe, und erst nach ca. 60 Schritten ist 
er auf über 250.

(Und wenn man noch mehr Aufwand betreiben will, dann kann man auch den 
Median der im Ringpuffer gespeicherten Werte. Man behält schöne, steile 
Flanken und Aussetzer verschwinden trotzdem. Aber das ist fast immer 
Overkill und für manche Anwendungen auch ungeeignet).

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.