Go Down

Topic: millis() genau genug für eine uhr? (Read 5978 times) previous topic - next topic

foo_mep

#15
Jan 28, 2011, 09:02 pm Last Edit: Jan 28, 2011, 09:06 pm by foo_mep Reason: 1
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:

Code: [Select]
/*------------------------------------------------------------
--- 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

bytzmaster

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:

Code: [Select]
/*

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:
http://www.maxim-ic.com/datasheet/index.mvp/id/2688
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!


foo_mep

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

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 ;)
ich habe eh überlegt, ob ich evtl später eine funkuhr einbaue. aber erstmal sollte die uhr so laufen.

stundenblume

Code: [Select]
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.
Libraries:
  - multiCameraIrControl [V1.6]
  -

foo_mep


Code: [Select]
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?

stundenblume

Die Erkenntnis: Du sendest nicht bei 60.000ms, 120.000ms, 180.000ms, ... sondern bei 60.000ms, 120.013ms, 180.026ms einen Impuls... Die Bibliothek dagegen, sorgt dafür, dass wirklich bei 60.000ms, 120.000ms, ... ein Impuls gesendet wird. Du könntest natürlich auch die Berechnungszeit von Deinen 60.000ms abziehen. Also einen Impuls alle 59.987ms senden ;-)
Libraries:
  - multiCameraIrControl [V1.6]
  -

bytzmaster


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

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 ;)
ich habe eh überlegt, ob ich evtl später eine funkuhr einbaue. aber erstmal sollte die uhr so laufen.


Man könnte sich die Kabel sparen in dem man das WiFi Shield nutzt http://www.shieldlist.org/asynclabs/wishield-v2 vorausgesetzt du hast WLan.

stundenblume

Ich hab jetzt mal mit 13ms gerechnet, Du kannst das natürlich auch auf 85ms rechnen...
Libraries:
  - multiCameraIrControl [V1.6]
  -

stundenblume

Ich finde diese Zeitservergeschichte etwas überkandiedelt. Wenn man es machen möchte weil es geht, dann ist es ok. Aber meine Armbanduhr hat ja auch kein WLAN, geschweige denn einen Ethernet Anschluß ;-) Ich kann höchstens zu einer RTC raten. Dazu wird die RTC, ein passender Quarz & eine Knopfzelle benötigt...
Libraries:
  - multiCameraIrControl [V1.6]
  -

foo_mep

#24
Jan 28, 2011, 09:37 pm Last Edit: Jan 28, 2011, 09:40 pm by foo_mep Reason: 1
@bytzmaster
na erstmal das lösen ;)

@stundenblume
wenn du das ganze über usb anschließt und laufen läst, dann kannste dir ja die impulse anzeigen lassen. da werde ja dann auch die millis mit angezeigt. da stimmt das doch dann aber mit der zeit.

wenn ich also statt millis() now() nehme dürfe sich ja nicht viel ändern, oder? ich muss ja trotzdem abfragen und die variable setzen

stundenblume

Quote

unsigned long lastTick;

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

void loop(){
  if (millis()-lastTick>=1000){
    Serial.print("Tick @ ");
    Serial.println(millis());
    lastTick=millis();
  }
}



Versuche mal den Code. Dort siehst Du, dass bei jedem Tick deutlich ms verloren gehen. Das ist jetzt absichtlich ungünstig programmiert, um es zu verdeutlichen.
Libraries:
  - multiCameraIrControl [V1.6]
  -

bytzmaster


Ich finde diese Zeitservergeschichte etwas überkandiedelt. Wenn man es machen möchte weil es geht, dann ist es ok. Aber meine Armbanduhr hat ja auch kein WLAN, geschweige denn einen Ethernet Anschluß ;-) Ich kann höchstens zu einer RTC raten. Dazu wird die RTC, ein passender Quarz & eine Knopfzelle benötigt...


Jo das habe ich ja vorhin auch schon vorgeschlagen  XD :




Weitere Möglichkeit wäre ein DS1307 zB. mit diesem hier:
http://www.maxim-ic.com/datasheet/index.mvp/id/2688
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.



stundenblume

@bytzmaster

ich hab jetzt mal Deinen Code versucht & die 60.000ms auf 1.000ms geändert um jede Sekunde einen Impuls zu erhalten.

Ich nehme alles zurück & behaupte das Gegenteil... Bei Dir werden tatsächlich die Impulse zur "richtigen" Zeit, also bei vollen ms gesendet & ein Versatz ist auf die Schnelle nicht zu erkennen... Auch bei dem folgenden Code ist das so.
Code: [Select]
unsigned long lastTick;
unsigned long tack;

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

void loop(){
  tack=millis();
  if (tack-lastTick>=1000){
    Serial.print("Tick @ ");
    Serial.println(tack);
    lastTick=tack;
  }
}


Ich werde das morgen vielleicht mal im Vergleich zu meiner DS1307 laufen lassen...
Libraries:
  - multiCameraIrControl [V1.6]
  -

foo_mep

#28
Jan 28, 2011, 10:09 pm Last Edit: Jan 28, 2011, 10:12 pm by foo_mep Reason: 1
ok. ich möchte das jetzt aber esrtmal OHNE weitere hardware machen (möchte jetzt basteln und das dann mal an die wand hängen ;) )

hmm... dann liegt es doch daran, dass millis() ungenau wird?

vlt. kann die time biblo die zeit synchron halten. in der time.cpp gibt es recht weit unten was mit sysUnsyncedTime u.s.w.
ich sehe da nicht wirklich durch aber vlt. haben sie dort eine möglichkeit gefunden fehler auszumerzen.

foo_mep

so, habe das ganze jetzt mal mit der time und RTC biblo sowie now() realisiert und festgestellt, dass sich aus dort der fehler einschleicht. dann wird wohl der quartz auf dem arduino nicht wirklich genau sein.
bleiben also doch nur die möglichkeiten:
- einen externen quartz als taktgeben nehmen
- über lan oder wlan den takt zu holen
- eine funkuhr zu bauen
- den fehler mit einberechnen

ich werde jetzt erstmal die letzte ausprobieren. vlt widme ich mich dann nochmal der funkuhr :)

Go Up