Forum: Mikrocontroller und Digitale Elektronik Arduino - zyklisch Meßwerte aufzeichnen


von Manfred (Gast)


Lesenswert?

Ich messe Spannungen und Ströme, das funktioniert. Die Werte werden alle 
10 Sekunden auf eine SD-Karte geschrieben, das funktioniert auch, aber 
die Zeit driftet mir ab.

Den wesentlichen Teil als Beispiel:
1
unsigned long LetzteMessung = 1;
2
3
void setup() { 
4
//  Inhalt hier unwichtig;
5
}
6
7
void loop() {  // Hauptschleife
8
  MacheIrgendwas();
9
10
  if (millis() - LetzteMessung > 10000) { // zyklisch Messwerte holen
11
    LetzteMessung = millis();
12
    HoleMesswerte();
13
    SchreibeInsDisplay();
14
  }
15
16
  MacheNochwas();
17
}
18
19
void MacheIrgendwas() {
20
  delay(25);
21
}
22
23
void MacheNochwas() {
24
  delay(15);
25
}
26
27
void HoleMesswerte() {
28
  delay(50);
29
}
30
31
void SchreibeInsDisplay() {
32
  delay(20);
33
}

Das Problem: Wenn die Millisekunde erreicht ist, während gerade 
"MacheIrgendwas" begonnen hat, komme ich 25ms zu spät zur Ausführung 
(stört mich nicht).

Der neue Zeitstempel "LetzteMessung" liegt nun aber auch nicht mehr 
10000ms nach dem letzten, sondern versetzt um 25ms - diese ms addieren 
sich über die Zeit.

Wie bekomme ich diesen Versatz weg?

von Oliver S. (oliverso)


Lesenswert?

Indem du die vergangene Zeit seit der letzten Messung berücksichtigst.

Oliver

von Sylvester (Gast)


Lesenswert?

Zum Beispiel anstatt:

>  if (millis() - LetzteMessung > 10000) { // zyklisch Messwerte holen
>     LetzteMessung = millis();

so:

  if (millis() >= LetzteMessung + 10000) { // zyklisch Messwerte holen
      LetzteMessung += 10000;

von Sylvester (Gast)


Lesenswert?

Vergiss den vorigen Beitrag. Das funktioniert nicht wenn es einen 
Overflow bei "LetzteMessung + 10000" gibt.

von Manfred (Gast)


Lesenswert?

Oliver S. schrieb:
> Indem du die vergangene Zeit seit der letzten Messung berücksichtigst.

Dein Hinweis ist etwas arg knapp, kannst Du den möglichen Algorithmus 
umschreiben?

Sylvester schrieb:
> Vergiss den vorigen Beitrag. Das funktioniert nicht wenn es einen
> Overflow bei "LetzteMessung + 10000" gibt.

Ich ueberlege gerade, warum Du Deinen Ansatz widerrufen hast. Mir 
schwebt etwas in dieser Richtung vor, den ersten Zeitstempel immer in 
10000er-Schritten zu erhöhen, ich bekomme es nur nicht als Ablauf 
formuliert.

von Michael (Gast)


Lesenswert?

Ich würde das ganze mit einem Timer + Interrupt lösen.

1.) Timer konfigurieren dass jede Sekunde eine Interrupt Service Routine 
aufgerufen wird. (10s ist wahrscheinlich nicht möglich)

2.) Bei jedem 10. Aufruf des Interrupts rufst du dann eine Funktion auf 
um die Werte auf die SD Karte zu schreiben. Dabei erhöhst du eine 
einfache Zählervariable (int).
Zeit = counter * 10;

von Roth (Gast)


Lesenswert?

Michael schrieb:
> Ich würde das ganze mit einem Timer + Interrupt lösen.

Genau so ist es.

1
volatile uint8_t fNeueMesswerte = 0;
2
volatile double iHolzeitAlt = 0;
3
volatile double iHolzeit = 0;
4
5
ISR(TIMER2_COMPA_vect) {
6
  static uint16_t iPrescaler = 0;
7
  if(iPrescaler++ > 993) { // Takt hier feintunen
8
     iPrescaler = 0;
9
     HoleMesswerte();
10
     fNeueMesswerte = 1;
11
  }
12
}
13
14
15
void setup() {
16
  Serial.begin(9600);
17
18
  // ISR config (Timer 2)
19
  cli();
20
  TCNT2 = 0;
21
  TCCR2A = 0;
22
  TCCR2B = 0;
23
  OCR2A = 156;  // entspricht einem ISR-Takt von ca. 100 x pro Sekunde
24
  TCCR2B |= (1<<CS22) | (1<<CS21) | (1<<CS20);
25
  TCCR2A |= (1<<WGM21);
26
  TIMSK2 |= (1<<OCIE2A);
27
  sei();
28
}
29
30
void loop() {
31
  MacheIrgendwas();
32
  if (fNeueMesswerte) {
33
     fNeueMesswerte = 0;
34
     SchreibeInsDisplay();
35
  }
36
  MacheNochwas();
37
}
38
39
void MacheIrgendwas() {
40
  delay(25);
41
}
42
43
void MacheNochwas() {
44
  delay(15);
45
}
46
47
void HoleMesswerte() {
48
  iHolzeitAlt = iHolzeit;
49
  iHolzeit = millis();
50
}
51
52
void SchreibeInsDisplay() {
53
  Serial.print((iHolzeit - iHolzeitAlt) / 1000.0);
54
  Serial.println(" Sekunden");
55
  delay(20);
56
}

__________________________________________


funktioniert sebst dann noch
1
void MacheIrgendwas() {
2
  delay(2500);
3
}
4
void MacheNochwas() {
5
  delay(1500);
6
}

solange loop() unter 10 Sek. bleibt

von Falk B. (falk)


Lesenswert?

Siehe Multitasking, 3. Beispiel mit Timer. Dort kann man auch live 
prüfen, ob das Timing stimmt und die Schleifenfunktionen schnell genug 
sind.

von Sylvester (Gast)


Lesenswert?

Manfred schrieb:
> Sylvester schrieb:
>> Vergiss den vorigen Beitrag. Das funktioniert nicht wenn es einen
>> Overflow bei "LetzteMessung + 10000" gibt.
>
> Ich ueberlege gerade, warum Du Deinen Ansatz widerrufen hast. Mir
> schwebt etwas in dieser Richtung vor, den ersten Zeitstempel immer in
> 10000er-Schritten zu erhöhen, ich bekomme es nur nicht als Ablauf
> formuliert.

Unsigned Counter die unabhängig in eine Overflow Bedingung laufen können 
mit größer oder kleiner zu vergleichen funktioniert nicht. Das kann man 
anhand eines kleinen Beispiels zeigen. Damit die Zahlen nicht so 
unhandlich werden, nehmen wir an, daß millis() unsigned 8-bit Werte 
liefert, LetzteMessung ebenso eine unsigned 8-bit Variable ist, und als 
Schrittweite nehmen wir 10.

 if (millis() >= LetzteMessung + 10) { // zyklisch Messwerte holen
      LetzteMessung += 10;

LetzteMessung = 242 und millis() steht auf 252, dann ist die 
if-Bedingung erfüllt, und LetzteMessung wird auf 252 gesetzt. Soweit 
noch ok. Aber schon beim nächsten Schleifendurchlauf ist die 
if-Bedingung wieder erfüllt, da der millis() Wert nun mit 252+10 = 6 
(wegen 8-bit overflow) verglichen wird. Und das ist falsch.

Was bei Unsigned aber trotz Overflow funktioniert ist die 
Differenzbildung, d.h. so wie du es ursprünglich hattest ist es richtig. 
Du mußt nur "LetzteMessung = millis();" durch "LetzteMessung += 10000" 
ersetzen.

von Manfred (Gast)


Lesenswert?

Sylvester schrieb:
>> Ich ueberlege gerade, warum Du Deinen Ansatz widerrufen hast. Mir
>> schwebt etwas in dieser Richtung vor, ...

> Unsigned Counter die unabhängig in eine Overflow Bedingung laufen können
> mit größer oder kleiner zu vergleichen funktioniert nicht.

Ich weiß. Variablen und deren Typen sind nicht meine Freunde, man lernt, 
sich zu respektieren.

> Was bei Unsigned aber trotz Overflow funktioniert ist die
> Differenzbildung, d.h. so wie du es ursprünglich hattest ist es richtig.

Jou, in einem bestehenden Gerät funktioniert das mit zweistelligen 
Stunden, längere Betriebszeiten treten nicht auf. Rechnerisch sollte es 
knapp 50 Tage bis zum Überlauf dauern. Da fiel mir halt der sich 
addierende Fehler auf, den ich fuer ein anderes Projekt aber nicht 
ertragen mag.

> Du mußt nur "LetzteMessung = millis();" durch "LetzteMessung += 10000" ersetzen.

Genau so habe ich das heute geschrieben und getestet:
1
  if (millis() - LastTimeMess >= MessInterval) { // zyklisch Messwerte holen
2
    LastTimeMess = LastTimeMess + MessInterval;
3
4
    if ((LastTimeMess + MessInterval) < millis()) {
5
      LastTimeMess = millis(); // nur falls Timing grob aus dem Ruder laeuft
6
      Serial.print ("LastTimeMess neu gesetzt: ");
7
      Serial.println (LastTimeMess);
8
    }
9
  }

Die Routine kommt beim erstmaligen Aufruf nicht auf die Strümpfe, da 
wird dann millis() eingesetzt. Bei weiteren Durchläufen passt das dann, 
es sei denn, eine Unterroutine blockiert extrem lange - was aber nicht 
passieren wird.

Das bleibt jetzt so, einfacher kann ich es nicht haben!

von Uwe C. (Firma: privat) (olmuk)


Lesenswert?

Was delay() mit dem Prozessor macht ist bekannt ?

Da nützen die millis() im Aufruf der Unterprogramme gar nix....

von Nico W. (nico_w)


Lesenswert?

Double für iHolzeit? Macht irgendwie wenig Sinn. Mal davon abgesehen 
dass der AVR-GCC afaik kein Double kennt.

von Falk B. (falk)


Lesenswert?

Uwe C. schrieb:
> Was delay() mit dem Prozessor macht ist bekannt ?
>
> Da nützen die millis() im Aufruf der Unterprogramme gar nix....

Das sind nur sinnbildliche Beispiele!

von Sylvester (Gast)


Lesenswert?

Manfred schrieb:
>
>
1
>  if (millis() - LastTimeMess >= MessInterval) { // zyklisch 
2
> Messwerte holen
3
>     LastTimeMess = LastTimeMess + MessInterval;
4
> 
5
>     if ((LastTimeMess + MessInterval) < millis()) {
6
>       LastTimeMess = millis(); // nur falls Timing grob aus dem Ruder 
7
> laeuft
8
>       Serial.print ("LastTimeMess neu gesetzt: ");
9
>       Serial.println (LastTimeMess);
10
>     }
11
>   }

Bei dem zweiten Vergleich hast du wieder das Problem bei einem Overflow. 
Der erste Vergleich funktioniert ja mit/trotz Overflow auch noch nach 50 
Tagen, deshalb solltest du auch den zweiten Vergleich entsprechend 
umformulieren,

anstelle: if ((LastTimeMess + MessInterval) < millis()) {
schreibe: if ((millis() - LastTimeMess) > MessInterval) {

Solche Overflow Probleme, die erst nach langer Zeit in Erscheinung 
treten, sollte man grundsätzlich vermeiden.

Du kannst auch auf MessInterval/2..x prüfen, wenn diese Korrektur früher 
zuschlagen soll.
Um definiert anzufangen würde ich am Ende von setup() "LastTimeMess = 
millis() - MessIntervall;" setzen, wenn gleich beim ersten loop() 
Durchlauf ein Messergebnis gewünscht ist, oder "LastTimeMess = 
millis();", dann wird der erste Messwert erst nach Ablauf von 
"MessInterval" geholt.

von Manfred (Gast)


Angehängte Dateien:

Lesenswert?

Sylvester schrieb:
> Bei dem zweiten Vergleich hast du wieder das Problem bei einem Overflow.
> Der erste Vergleich funktioniert ja mit/trotz Overflow auch noch nach 50
> Tagen, deshalb solltest du auch den zweiten Vergleich entsprechend
> umformulieren,
>
> anstelle: if ((LastTimeMess + MessInterval) < millis()) {
> schreibe: if ((millis() - LastTimeMess) > MessInterval) {
>
> Solche Overflow Probleme, die erst nach langer Zeit in Erscheinung
> treten, sollte man grundsätzlich vermeiden.

Ein Überlauf nach 50 Tagen ist unwichtig, das Gerät wird nicht länger 
als ein paar Stunden laufen. Aber egal, die Korrektur kostet kein Geld 
und wird eingearbeitet! Wer weiß, ob ich den Fehler ansonsten in Zukunft 
mal in eine Anwendung kopiere, wo er relevant ist.

> Du kannst auch auf MessInterval/2..x prüfen, wenn diese Korrektur früher 
zuschlagen soll.

Ich habe hier nur ein sehr kleines Stück dargestellt, bei dem ich den 
Knoten im Kopf hatte.

> Um definiert anzufangen würde ich am Ende von setup() "LastTimeMess =
> millis() - MessIntervall;" setzen, wenn gleich beim ersten loop()
> Durchlauf ein Messergebnis gewünscht ist, oder "LastTimeMess =
> millis();", dann wird der erste Messwert erst nach Ablauf von
> "MessInterval" geholt.

Das Gebilde läuft vor sich hin und aktualisiert alle paar Sekunden 
Meßwerte im Display. Es gibt Tasten Start und Stop, mit denen die 
zyklische Protokollierung zur SD-Karte gestartet wird, da habe ich es 
direkt in der Hand, sofort bei Start eine Messung zu triggern.

Das Problem ist eigentlich Kindergarten, ich danke Dir, dass Du Dich 
damit so detailliert befasst hast.

--------

Was das wird: Ein Netzteil liefert 5V / 2A, ein INA219 misst den Strom 
und errechnet daraus die Millimaperestunden, die in ein Gerät per USB 
reingeladen werden. Es sind drei Analogeingänge des AT328 (Nano) 
beschaltet, damit der die Betriebsart erkennen kann.

Es kann eine externe Versorgung angeschlossen werden, dann werden mAh 
und Spannung am blanken Akku gemessen, das kann z.B. ein Blei bis 12V 
sein.

Zum Laden einer LiIon-Einzelzelle ist ein TP4056 einschaltbar.

Alle Werte können auf eine µSD-Karte protokolliert werden, um später am 
PC die zugehörige Grafik darstellen zu können.

(Die Daten auf dem Bild sind sinnlos, dienen nur dem Test, die 
Formatierung korrekt hinzubiegen. Es ist eine rein private Bastelei.)

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.