Sekundenintervalle ungenau

Hallo zusammen,

ich bräuchte Hilfe bei einem Problem mit meinen Messwerten. Die Idee war sekundenweise von 3 Sensoren Daten auszulesen.

for i 0->X
(
ReadS1
ReadS2
ReadS3
Write values
delay(1000)
)

Nun habe ich die Messung mal über ne Minute laufen lassen und dann festgestellt, dass der 60. Wert bei 1:05 im seriellen Monitor angezeigt wurde.
Eine zweite Messung über 120 Intervalle mit 0,5 s hat gar 80 s mit der Stoppuhr gebraucht.

Welche Lösung bietet sich hier an. Gibt es bessere Befehle als die Laufvariable? Habe schon überlegt ein RTC Modul mit auszulesen und statt "i" die Uhrzeit auszugeben, aber das wirft bisher eher neue Probleme auf.

Danke für den Input

V

Hanswurst123:
......
Welche Lösung bietet sich hier an. Gibt es bessere Befehle als die Laufvariable? Habe schon überlegt ein RTC Modul mit auszulesen und statt "i" die Uhrzeit auszugeben, aber das wirft bisher eher neue Probleme auf.

Wieso wirft das mit einer RTC neue Probleme auf ?
Wenn es genau sein muss, ist eine RTC sicher die bessere Lösung.

Ein Quarz/Keramikresonator auf dem Arduino ist nie genau genug.

Wenn die Genauigkeit dann doch reichen sollte, musst du die Steuerung über die Funktion millis() machen, dann blockiert nix durch delay.

Deine Read und Write sind auch nicht unendlich schnell. Außerdem ist der Takt des Arduino nicht sehr genau.
Genaueres kann man nur beim kompletten Sketch mit Angabe der Sensoren und der verwendewten Libs (Links).

Gruß Tommy

Hi

Wie meine Vorredner schon sagten: Der Arduino ist keine Uhr - warum sollte Er also besonders genau gehen?
Für Sein Dasein reicht die Genauigkeit - wenn's genauer sein muß - mir wäre keine RTC bekannt, Die keinen Sekunden-Impuls ausgeben kann (und wenn doch: Nimm Eine, Die Das kann).

Im Endeffekt: Warte auf den Sekunden-Impuls, dann rattere die Messungen ab.

Das Warten kann wirklich Warten sein, oder irgend etwas Sinnvolles, was der Sketch sonst noch so von sich geben kann, während Er wartet ... LEDs blinken lassen und so Kram.

MfG

Na ja, 1 Millisekunde auf eine Minute, OK, aber 5 Sekunden auf eine Minute hat wohl ein bischen andere Ursachen :slight_smile: Man sollte halt einen Interrupt für die Zeit nehmen, oder warten bis (millis()%1000)==0 ist, aber nicht delay() ...

Hanswurst123:
Die Idee war sekundenweise von 3 Sensoren Daten auszulesen.

for i 0->X
(
ReadS1
ReadS2
ReadS3
Write values
delay(1000)
)

Ich nenne es mal typischer Fehler: Delay is not time

Das was Du suchst, kann man wie folgt interpretieren:

Lese immer die Sensoren
Gib einmal nach 1 Sekunde die Daten aus

ODER

Lese einmal die Sensoren nach einer Sekunde und gebe die Daten aus.

Typische Nachtwächtergeschichte....

Variante1:

const int intervall=1000 || Alle Sekunde ist das Intervall abgelaufen
long lastmillis= 0

loop{
read sensor[1]  || Liest bei jedem Durchlauf den/die Sensor
read sensor[2]
read sensor[x]


if (millis()-lastmillis >intervall) || Wenn Timing Intervall erreicht,speichere Zeit und dann Ausgabe
 lastmillis=millis()

//---> Hier wenn nicht bei jedem loopdurchlauf notwendig das read eintragen
 Serial.print(sensor[1], sensor[2], sensor[x])
}

RTC ist bei tatsächlich zeitkritischen Operationen ein Muss!

Hanswurst, du hast einen Denkfehler im Pseudocode:

for i 0->X
(
ReadS1 // braucht Zeit
ReadS2 // braucht Zeit
ReadS3 // braucht Zeit
Write values // braucht Zeit
delay(1000) // warte "genau" 1000 Millisekunden - ok übers genau kann man streiten, aber das kommt dazu
)

alle 4 Schritte zusammen sind somit mehr als 1000 Millisekunden.

probier mal aus:

/*
  Sekündliches Messen
  by noiasca
*/

void setup() {
  Serial.begin(115200);
  Serial.println(F("\nStart"));
  Serial.print("count");
  Serial.print("\t");
  Serial.print("foo");
  Serial.print("\t");
  Serial.print("bar");
  Serial.print("\t");
  Serial.print("baz");
  Serial.print("\t");
  Serial.println("Zeit");
}

void loop() {
  messen();
}

void messen()
{
  uint32_t currentMillis = millis();
  static uint32_t previousMillis = 0;
  static uint32_t count = 0;
  if (currentMillis - previousMillis >= 1000)
  {
    uint16_t foo = analogRead(A0);
    uint16_t bar = analogRead(A1);
    uint16_t baz = analogRead(A1);
    count++;
    Serial.print(count);
    Serial.print("\t");
    Serial.print(foo);
    Serial.print("\t");
    Serial.print(bar);
    Serial.print("\t");
    Serial.print(baz);
    Serial.print("\t");
    Serial.println(millis() - currentMillis);
    previousMillis = currentMillis;
  }
}

23:17:33.058 -> Start
23:17:33.058 -> count foo bar baz> Zeit
23:17:34.042 -> 1 256 218 216 0
23:17:35.072 -> 2 132 128 126 0
23:17:36.055 -> 3 123 116 114 0
23:17:37.085 -> 4 104 101 99 0
23:17:38.069 -> 5 119 111 110 0
23:17:39.052 -> 6 107 103 101 0
23:17:40.082 -> 7 118 111 109 0
23:17:41.065 -> 8 107 102 99 0
23:17:42.048 -> 9 96 93 92 0
23:17:43.078 -> 10 110 103 100 0
23:17:44.061 -> 11 99 95 93 0
23:17:45.044 -> 12 117 109 106 0
23:17:46.076 -> 13 105 100 97 0
23:17:47.044 -> 14 121 112 110 0
23:17:48.074 -> 15 115 109 107 0
23:17:49.057 -> 16 125 117 116 0
23:17:50.041 -> 17 125 117 114 0
23:17:51.071 -> 18 115 109 106 0
23:17:52.054 -> 19 130 121 120 0
23:17:53.037 -> 20 125 118 116 0
23:17:54.067 -> 21 155 141 139 0
23:17:55.051 -> 22 158 151 151 0
23:17:56.034 -> 23 160 153 151 0
23:17:57.063 -> 24 136 132 130 0
23:17:58.046 -> 25 129 124 123 0
23:17:59.077 -> 26 138 129 127 0
23:18:00.060 -> 27 133 125 124 0
23:18:01.043 -> 28 142 132 130 0
23:18:02.074 -> 29 139 129 128 0
23:18:03.057 -> 30 151 138 136 0
...
23:18:33.041 -> 60 138 133 134 0
...
23:19:13.032 -> 100 60 66 66 0
...
23:19:33.025 -> 120 80 82 84 0
...
23:20:33.009 -> 180 76 81 81 0
...
23:21:32.955 -> 240 177 161 161 0
...
23:22:32.966 -> 300 173 172 171 0

mir würde das reichen, da klatsch ich keinen RTC deswegen dran.

(der xy war jetzt schneller, der Text war aber jetzt schon fertig ...daher trotzdem)

noiasca:
mir würde das reichen, da klatsch ich deinen RTC deswegen dran.
war jetzt schneller, der Text war aber jetzt schon fertig ...daher trotzdem)

@noiasca #6
Ich hab im Quote etwas anderes als ich in Deinem Post sehe.

Vielen Dank, das Du Dir soviel Mühe gabst! Ich war zu faul. dafür.
Wanne wartet...

or i 0->X
(
ReadS1
ReadS2
ReadS3
Write values
delay(1000)
)

ReadS1 braucht Zeit
ReadS2 braucht Zeit
ReadS3 braucht Zeit
Write values braucht Zeit
delay(1000) Stopt den Arduino für ca 1 Sekunde

Im ganzen Sind das leicht mehr als eine Sekunde.

Lösungen:
Delay() mit kürzeren, geeichter Dauer
millis()
Interrupt mit einem internen Counter.
Interrupt mit einem SekundenTakt zB 1Hz Ausgang eines RTC

Grüße Uwe

So, dass waren nun einige Antworten in kurzer Zeit.

for i 0->X
(
ReadS1 // braucht Zeit
ReadS2 // braucht Zeit
ReadS3 // braucht Zeit
Write values // braucht Zeit
delay(1000) // warte "genau" 1000 Millisekunden - ok übers genau kann man streiten, aber das kommt dazu
)

alle 4 Schritte zusammen sind somit mehr als 1000 Millisekunden.

Das ist mir natürlich einleuchtend, da hatte ich auch den Fehler vermutet.
Ein RTC Modul liegt zwar hier herum, ich würde aber gerne darauf verzichten, da der Bauraum langsam knapp wird und das Ding auch mit neuem Code daherkommt, mit dem ich mich noch nicht wirklich auseinandergesetzt habe.

Und anscheinend gibt es ja auch andere Lösungen, die hier ausreichend wären. Es kommt nicht auf die Millisekunde an, aber meine Werte waren ja schon stark verschoben.

Leider habe ich die vorgeschlagenen Code-Bsp. noch nicht ganz verstanden. Aber etwas in diesem Sinne ist machbar, richtig?

if Button A pressed ->
Start Timer T;
T == 0s;
Read(S1,S2,S3);
Print(T)
Print(S1,S2,S3);
do nothing;
T == 1s;
if Button B pressed ->
End;

Hanswurst123:
if Button A pressed ->
Start Timer T;
T == 0s;
Read(S1,S2,S3);
Print(T)
Print(S1,S2,S3);
do nothing;
T == 1s;
if Button B pressed ->
End;

Was genau soll das sein ?
Ein lauffähiger Sketch sicher nicht.

Hanswurst123:
Aber etwas in diesem Sinne ist machbar, richtig?

if Button A pressed ->
Start Timer T;
T == 0s;
Read(S1,S2,S3);
Print(T)
Print(S1,S2,S3);
do nothing;
T == 1s;
if Button B pressed ->
End;

Dein rudimentärer PAP ist doch etwas sehr gewöhnungsbedürftig.
Als erstes: T == 0s ist ein Vergleich. Wenn es eine Zuweisung sein soll, lautet es T=0.
Wenn das als Teil einer Bedingung dienen soll, gehört das vor Start Timer T.

Mein PAP würde so aussehen, wenn ich das richtig verstanden habe:

Wenn Taster A gedrückt dann
T=aktuelle_millis

solange aktuelle_millis minus T kleiner 1000 millis
dann Schleife Anfang
  Read(S1,S2,S3);
  Print(T)
  Print(S1,S2,S3);
  do nothing;
Schleife Ende

Wenn Du das so umsetzt würde das Deinen Code für die Zeit zwischen Schleife_Anfang und Schleife_Ende blockieren. Das lässt sich verhindern...

Vielleicht schaust Du mal in das PDF rein: ArduinoForum.de - Das deutschsprachige Forum rund um den Arduino - Arduino Code-Referenz (deutsch)

Leider habe ich die vorgeschlagenen Code-Bsp. noch nicht ganz verstanden. Aber etwas in diesem Sinne ist machbar, richtig?

Das kann ich nicht nachvollziehen:
Ich habe dir einen LAUFFÄHIGEN Sketch gepostet.
Der macht GENAU das was du angedeutet hast.
Hast du den ausprobiert?
Was siehst du im Serial-Monitor?
Welche Zeile Code verstehst du nicht?

Stelle KONRKETE Fragen!

Da mein Pseudo-Code etwas unverständlich war, kommt hier noch der eigentliche Sketch:

#include <TSIC.h>
#include <SPI.h>
#include <SD.h>

// instantiate the library, representing the sensor
TSIC Sensor1(5, 2, TSIC_50x);
TSIC Sensor2(6, 2, TSIC_50x);
TSIC Sensor3(7, 2, TSIC_50x);// Signalpin, VCCpin, Sensor Type
//TSIC Sensor1(4);    // only Signalpin, VCCpin unused by default
//TSIC Sensor1(4, NO_VCC_PIN, TSIC_50x);    // external powering of 50x-Sensor
//TSIC Sensor1(4, 2, TSIC_30x);    // Signalpin, VCCpin, Sensor Type
//TSIC Sensor2(5, 2, TSIC_50x);  // Signalpin, VCCpin, Sensor Type NOTE: we can use the same VCCpin to power several sensors

uint16_t temperature = 0;
float Temperatur_C1 = 0;
float Temperatur_C2 = 0;
float Temperatur_C3 = 0;

int buttonApin = 9;
int buttonBpin = 10;
int ledPin = 8;
bool status;

char T1char[15];
char T2char[15];
char T3char[15];

int Messzeit = 120;
//Messzeit in Iterationsintervallen

File myFile;

void setup() {
 // Open serial communications and wait for port to open:
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }

  pinMode(ledPin, OUTPUT);
  pinMode(buttonApin, INPUT_PULLUP);
  pinMode(buttonBpin, INPUT_PULLUP);    
  status = false;
  
  Serial.print("Initializing SD card...");

  if (!SD.begin(4)) {
    Serial.println("initialization failed!");
    while (1);
  }
  Serial.println("initialization done.");

  if (SD.exists("example.txt")) {
    Serial.println("example.txt exists.");
  } else {
    Serial.println("example.txt doesn't exist.");
  }

  Serial.println("Creating example.txt...");
}

void loop() {
  if (digitalRead(buttonApin) == LOW) {
    digitalWrite(ledPin, HIGH);
    status = true;
  }
  if (status == true) {
  myFile = SD.open("example.txt", FILE_WRITE);
  myFile.println("Hier beginnt eine neue Messung:");
  myFile.println("Zeit [s], Temperaturen T1, T2 [°C]");
  
  for (int i=0; i <= Messzeit; i++) {
    
  if (Sensor1.getTemperature(&temperature)) {
    Temperatur_C1 = Sensor1.calc_Celsius(&temperature);
    Serial.print("Temperature1: ");
    Serial.print(Temperatur_C1, 1);
    Serial.println(" °C");
  }
  if (Sensor2.getTemperature(&temperature)) {
    Temperatur_C2 = Sensor2.calc_Celsius(&temperature);
    Serial.print("Temperature2: ");
    Serial.print(Temperatur_C2, 1);
    Serial.println(" °C");
  }
    if (Sensor3.getTemperature(&temperature)) {
    Temperatur_C3 = Sensor3.calc_Celsius(&temperature);
    Serial.print("Temperature3: ");
    Serial.print(Temperatur_C3, 1);
    Serial.println(" °C");
  }
  Serial.println(i);
  myFile.print(i);
  myFile.print("   ,   ");
  myFile.print(Temperatur_C1, 1);
  myFile.print("   ,   ");
  myFile.print(Temperatur_C2, 1);
  myFile.print("   ,   ");
  myFile.println(Temperatur_C3, 1);

  Temperatur_C1 = 0;
  Temperatur_C2 = 0;
  Temperatur_C3 = 0;

  if (digitalRead(buttonBpin) == LOW) {
  digitalWrite(ledPin, LOW);
  i = Messzeit;
  }
  delay(500);    // wait (x) ms
  }
  myFile.println("Ende der Messung");
  myFile.close();
  digitalWrite(ledPin, LOW);
  status = false;
  }
}

Nochmal zum Problem: Wenn möglich, würde ich gerne anstatt der Laufvariable einen Timer starten und jedes Mal, wenn der Timer eine volle Sekunde passiert, die drei Sensoren auslesen und die Daten auf die SD schreiben lassen.

Du solltest das auslesen der Sensoren in eine eigene Funktion packen.
Diese Funktion rufst du dann mittels millis() in deinem gewünschten Intervall auf und schreibst das dann auf die SD-Card, die auch wiederum in einer eigenen Funktion ist.

Wieso delay(500)???

Nimm den Beispiel-Sketch aus #6
Ändere dort das
previousMillis = currentMillis;
in
previousMillis+=1000;

... dann sind sogar die Aufrufe IMMER 1000ms auseinander.
Ausnahme: Du spielst wieder mit delay() herum - dann ist's natürlich Essig und jegliches Geschwafel von Ungenauigkeiten ist mehr Hohn als Fakt.

MfG

Also, ich denke ich komme meinem Ziel näher.
Der Code aus Nr. 6 hat einen funktionierenden Timer laufen lassen. Dieses Mal haben die Sekunden gepasst. Aber leider habe ich mich dabei verhaspelt meine Sensorauslesung in den Code einzubauen. Ganz zu schwiegen vom Schreiben der Daten auf die SD Karte.

Eine Frage zu millis() : Misst den Zeit seit Programmstart...
Heißt das:
Die Zeit seit void loop(), die Zeit seit messen() oder die Zeit, seit der Arduino Strom hat und läuft/bzw. der Zeit seit Drücken der Reset Taste?

Für einen einfacheren Aufbau und Code, habe ich die Sensoren getauscht und durch einen Analog Thermistor ersetzt.
Hier nochmal eine PAP/Pseudocode. Das Ziel wäre es, dass ein erneutes Drücken des A Tasters die Messung erneut durchführt (mit neuem Counter). Je nachdem, wie millis() funktioniert, ist das wahrscheinlich nur mit Subtraktion von Zwischenzeiten möglich, nehme ich an...

Program start

OpenSD
Create .txt

Read(Input Button A)
If Button A pressed
Open .txt
Write header
Start counter
For Every 1000ms repeat
{
ReadSen1
ReadSen2
ReadSen3
Write (Counter, Sen1Val, Sen2Val, Sen3Val)
Read(Input Button B)
}
if button B pressed
end counter
close .txt

Millis beginnt für dich quasi nach Stromversorgung.

PAP und Beschreibung sind unklar.

das heißt du möchtest erst nach Tastendruck von A mit dem Messen beginnen.
Die Messung alle 1000ms wiederholen
und du möchtest nur mit Tastendruck B aus den Wiederholungen rauskommen
und Tastendruck A soll wieder starten?

Korrekt?

Erkläre: Was soll mit "counter" passieren?

der die Zeit, seit der Arduino Strom hat und läuft/bzw. der Zeit seit Drücken der Reset Taste?

Ja!
und
Ja!
Überlauf nach 49,x Tagen.

Je nachdem, wie millis() funktioniert, ist das wahrscheinlich nur mit Subtraktion von Zwischenzeiten möglich, nehme ich an...

Ich behaupte:
Du solltest lernen in Nebenläufigkeiten zu denken und bevorzugt klar definierte endliche Automaten zu bauen.
Auf dem Wege lösen sich dann auch alle Knoten.

Je nachdem, wie millis() funktioniert, ist das wahrscheinlich nur mit Subtraktion von Zwischenzeiten möglich, nehme ich an

Richtig.
Der absolute Wert von millis() ist völlig egal. Selbst während eines Überlaufs (nach 49,x Tagen) ergibt sich die Differenz immer richtig.

static unsigned long startzeit; // static oder als globale Variable
if (millis() - startzeit >= INTERVALL) {
   startzeit = millis();
   // Dies hier wird einmal alle INTERVALL ms ausgeführt
}

Es können beliebig viele verschiedene Zyklen gleichzeitig laufen, wenn alle ihre eigene Startzeit-Variable haben ( combies "Nebenläufigkeit").

Wichtig ist nach meiner Meinung, dass loop() nicht das "Programm" in seinem zeitlichen Verlauf darstellt, sondern selbst (so gut wie) keine Zeit braucht, dafür unendlich häufig drankommt, und jeweils nur entscheidet, was jetzt zu tun ist. Dieser Denkansatz fällt anfangs schwer.
Ein Programm-Ende gibt es für einen Controller normalerweise nicht. Das ist einfach der Zustand "Warte auf einen Start-Befehl".