Forum: Mikrocontroller und Digitale Elektronik Mittelwertbildung von Temperaturmessung?


von Dieter B. (Gast)


Lesenswert?

Hallo Leute,

Habe mir ein Programm geschrieben was meinen I2C Temperatursensor 
(DS1621) auswertet und auf einer 7 - Segmentanzeige darstellt.

Das passt soweit alles. Das Problem ist jetzt. Das der Wert sehr stark 
toggelt... Immer zwischen zwei Werten. Da habe ich mir gedacht, dass man 
das mit hilfe einer Mittelwert ermittlung unterbinden könnte?!

Wie seht ihr das ?
Wie mache ich das am klügsten ?

Ich habe da an ca. 30 Messungen gedacht...

(Temperatur = ((Messwert_1 ... + Messwert_30) / 30);

Wie setze ich das in meinem Quellcode nun um ?...
1
#define F_CPU 8000000
2
3
#define DS1621_Write  0x9E
4
#define DS1621_Read   0x9F
5
6
#define Segment_1_AN PORTB &= ~(1<<PB3)
7
#define Segment_1_AUS PORTB |= (1<<PB3)
8
9
#define Segment_2_AN PORTB &= ~(1<<PB2)
10
#define Segment_2_AUS PORTB |= (1<<PB2)
11
12
#define Segment_3_AN PORTB &= ~(1<<PB1)
13
#define Segment_3_AUS PORTB |= (1<<PB1)
14
15
#define Segment_4_AN PORTB &= ~(1<<PB0)
16
#define Segment_4_AUS PORTB |= (1<<PB0)
17
18
#define Segmentanzeige PORTD 
19
20
int Zahlen[13]= {
21
          0b00111111, // 0
22
          0b00000110, // 1
23
          0b01011011, // 2
24
          0b01001111, // 3
25
          0b01100110, // 4
26
          0b01101101, // 5
27
          0b01111101, // 6
28
          0b00100111, // 7
29
          0b01111111, // 8
30
          0b01101111, // 9
31
            0b01100011, // *
32
            0b01000000, // Minus
33
          0b00000000  // alle Aus
34
        };
35
36
#include <avr/io.h>
37
#include <util/delay.h>
38
#include "i2cmaster.h"
39
#include <avr/interrupt.h>
40
41
volatile unsigned char  TempCounter, TempH, TempL = 0;
42
volatile int16_t Wert, Slave_Anwesenheit = 0;
43
volatile int8_t Temp, TempAbs = 0;
44
volatile uint8_t IsNegative;
45
 
46
47
int main(void)
48
{
49
50
  
51
  DDRB |= ((1<<PB0) | (1<<PB1) | (1<<PB2) | (1<<PB3));
52
  DDRD |= ((1<<PD0) | (1<<PD1) | (1<<PD2) | (1<<PD3) | (1<<PD4) | (1<<PD5) | (1<<PD6));
53
  
54
  TIMSK = (1<<OCIE1A); // Compare Match Enable
55
  TCCR1B = ((1<<CS10)); // Prescaler 64
56
  OCR1A = 100;
57
  sei();
58
59
  i2c_init();
60
  
61
  i2c_start(DS1621_Write); // Starten der Umwandlung (Messung)
62
  i2c_write(0xEE);
63
  i2c_stop();
64
  
65
  
66
  i2c_start(DS1621_Write); // Starten der Auslesung (Temperaturregister)
67
  i2c_write(0xAA);
68
  i2c_stop();
69
     
70
  Segment_1_AUS; Segment_2_AUS; Segment_3_AUS; Segment_4_AUS;
71
72
  
73
  while (1)
74
  {
75
    
76
    i2c_start(DS1621_Read);
77
    TempH = i2c_readAck();
78
    TempL = i2c_readNak();
79
    i2c_stop();
80
81
    Temp = TempH;
82
    
83
    
84
    if (Temp < 0)
85
    {
86
      IsNegative = 1;
87
      TempAbs = ~(Temp) + 1;
88
    }
89
    else
90
    {
91
      IsNegative = 0;
92
      TempAbs = Temp;
93
    }
94
95
    
96
    Wert = ((((TempAbs / 10) % 10) << 8) | ((TempAbs % 10) << 4) | (0x0A)); // Wert entspricht dem "Wert" aus dem Array (Zahlen)
97
    
98
    if (TempAbs == 0)
99
    {
100
      Wert = 0xCC0A;
101
    }
102
    else if (TempAbs >= 100)
103
    {
104
      Wert = (Wert | 0x1000);
105
    }
106
    else if (TempAbs < 10)
107
    {
108
      Wert = (Wert | 0xCC00);
109
    }
110
    else if (TempAbs < 100)
111
    {
112
      Wert = (Wert | 0xC000);
113
    }
114
    
115
    if (IsNegative == 1)
116
    {
117
      Wert = (Wert & 0x0FFF) | (0xB000);
118
    }
119
    
120
      _delay_ms(5000);    
121
  }
122
  
123
124
}
125
126
127
ISR (TIMER1_COMPA_vect)
128
{
129
130
    Segment_1_AN;
131
    PORTD = Zahlen[Wert & 0x000F];
132
    _delay_ms(1);
133
    Segment_1_AUS;
134
    asm volatile ("nop");
135
    asm volatile ("nop");
136
  
137
    Segment_2_AN;
138
    PORTD = Zahlen[(Wert & 0x00F0) >> 4];
139
    _delay_ms(1);
140
    Segment_2_AUS;
141
    asm volatile ("nop");
142
    asm volatile ("nop");
143
  
144
    Segment_3_AN;
145
    PORTD = Zahlen[(Wert & 0x0F00) >> 8];
146
    _delay_ms(1);
147
    Segment_3_AUS;
148
    asm volatile ("nop");
149
    asm volatile ("nop");
150
  
151
    Segment_4_AN;
152
    PORTD = Zahlen[(Wert & 0xF000) >> 12];
153
    _delay_ms(1);
154
    Segment_4_AUS;
155
    asm volatile ("nop");
156
    asm volatile ("nop");
157
  
158
}

von holger (Gast)


Lesenswert?

>Das Problem ist jetzt. Das der Wert sehr stark
>toggelt... Immer zwischen zwei Werten. Da habe ich mir gedacht, dass man
>das mit hilfe einer Mittelwert ermittlung unterbinden könnte?!

Zwischen welchen Werten toggelt der?

>Wie seht ihr das ?
>Wie mache ich das am klügsten ?
>
>Ich habe da an ca. 30 Messungen gedacht...

Blödsinn. Temperaturen ändern sich nur sehr langsam.
Dein Problem liegt ganz woanders.

von Dieter B. (Gast)


Lesenswert?

holger schrieb:
> Blödsinn. Temperaturen ändern sich nur sehr langsam.
> Dein Problem liegt ganz woanders.

Ahja ? Wo liegt denn mein Problem ???

von holger (Gast)


Lesenswert?

>Ahja ? Wo liegt denn mein Problem ???

Beantworte meine Frage. Vieleicht kommt dann jemand
drauf wo dein Problem ist.

von Dieter B. (Gast)


Lesenswert?

holger schrieb:
> Zwischen welchen Werten toggelt der?

Nehmen wir an, Es sind aktuell 20 Grad... Wenn es wärmer wird, Toggelt 
der zwischen 20 & 21 Grad ständig hin und her...

von Amateur (Gast)


Lesenswert?

In vielen Fällen liegt's an der Anzeige.

a) Wenn sich nichts tut, wozu dann mit high-speed messen?

b) Messwerte, vor allem Temperaturen liegen nun mal selten bei Irgendwas
   Komma Null.

Also zwei Möglichkeiten fallen mir dazu ein.

1. Alle 5 Sekunden ein paar Messungen und durch die Anzahl teilen.
   Dann anzeigen. Zwischenzeitlich kannst Du den µP ins Bettchen
   schicken und Strom sparen.

2. Wenn Du auf volle Pulle stehst. Dauermessung aufsummieren alle paar
   Sekunden durch die Anzahl teilen und die Anzeige aktualisieren.

Beides läuft darauf hinaus, dass nur gelegentlich die Anzeige 
aktualisiert wird.

von M. V. (-_-)


Lesenswert?

Ich sehe das Problem nicht. Nimmst du ein Array, schreibst da die Werte 
rein - kannst ringbuffer-mässig dir immer den ältesten Index merken und 
den updaten - dann entsprechend Formel mitteln.

von Hans (Gast)


Lesenswert?

Dass der Wert genau zwischen zwei Werten hin- und herspringt, kann Dir 
mit Mittelwertbildung genauso passieren. Du brauchst eher eine 
Hysterese, d.h. die Anzeige ändert sich z.B. erst, wenn die Temperatur 
sich um mindestens zwei Auflösungsschritte geändert hat. Das kann man 
natürlich mit einer Mittelwertbildung kombinieren.

von Dieter B. (Gast)


Lesenswert?

Hans schrieb:
> Dass der Wert genau zwischen zwei Werten hin- und herspringt, kann Dir
> mit Mittelwertbildung genauso passieren. Du brauchst eher eine
> Hysterese, d.h. die Anzeige ändert sich z.B. erst, wenn die Temperatur
> sich um mindestens zwei Auflösungsschritte geändert hat. Das kann man
> natürlich mit einer Mittelwertbildung kombinieren.

Hättest du einen Pseudocode für mich ?

Kann mir das gerade nicht vorstellen, wie du das meinst.

von Mike (Gast)


Lesenswert?

Dieter B. schrieb:
> Ich habe da an ca. 30 Messungen gedacht...

Eine Zahl von "ca. 30" ist eine blöde Idee.

1. Mußt du dich für einen Wert entscheiden

2. Da du für die Mittelung durch die Zahl der Messwerte teilen mußt, ist 
es grundsätzlich eine gute Idee, eine glatte 2er-Potenz von Messwerten 
zu mitteln und dies auch im Programm über eine Konstante so zu coden. 
Das gibt dem Compiler die Möglichkeit, diesen Rechenschritt in Bezug auf 
den Rechenaufwand sehr effektiv zu implementieren.

von Thomas E. (thomase)


Lesenswert?

Mike schrieb:
> Das gibt dem Compiler die Möglichkeit, diesen Rechenschritt in Bezug auf
> den Rechenaufwand sehr effektiv zu implementieren.
Bei einem Controller, der alle fünf Minuten die Temperatur misst, ist 
das völlig egal.

Man braucht das gar nicht zu teilen. Man addiert einfach 10 Werte und 
gibt diese 3-Stellige Zahl aus. Vor die rechte Ziffer setzt man den 
Dezimalpunkt. Sowas nennt sich dann 10-fach Oversampling.

mfg.

von Ulrich H. (lurchi)


Lesenswert?

Durch das Mitteln kann man das hin und her schwanken vermindern, aber 
nicht ganz vermeiden. So lange die Schwankungen nicht so schnell nicht, 
das man der Anzeige nicht folgen kann (also etwa schneller als etwa 2 
Hz), stört das normalerweise auch nicht.

Vollständig unterdrücken des hin und her Schwankens geht per Hysterese - 
dabei hat man dann aber zeitweise einen leicht falschen Angezeigten 
Wert. Um den Fehler zu reduzieren, kann man erst mit erhöhter Auflösung 
messen (gff. durch mitteln), und dann die Hysterese deutlich kleiner 
wählen als einen Schritt der Anzeige.

von Hans (Gast)


Lesenswert?

Dieter B. schrieb:
> Hans schrieb:
>> Dass der Wert genau zwischen zwei Werten hin- und herspringt, kann Dir
>> mit Mittelwertbildung genauso passieren. Du brauchst eher eine
>> Hysterese, d.h. die Anzeige ändert sich z.B. erst, wenn die Temperatur
>> sich um mindestens zwei Auflösungsschritte geändert hat. Das kann man
>> natürlich mit einer Mittelwertbildung kombinieren.
>
> Hättest du einen Pseudocode für mich ?
>
> Kann mir das gerade nicht vorstellen, wie du das meinst.

In etwa so:
1
#define THRESHOLD 2
2
int display;
3
4
void update_temp()
5
{
6
  int temp = get_temp();
7
  if (temp > (display + THRESHOLD) || temp < (display - THRESHOLD))
8
  {
9
    display = temp;
10
    print_temp(display);
11
  }
12
}

Sinnvollerweise enthält die Variable temp hier nicht die Temperatur in 
°C, sonst würde sich die Anzeige ja erst nach 3°C Unterschied ändern. 
Sondern eben Zehntel- oder Hunderstelgrad, die man aus mehreren 
Messungen gemittelt hat. THRESHOLD kann man dann so einstellen, dass die 
Anzeige ruhig genug ist.

von holger (Gast)


Lesenswert?

>> Zwischen welchen Werten toggelt der?
>
>Nehmen wir an, Es sind aktuell 20 Grad... Wenn es wärmer wird, Toggelt
>der zwischen 20 & 21 Grad ständig hin und her...

Dann miss halt nur alle 10 Minuten und gib den Wert
einmal alle 10 Minuten aus.
Dann fällt das gar nicht auf. Schneller wird sich deine
Temperatur sowieso nicht ändern.

von B e r n d W. (smiley46)


Lesenswert?

Hallo Dieter

Könnte es sein, dass die Nachkommastellen in TempL stehen? Um eine 
Nachkommastelle zu bekommen, müßte es ungefähr so aussehen:
1
uint8_t  TempH, TempL;
2
int16_t  Temp;
3
4
i2c_start(DS1621_Read);
5
TempH = i2c_readAck();
6
TempL = i2c_readNak();
7
i2c_stop();
8
9
Temp = ((int16_t)TempH<<8) & TempL;
10
Temp = (long)Temp * 10 / 256;


Du solltest globale Variablen nur als volatile deklarieren, falls sie 
z.B. durch eine Interrupt-Service-Routine verändert werden können.

Gruß, Bernd

von Hans (Gast)


Lesenswert?

Noch zur Mittelwertbildung: Das kann man recht einfach und 
speicherschonend so machen:
1
#define AVG_FACTOR 10
2
long avg = 0;
3
4
void update_temp()
5
{
6
  int temp = get_temp();
7
  avg = avg + temp - (avg / AVG_FACTOR);
8
  print_temp(avg / AVG_FACTOR);
9
}

Die Variable avg enthält den zehnfachen (AVG_FACTOR) Mittelwert der 
letzten Messungen. Mit jeder Messung wird die aktuelle Temperatur 
addiert und dafür ein Zehntel des bisherigen Werts abgezogen.

Das ist nicht genau das gleiche wie der Mittelwert der letzten 10 Werte, 
sondern die aktuellen Werte gehen mit höherer Gewichtung als ältere 
Werte ein. Der Effekt ist der gleiche: Temperaturänderungen und 
Schwankungen wirken sich langsamer auf den angezeigten Mittelwert aus.

Wenn man auf Rechenzeit beim Mitteln bedacht ist, wählt man für 
AVG_FACTOR eine Zweierpotenz. Für die Anzeige als Dezimalwert sind 
dagegen wie schon erwähnt 10er-Potenzen praktischer, weil man da nur das 
Komma verschieben muss.

von Easylife (Gast)


Lesenswert?

....oder wenn's auch der Faktor 16 sein darf ohne Division ;-)
1
long avg = 0;
2
3
void update_temp()
4
{
5
  int temp = get_temp();
6
  avg = avg + temp - (avg >> 4);
7
  print_temp(avg >> 4);
8
}

von Brater (Gast)


Lesenswert?

Wie wäre es mit einem einfachen Median über die letzten x Werte? Das 
wäre zumindest sinnvoll, so lange die Temperatur weitestgehend konstant 
bleibt.

von Bernd N (Gast)


Lesenswert?

> Dass der Wert genau zwischen zwei Werten hin- und herspringt, kann Dir
> mit Mittelwertbildung genauso passieren. Du brauchst eher eine
> Hysterese, d.h. die Anzeige ändert sich z.B. erst, wenn die Temperatur
> sich um mindestens zwei Auflösungsschritte geändert hat. Das kann man
> natürlich mit einer Mittelwertbildung kombinieren.

> Hättest du einen Pseudocode für mich ?

Hysterese PseudoCode...
1
    if ((measVal > (AdcVal + SigWindow))
2
            || (measVal < (AdcVal - SigWindow))) {
3
        AdcVal = measVal;                          // Referenz für die nächste Messung
4
        AdcValOut (measVal);
5
    }

Du kannst einen Messwert auch "nachlaufen" lassen. Hierbei wird nicht 
direkt auf jede Änderung reagiert sondern in einem kleineren Raster 
nachgelaufen. Folgender Code sollte dir eine Idee geben, bezogen auf 
dein Temperatur Mess Problem musst du den Code natürlich umbauen. Das 
Beispiel ist hier von einer ADC Messung aber ich denke das Prinzip wird 
klar.

Hysterese kombinert...
1
    if (measVal > AdcVal) {
2
        (measVal - AdcVal > AdcWindow) ? (AdcVal = measVal) : (AdcVal += 1);
3
    } else if (measVal < AdcVal) {
4
        (AdcVal - measVal > AdcWindow) ? (AdcVal = measVal) : (AdcVal -= 1);
5
    }

Hierdurch erfolgt eine Annäherung an den Messwert. Denk dir einfach das 
+1, -1 hinter deiner Sichtbaren Stelle sitzt, erst nach 10 gleichen 
Trends wird der Messwert sichtbar. Auflösung geht hierdurch keine 
verloren aber die Anzeige wird träge und läuft zeitlich hinterher.

von B e r n d W. (smiley46)


Lesenswert?

Eine kurze Studie des Datenblatts des DS1621 hat ergeben: Das Teil 
liefert im zweiten Byte noch 0,5°C Schritte. Die würde ich auf jeden 
Fall mit auswerten. Danach kann ja eine Mittelwertbildung, Hysterese 
oder ähnliches folgen.

von Dieter B. (Gast)


Lesenswert?

Bernd N schrieb:
>> Dass der Wert genau zwischen zwei Werten hin- und herspringt,
> kann Dir
>> mit Mittelwertbildung genauso passieren. Du brauchst eher eine
>> Hysterese, d.h. die Anzeige ändert sich z.B. erst, wenn die Temperatur
>> sich um mindestens zwei Auflösungsschritte geändert hat. Das kann man
>> natürlich mit einer Mittelwertbildung kombinieren.
>
>> Hättest du einen Pseudocode für mich ?
>
> Hysterese PseudoCode...
>     if ((measVal > (AdcVal + SigWindow))
>             || (measVal < (AdcVal - SigWindow))) {
>         AdcVal = measVal;                          // Referenz für die
> nächste Messung
>         AdcValOut (measVal);
>     }

> Hysterese kombinert...    if (measVal > AdcVal) {
>         (measVal - AdcVal > AdcWindow) ? (AdcVal = measVal) : (AdcVal +=
> 1);
>     } else if (measVal < AdcVal) {
>         (AdcVal - measVal > AdcWindow) ? (AdcVal = measVal) : (AdcVal -=
> 1);
>     }
>
Hallo Bernd,

Dein Tipp gefällt mir ;)
Hast du evtl. Skype ?

von Bernd N (Gast)


Lesenswert?

>> Hast du evtl. Skype ?

Nein, stell einfach Fragen wenn dir etwas unklar ist, dafür ist das 
Forum ja gedacht.

von U. M. (oeletronika)


Lesenswert?

B e r n d W. schrieb:
> Danach kann ja eine Mittelwertbildung, Hysterese
> oder ähnliches folgen.
Hallo,
statt Mittelwertbildung wie vorgeschlagen über Intervalle ist eine 
Tiefpassfunktion (gleitender Wittelwert, Wichtungsfunktion) oft 
zweckmäßiger und auch einfacher zu programmieren.
http://de.wikipedia.org/wiki/Gleitender_Mittelwert

Ausgabewert = ((MerkWert * TPC-1) + aktueller Messwert) / TPC

TPC = Tiefpasskonstante (1.....n).

Der Ausgabewert wird sich wieder gemerkt und in der nächsten 
Ausgabeschleife wieder als "MerkWert" verwendet.

Um ständiges Flackern der Anzeige zwischen 2 Zuständen zu vermeiden, ist 
wie schon geschrieben eine Hysterese zweckmäßig.
Damit eine Anzeige z.B. wegen Rauschen oder Störeinstrhalungen nicht 
ständig zwischen 22°C und 21,9°C hin und her flackert, weil der Messwert 
gerade auf der Schaltschwelle von 21,95 herum dümmpelt, darf man bei 
ansteigender Temp. erst bei z.B. 21,97°C auf 22,0°C schalten aber 
umgekehrt bei ansinkender Temp. erst bei 21,93°C auf 21,9°C 
zurückschalten. Die Hysterese wäre in diesem Fall +/-0,2Grad.

Programmtechnisch kann man dazu auch wieder den "MerkWert" benutzen und 
je nach Richtung der Temperaturentwicklung für die Ausgabe aufs Display 
einen kleinen Offset dazu gegeben oder abziehen.
In die Tiefpassfunktion darf man diesen Offset natürlich nicht geben.
Gruß Öletronika

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.