Timing verbessern mit dem DS3231

Hallo zusammen,

leider finde ich im Selbststudium (googeln) keine Antwort auf meine Frage. Deshalb versuch ich Grünschnabel es bei euch :smiley:

Ich mache im Moment erste Gehversuche mit dem Arduino. Mein "Hello World" ist eine Stoppuhr. Das klappt alles ganz gut.

#include <YetAnotherPcInt.h>

#define READY 1
#define RUNNING 2
#define STOPPED 3

#define START_PIN 6
#define STOP_PIN 7

volatile unsigned long start, finished, elapsed, status;

void startMeasuring(const char* message, bool pinstate) {
  if (status != RUNNING) {
    start = millis();
    status = RUNNING;
    Serial.println("Running");  
  }
}

void stopMeasuring(const char* message, bool pinstate) {
  if (status == RUNNING) {
    elapsed = millis() - start;
    status = STOPPED;
    Serial.println("Stopped");  
  }
}

void setup() {
  Serial.begin(9600);

  pinMode(START_PIN ,INPUT);
  pinMode(STOP_PIN ,INPUT);
  digitalWrite(START_PIN,HIGH); 
  digitalWrite(STOP_PIN,HIGH); 
 
  PcInt::attachInterrupt(START_PIN, startMeasuring, "", CHANGE);
  PcInt::attachInterrupt(STOP_PIN, stopMeasuring, "", CHANGE);
        
  status=READY;  
  Serial.println("Ready");
}

void loop() {
  if (status == STOPPED) {
    serialResult();
  }
}

void serialResult() {
  int durMS = (elapsed%1000);       //Milliseconds
  int durSS = (elapsed/1000)%60;    //Seconds
  int durMM = (elapsed/(60000))%60; //Minutes
  int durHH = (elapsed/(3600000));  //Hours
  durHH = durHH % 24; 
  Serial.println(String(durHH)+":"+String(durMM)+":"+String(durSS)+"."+String(durMS));
  status = READY;  
  Serial.println("Ready");
}

Ich habe ein DS3231 RTC Modul in meinem Anfängerpaket mit dem UNO zusammen bekommen. Wie kann ich mir den 32k Pin oder den SQW Pin des DS3231 zu nutze machen um die Messergebnisse (auch bei längerer Messung genau(er) zu halten? Hat jemand ein Beispiel für mich?
Oder gibt es da andere Mittel und Wege?

Liebe Grüße,
Skrelpawin

Gib uns mal den link zur YetAnotherPcInt.h Bibliothek
Grüße Uwe

Hi Uwe,

klar, sorry. Hier ist der Link

Liebe Grüße,
Skrelpawin

erste Gehversuche mit dem Arduino. Mein "Hello World" ist eine Stoppuhr

Welch eine Untertreibung :wink:

Ist wohl eher eine Interrupt-Übung als ein "Hello World" Beispiel und zeigt sehr schön deren Probleme:

  • Interrupt Routinen dürfen nur das nötigste enthalten, auf keinen Fall Serial - Ausgaben.

  • volatile Variable größer als 1 byte müssen auch in loop unter geschlossenem Interrupt geändert werden.

  • Interrupt-Routinen machen eher Probleme als dass sie welche lösen und sind selten erforderlich.
    Schon gar nicht für eine tastenbetätigte Stoppuhr.

Willkommen im Forum, übrigens :slight_smile:

Ich stimme Michael zu.

Was Du vorhast, ist für ein „hello, world!“ wirklich arg ambitioniert :slight_smile: Aber mach' mal ...

Gruß

Gregor

Wie kann ich mir den 32k Pin oder den SQW Pin des DS3231 zu nutze machen um die Messergebnisse (auch bei längerer Messung genau(er) zu halten?

Den Pin im Control Register der RTC aktivieren.

Und dann eben mit deinem UNO auswerten.
Timer1 bietet sich da an, PD5 kann als TimerSource genutzt werden.

Hallo,

zu der lib gehören mehrere Beispiele eines behandelt deinen Wunsch.

wenn Du eine Langzeit Stopuhr bauen willst und eine Auflösung von 1 s reicht könntest Du die Unix Zeit auslesen und die Differenz zwischen start und stop bilden.

Du hast ja sicher aus den Antworten hier schon mitbekommen das Dein Lösungsansatz mit Interupt nicht wirklich zielführend ist. Versuche das erst mal mit Deinem millis() Ansatz ohne Interupt , dabei lernst Du dann auch schon mal ein paar Dinge.

Heinz

Hi

Ob Du die 32kHz brauchst?
Denke, ms-genau sollte halbwegs reichen.
Lasse Dir den Sekunden-Takt ausgeben und polle in der loop(), zu welcher millis()-Zeit Der kam.
Wird <=1000 Unterschied sein.
Bei Start/Stop merkst Du Dir den Abstand zum Sekunden-Signal.
Daraus - und der Anzahl der Sekunden-Signale während der Messung - berechnest Du die Zwischenzeit.
Auf Millisekunden wirst Du So wohl nicht kommen, aber ich denke, zumindest recht nahe dran.

Du brauchst keinen Interrupt, Pollen des Taster und des Sekunden-Signal wären (mehr oder minder) die einzigen Aufgaben der loop() (Anzeige während der Messung wird das Ergebnis stark beeinflussen)

MfG
MfG

Anzeige nur mit Sekunden während der Messung und nur wenn sich diese ändert. Nur neu anzeigen, was sich geändert hat.
Das sollte das Problem entschärfen.

Gruß Tommy

Hi zusammen,

danke für die ersten Tipps und danke für die freundliche Aufnahme im Forum.
Da das mein erste mal ist, wo ich selber was 'Bastel' ist mein Ziel wie bei den meisten
Anfängern wahrscheinlich zu hoch. Trotzdem -finde ich- wächst man mit den Aufgaben.

Inspiriert hat mich ein Schwimmwettkampf meiner Kinder. Dort wurde mit Stoppuhren gemessen und ich dachte, dass das auch anders gehen kann.

Jetzt versuche ich (zum Glück ohne Erfolgsdruck :wink: ) eine Arduino-Basierte Stoppuhrbox mit 4 Kanälen. Deshalb hätte ich gerne die Präzision einer guten Stoppuhr mit minuten, sekunden, zehntel und hundertstel.

Die Interrupts habe ich gewählt weil ich vermutet habe, dass ich so auf jeden Fall die Drücker in der richtigen Reihenfolge stoppe.
Das die Interrupts zu viel machen (serial.print) ist ein guter Hinweis. Das ist schon rausgeflogen. Ich Zeige nur noch an, wenn der status STOPPED ist.

@michael_x: Was ich leider nicht verstanden habe ist der Punkt

- volatile Variable größer als 1 byte müssen auch in loop unter geschlossenem Interrupt geändert werden.

Sollte ich diese 4-Kanal-Stoppuhr hinbekommen würde ich eine Übertragung an einen "Master Arduino" per RS485 ins Fassen. Für mich ambitioniert (wie die Stoppuhr in dem Format auch, aber ich hab Blut geleckt 8) )

Liebe Grüße,
Skrelpawin

Nehmen wir an, Du willst ein int (also 2Byte) lesen. Nach dem 1. Byte schlägt der Interrupt zu und ändert das 2. Byte. Du liest also Mist.

volatile int myInt;
...

int myTempInt;
nointerrupt();
myTempInt = myInt; // hier pfuscht keiner dazwischen
interrupt();
// jetzt mit myTempInt weiter arbeiten

Gruß Tommy

Hi

Alle Variablen, Die größer als 1 Byte sind (int, long, float) können sowohl beim Lesen wie beim Schreiben durch einen Interrupt unterbrochen werden - und der Interrupt kann diese variablen auch lesen/beschreiben - wenn Das mitten drin passiert, hast Du Müll.
So musst Du die Interrupts sperren, die Daten schreiben/lesen, die Interrupts wieder frei geben.

Volatile verhindert, daß die Daten in Registern (sehr schnelle Speicher) behalten werden, da ja in der Zwischenzeit - der Sketch weiß Nichts von Interrupts - genau ein Solcher den Wert verändert haben kann.
Der Arduino aber 'denkt', daß Er gerade ja noch mit genau dieser variable gearbeitet hat - nun soll Er 1 dazu zählen - und statt den aktuellen Wert aus dem Speicher auszulesen, macht Er nur 'Erinnerung plus ein' und speicher das nun falsche Ergebiss ab.
Wieder Müll.

In einer ISR werden Flags (Flaggen) gesetzt.
Diese sind, da Die ja im Hauptprogramm ausgewertet werden, ebenfalls volatile.
Aber, da maximal Byte, ohne Probleme auslesbar.
Denke aber: Brauchst Du nicht - für Hunderstel reicht ein Arduino gerade so.

Baue zuerst eine Uhr, Die per Knopfdruck den aktuellen millis()-Wert als Startwert übernimmt und beim nächsten Knopfdruck millis() als Stop-Wert.
Mit millis() bekommst Du die Millisekunden seit Start des µC (so ungefähr ... der Arduino ist keine hoch präzise Uhr, aber für Hundertstel sollte Das reichen).

Tasten(ent)prellen wird Dir auch noch begegnen :slight_smile:

Wenn Das läuft - einen Taster für Start, vier Taster für Stop - also vier Stop-Zeiten.

Dann kann man langsam mit der Metall-Anschlagplatte ins Schwimmbecken - dort sollte die Versorgung aber aus einer Power-Bank bestehen - Sicher ist halt sicher.

MfG

Für eine Stoppuhr im Minutenbereich brauchst du imho keinen RTC.
Auch bei 4 Lanes wird sich kaum ein größerer Fehler ergeben als bei einer Handmessung.
Einen "Abbruch-Button" würde ich noch vorsehen, damit du die Messung stoppen und resetten kannst bevor alle 4 Lanes stopp gemeldet haben (z.B. bei Fehlstart).

Skrelpawin:
... zum Glück ohne Erfolgsdruck :wink: ...
... aber ich hab Blut geleckt 8) ...

Beste Voraussetzungen für jede Menge Spaß :slight_smile:

Gruß

Gregor

Hallo,

jetzt wo ich weiß was das werden soll gibts 2 Möglichkeiten.

a)
Die Erste wie schon Micha und noiasca schrieb ohne RTC. Die Resonatorgenauigkeit sollte reichen für den kurzen Messzeitraum. Kannst dir auch eine Arduino Chinaclone mit Quarz suchen.
Du programmierst einen Timer in gewünschter Auflösung und lässt ihn laufen. Die TimerCounter Differenzen, ggf. mit Überläufen, rechnest du in deine gewünschte Zeitangabe um.

b)
Wenn du Spass am basteln hast, kannste auch den RTC 32kHz Takt an einen Timereingang anschließen und auch hier den Timercounter, ggf. mit Überläufen, am Ende auslesen und umrechnen.
Du kannst den 32kHz Takt auch an einen normalen Interruptpin anschließen, PCINTs reicht auch, und lässt im Interrupt einen Zähler hochzählen. Hat den gleichen Endeffekt.

c)
Wenn dir eine Genauigkeit von +/- 4µs reichen nimmste einfach micros(). :wink:

Hast viele Möglichkeiten zum Ziel und lernst eine Menge.

Die Resonatorgenauigkeit sollte reichen für den kurzen Messzeitraum

Finde ich auch.
Um es sich vorstellen zu können: Eine echte Sekunde dauert 1000 +- 1 Arduino-millis().
Nach 100 Sek zeigt der Arduino evtl. eine ZehntelSekunde anders als die Handgestoppte Zeit. Bei 1000 Sekunden (eine Viertelstunde) kann schonmal eine ganze Sekunde Unterschied zu sehen sein, das ist mehr als bei zwei manuell getriggerten echten Stoppuhren. Wenn man will, kann man also zeigen, dass der Arduino ungenau ist.
Wenn allerdings alle 4 Kanäle auf einem einzigen Arduino gemessen werden, passen die Zeiten relativ zueinander "genau". Zumal die Unterschiede an der hochgenauen Hundertstel klar zu sehen sind.

Die Probleme, wenn ich es realisieren sollte, wären eher mechanischer Art
(spritzwasserdichtes Gehause, ausreichend lesbare Anzeige, zuverlässige Taster (Anschlag-Sensor ?) usw. )

michael_x:
Die Probleme, wenn ich es realisieren sollte, wären eher mechanischer Art
(spritzwasserdichtes Gehause, ausreichend lesbare Anzeige, zuverlässige Taster (Anschlag-Sensor ?) usw. )

Diese Sorge gilt für uns. Der TO muss erstmal das Programm sauber hinbekommen. :slight_smile:
Wir würden dabei sicherlich schon an bzw. in Klassen denken.
Der TO sollte zumindestens struct für seine Daten verwenden, so als Hinweis, sonst endet der Sketch im Kaos.

Eine DS3231 Library hat meistens auch eine Funktion um den 32khz Eingang einzuschalten.

Mit dem Signal einfach auf einen Eingang mit Interrupt in einer Funktion hochzählen.

Dann Sekunden=Ticks/32768 mit Nachkommastellen:1000/32768*Rest

Ich hatte dafür mal eine Library umgeschrieben, die würde ich für Deinen Fall aber nicht empfehlen.

Eine DS3231 Library hat meistens auch eine Funktion um den 32khz Eingang einzuschalten.

Ja, und wenn auch nicht, die Uhr selber kann das.
Ein einsames kleines Bit will geändert werden.

Mit dem Signal einfach auf einen Eingang mit Interrupt in einer Funktion hochzählen.

Muss gar nicht, denn der ATMega328P hat einen Zählereingang an Timer 1
Da Timer1 ein 16 Bit Zähler ist, reicht dann ein Interrupt alle 2 Sekunden

Auch eine Alternative:
Weg mit der RTC
Einen nackten ATMega328p mit einem Uhrenquarz betreiben.
Dann kann die Stoppuhr auch Monate an einer CR2032 laufen. (ohne Display)

combie:
Ja, und wenn auch nicht, die Uhr selber kann das.
Ein einsames kleines Bit will geändert werden.

Ja und initialisiert werden muss diese auch noch, da der TO ja sowieso den DS3231 nutzt, vielleicht benötigt er auch noch Uhrzeit/Datum.. dann lohnt sich ein Lib schon. (ich mache das auch per Hand, da die Libs teils Frequenzen verwürfeln.)

combie:
Muss gar nicht, denn der ATMega328P hat einen Zählereingang an Timer 1
Da Timer1 ein 16 Bit Zähler ist, reicht dann ein Interrupt alle 2 Sekunden

Den verstehe ich jetzt nicht.. ist doch egal wie man es macht, man kann auch T0 nehmen wenn man das Core umschreibt :smiling_imp: Achja ,also ich finde den DS3231 Perfekt für einen Präzisionstimer.. da kommt kein Uhrenquarz hinterher.