millis() genau genug für eine uhr?

hallo,

ich beschäftige mich jetzt das erste mal mit dem arduino und habe ein frage.
ich habe ein uhr, bzw. nur den motor (nebenuhr). die würde ich gerne mit einem rechecksignal jede minute für 600ms versorgen.
dafür habe ich ein kleines programm geschrieben und nutze millis() um die minute zu errechnen.
da auf dem arduino uno board ein quartz drauf ist, dachte ich dass der tackt schon stimmt. leider geht die uhr jetzt nach 19h schon 97s nach.

sind die millis() so genau, dass man sie für eine uhr verwenden kann (vlt is was in meinem code falsch) oder sollte ich einen externen taktgeber nutzen?

Hi Thomas,

ich würde diese Library verwenden. Dort gibt es als Beispiel eine "Software Realt Time Clock".

Gruß

Sebastian

das würde aber heißen, ich bräuchte noch etwas extra.
kann ich mich also auf den takt, den der arduino gibt nicht verlassen?

Hi,

nein, Du benötigst nichts extra. Es ist ja eine Softwarelösung. Nur diese Library einbinden und und die Soft RTC benutzen.

Sebastian

ahh ok danke.
leider habe ich vom arduino und programmieren nicht so viel ahnung. ich habe mir auch nin kumpel ran geholt der davon mehr weiß. vlt gehe ich mit ihm das mit der biblothek nochmal durch.
aber was macht denn die biblothek? die kann doch auch nur den takt über millis() nutzen oder?
na ich versuche da jetzt erstmal durch zu steigen. melde mich bestimmt wieder :wink:

ich würde diese Library verwenden. Dort gibt es als Beispiel eine "Software Realt Time Clock".

@stundenblume
dieses erwähnte Beispiel scheint aber nicht in der erwähnten Bibliothek zu sein. =(
Ich befasse mich auch gerade mit dem Thema und wollte meine Library aktualisieren.

Nachtrag
Die erwähnte Software Real Time Clock benötigt zusätzlich die Time-Library.
http://www.arduino.cc/playground/Code/Time

Lösung mit der Time Library:

The main advantages of software only clock is no hardware to buy or integrate, everything runs on the Arduino.

dieses erwähnte Beispiel scheint aber nicht in der erwähnten Bibliothek zu sein.

mmmh, bei mir war das softRTC Beispiel bei der RTC Library mit drin...

foo_mep:
ahh ok danke.
leider habe ich vom arduino und programmieren nicht so viel ahnung. ich habe mir auch nin kumpel ran geholt der davon mehr weiß. vlt gehe ich mit ihm das mit der biblothek nochmal durch.
aber was macht denn die biblothek? die kann doch auch nur den takt über millis() nutzen oder?
na ich versuche da jetzt erstmal durch zu steigen. melde mich bestimmt wieder :wink:

Die Biliothek holst du im Code über

#include <DS1307.h>

und die dazugehörige Time-Lib über

#include <Time.h>

das Ganze schreibste über die

void setup()  {}

ok. habe mir jetzt das ganze auf Arduino Playground - Time mal angeschaut.
das is ja alles gut. doch ich brauche ja eigentlich gar nicht so viel. nur halt jede minute ein takt.
basiert denn die time bibliothek nicht auch auf millis()?
warum geht denn die uhr mit meinem code so nach?
na wenns gar nicht anders geht, werde ich wohl die biblo nehmen

foo_mep:
ok. habe mir jetzt das ganze auf Arduino Playground - Time mal angeschaut.
das is ja alles gut. doch ich brauche ja eigentlich gar nicht so viel. nur halt jede minute ein takt.
basiert denn die time bibliothek nicht auch auf millis()?
warum geht denn die uhr mit meinem code so nach?
na wenns gar nicht anders geht, werde ich wohl die biblo nehmen

Weil die Bibliothek nunmal anders arbeitet als Du es tust. Sie arbeitet genauer, verliert keine Zeit bei der Berechnung. Natürlich auch auf Basis des selben Quarzes.

ok. ich will das ja nur verstehen.
wenn wir, sagen wir mal millis() 60000 erreicht hat, ich berechne und dann meinen impulse raus schicke.
dann warten und wenn millis() dann 120000 erreicht hat ich wieder einen los schicke. dann dürfe sich doch kein fehler addieren und so die zeit eingehalten werden, oder?

@foo_mep
Versuche doch beide Lösungen. So kannst du vergleichen wie die Genauigkeit ist und auch den nötigen Aufwand für die Realisierung.

naja. scheint ja schon was dran zu sein. weil bei meine lösung geht ja die uhr nach.
aber ich glaube ich habe jetzt ne lösung gefunden. wollte ja gerne meinen code nicht so stark ändern. von daher hat mir second() nicht so gut gefallen. aber now() ist sehr gut :slight_smile:
werde jetzt mal meine code umbauen, und die uhr nin tag laufen lassen. mal sehen ob sich was ändert. resultat folgt...

Ansonsten zeig doch bitte mal den Code, mit dem Du den Takt erzeugst. Dann kann man vielleicht mehr sagen.

Sebastian

was noch ein vorteil wäre, das zurücksetzten der zeit mit millis(). wird ja nach ca. 50 tagen auf 0 gesetzt. beachtet denn die biblo das auch?

joa. hier mein code wie er bis jetzt ist:

/*------------------------------------------------------------
 --- BAHN-UHR
 --- von Christoph Schwantuschke
 --- version 1.1
 --- 23.01.2011
 --------------------------------------------------------------
 --- Steuerung für das Nebenuhrwerk einer Bahnuhr der Firma Bürk Moba Time
 --- mit der Bezeichnung NU 90
 --- Mit Möglichkeit über einen Schalter die Uhr vorzustellen
 ------------------------------------------------------------*/


/*-----------------------------
 --- Globale Variablen ---
 -----------------------------*/
// Enum mit den Zuständen der Uhr:
// Idle (Uhr im Ruhzustand, kann vorstellt werden, ansonsten 1min warten)
// Impulse (Impulse wird an die Uhr gesandt)
// Wait (Impulse wird eingestellt und die Uhr hat eine "Pause". Erst danach kann ein weiterer Impulse folgen
enum cl_status_t {idle, impulse, wait};

// Enum für den Zustand des Buttons:
// btn_is_down: der Button ist gedrück
// btn_is_up: der Button ist nicht gedrückt
enum cl_btn_status_t {btn_is_down, btn_is_up};

int cl_fast_cnt = 0; // Variable wie oft der Zeiger vorgestellt wird

// Ein- und Ausgänge
int cl_btn_pin = 2; // Input für den Button
int cl_imp_pin_1 = 3; // Output 1 für den Impulse
int cl_imp_pin_2 = 4; // Output 2 für den Impulse


/*-----------------------------
 --- Func Setup ---
 -----------------------------*/

void setup() {
  Serial.begin(115200);
  Serial.print("---------------------\nBahn-Uhr\n");
  Serial.print("von Christoph Schwantuschke\nversion 1.1\n23.01.2011");
  Serial.print("\n---------------------\n\n");
    pinMode(13, OUTPUT); 
  pinMode(cl_imp_pin_1, OUTPUT); // Output 1 für den Impulse wird gesetzt
  pinMode(cl_imp_pin_2, OUTPUT); // Output 2 für den Impulse wird gesetzt
  pinMode(cl_btn_pin, INPUT); // Button zum vorstellen

}

/*-----------------------------
 --- Func Loop ---
 -----------------------------*/

void loop() {
  //  serial_interpret();
  cl_main();
  cl_btn_main();
}

/*-----------------------------
 --- Func Clock Main ---
 -------------------------------
 --- Funktion zum ablauf der Uhrsteuerung mit drei Stati (idle, impulse, wait).
 --- Es wird immer die Zeit zum letzten Impulse berechnet und nach einer Minute neugestartet
 --- Im Idle Modus kann vorgestellt werden
 -----------------------------*/

void cl_main() {
  // Variablen Initialisieren
  static cl_status_t cl_status = idle; // Status Enum für drei Stati der Uhr: idle, impulse, wait
  static long cl_time_ref = 0; // Referenz-zeit
  static long cl_time_impulse = 600; // Dauer des Impulses in ms
  static long cl_time_wait = 400; // Dauer der Zeit des Abwartens nach dem Impulse in ms
  static long cl_time_min = 60000; // Dauer für eine Minute

  // Zeit zum letzten Impulse errechnen
  long cl_time_dif = (signed long) millis() - cl_time_ref; 
  /*  Serial.print(millis());
   Serial.print(" --- dif: ");
   Serial.print(cl_time_dif);
   Serial.print(" --- ref: ");
   Serial.print(cl_time_ref);
   Serial.print("\n");
   */

  switch(cl_status){

    /* IDLE
     Wenn eine Minute vergangen oder vorgestellt werden soll,
     dann zu Impulse gehen
     */
  case idle:
    if(cl_time_dif >= cl_time_min || cl_fast_cnt > 0){
      if(cl_fast_cnt > 0) {
        cl_fast_cnt--;
        cl_time_ref = millis();
      }
      else{
        cl_time_ref += cl_time_min;
      }
      output("impulse");
      cl_status = impulse;
      cl_impulse(true);
    }
    break;

    /* IMPULSE
     Wenn Zeitdif 600ms erreicht hat, dann
     stoppe den Impulse und gehe in die Wait-Phase
     */
  case impulse:
    if(cl_time_dif >= cl_time_impulse){
      output("wait");
      cl_status = wait;
      cl_impulse(false);
    }
    break;

    /* WAIT
     Wenn Zeitdif 1000ms erreicht hat, dann
     stoppe den Impulse und gehe in die Idle-Phase
     */
  case wait:
    if(cl_time_dif >= cl_time_wait + cl_time_impulse){
      cl_status = idle;
      output("idle");
    }
    break;
  }
}

/*-----------------------------
 --- Func Clock Button Main ---
 -------------------------------
 --- Funktion zum überprüfen des Buttonstatus und vorstellen wenn er gedrückt wurde
 -----------------------------*/

void cl_btn_main(){
  // Enum für den Button-Status (ob er gedrück ist oder nicht)
  static cl_btn_status_t cl_btn_status = btn_is_up;
  // Zeitvariable für den Zeitpunkt wann der Button gedrückt wurde
  static long cl_btn_down_time;
  // Zeitdifferenz
  long cl_btn_time_dif;

  switch(cl_btn_status){
    // Button ist gedrückt
  case btn_is_down:
    // Wenn der Button losgelassen wird
    if(!digitalRead(cl_btn_pin)){
      digitalWrite(13, HIGH);
      // Wenn vorstellen schon läuft, resete es
      if(cl_fast_cnt > 0){
        cl_fast_cnt = 0;
      }
      else{
        // Wenn nicht überprüfe wie lange gedrückt wurde
        cl_btn_time_dif = millis() - cl_btn_down_time;
        // Bei unter einer sek stelle um 1 vor
        if(cl_btn_time_dif < 1000){
          cl_fast_cnt = 1;
        }
        else if(cl_btn_time_dif < 5000){
          // Bei unter 5 sek stelle um 10 vor
          cl_fast_cnt = 61;
        }
        else{
          // bei allem was über 5 sek ist stelle um 20 vor
          cl_fast_cnt = 1440;
        }
        // setzte den Button-status auf losgelassen
        cl_btn_status = btn_is_up;
      }
    }
    break;

    // Button ist nicht gedrückt
  case btn_is_up:
    digitalWrite(13, LOW);
    // Wenn der button gedrückt wird, dann setzte den Status und starte den Timer
    if(digitalRead(cl_btn_pin)){
      cl_btn_status = btn_is_down;
      cl_btn_down_time = millis();
    }
    break;
  }
}

/*-----------------------------
 --- Func Clock Impulse ---
 -------------------------------
 --- Funktion zum HIGH und LOW setzten der Ausgänge, abwechselnt Ausgang 1 und Ausgang 2
 -------------------------------
 */

void cl_impulse (boolean cl_imp_status) {
  static int cl_imp_pin_cur = 3; // Aktueller Ausgang

  // Wenn der Impulse an gehen soll (cl_imp_status == true) dann setzte HIGH sonst LOW
  if(cl_imp_status == true){
    digitalWrite(cl_imp_pin_cur, HIGH);
  }
  else{
    digitalWrite(cl_imp_pin_cur, LOW);

    // Der Ausgang wird nach dem Rücksetzten des Impulses geändert
    if(cl_imp_pin_cur == cl_imp_pin_1){
      cl_imp_pin_cur = cl_imp_pin_2;
    }
    else{
      cl_imp_pin_cur = cl_imp_pin_1;
    }
  }
}




/*-----------------------------
 --- Debug Code ---
 -----------------------------*/

void output (char *txt) {
  Serial.print(millis());
  Serial.print(": ");
  Serial.print(txt);

  if(cl_fast_cnt > 0){
    Serial.print("--- ");
    Serial.print(cl_fast_cnt);
  }
  Serial.print("\n");
}

//Edit: geändert: static long cl_time_min = 60000; // Dauer für eine Minute

Zwei weitere Möglichkeiten wären zb:

Mit nem EthernetSchield http://arduino.cc/en/Main/ArduinoEthernetShield die Zeit über einem Zeitserver abgreifen.
Der Beispielcode der im Beispielsketch "Udp NTP Client" steht funktionier da echt prima!

Hier kurz der Code des Beispieles:

/*

 Udp NTP Client
 
 Get the time from a Network Time Protocol (NTP) time server
 Demonstrates use of UDP sendPacket and ReceivePacket 
 For more on NTP time servers and the messages needed to communicate with them, 
 see http://en.wikipedia.org/wiki/Network_Time_Protocol
 
 created 4 Sep 2010 
 by Michael Margolis
 modified 17 Sep 2010
 by Tom Igoe
 
 This code is in the public domain.

 */

#include <SPI.h>         
#include <Ethernet.h>
#include <Udp.h>

// Enter a MAC address and IP address for your controller below.
// The IP address will be dependent on your local network:
byte mac[] = {  
  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] = { 
  192,168,1,177 };


unsigned int localPort = 8888;      // local port to listen for UDP packets

byte timeServer[] = { 
  192, 43, 244, 18}; // time.nist.gov NTP server

const int NTP_PACKET_SIZE= 48; // NTP time stamp is in the first 48 bytes of the message

byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets 

void setup() 
{
  // start Ethernet and UDP
  Ethernet.begin(mac,ip);
  Udp.begin(localPort);

  Serial.begin(9600);
}

void loop()
{
  sendNTPpacket(timeServer); // send an NTP packet to a time server

    // wait to see if a reply is available
  delay(1000);  
  if ( Udp.available() ) {  
    Udp.readPacket(packetBuffer,NTP_PACKET_SIZE);  // read the packet into the buffer

    //the timestamp starts at byte 40 of the received packet and is four bytes,
    // or two words, long. First, esxtract the two words:

    unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
    unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);  
    // combine the four bytes (two words) into a long integer
    // this is NTP time (seconds since Jan 1 1900):
    unsigned long secsSince1900 = highWord << 16 | lowWord;  
    Serial.print("Seconds since Jan 1 1900 = " );
    Serial.println(secsSince1900);               

    // now convert NTP time into everyday time:
    Serial.print("Unix time = ");
    // Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
    const unsigned long seventyYears = 2208988800UL;     
    // subtract seventy years:
    unsigned long epoch = secsSince1900 - seventyYears;  
    // print Unix time:
    Serial.println(epoch);                               


    // print the hour, minute and second:
    Serial.print("The UTC time is ");       // UTC is the time at Greenwich Meridian (GMT)
    Serial.print((epoch  % 86400L) / 3600); // print the hour (86400 equals secs per day)
    Serial.print(':');  
    Serial.print((epoch  % 3600) / 60); // print the minute (3600 equals secs per minute)
    Serial.print(':'); 
    Serial.println(epoch %60); // print the second
  }
  // wait ten seconds before asking for the time again
  delay(10000); 
}

// send an NTP request to the time server at the given address 
unsigned long sendNTPpacket(byte *address)
{
  // set all bytes in the buffer to 0
  memset(packetBuffer, 0, NTP_PACKET_SIZE); 
  // Initialize values needed to form NTP request
  // (see URL above for details on the packets)
  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum, or type of clock
  packetBuffer[2] = 6;     // Polling Interval
  packetBuffer[3] = 0xEC;  // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  packetBuffer[12]  = 49; 
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;

  // all NTP fields have been given values, now
  // you can send a packet requesting a timestamp: 		   
  Udp.sendPacket( packetBuffer,NTP_PACKET_SIZE,  address, 123); //NTP requests are to port 123
}

Weitere Möglichkeit wäre ein DS1307 zB. mit diesem hier:

Zusammen mit nem 32kHz Quarz und 2 Wiederständen haste da ne ECHTE Hardwareuhr, die auch ne Batterie-Backup Lösung bietet.
Ansprechen kannste die über den I²C.

Das sollen aber nur Alternativen sein, falls du evtl doch einen anderen Weg einschlagen willst!

erstmal vielen dank für die schnelle und gründliche hilfe hier :slight_smile:

die lösung mit dem ethernet kommt auf jeden fall nicht in frage, weil die uhr in der küche hängt und ich dort kein netzwerk habe. ich kann ja nicht die ganze wohnung mit netzerkkabeln durch ziehen :wink:
ich habe eh überlegt, ob ich evtl später eine funkuhr einbaue. aber erstmal sollte die uhr so laufen.

if(cl_time_dif >= cl_time_min || cl_fast_cnt > 0){
      if(cl_fast_cnt > 0) {
        cl_fast_cnt--;
        cl_time_ref = millis();
      }

Hier siehst Du weshalb Deine Uhr zu langsam läuft. Es vergeht einfach eine gewisse (wenn auch sehr kurze Zeit) von der Prüfung bis die neue cl_time_ref gesetzt wird. Das fehlt Dir dann jedes mal.

stundenblume:

if(cl_time_dif >= cl_time_min || cl_fast_cnt > 0){

if(cl_fast_cnt > 0) {
       cl_fast_cnt--;
       cl_time_ref = millis();
     }




Hier siehst Du weshalb Deine Uhr zu langsam läuft. Es vergeht einfach eine gewisse (wenn auch sehr kurze Zeit) von der Prüfung bis die neue cl_time_ref gesetzt wird. Das fehlt Dir dann jedes mal.

echt daran kann das liegen? dann bringt es mir ja auch nichts wenn ich statt millis() now() nehme. also nur für die minutenberechnung. das andere muss ja in ms gehen.
mir fehlen pro min ungefähr 85ms. das is doch schon ganz schön viel. meinste das kommt davon?