Arduino-DCF77.php 20217 Bytes 04-06-2024 19:41:58
Arduino / Genuino DCF-77 Signal Generator
Time machine without the (difficult to obtain) flux capacitor
WARNING : TRANSMITTING YOUR OWN TIME-SIGNAL MAYST CONFUSE OTHER PEOPLE OR SYSTEMS. HANDLE WITH CARE AND INTELLECT.
✈ Approach
This second version was designed with the scope on easy reproduction. It's portability factor was also optimised. The heart of this
circuit is the Real Time Clock DS1307. It is buffered with a huge capacitor. Based on this clock, the Arduino modulates a 77.5 kHz carrier
(SiTime 8208 running at 9.92 MHz divided by 128). The modulation is done with 2 AND gates. If the modulation signal is high, both
outputs are 5 Vpp and in phase. If the modulation signal is low, the output of the second AND is low and connects the voltage divider to
GND. The amplitude at the tap is therefore lowered to 25 %.
The lowpass at the output is realised by a Sallen-Key Lowpass, designed for about 100 kHz. An antenna does the rest of filtering (design goal).
The Modulation signal, created by the Arduino (blue). We can (not) see the missing bit of the 59 th second. This absence is to allow
synchronizing the seconds of the clock. Top trace (yellow) is the modulated carrier.
Digital Modulation : Not only is the carrier a squarewave (rich in harmonics) but also the modulation signal has a high slew rate
which is likely to cause unwanted spurii.
✈ Similiar Systems just need a different Oscillator
LOCATION |
CARRIER |
OSCILLATOR IN USE |
Frankfurt, DL |
77.5 kHz |
SiT8208AC-81-33E-9.920000 |
Anthorn, UK |
60.0 kHz |
7.68 MHz |
Prangins, HB, offline |
75.0 kHz |
9.60 MHz |
The carrier, after having been divided by 128
✈ Test Sketch for Arduino/Genuino UNO
Double click on code to select ...
/* /////////////////////////////////////////////////////////////
ARDUINO/Genuino (UNO) Test/Demo Sketch for Low Frequency
ASK Synthesizer
Software Version 2.4,
24.04.2017, Alexander C. Frank
//////////////////////////////////////////////////////////////*/
#include <Wire.h>
#include "TimerOne.h"
#include <LiquidCrystal.h>
LiquidCrystal lcd(6,7,2,3,4,5);
byte Data[62] ;
volatile unsigned int SekundenZeiger = 0 ;
volatile boolean ModulationNecessary = false ;
const int Modulateur = 9 ;
const int Illuminateur = 8 ;
const int Correcteur = 10 ;
unsigned int Parity = 0; // EVEN PARITY IS USED HERE
const int SET = A0 ;
const int PLUS = A1 ;
const int MINUS = A2 ;
const int ENTER = A3 ;
// TIME - DS1307
byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
const int DS1307_I2C_ADDRESS = 0x68 ;
// TIME SUBROUTINES
// Convert normal decimal numbers to binary coded decimal
byte decToBcd(byte val) { return( (val/10*16) + (val%10) ); }
// Convert binary coded decimal to normal decimal numbers
byte bcdToDec(byte val) { return( (val/16*10) + (val%16) ); }
// /////////////////////////////////////////////////////////////
// TIME AND CLOCK SUBROUTINES
// /////////////////////////////////////////////////////////////
void SetTimeDS1307(byte second, byte minute, byte hour, byte dayOfWeek, byte
dayOfMonth, byte month, byte year)
{
// sets time and date data to DS3231
Wire.beginTransmission(DS1307_I2C_ADDRESS);
Wire.write(0); // set next input to start at the seconds register
Wire.write(decToBcd(second)); // set seconds
Wire.write(decToBcd(minute)); // set minutes
Wire.write(decToBcd(hour)); // set hours
Wire.write(decToBcd(dayOfWeek)); // set day of week (1=Monday)
Wire.write(decToBcd(dayOfMonth)); // set date (1 to 31)
Wire.write(decToBcd(month)); // set month
Wire.write(decToBcd(year)); // set year (0 to 99)
Wire.endTransmission();
}
void readDS1307time(byte *second,
byte *minute,
byte *hour,
byte *dayOfWeek,
byte *dayOfMonth,
byte *month,
byte *year)
{
Wire.beginTransmission(DS1307_I2C_ADDRESS);
Wire.write(0); // set DS1307 register pointer to 00h
Wire.endTransmission();
Wire.requestFrom(DS1307_I2C_ADDRESS, 7);
// request seven bytes of data from DS3231 starting from register 00h
*second = bcdToDec(Wire.read() & 0x7f);
*minute = bcdToDec(Wire.read());
*hour = bcdToDec(Wire.read() & 0x3f);
*dayOfWeek = bcdToDec(Wire.read());
*dayOfMonth = bcdToDec(Wire.read());
*month = bcdToDec(Wire.read());
*year = bcdToDec(Wire.read());
}
void displayTime()
{
// byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
// retrieve data from DS3231
readDS1307time(&second,&minute,&hour,&dayOfWeek,&dayOfMonth,&month,&year);
// send it to the lcd
lcd.setCursor(0,0);
if (hour<10) lcd.print("0");
lcd.print(hour);lcd.print(":");
if (minute<10) lcd.print("0");
lcd.print(minute);
lcd.print(":");
if (second<10) lcd.print("0");
lcd.print(second);
}
void advance()
{
SekundenZeiger += 1;
if (SekundenZeiger >= 60) SekundenZeiger = 0;
ModulationNecessary = true ;
}
void setup()
{
Timer1.initialize(1000000);
Timer1.attachInterrupt(advance);
lcd.begin(8,1);
Serial.begin(9600);
Wire.begin();
pinMode(Modulateur, OUTPUT);
digitalWrite(Modulateur, LOW);
pinMode(Illuminateur, OUTPUT);
// SAVE ENERGY :-)
digitalWrite(Illuminateur, LOW);
pinMode(Correcteur, OUTPUT);
pinMode(SET, INPUT_PULLUP);
pinMode(PLUS, INPUT_PULLUP);
pinMode(MINUS, INPUT_PULLUP);
pinMode(ENTER, INPUT_PULLUP);
// SET CLOCK - UNCOMMENT IF NECESSARY
// Sec Min Hour DayOfWeek DayOfMonth Month Year
// CHECK THAT TIME EXISTS !!!
// SetTimeDS1307(0, 22, 16, 1, 24, 4, 17);
}
void loop()
{
if (ModulationNecessary)
{
switch (Data[SekundenZeiger])
{
case 0 :
// NO MODULATION NECESSARY, IT's THE 59th SECOND
// READ THE TIME AND CALCULATE ARRAY FOR NEXT MINUTE
ModulationNecessary = false ;
readDS1307time(&second,&minute,&hour,&dayOfWeek,&dayOfMonth,&month,&year);
// 0 : UNMODULATED
// 1 : 100 ms = LOGIC ZERO
// 2 : 200 ms = LOGIC ONE
Data[0] = 1 ; // ALWAYS
Data[1] = 1 ; // WEATHER
Data[2] = 1 ; // WEATHER
Data[3] = 1 ; // WEATHER
Data[4] = 1 ; // WEATHER
Data[5] = 1 ; // WEATHER
Data[6] = 1 ; // WEATHER
Data[7] = 1 ; // WEATHER
Data[8] = 1 ; // WEATHER
Data[9] = 1 ; // WEATHER
Data[10] = 1 ; // WEATHER
Data[11] = 1 ; // WEATHER
Data[12] = 1 ; // WEATHER
Data[13] = 1 ; // WEATHER
Data[14] = 1 ; // WEATHER
Data[15] = 1 ; // ANTENNA (NORMAL)
Data[16] = 1 ; // CHANGE SUMMER/WINTERTIME = NO
Data[17] = 2 ; // 0 = MEZ, 1 = MESZ
Data[18] = 1 ; // 0 = MESZ, 1 = MEZ
Data[19] = 1 ; // 0 = NO SECOND TO BE ADDED
Data[20] = 2 ; // ALWAYS = 1, START OF DATA
Parity = 0 ;
Data[27] = 1 ; // MINUTE 40
if (minute >39) { Data[27] = 2 ; Parity += 1; minute -= 40 ; }
Data[26] = 1 ; // MINUTE 20
if (minute >19) { Data[26] = 2 ; Parity += 1; minute -= 20 ; }
Data[25] = 1 ; // MINUTE 10
if (minute >9) { Data[25] = 2 ; Parity += 1; minute -= 10 ; }
Data[24] = 1 ; // MINUTE 8
if (minute >7) { Data[24] = 2 ; Parity += 1; minute -= 8 ; }
Data[23] = 1 ; // MINUTE 4
if (minute >3) { Data[23] = 2 ; Parity += 1; minute -= 4 ; }
Data[22] = 1 ; // MINUTE 2
if (minute >1) {Data[22] = 2 ;Parity += 1;minute -= 2 ;}
Data[21] = 1 ; // MINUTE 1
if (minute == 1) { Data[21] = 2 ; Parity += 1; }
Data[28] = 1 ; // PARITY BIT MINUTE
if ((Parity & 0x01) > 0) { Data[28] = 2 ; }
Parity = 0;
Data[34] = 1 ; // HOUR 20
if (hour > 19) { Data[34] = 2 ; hour -= 20 ; Parity += 1; }
Data[33] = 1 ; // HOUR 10
if (hour > 9) { Data[33] = 2 ; hour -= 10 ; Parity += 1; }
Data[32] = 1 ; // HOUR 8
if (hour > 7) { Data[32] = 2 ; hour -= 8 ; Parity += 1; }
Data[31] = 1 ; // HOUR 4
if (hour > 3) { Data[31] = 2 ; hour -= 4 ; Parity += 1; }
Data[30] = 1 ; // HOUR 2
if (hour > 1) { Data[30] = 2 ; hour -= 2 ; Parity += 1; }
Data[29] = 1 ; // HOUR 1
if (hour == 1) { Data[29] = 2 ; Parity += 1; }
Data[35] = 1 ; // PARITY BIT HOUR
if ((Parity & 0x01) > 0) { Data[35] = 2 ; }
Parity = 0;
Data[41] = 1 ; // DAY OF THE MONTH 20
if (dayOfMonth > 19) { Data[41] = 2 ; dayOfMonth -= 20 ; Parity += 1; }
Data[40] = 1 ; // DAY OF THE MONTH 10
if (dayOfMonth > 9) { Data[40] = 2 ; dayOfMonth -= 10 ; Parity += 1; }
Data[39] = 1 ; // DAY OF THE MONTH 8
if (dayOfMonth > 7) { Data[39] = 2 ; dayOfMonth -= 8 ; Parity += 1; }
Data[38] = 1 ; // DAY OF THE MONTH 4
if (dayOfMonth > 3) { Data[38] = 2 ; dayOfMonth -= 4 ; Parity += 1; }
Data[37] = 1 ; // DAY OF THE MONTH 2
if (dayOfMonth > 1) { Data[37] = 2 ; dayOfMonth -= 2 ; Parity += 1; }
Data[36] = 1 ; // DAY OF THE MONTH 1
if (dayOfMonth == 1) { Data[36] = 2 ; Parity += 1; }
Data[44] = 1 ; // DAY OF THE WEEK 4
if (dayOfWeek > 3) { Data[44] = 2 ; dayOfWeek -= 4 ; Parity += 1; }
Data[43] = 1 ; // DAY OF THE WEEK 2
if (dayOfWeek > 1) { Data[43] = 2 ; dayOfWeek -= 2 ; Parity += 1; }
Data[42] = 1 ; // DAY OF THE WEEK 1
if (dayOfWeek == 1) { Data[42] = 2 ; Parity += 1; }
Data[49] = 1 ; // MONTH 10
if (month > 9) { Data[49] = 2 ; month -= 10 ; Parity += 1 ; }
Data[48] = 1 ; // MONTH 8
if (month > 7) { Data[48] = 2 ; month -= 8 ; Parity += 1 ; }
Data[47] = 1 ; // MONTH 4
if (month > 3) { Data[47] = 2 ; month -= 4 ; Parity += 1 ; }
Data[46] = 1 ; // MONTH 2
if (month > 1) { Data[46] = 2 ; month -= 2 ; Parity += 1 ; }
Data[45] = 1 ; // MONTH 1
if (month == 1) { Data[45] = 2 ; Parity += 1 ; }
Data[57] = 1 ; // YEAR 80
if (year > 79) { Data[57] = 2 ; year -= 80 ; Parity += 1 ; }
Data[56] = 1 ; // YEAR 40
if (year > 39) { Data[56] = 2 ; year -= 40 ; Parity += 1 ; }
Data[55] = 1 ; // YEAR 20
if (year > 19) { Data[55] = 2 ; year -= 20 ; Parity += 1 ; }
Data[54] = 1 ; // YEAR 10
if (year > 9) { Data[54] = 2 ; year -= 10 ; Parity += 1 ; }
Data[53] = 1 ; // YEAR 8
if (year > 7) { Data[53] = 2 ; year -= 8 ; Parity += 1 ; }
Data[52] = 1 ; // YEAR 4
if (year > 3) { Data[52] = 2 ; year -= 4 ; Parity += 1 ; }
Data[51] = 1 ; // YEAR 2
if (year > 1) { Data[51] = 2 ; year -= 2 ; Parity += 1 ; }
Data[50] = 1 ; // YEAR 1
if (year == 1) { Data[50] = 2 ; Parity += 1 ; }
Data[58] = 1 ; // PARITY BIT DATE
if ((Parity & 0x01) > 0) { Data[58] = 2 ; }
Data[59] = 0 ; // ALMOST ALWAYS
// UPDATE TIME
lcd.setCursor(0,0);
displayTime();
// DEBUG
for (int l=0; l<60; l++)
{
if (Data[l] == 0) Serial.print(" ");
if (Data[l] == 1) Serial.print("0");
if (Data[l] == 2) Serial.print("1");
if (l == 0) Serial.print("-");
if (l == 14) Serial.print("-");
if (l == 20) Serial.print("-");
if (l == 28) Serial.print("-");
if (l == 35) Serial.print("-");
if (l == 41) Serial.print("-");
if (l == 44) Serial.print("-");
if (l == 49) Serial.print("-");
}
Serial.print(" ");
if (hour<10) Serial.print("0");
Serial.print(hour);Serial.print(":");
if (minute<10) Serial.print("0");
Serial.print(minute);
Serial.print(" ");
if (dayOfWeek == 1) Serial.print("Monday, ");
if (dayOfWeek == 2) Serial.print("Tuesday, ");
if (dayOfWeek == 3) Serial.print("Wednesday, ");
if (dayOfWeek == 4) Serial.print("Thursday, ");
if (dayOfWeek == 5) Serial.print("Friday, ");
if (dayOfWeek == 6) Serial.print("Saturday, ");
if (dayOfWeek == 7) Serial.print("Sunday, ");
if (dayOfMonth<10) Serial.print("0");
Serial.print(dayOfMonth);Serial.print(".");
if (month<10) Serial.print("0");
Serial.print(month);Serial.print(".");
Serial.print((year+2000),DEC);Serial.println(" ");
break;
case 1 :
// SHORT PULSE MODULATION: 100 ms
// delay(100); // NOT :-) ALIGN PULSES
digitalWrite(Modulateur, LOW);
digitalWrite(Correcteur, HIGH) ;
delay(100);
digitalWrite(Modulateur, HIGH);
digitalWrite(Correcteur, LOW) ;
ModulationNecessary = false ;
lcd.setCursor(0,0);
displayTime();
break;
case 2 :
// LONG PULSE MODULATION: 200 ms
digitalWrite(Modulateur, LOW);
digitalWrite(Correcteur, HIGH) ;
delay(200);
digitalWrite(Modulateur, HIGH);
digitalWrite(Correcteur, LOW) ;
ModulationNecessary = false ;
lcd.setCursor(0,0);
displayTime();
break;
} // END SWITCH
} // END IF
delay(1);
}
// /////////////////////////////////////////////////////////////
// END OF FILE.
// /////////////////////////////////////////////////////////////
The sketch outputs all data to the serial bus - for easy debugging - e.g. with
this website.
✈ Downloads
✈ Notes
The sketch presented above does not make use of the buttons to set the time. Instead it is set by the "setup".
Usually such "nice to have" functions are declared as homework. If your lab is veeeeery big, you mayst want to use
a power amplifier. The vellemann K8060 (200W amplifier kit) delivers 100 Watts (rms) into a 4 Ω load, up to 200 kHz and seems very promising.
And yes, the time on the lcd display is 1 minute in advance, as the sent bitstream is the time of the next minute.
Some clocks are very smart. Do not send MEZ (= winter time) in June, this mayst cause them to reject the received information.
Some are known to synchronise once a day, at 04:00 (early in the morning :-) so if you want to surprise someone - get up early ...
✈ DISCLAIMER
The board is used to synchronize our experiments (non-radiated, different frequency). This (single) one here has been developped
in my free time and is a fully private project. It is not intended to interfer with other countries time standards. But it does
well serve to attract trainees and build/test clocks - where the original signal is too weak.
✈ Share your thoughts
The webmaster does not read these comments regularely. Urgent questions should be send via email.
Ads or links to completely uncorrelated things will be removed.
Your Browser says that you allow tracking. Mayst we suggest that you check that DNT thing ?