Forum: Compiler & IDEs gleitender Mittelwert (ADC)


von markus (Gast)


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]);
}

von mcl024 (Gast)


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.

von markus (Gast)


Lesenswert?

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

von STK500-Besitzer (Gast)


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.

von Johannes M. (johnny-m)


Lesenswert?

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

von markus (Gast)


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)

von Hüter (Gast)


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.

von Tom (Gast)


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.

1
#define LOW_PASS_VAL 3
2
3
void low_pass(uint8_t *p_speed)
4
{
5
  // tiefpass nach http://www.ibrtses.com/embedded/exponential.html
6
7
  // startwert bei programmstart.
8
  static uint16_t speed_filtered = (127 * 256);
9
10
  // in 16bit umwandeln (aufloesung)
11
  uint16_t speed_local = ((uint16_t) *p_speed) << 8;
12
13
  // gleitender mittelwert
14
  // zuerst subtrahieren, damit nichts ueberlaeuft.
15
  speed_filtered -= (speed_filtered >> LOW_PASS_VAL);
16
  speed_filtered += (speed_local >> LOW_PASS_VAL);
17
18
  *p_speed = (uint8_t) (speed_filtered >> 8);
19
}
20
21
22
void main(void)
23
{
24
  ...
25
  foo = get_speed();
26
  low_pass(&foo);
27
  do_something(foo);
28
  ...
29
}

von Bernhard R. (barnyhh)


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

von Blackbird (Gast)


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

von Andreas R. (rebirama)


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.

von Blackbird (Gast)


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

von Blackbird (Gast)


Lesenswert?

Den kann man mit:

    temp=(speicher + 16) / 32;

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

Blackbird

von Andreas R. (rebirama)


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

von Markus (Gast)


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

von Stefan B. (stefan) Benutzerseite


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

von Urs (Gast)


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

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.