There are four possible ways as far as I can see, get time from your computer if you are using a computer program and you have accurately set your computer's time; get time from a GPS receiver or a DFC77 receiver or a Real Time Clock chip which has been set correctly.
RTC solution
So far I have looked at only the RTC solution using an IC the DS1307, one of the simplest RTCs, but not one of the most accurate.
DS1307
This is a module from Adafruit:
It has the DS1307 IC, a couple of I2C bus pull-up resistors, the XTAL at 32768Hz and a back-up battery to maintain the circuit in operation when the +5V supply is off. The connections are
GND - ground
5V - +5V supply
SDA & SCL - the I2C bus
SQW - a pulse output which can be programmed to be 1Hz, 4.096kHz, 8.192kHz or 32.768kHz, HIGH or LOW output
Software
There seems to be a multitude of libraries for the RTC on the web, notably RTClib from Adafruit. But I believe most are not necessary to read the time from the chip as its operation is quite simple. For setting the time there is a clever program from Adafruit to set the initial time - see their web site. This only need be run once, or as frequently as your clock needs calibrating, it transfers your computer time to the DS1307).
DS1307 registers
The DS1307 has its main I2C address at 0x68h, this allows you to address 7 registers. The time is available in these registers in BCD (binary coded decimal) format (e.g. 15 = 0001 1001).
0x00 Seconds, b7 = 1 is halt oscillator, so mask this if writing time to the IC 0x01 Minutes 0x02 Hours, b6 = HIGH = 12, LOW = 24. In 12 mode, b6 reads AM = LOW/PM = HIGH 0x04 Date 0x05 Month 0x06 Year
A further register can be addressed at 0x07h, this is a control register like this
b0 RS0 set freq output on SQWE b1 RS1 “ - “ b2&3 - b4 SQWE/OUT, square wave SQW enable, set SQWE = HIGH b5&6 - b7 OUT output level of pin OUT = 1, HIGH, out = 0 LOW RS0 RS1 SQWE 0 0 1Hz 0 1 4kHz (4096Hz) 1 0 8kHz (8092Hz) 1 1 32kHz (32768Hz)This control register is set to 0x00h (0000 0000b) on power up, it should not need to be set for normal use.
Here is a small example of how to read the time, and convert it from BCD to decimal and display it on an LCD.
Code
#include "Wire.h" #include "RTClib.h" #include "LiquidCrystal_I2C.h" // LCD #define LCDADDR 0x27 #define LCDCOLS 16 #define LCDROWS 2 #define RTCADDR 0x68 // LCD object LiquidCrystal_I2C lcd(LCDADDR, LCDCOLS, LCDROWS); RTC_DS1307 rtc; byte sec, mts, hr, wkday, mthday, mth, yr; void setup() { // init LCD & backlight on lcd.init(); lcd.backlight(); // init time from computer rtc.adjust(DateTime(__DATE__, __TIME__)); } void loop() { // read registers and load bytes with time getTime(); dispTime(4, 1); } void getTime() { // Reset the register pointer Wire.beginTransmission(RTCADDR); byte zero = 0x00; Wire.write(zero); Wire.endTransmission(); // request 7 bytes from the RTC address Wire.requestFrom(RTCADDR, 7); // get the time data sec = bcdToDec(Wire.read()); mts = bcdToDec(Wire.read()); hr = bcdToDec(Wire.read() & 0b111111); //24 hour time wkday= bcdToDec(Wire.read()); //0-6 -> Sunday - Saturday mthday = bcdToDec(Wire.read()); mth = bcdToDec(Wire.read()); yr = bcdToDec(Wire.read()); } // Convert binary coded decimal to normal decimal numbers byte bcdToDec(byte val) { return ( (val/16*10) + (val%16) ); } // display time at col, row void dispTime(byte c, byte r) { lcd.setCursor(c, r); if(hr < 10) lcd.print("0"); lcd.print(hr); lcd.print(":"); if(mts < 10) lcd.print("0"); lcd.print(mts); lcd.print(":"); if(sec < 10) lcd.print("0"); lcd.print(sec); }
The main steps are to reset the internal address pointer to point to the first register (reg0), then to sequentially read the registers to get the data, and convert it to decimal.
Following this idea I think that this code would work to trigger a WSPR transmission at an even minute.
Code
// Universal_WSPR_RTC transmits WSPR // use WSPR_Symbol_Generator sketch to make symbol tone table // here it is hard coded for msgtxt " M6KWH IO92 20", note leading space, 3rd char must be number // insert symbols and msgtxt below // step in 50Hz steps, giving 5 frequencies per band // output on CLK0, Universal VFO shield "VFO" pin // Interface by I2C bus to RTC DS1307 // DS1307 must be intitialised with time before use, button selects repeat time 2, 4, 6, 8 min // CONNECTIONS // RTC DS1307 // SCL = A5 // SDA = A4 // I2C address 0x68 // ----- // DDS I2C SI5351 // SCL = A5 // SDA = A4 // I2C address 0x60 // ------ // display I2C LCD 16 * 2 // o A5 SCL (y) // o A4 SDA (or) // o +5 (r) // o GND (bwn) // I2C address 0x27 // ----- // encoder KY-040 // o D2 DT (y) // o D3 CLK (g) // o D4 SW (or) // o +5 (r) // o GND (bwn) // ----- // libraries #include "Wire.h" #include "si5351.h" #include "LiquidCrystal_I2C.h" #include "Rotary.h" #include "TimerOne.h" // RTC I2C address #define RTCADDR 0x68 // LCD #define LCDADDR 0x27 #define LCDCOLS 16 #define LCDROWS 2 // rotary Encoder pins 2 & 3 (DT & CLK), button 4 (SW) #define DT 2 #define CLK 3 #define SW 4 // receive & transmit enable output pins #define RX 13 #define TX 12 // dds object Si5351 dds; // LCD object LiquidCrystal_I2C lcd(LCDADDR, LCDCOLS, LCDROWS); // rotary sncoder object Rotary rot = Rotary(DT, CLK); uint32_t freq; uint8_t tone_ptr = 0; // Pointer to the current symbol volatile uint8_t next_tone = 0; // Incremented by the ISR uint8_t next_tone2 = 0; // Local store, to compare against. uint8_t step_tone = 0; // Flag used to signify we need to move to the next symbol uint8_t b; // band 0 - 26 // band frequencies (cHz) uint32_t band[15] = { 704000000, 704005000, 704010000, 704015000, 704020000, 1014010000, 1014015000, 1014020000, 1014025000, 1014030000, 1409700000, 1409705000, 1409710000, 1409715000, 1409720000 }; // 40, 30, 20m // ====================== insert your message text & symbol char msgtxt[] = {" M6KWH IO92 20 "}; // insert symbols - use WSSPR Symbol Generator program to create uint8_t msg[162] = { 3, 1, 2, 0, 0, 0, 2, 2, 1, 2, 0, 2, 3, 1, 3, 0, 2, 2, 1, 2, 0, 3, 0, 1, 3, 1, 1, 0, 2, 0, 0, 2, 0, 0, 1, 0, 0, 1, 0, 1, 2, 0, 0, 0, 0, 2, 1, 2, 1, 3, 0, 0, 1, 3, 0, 1, 2, 0, 0, 1, 3, 2, 1, 0, 2, 2, 0, 3, 3, 2, 3, 2, 3, 0, 3, 0, 3, 2, 0, 3, 2, 0, 1, 2, 1, 3, 0, 0, 0, 3, 1, 0, 1, 0, 1, 0, 2, 2, 3, 0, 0, 0, 0, 0, 1, 0, 2, 1, 0, 2, 1, 3, 1, 2, 1, 1, 0, 0, 3, 3, 2, 1, 2, 0, 0, 1, 1, 1, 2, 2, 2, 0, 0, 3, 0, 3, 2, 0, 3, 1, 2, 2, 2, 0, 2, 0, 2, 3, 1, 0, 1, 0, 1, 1, 2, 0, 0, 3, 1, 0, 2, 0 }; // ====================== // tone out at tuned freq + tone (cHz above) uint32_t tones[4] = {0, 146, 293, 440}; // RTC time sec & mins, sec = 1 means no WSPR TX, yr = 0 means RTC not running byte sec, mns, hr; // repeat frequency uint8_t repeat; void setup() { // initialise the wire library for I2C comms Wire.begin(); // init LCD & backlight on lcd.init(); lcd.backlight(); // init dds dds.init(SI5351_CRYSTAL_LOAD_8PF, 0); // default 25MHz XTAL // all CLK disabled dds.output_enable(SI5351_CLK0, 0); dds.output_enable(SI5351_CLK1, 0); dds.output_enable(SI5351_CLK2, 0); // encoder, button, RX & TX pins pinMode(DT, INPUT_PULLUP); pinMode(CLK, INPUT_PULLUP); pinMode(SW, INPUT_PULLUP); pinMode(RX, OUTPUT); pinMode(TX, OUTPUT); digitalWrite(RX, LOW); // RX digitalWrite(TX, HIGH); // no TX b = 7; // band, middle 30m repeat = 2; // init to send every 2 mins dispMsg(0, 0, "WSPR "); dispFreq(5, 0, band[b], 2); // display freq b dispMsg(1, 1, " min"); dispNum(0, 1, repeat); } void loop() { tune(); // tune freq? event(); // set repeat timing 2,4,6,8 min? getRTC(); // read RTC, display time // send WSPR now? if (mns % repeat == 0 && sec == 0) { // display message dispMsg(0, 0, msgtxt); dispMsg(0, 1, " Symbol TX"); // show symbol number & transmit txWspr(b); // transmit on freq b // restore display dispMsg(0, 0, "WSPR "); dispFreq(5, 0, band[b], 2); // display freq dispMsg(1, 1, " min"); dispNum(0, 1, repeat); } } void tune() { unsigned char dir; // tuning direction CW/CCW dir = rot.process(); // read encoder if (dir != DIR_NONE) // turned? { if (dir == DIR_CW && b > 0) b += 1; // increment band freq +/- 1 if (dir == DIR_CCW && b < 15) b -= 1; dispFreq(5, 0, band[b], 2); // display freq } } // set event time 4,6,8,10 min void event() { if (digitalRead(SW) == LOW) { while (!digitalRead(SW)); // wait for release if (repeat == 8) repeat = 2; else repeat += 2; dispMsg(1, 1, " min"); dispNum(0, 1, repeat); } } // transmit msg at freq fTable[b] void txWspr(uint8_t b) { // Start Transmitter digitalWrite(RX, HIGH); digitalWrite(TX, LOW); // Start the timer interrupt, is called every 8192/12000 seconds. Timer1.initialize(682666UL); Timer1.attachInterrupt(wspr_isr); // Transmit! while (1) { // do the increment checking without the interrupts, in case it gets // modified while we are checking. noInterrupts(); if (next_tone > next_tone2) { step_tone = 1; // next_tone has incremented, raise flag next_tone2 = next_tone; } interrupts(); if (step_tone) { // Got a call from the ISR to increment the tone, reset flag step_tone = 0; // We enable the clock here to avoid having it be active before the first tone // is due to be transmitted. dds.output_enable(SI5351_CLK0, 1); // output freq freq = band[b] + tones[msg[tone_ptr]]; freqOut(freq); dispNum(8, 1, tone_ptr); // display tone number tone_ptr++; // next tone // If we're at the end of the symbol array, disable the timer and break. if (tone_ptr == 162) { Timer1.detachInterrupt(); next_tone = 0; next_tone2 = 0; tone_ptr = 0; break; } } } // Disable clock, stop Tx dds.output_enable(SI5351_CLK0, 0); digitalWrite(TX, HIGH); digitalWrite(RX, LOW); } // Timer ISR. void wspr_isr(void) { next_tone += 1; } // frequency (in cHz) for VFO, on CLK0 void freqOut(uint32_t f) { dds.set_freq(f, 0ULL, SI5351_CLK0); // f cHz } // get time from RTC, convert bcd to decimal void getRTC() { // Reset the register pointer Wire.beginTransmission(RTCADDR); byte zero = 0x00; Wire.write(zero); Wire.endTransmission(); // request 1st 7 bytes from the RTC address Wire.requestFrom(RTCADDR, 3); // get the s/m/h time data sec = bcdToDec(Wire.read()); mns = bcdToDec(Wire.read()); hr = bcdToDec(Wire.read() & 0b111111); //24 hour time dispTime(8, 1); } // Convert binary coded decimal to normal decimal numbers byte bcdToDec(byte val) { return ( (val / 16 * 10) + (val % 16) ); } // display char msg at col c, row r void dispMsg(uint8_t c, uint8_t r, char *m) { lcd.setCursor(c, r); lcd.print(m); } // display a number at col c, row r void dispNum(uint8_t c, uint8_t r, uint16_t n) { lcd.setCursor(c, r); lcd.print(n); } // display freq in kHz,col c, row r, d decimal places void dispFreq(uint8_t c, uint8_t r, uint32_t f, uint8_t d) { lcd.setCursor(c, r); // clear last freq display lcd.print((float)f / 100000, d); // convert to float for print function lcd.print("kHz "); // + trailing spaces to clear previous display } // display time at col, row void dispTime(byte c, byte r) { lcd.setCursor(c, r); if(hr < 10) lcd.print("0"); lcd.print(hr); lcd.print(":"); if(mns < 10) lcd.print("0"); lcd.print(mns); lcd.print(":"); if(sec < 10) lcd.print("0"); lcd.print(sec); }
I have not verified this actually transmits yet, when I build the Concept stack (RTC + VFO) I will let you know