/* Arduino horloge UTC/GPS 24 heures Horloge pseudo synchronisée par signal GPS. Pseudo car seule les secondes sont maintenues en phase (+-10 msec.) par le signal PPS d’un module GPS (Adafruit) et un module RTC DS3231. Le circuit DS3231 possède un réseau de capacités que l’on peut commuter pour ajuster l’oscillateur. Utilise un module nano et un mouvement d'horloge 24 heures à quartz de type Lavet modifié, un mouvement qui saute par seconde et non un pseudo mouvement continu. 1 bouton sert a stopper le moteur afin de mettre l’horloge a l’heure. Le cadran de l’horloge est imprimé sur une feuille transparente collée sur un disque de bois (Amazon ou boutique de bricolage/peinture) de 20 cm de diamètre. Le système est alimenté par un chargeur USB et pour la sauvegarde par une batterie lithium de 3.7 Volts. Référence: https://wiki.logre.eu/index.php/Horloge_analogique_24h/en https://makezine.com/article/technology/arduino/how-to-control-a-clock-mechanism-wi/ */ #include #include #include #include #include #include #include #define MOSI 11 // SPI output pin #define MISO 12 // SPI input pin #define SCKP 13 // SPI clock pin #define DS3231 0x68 // RTC module address #define BUTTON MISO // stop motor driving switch #define ENABLE MOSI // GPS enable pin #define RTC_LED A0 // RTC led #define GPS_LED A1 // GPS led #define INTERRUPT0 PD2 // interrupt pin RTC #define INTERRUPT1 PD3 // interrupt pin GPS #define TX PD4 // software serial TX pin #define RX PD5 // software serial RX pin #define CLOCKA PD6 // pin number where clock #define CLOCKB PD7 // motor is connected #define POWER A3 // ADC pin used to check VCC #define PMTK_SET_NMEA_UPDATE_1HZ "$PMTK220,1000*1F" #define PMTK_SET_NMEA_UPDATE_5HZ "$PMTK220,200*2C" #define PMTK_SET_NMEA_UPDATE_10HZ "$PMTK220,100*2F" #define PMTK_SET_NMEA_OUTPUT_RMCONLY "$PMTK314,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*29" #define PMTK_SET_NMEA_OUTPUT_RMCGGA "$PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*28" #define PMTK_SET_NMEA_OUTPUT_ALLDATA "$PMTK314,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0*28" #define PMTK_SET_NMEA_OUTPUT_OFF "$PMTK314,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*28" #define PMTK_SET_NMEA_OUTPUT_ZDA "$PMTK314,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0*29" SoftwareSerial serial(RX, TX); // configure soft serial library Adafruit_GPS GPS(&serial); extern volatile unsigned long timer0_millis; volatile boolean RTC_FLAG = false; // interrupt flag volatile boolean PPS_FLAG = false; // interrupt flag volatile boolean STOP = false; // stop motor for adjustement volatile boolean PRESSED = false; // stop button status volatile boolean FIXED = false; // GPS fixed byte TICKPIN = CLOCKA; const char copyright[] PROGMEM = {"2023 VA2CPJ j-p.cornut@va2cpj.ca / va2cpj@gmail.com https://va2cpj.ca Free for use."}; const int PULSE_LENGTH = 30; // motor pulse 150 ms unsigned long RTC_T; // RTC interrupt time unsigned long PPS_T; // GPS interrupt time byte RTC_COUNTER = 0; // to check if GPS fixed byte PPS_COUNTER = 0; int DELTA = 0; // RTC-GPS interrupt time difference int RATE = 60; // DS3231 update cycle (second) until locked int UPDATE = RATE; // when locked 600 seconds float PWR = 5; // power input value, under 1.0 volts disable gps RTC_DS3231 rtc; void RTC() { RTC_T = millis(); RTC_COUNTER++; digitalWrite(LED_BUILTIN, HIGH); // turn builtin led on digitalWrite(RTC_LED, HIGH); RTC_FLAG = true; } void PPS() { PPS_T = millis(); PPS_COUNTER++; digitalWrite(LED_BUILTIN, LOW); // turn builtin led on digitalWrite(GPS_LED, HIGH); PPS_FLAG = true; } void Delay(int msec) { for(int i = 0; i < msec; i++){ delayMicroseconds(1000); } // non timer delay in milliseconds } boolean Pressed() { if(digitalRead(BUTTON) == LOW && !PRESSED) { Delay(20); if(digitalRead(BUTTON) == LOW) { PRESSED = true; return true; } } return false; } boolean Released() { if(digitalRead(BUTTON) == HIGH && PRESSED) { Delay(20); if(digitalRead(BUTTON) == HIGH) { PRESSED = false; return true; } } return false; } double Gettemp() { byte th = 0; byte tl = 0; double temp = 0; Wire.beginTransmission(DS3231); Wire.write(0x11); Wire.endTransmission(); Wire.requestFrom(DS3231, 2); while(Wire.available()) { th = Wire.read();tl = Wire.read(); } if(th > 127) temp = th - 256 + (double) (tl >> 6) / 4.0; else temp = th + (double) (tl >> 6) / 4.0; return temp; } int Getoffset() { byte data = 0; int offset = 0; Wire.beginTransmission(DS3231); Wire.write(0x10); Wire.endTransmission(); Wire.requestFrom(DS3231, 1); while(Wire.available()) { data = Wire.read(); } if(data > 127) offset = data - 256; else offset = data; return offset; } void Setoffset(int offset) { Wire.beginTransmission(DS3231); Wire.write(0x10); Wire.write(offset); Wire.endTransmission(); } void Compensate(int delta) // compensate TCXO, switch internal capacitor { byte compensation; if(delta < -5) { compensation = (Getoffset()); compensation++; // add capacitance, slowing the oscillator frequency if(compensation > 127) compensation = 0; Setoffset(compensation); RATE = 60; return; // update rate 1 mn. } if(delta > +5) { compensation = (Getoffset()); compensation--; // remove capacitance, increasing the oscillator frequency if(compensation < 128) compensation = 0; Setoffset(compensation); RATE = 60; return; // update rate 1 mn. } RATE = 600; return; // synchronized within 10 msec., update rate 10 mn. } void Tick() // energize the motor electromagnet { if(STOP) return; digitalWrite(TICKPIN, HIGH); Delay(PULSE_LENGTH); digitalWrite(TICKPIN, LOW); if(TICKPIN == CLOCKA){ TICKPIN = CLOCKB; } // switch the direction for next time else { TICKPIN = CLOCKA; } } void Todo() { Tick(); digitalWrite(RTC_LED, LOW); digitalWrite(GPS_LED, LOW); DELTA = RTC_T - PPS_T; // time difference between RTC and GPS if(!FIXED) DELTA = -1; if(--UPDATE == 0) { UPDATE = RATE; if(FIXED) Compensate(DELTA); } // if GPS fixed correct oscillator frequency, otherwise do nothing PWR = analogRead(POWER) * (5.0 / 1023.0); // check power voltage input //if(PWR < 1.000) digitalWrite(ENABLE, LOW); // running on battery only, disable GPS // else digitalWrite(ENABLE, HIGH); Monitor(); } void setup() { noInterrupts (); timer0_millis = 0; interrupts(); SPI.end(); pinMode(MISO, INPUT); pinMode(MOSI, OUTPUT); pinMode(INTERRUPT0, INPUT_PULLUP); pinMode(INTERRUPT1, INPUT_PULLUP); pinMode(RTC_LED, OUTPUT); pinMode(GPS_LED, OUTPUT); pinMode(LED_BUILTIN, OUTPUT); pinMode(CLOCKA, OUTPUT); // motor drive pins, PD6 and pinMode(CLOCKB, OUTPUT); // PD7 as output digitalWrite(CLOCKA, LOW); digitalWrite(CLOCKB, LOW); pinMode(POWER, INPUT); pinMode(BUTTON, INPUT_PULLUP); pinMode(ENABLE, OUTPUT); digitalWrite(ENABLE, HIGH); Serial.begin(57600); Serial.flush(); Serial.println((const __FlashStringHelper *) copyright); Serial.flush(); rtc.begin(); rtc.writeSqwPinMode(DS3231_SquareWave1Hz); attachInterrupt(digitalPinToInterrupt(INTERRUPT0), RTC, FALLING); attachInterrupt(digitalPinToInterrupt(INTERRUPT1), PPS, RISING); GPS.begin(9600); GPS.println(PMTK_SET_NMEA_OUTPUT_ZDA); // setup time only (GPZDA) GPS.println(PMTK_SET_NMEA_UPDATE_1HZ); // setup PPS output at 1 Hz Setoffset(0); } void loop() { if(RTC_FLAG) { Todo(); RTC_FLAG = false; } if(PPS_FLAG) { PPS_FLAG = false; } if(RTC_COUNTER == 10) // check if GPS 3D fixed, fix pin implementation not very useful { if(PPS_COUNTER > (RTC_COUNTER - 1)) { FIXED = true; } else { FIXED = false; } RTC_COUNTER = 0; PPS_COUNTER = 0; } if (Pressed()) STOP = true; else if (Released()) STOP = false; } void Monitor() { Serial.print("DELTA: "); Serial.print(DELTA); Serial.print(" OFFSET: "); Serial.print(Getoffset()); Serial.print(" TEMP: "); Serial.print(Gettemp()); Serial.print(" FIXED: "); Serial.print(FIXED); Serial.print(" STOP: "); Serial.print(STOP); Serial.print(" PWR: "); Serial.println( PWR); Serial.flush(); }