Forum: Mikrocontroller und Digitale Elektronik A/D-Werte glätten


von Ste (Gast)


Lesenswert?

Hallo!

Ich habe ein Problem, ich habe ADC-Werte, die ich auf einem LCD anzeigen 
möchte. Da diese naturgemäss immer ein wenig flattern möchte icheinige 
Werte nehmen und daraus ein brauchbares Resultat machen. Nun mein 
Problem, wie mache ich das am besten? Ich möchte, wenn immer das geht, 
nicht die Werte aufaddieren, denn so bekomme ich zu grosse Zahlen, oder 
stimmt das nicht? Wie macht ihr das? Ich benutze einen Atmega8, hat 
jemand vielleicht Beispiel code, oder sonstige Tipps??

Vielen Dank für Tipps und alles andere nutzliche!

von franz (Gast)


Lesenswert?

Hi,

nicht Aufaddieren?! Das wirst Du bei einfachen Filtern aber 
zwangsweise immer müssen.
Am schnellsten wird sich wohl ein gleitender Durschnitt realisieren 
lassen:
eine bestimmte Anzahl von Werten (z.B. 20) in einem Ringbuffer ablegen, 
aufaddieren und dann durch die Anzahl der Werte dividieren.
Kommt ein neuer Wert vom ADC, haust Du den ersten Wert raus usw...

Grüße,
Peter

von franz (Gast)


Lesenswert?

meine statt Durschnitt natürlich DurCHschnitt ;-)

Grüße,
Peter

von Ste (Gast)


Lesenswert?

Das klingt einleuchtend, nur wie kann ich einen solchen Ringbuffer 
realisieren? Bei 20 Werten von je 10bit, das ist bereits recht viel, so 
dass ich zwangsläufig aufs SRam zugreiffen muss, das kann ich (noch) 
nicht, oder komme ich darumherum?
Könntest du mir das mit dem Ringbuffer ein wenig besser erklären? Nicht 
das Prinzip, sondern eher, wie das Softwaretechnisch zu lösen ist?

Danke!

von Olaf (Gast)


Lesenswert?

Hi Ste,

der Vorschlag mit dem gleitenden Mittelwert wäre auf jeden Fall eine 
Lösung. Mit einem Ringpuffer verheizt du aber eine ganze Menge Zeit und 
Speicher. Ein einfacher und auch sehr schneller (wenig Rechenzeit) Weg 
wäre folgender:

unsigned long value = 0;
unsigned int  avg = 0;

for(;;){
   value -= avg;
   // hier: warten auf neuen ADC-Wert
   value += 'ADC-Wert';
   avg = value >> 3;
   // hier: Ausgabe von avg (= Mittelwert)
}

Ist vielleicht nicht auf den ersten Blick einzusehen wie das 
funktioniert, kann man sich aber recht einfach klar machen.

Du kannst den Krempel natürlich auch in eine Funktion stecken, falls 
dich die for-Schleife stört (Variable müssen dann natürlich static 
sein).

... und jetzt erzählst du mir bestimmt, dass du den Kram in Assembler 
programmieren möchtest, oder? ;o)

Gruß
Olaf

von franz (Gast)


Lesenswert?

Du programmierst anscheinend in Assembler!?
Wenn du Dich nicht ans SRAM rantraust, könntest Du auch einfach über 20 
(oder mehr) Werte aufsummieren und dann durch die Anzahl der Werte 
dividieren, also ohne Ringbuffer.

in C-Pseudocode (hoffe, Du kennst Dich aus):

while(1)
{
   summe=0;
   for(i=0;i<=19;i++)
   {
     hole ADCwert;
     summe=summe+ADCwert;
   }
   mittelwert=summe/20;
   mittelwert ausgeben;
}

Sollte stimmen.

Grüße!

von Olaf (Gast)


Lesenswert?

@franz:
... das ist aber kein gleitender Durchschnitt. Und wenn du schon 
dividierst, addiere doch z. B. 16 oder 32 Werte; die kannst du 
wenigstens kommod durch schieben dividieren (man muss den kleinen Kerl 
ja nicht unnötig quälen).

Gruß
Olaf

von franz (Gast)


Lesenswert?

Da hast Du natürlich recht, Olaf.

Hab' ich vergessen, dazuzuschreiben.

Grüße,
Peter

von Jangomat (Gast)


Lesenswert?

Ich weiß ja nicht
1. wie oft Du mißt
2. wie schnell ein genauer Wert angezeigt werden soll

Evtl. kannst Du ja das aktuelle Ergebnis mit dem gespeicherten von 
vorhin vergleichen. Ist das neue Ergebnis größer, so wird der 
gespeicherte Wert lediglich um 1 inkrementiert, ist er gleich bleibt er 
gleich, ist er kleiner wird der gespeicherte Wert um 1 dekrementiert.
Lediglich wenn sich die beiden Werte um einen größeren Betrag 
unterscheiden (z.B. 20), wird sofort der aktuellste Wert übernommen und 
gespeichert.
Den gespeicherten Wert verwendest Du für Deine Anzeigeroutine.

Nur so als Idee! Du brauchst damit nur 2 Byte opfern (für Dein 
gespeichertes 10-Bit Ergebnis)

von Ste (Gast)


Lesenswert?

Tja, ich mache das ganze in Assembler...:)
Muss mir aber langsam wirklich überlegen, ob ich nicht doch besser 
wechseln soll, wenn ich 20 werte aufaddieren will, dann erhalte ich eine 
riesen Zahl, falsch, ich weiss gar nicht wie ich eine so grosse Zahl 
erhalten kann in Assembler. Könnte mir da jemand mal auf die Sprünge 
helfen? Wie ich dann durch 16 teilen kann weiss ich (glaube ich 
zumindest ==> 4 mal schieben oder??)
Ich brauche die Daten schon nicht so schnell, dass ich einen Ringbuffer 
basteln müsste (was sowieso nicht zu schaffen ist)!!
Danke für eure Hilfe!

von Markus Kaufmann (Gast)


Lesenswert?

Stell Dir Rechnen mit Zahlen > 8Bit einfach so wie schriftliches Rechnen 
vor, nur daß man statt Ziffern Bytes nimmt. Außerdem kann man nicht 
mehrere Zahlen gleichzeit addieren, sondern muß das halt nacheinander 
machen.

Wenn Du zwei 8Bit-Zahlen addierst, dann kann das Ergebnis auch 9 Bit 
haben, das 9. Bit ist dabei der Übertrag und wird als Carry-Bit 
bezeichnet. Dieses Carry-Bit mußt Du jetzt in einem extra Byte 
speichern. Was in der Schule die 10er-Stelle war, das ist hier halt das 
"obere" Byte einer 16Bit-Zahl. Nun braucht man praktischerweise keine 
Abfrage ob das Bit gesetzt ist und falls ja dann ein extra Add oder so, 
sondern es gibt einen Befehl "Add with carry".

Vier 8Bit-Meßwerte zu addieren geht dann etwa so:
R0: unteres Byte des Ergebnisses
R1: oberes Bytes des Ergebnisses
R2: Meßwert
R3: 0

add r0, r2
Es kann noch keinen Übertrag geben, nächsten Meßwert lesen
add r0, r2
adc r1, r3
Meßwert einlesen
add r0, r2
adc r1, r3
Meßwert einlesen
add r0, r2
adc r1, r3

bye
  Markus

von tobias hofer (Gast)


Lesenswert?

@olaf

hallo

ich habe das dein programm :

unsigned long value = 0;
unsigned int avg = 0;

for(;;){
   value -= avg;
   // hier: warten auf neuen ADC-Wert
   value += 'ADC-Wert';
   avg = value >> 3;
   // hier: Ausgabe von avg (= Mittelwert)
}

ein bisschen studiert aber komme nun nicht darauf wie das funktioniert. 
kannst du mir das vieleicht schnell erklären?
auf jeden fall sieht diese lösung sehr elegant aus!

besten dank
tobias

von Michael (Gast)


Lesenswert?

ein älterer Beitrag, viel zu lesen:
http://www.mikrocontroller.net/forum/read-1-8170.html#8743

von Olaf (Gast)


Angehängte Dateien:

Lesenswert?

Hi Tobias,

einfach ausgedrückt:
Im Normalfall ergibt sich der gleitende Mittelwert zum Zeitpunkt t0 z. 
B. aus der Summe der Werte von t-3 bis t0, geteilt durch die Anzahl der 
Werte (hier: 4 [t-3, t-2, t-1 und t0]). Bei der Berechnung des nächsten 
Mittelwertes fällt t-3 aus der Berechnung heraus und der neue 
Sample-Wert t1 kommt hinzu, usw. Dabei musst du immer auf vergangene 
Werte zurückgreifen.

In erster Näherung kann man nun sagen, dass in avg bereits die Werte aus 
der Vergangenheit enthalten sind. Diese werden nun quasie als 
Rückkopplung wieder eingespeist.

Du musst natürlich nicht unbedingt durch 8 teilen, kannst auch durch 2, 
4, 16, 32, ... teilen, kommt natürlich immer auf dein Signal an (zu 
erwartende Signaländerung im vorgegebenen Zeitintervall).

Ach ja, im obigen Beispiel ist die Variable value etwas 
überdimensioniert. Der Wert kann ja maximal 8192 erreichen (1024 [max. 
ADC-Wert] * 8 [Teiler] = 8192), somit würde auch ein unsigned int 
genügen.

Ich habe mal eine Excel-Tab angehängt, mit der man sich den Output 
ansehen kann.

Gruß,
Olaf

von Moritz (Gast)


Lesenswert?

Hallo Olaf,

hab grad deinen Artikel hier entdeckt und bin sehr begeistert. Werds 
gleich mal ausprobieren und hoffentlich dann in meine Diplomarbeit 
einbauen.

Vielen Dank fürs bereitstellen von dem Excell file!

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.