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!
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
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!
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
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!
@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
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)
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!
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
@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
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
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!