[Erledigt] Messwerte auf SD-Karte, Mittelwert bilden

Hallo,

kurz zu mir: ich heiße Jan, bin 14 Jahre alt und komme aus dem Südwesten Deutschlands.
Als Hobby gestalte ich Webseiten und programmiere "Webanwendungen" in PHP (mit ein bisschen SQL).

Als erstes richtiges Projekt habe ich mir vorgenommen, einen Datenlogger mit dem Arduino UNO, dem Ethernet Shield und dem Datalogging Shield zu konstruieren.
Das Alles geht schonmal:
Am UNO hängen verschiedene Sensoren (Ultraschallentfernungssensor, Temperatursensoren, Niederschlagsmesser, ...). Die Uhrzeit bekomme ich von der Uhr auf dem Datalogging Shield. Die Sensoren werden im Sekundentakt ausgelesen und die Werte an einem I²C-Bus Display ausgegeben. Mit einem Drehencoder kann man bestimmen, welcher Sensorwert am Display angezeigt werden soll.

Damit ich die Werte auch schön visualisieren kann müssen sie in meine MySQL-Datenbank. Das habe ich mit so vorgestellt:
Die Werte werden im Sekundentakt auf die SD-Karte geschrieben (in eine CSV-Datei). Alle 10 Min wird ein Mittelwert, von Werten auf der SD-Karte gebildet und die Mittelwerte werden per Ethernet Shield auf den Webserver geschoben. Danach werden die Werte auf der SD-Karte gelöscht.
Heißt also, ich messe jede Sekunde, aber übertrage nur 10 Min-Mittelwerte auf den Webserver. Und als Datenablage dient die SD-Karte.

Wie ich Messwerte auf die SD-Karte schreibe ist kein Problem, das Problem ist wie ich die 300 Messwerte von der SD-Karte auslese und den Mittelwert bilde und und anschließend die Messwerte aus der CSV lösche.

Nun meine Frage: Gibt's irgendwo gute Tutorials zum Thema "Lesen von Werten aus einer CSV von SD-Karte" oder hat jemand hier schonmal das Selbe gemacht und noch einen Code parat?

Schonmal Danke für Eure Hilfe!

webworker

Hallo,
ich weiß nicht so recht...
10 Min messen/ alle Sek sind bei mir 600 Werte pro sensor. Diese Rohdaten würde ich auf SD/Datei schreiben.
Mittelwert würde ich hochzählen...
Wert=Wert plus Neuwert. Wenn 600 mal gemacht, Mittelwert bilden, Mittelwert speichern/senden.
Gruß und Spaß
Andreas

SkobyMobil:
[...]10 Min messen/ alle Sek sind bei mir 600 Werte pro sensor. [...]

:blush: Stimmt ja, ich wollte zuerst 10 Min-Mittelwerte senden, habe mich dann aber doch für 5 Min-Mittelwerte entschieden :), deshalb die 300 Messungen. Die 10 Min-Mittelwerte erzeuge ich dann auf dem Webserver mit PHP.

Ich bin dann mal ein bisschen Testen.... :wink:

ich würde das ganz anders machen, mittels eines gleitenden Mittelwertes.

Mittelw = (messwert + Mittelw * 300.0)/301.0; // muß in float gerechnet werden, float Mittelw
Die Funktion braucht eine Periode zum Einschwingen, aber dann folgt sie dem arithmetischem Mittelwert mit einer Genauigkeit besser 0,1%.

Und du hast den Vorteil dass du immer, zu jedem Zeitpunkt, den Mittelwert hast.
Die Funktion wrid auch oft als einfacher Filter von Sensorsignalen benutzt. Das ist ein digitaler Tiefpassfilter mit Tau = filterfaktor * Abtastung (filterfaktor ist hier 300)

Moin,

jede Sekunde messen, um einen 5 ober 10 min Mittelwert zu bilden? Is dies nicht zu stressig für Prozessor/ Karte? Bei einem Zyklus von 10 oder 30 sec wird die 'Mittelwertlast' um einiges geringer, damit wäre doch 'mehr Luft zum Atmen' für andere Prozesse?

Wie sieht dies mit den Schreibzyklen auf die Karte aus? Wenn dort weniger Traffic ist, dann würde die doch länger halten, oder?

Greetz Linpo

Wenn man das mit dem dem digitalen Tiefpass macht, wie oben beschrieben, dann hat du jede Sekunde diese eine Codezeile auszuführen und brauchst gar keine SD-Karte, weil du als zusätzlichen Speicher nur einen Float-Wert brauchst.

Hallo,

danke für Eure Anworten. Ich habe mich jetzt für einen gleitenden Mittelwert entschieden, da es der SD-Karte bestimmt nicht gut tut, wenn sie jede Sekunde 365 Tage im Jahr beschrieben wird. Der ausschlaggebende Grund ist aber der, dass es viel zu aufwändig wäre, die Werte danach wieder auszuwerten und zu mitteln - für mich als Anfänger jedenfalls!

Gruß
webworker

Hallo,

guntherb:
ich würde das ganz anders machen, mittels eines gleitenden Mittelwertes.

Mittelw = (messwert + Mittelw * 300.0)/301.0; // muß in float gerechnet werden, float Mittelw
Die Funktion braucht eine Periode zum Einschwingen, aber dann folgt sie dem arithmetischem Mittelwert mit einer Genauigkeit besser 0,1%.

Und du hast den Vorteil dass du immer, zu jedem Zeitpunkt, den Mittelwert hast.
Die Funktion wrid auch oft als einfacher Filter von Sensorsignalen benutzt. Das ist ein digitaler Tiefpassfilter mit Tau = filterfaktor * Abtastung (filterfaktor ist hier 300)

Deine Mittelwertbildungsmethode finde ich gut und wollte ich gleich ausprobieren. Leider erhalte ich nur Werte in Größenordnung 2,67. Es müßten jedoch Werte um 21 herauskommen.

Dein Filterfaktor 300 (bzw. meine 10) gibt doch an mit wievielen Meßwerten gemittelt werden soll. Oder?
Außerdem ist mir nicht klar, wie stets der aktuelle Mittelwert ermittelt werden soll. Es wird nirgends ein frischer Wert hinzugenommen und gleichzeitig ein alter Wert (älteste) rausgenommen. Man benötigt dazu doch bestimmt eine Schleife zum neuen Wert rein, ältester raus, mit den verbliebenen Werten den Mittelwert bilden, Spiel von vorn.

//  Arduino Mega 2560

// Deklaration der Funktionen, übernimmt die Arduino IDE selbst
// unsigned int read_MAX6675();
  
//  Arduino Mega 2560

// Port-Pins definieren
int    MAX_CS = 24;               // Arduino Pin, SPI ChipSelect, 1. MAX6675
int    MAX_SO  = 26;              // Arduino Pin, SPI Datenleitung
int    MAX_CLK = 28; 	          // Arduino Pin, SPI Clockleitung

unsigned int value = 0;

// Deklaration Datentypen und Variablen
unsigned int Temp = 0;		  // Zwischenspeicher
float Mittelwert = 0;

void setup() {

  Serial.begin(9600); 

  pinMode(MAX_CS, OUTPUT);
  pinMode(MAX_SO, INPUT);
  pinMode(MAX_CLK, OUTPUT);
  
  digitalWrite(MAX_CS, HIGH);               // Ruhezustand
  digitalWrite(MAX_CLK, LOW);               // Ruhezustand
  
}

void loop() {
  
  delay(1000);
  
  Temp = read_MAX6675();
  
  Mittelwert = (Temp + Mittelwert * 10) / 11 / 4.0;
     
  // Temp = read_MAX6675()/4.0;    // Roh-Temperaturwert durch 4 (wegen °C) vom MAX6675 einlesen
  
Serial.println(Mittelwert,2);
  

}

// -----------------------------------------------------------------------------------------------------	 
// Der MAX6675 wird ausgelesen und der 12-Bit-Wert zurückgegeben ********
unsigned int read_MAX6675 ()
{
  int i;
  unsigned int data;
  data = 0;
  digitalWrite(MAX_CS, LOW);            // CS=Low
  for(i=0;i<16;i++)
    {
     delayMicroseconds(1);             // warte 100us
     digitalWrite(MAX_CLK,HIGH);	 // SCK=High
     delayMicroseconds(1);		 // warte 100us
     // data = ((data<<1) | MAX_SO);	 // schiebe nach links und lies SO
     data = ((data<<1) | digitalRead(MAX_SO));	 // schiebe nach links und lies SO
     digitalWrite(MAX_CLK, LOW);	 // SCK=Low
    }
  delayMicroseconds(1); 	
  digitalWrite(MAX_CS, HIGH);         	 // CS=High
  delayMicroseconds(1); 		 // warte 100us
  return (data>>3);			 // gib 12-Bit Wert zurück
}

Zur Zeit bilde ich mit 10 Werten immer einen neuen Mittelwert.

  for(int i=0 ; i<10; i++)
    {  
     delay(220);                          // 220ms Wartezeit vorm erneuten auslesen
     value = value + read_MAX6675();      // Temperaturwert vom MAX6675 einlesen
    }
    Mittelwert = value / 10 / 4.0;
    value = 0;

ohne das jetzt überprüfen zu können, vermute ich , dass dein Fehler in dieser Zeile zu suchen ist:

Mittelwert = (Temp + Mittelwert * 10) / 11 / 4.0;

das muß heißen:
Mittelwert = (Temp + Mittelwert * 10.0) / 11.0 / 4.0;
Sonst werden die Berechnungen in int ausgeführt.

Nachtrag:
Noch ein Fehler. Du mußt den Rohwert umrechnen, nicht den Mittelwert. So vermischt du beides.
Mittelwert = (Temp /4.0 + Mittelwert * 10.0) / 11.0;

Hallo,

eigentlich möchte ich solange wie möglich mit den Rohwerten rechnen, sprich Ganzzahlen, um Rundungsfehler durch float zu vermeiden. Deshalb dachte ich mir, rechnest erst zum Schluss den Durchschnitt der Rohwerte in float um.

Edit:
Ich habe das in Excel nachgebaut. 3. Formel scheint zu stimmen.
Mir ist aber nachwievor nicht klar wie in der Formel alte Messwerte herausgenommen werden. Es wird doch nur immer ein neuer Wert hinzuaddiert. Laut meinem Verständnis driftet der ständig neu ermittelte Wert stetig ab. Man muß doch vorher eine Schleifen bauen wo immer die letzten x Messwerte enthalten sind und damit wird gemittelt. :roll_eyes:

Doc_Arduino:
Hallo,
eigentlich möchte ich solange wie möglich mit den Rohwerten rechnen, sprich Ganzzahlen, um Rundungsfehler durch float zu vermeiden. Deshalb dachte ich mir, rechnest erst zum Schluss den Durchschnitt der Rohwerte in float um.

Nein, das funktioniert nicht.
Du mußt das so sehen:
du nimmst jedes mal 1/11 tel des neue Wertes und 10/11 tel des alten Mittelwertes und bildest damit den neuen Mittelwert.
Das funktioniert nur komplett mit float.
Aber dein Hauptfehler war das "/4" an falscher Stelle.

Doc_Arduino:
Mir ist aber nachwievor nicht klar wie in der Formel alte Messwerte herausgenommen werden. Es wird doch nur immer ein neuer Wert hinzuaddiert. Laut meinem Verständnis driftet der ständig neu ermittelte Wert stetig ab. Man muß doch vorher eine Schleifen bauen wo immer die letzten x Messwerte enthalten sind und damit wird gemittelt. :roll_eyes:

Das funktioniert ungefähr so:
Nehmen wir mal als Faktor nur 1 an.
Also: "Mittelwert = (Temp/4.0 + Mittelwert * 1.0) / 2.0;" (Nur damit es sich schöner erklären läßt)
Bei jedem Zyklus nimmst du also die Hälte der aktuellen Temperatur und die Hälfte des alten Mittelwertes und addierst die zum neuen Mittelwert.
Nehmen nun einen Messwert und beobachten seine Wertigkeit vonZyklus zu Zyklus.

  1. Durchlauf: Mein Messwert (nennen wir ihn Otto) wird halbiert und mit der Hälfte des Mittelwertes addiert zum neuen Mittelwert. Im neuen Mittelwert steckt Otto nun zur Hälfte drin (50% des Mittelwertes werde von Otto bestimmt.
  2. Durchlauf: ein neuer Messwert kommt, der alte Mittelwert wird wieder halbiert. Damit auch Otto. Im neuen Mittelwert steckt Otto nun nur noch zu einem Viertel drin.
  3. Durchlauf: wieder ein neuer Meßwert. Der Mittelwert wird halbiet und die WErtigkeit von Otto sinkt auf 1/8 tel.
    Beim nächsten Durchlauf auf e 1/16 tel, dann 1/32tel usw.

Der Anteil von Otto (als Stellvertreter für alle vergangenen Meßwerte) im Ergebnis wrid immer kleiner, bis er keine Rolle mehr spielt. Er muß also nicht rausfallen, er verschwindet irgenwann in der Bedeutunglosigkeit.

Das ganze ist einfach nur ein digitaler Tiefpass. Er verhält sich genau so wie ein RC-Glied in der Hardware.

Nachtrag:
Ich habe auch mal versucht, das ganze mit long int zu rechnen, in dem ich die Werte um 16-bit geshiftet habe (also *32768), aber es hilft nix. Der Wert konvergiert nicht. Es muß float sein.
Andererseits ist die Rechenzeit auch nicht so groß. Ich habs nicht mehr im Kopf, aber einen Wert aufs LCD-Display zu schreiben dauert länger.

Hallo,

ich glaube :roll_eyes: ich habe es jetzt verstanden. Das bedeutet aber auch, dass man nicht sagen kann, man hat immer den Mittelwert der letzten x Messwerte. Sondern man hat den Mittelwert aller jemals gesammelten Messwerte. Ist das so richtig ausgedrückt oder immer noch falsch verstanden?

Das wiederum würde bedeuten, ab einer Anzahl x gesammelter Messwerte macht das keinen Sinn mehr, weil der Einfluss eines neues Wertes auch immer bedeutungsloser wird. Wie die Ladekurve eines Kondensators. Ich hoffe das Bsp. ist passend. Ab 5x Tau kann man den Versuch beenden. Die Ladespannung ist praktisch erreicht, alles weitere bis zu 100% Ladespannung würde ewig und 3 Tage dauern.

Nur die Wirkung vom Filterfaktor ist mir noch nicht klar. Denn auch wenn man mit 2 beginnt, halbiert sich der Einfluss immer weiter.

Ich dachte ich kann damit meine starre Mittelwertbildung beschleunigen und immer die letzten 10 aktuellsten Messwerte mitteln. Aktuell messe ich 10x und bilde dann erst davon den Mittelwert. Ich dachte ich kann mit dem gleitenden Mittelwert immer mit den letzten 10 aktuellen Werte sofort den neuen Durchschnitt bilden und muß nicht erst warten bis ich erneute 10x hintereinander gemessen habe.

Doc_Arduino:
Nur die Wirkung vom Filterfaktor ist mir noch nicht klar. Denn auch wenn man mit 2 beginnt, halbiert sich der Einfluss immer weiter.

Korrekt. Die "exponentielle Glättung" führt zu einem gewichteten Mittelwert, bei dem neuere Messwerte eine höhere Gewichtung haben als ältere, d.h. nicht alle Werte werden gleich gewichtet, sondern neuere Werte tragen stärker zum Mittelwert bei als ältere Werte.

Deshalb kann man die exponentielle Glättung auch endlos weiterlaufen lassen: Je älter ein eingerechneter Wert ist, desto geringer ist sein Beitrag zum Mittelwert, bis hin zur völligen Bedeutungslosigkeit.

Ein "arithmetisches Mittel" bekommst Du durch "exponentielle Glättung" jedenfalls nicht heraus.

Falls Du Deine Werte für irgendwelche meteorologischen Auswertungen verwenden möchtest, ist Dein gesamter Ansatz, was an Daten dauerhaft gespeichert wird, übrigens nicht meteorologisch korrekt und widerspricht den Grundsätzen der Wetterdatenerfassung. Ich gehe also mal davon aus, dass es nicht um die Auswertung meteorologischer Daten nach den üblichen meteorologischen Standards gehen soll.

Doc_Arduino:
ich glaube :roll_eyes: ich habe es jetzt verstanden. Das bedeutet aber auch, dass man nicht sagen kann, man hat immer den Mittelwert der letzten x Messwerte. Sondern man hat den Mittelwert aller jemals gesammelten Messwerte. Ist das so richtig ausgedrückt oder immer noch falsch verstanden?

Leider noch nicht ganz.

dass man nicht sagen kann, man hat immer den Mittelwert der letzten x Messwerte.

Das ist richtig.

Sondern man hat den Mittelwert aller jemals gesammelten Messwerte

Das ist falsch.

Du hast keinen Mittelwert im reinen mathematischen Sinn. (Aber ich denke, den brauchst du auch nicht.)
mathematisch sieht das ganze etwa so aus:
m = Mittelwert
x1 = aktueller Messwert
x2 = letzer Messwert
x3 = vorletzer Messwert
x4 = vorvorletzer Messwert
...

m = 1/2 * X1 + 1/4x2 + 1/8 * x3 * 1/16 x4 + 1/32x5 + 1/64x6 + 1/128*x6 ....
Für Filterfaktor 2.
Du siehst, dass schon der Messwert, der 6 Messungen zurückliegt, fast nicht mehr eingeht.

Für höhere Filterfaktoren nimmt das natürlich viel langsamer ab.

Ich habe dir mal ein Excel-datei zum spielen angehängt.

Tiefpassfilter_digital.xlsx (29 KB)

Hallo,

Dank der vielen Erklärungen und tollen Exceltabelle :slight_smile: bin ich mir sicher das jetzt verstanden zu haben. Manchmal drängelt sich zwar noch die geistige Vorstellung eines notwendigen Schieberegisters ins Hirn, dann muß ich Eure Erklärungen wieder lesen und der Nebel verzieht sich wieder. :wink:

Der Filterfaktor entspricht Tau, auf die Kondensatorladekurve bezogen, und nach mindestens 5x Tau erhalte ich Mittelwerte die praktisch meinen Meßwert entsprechen, solange der sich nicht ändert. Diese Erklärung laut meinem Verständnis sollte stimmen. :roll_eyes:

Vielleicht gehe ich an mein "Meßproblem" auch etwas falsch ran. Mag sein. Ich lese ja mittels MAX6675 Temperaturen ein. Diese schwanken jedoch hin und wieder in 0.25°C Sprüngen. Raumtemp. laut Fühler nicht konstant, sondern zwischen 23.25 und 24.00 schwankend. Jetzt dachte ich spontan, als ich diesen Thread hier las, ich könnte es mit der Methode schön mitteln und erhalte konstante Werte. Aber selbst mit Filterfaktor 20 schwankt die 1. Kommastelle doch für mich zu oft. Wenn ich nur mit einer Kommastelle anzeigen lasse. Ich brauche also noch eine Art Hysterese.

Jetzt denke ich mir, ich komme besser, wenn ich die Viertel-Kommawerte (0.25, 0.50, 0.75) separat auswerte und nur auf halbe °C anzeigen lasse. Das sollte stabil werden.

Die gleitende Mittelwertbildung kann ich ja dennoch verwenden um eine leichte Trägheit reinzubekommen. Dann muß ich nicht wie sonst gemacht wird sturr 10x aufaddieren und teilen für einen einzigen Mittelwert, sondern bekomme bei jeden Durchgang einen neuen Mittelwert. Nach der "Wartezeit" von 5x Filterfaktor.

Habe das obige vorhin in Code umgesetzt. Filterfaktor mir Poti veränderbar.

//  Arduino Mega 2560

// Deklaration der Funktionen, übernimmt die Arduino IDE selbst
// unsigned int read_MAX6675();
  
//  Arduino Mega 2560

// Port-Pins definieren
int    MAX_CS = 24;               // Arduino Pin, SPI ChipSelect, 1. MAX6675
int    MAX_SO  = 26;              // Arduino Pin, SPI Datenleitung
int    MAX_CLK = 28; 	          // Arduino Pin, SPI Clockleitung

// Deklaration Datentypen und Variablen
int Poti = 0;                     // Eingang A0
int Filterfaktor = 1;
float Teiler = 2.0;

unsigned int value = 0;

unsigned int Temp = 0;	
float Mittelwert = 0;
float actual = 0;

void setup() {

  Serial.begin(9600); 
 
  pinMode(MAX_CS, OUTPUT);
  pinMode(MAX_SO, INPUT);
  pinMode(MAX_CLK, OUTPUT);
  
  digitalWrite(MAX_CS, HIGH);               // Ruhezustand
  digitalWrite(MAX_CLK, LOW);               // Ruhezustand
    
}

void loop() {
  
  delay(1000);
  Filterfaktor = analogRead(Poti)/8;        // lese analog Pin A0 ein und Begrenzung auf 128
  if(Filterfaktor == 0) Filterfaktor = 1;   // wenn A0 = 0, dann setze es auf 1 (Mindestwert)
  Teiler = Filterfaktor + 1;                // Teiler immer eins größer wie Filterfaktor
  Temp = read_MAX6675();
  actual = Temp / 4.0;
  Serial.print(Filterfaktor);
  Serial.print("  ");
  Serial.print(actual,2);                   // aktueller Meßwert von A0
  Serial.print("  ");
  Mittelwert = (Temp/4.0 + Mittelwert * Filterfaktor) / Teiler;
  Serial.println(Mittelwert,1);             // gleitender Mittelwert

}

// -----------------------------------------------------------------------------------------------------	 
// Der MAX6675 wird ausgelesen und der 12-Bit-Wert zurückgegeben ********
unsigned int read_MAX6675 ()
{
  int i;
  unsigned int data;
  data = 0;
  digitalWrite(MAX_CS, LOW);            // CS=Low
  for(i=0;i<16;i++)
    {
     delayMicroseconds(1);             // warte 100us
     digitalWrite(MAX_CLK,HIGH);	 // SCK=High
     delayMicroseconds(1);		 // warte 100us
     // data = ((data<<1) | MAX_SO);	 // schiebe nach links und lies SO
     data = ((data<<1) | digitalRead(MAX_SO));	 // schiebe nach links und lies SO
     digitalWrite(MAX_CLK, LOW);	 // SCK=Low
    }
  delayMicroseconds(1); 	
  digitalWrite(MAX_CS, HIGH);         	 // CS=High
  delayMicroseconds(1); 		 // warte 100us
  return (data>>3);			 // gib 12-Bit Wert zurück
}

Doc_Arduino:
Der Filterfaktor entspricht Tau, auf die Kondensatorladekurve bezogen, und nach mindestens 5x Tau erhalte ich Mittelwerte die praktisch meinen Meßwert entsprechen, solange der sich nicht ändert. Diese Erklärung laut meinem Verständnis sollte stimmen. :roll_eyes:

Richtig!
Jetzt hast Du's! :slight_smile:

Die Temperaturschwankungen müssen nicht an deiner Messtechnik liegen. Was für Thermoelemente hast du denn? Thermoelemente sind teilweise so schnell, dass sie wirklich auf die kleinste Änderung reagieren. Ich habe ein professionelles Messgerät mit Thermoelementen, und da ist es auch so, dass es reicht wenn jemand nur durchs Zimmer geht, dass das reicht um 1°-2° Temperturänderung hervorzurufen.
Du mußt dir die Frage andersrum stellen: Wie schnell ändert sich die Temperatur, die du messen willst?
Wenn es wirklich die Raumtemperatur ist, dann kannst du meiner Meinung nach Zeitkonstanten von 10-20min ansetzen.
Oder einen Sensor nehmen, der thermisch träger ist, einen mit größerer Masse, aber das ist auch nichts anderes als ein Tiefpass.

Hallo,

ich messe mit einem K-Typ an einem MAX6675. Der reagiert wirklich extrem schnell auf Berührung. Das ein Luftzug ausreicht wußte ich noch nicht. Nie darüber nachgedacht. Ich dachte immer eine Raumtemperatur ist immer sehr konstant solange alle Fenster und Türen zu sind. Ich werde die Ausgabe auf halbe °C umstellen. Dann sind die normalen Schwankungen weg.

Ich bedanke mich jedenfalls für Deine/Eure Geduld mit den Erklärungen. Hat mir richtig weitergeholfen.