From c42f1d883bbd9a026f5c1e1a6a6daa3e0d0f2ea1 Mon Sep 17 00:00:00 2001 From: Jason Milldrum Date: Sun, 17 Apr 2016 17:05:56 -0700 Subject: [PATCH] --- Si5351_WSPR.ino | 195 ++++++++++++++++++++++++++++++++++++++++++++++++ ntpserial.py | 20 +++++ 2 files changed, 215 insertions(+) create mode 100644 Si5351_WSPR.ino create mode 100644 ntpserial.py diff --git a/Si5351_WSPR.ino b/Si5351_WSPR.ino new file mode 100644 index 0000000..056aa21 --- /dev/null +++ b/Si5351_WSPR.ino @@ -0,0 +1,195 @@ +// Si5351_WSPR +// +// Simple WSPR beacon for Arduino Uno, with the Etherkit Si5351A Breakout +// Board, by Jason Milldrum NT7S. +// +// Original code based on Feld Hell beacon for Arduino by Mark +// Vandewettering K6HX, adapted for the Si5351A by Robert +// Liesenfeld AK6L . Timer setup +// code by Thomas Knutsen LA3PNA. +// +// Time code adapted from the TimeSerial.ino example from the Time library. + +// Hardware Requirements +// --------------------- +// This firmware must be run on an Arduino Zero capable microcontroller +// +// Required Libraries +// ------------------ +// Etherkit Si5351 (Library Manager) +// Etherkit JTEncode (Library Manager) +// Time (Library Manager) +// Wire (Arduino Standard Library) +// +// License +// ------- +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject +// to the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +// ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF +// CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +#include +#include +#include +#include + +#include "Wire.h" + +#define TONE_SPACING 146 // ~1.46 Hz +#define SUBMODE_A 10672 // CTC value for WSPR +#define SYMBOL_COUNT WSPR_SYMBOL_COUNT +#define CORRECTION 0 // Change this for your ref osc + +#define TIME_HEADER "T" // Header tag for serial time sync message +#define TIME_REQUEST 7 // ASCII bell character requests a time sync message + +#define TX_LED_PIN 12 +#define SYNC_LED_PIN 13 + + +// Global variables +Si5351 si5351; +JTEncode jtencode; +unsigned long freq = 10140200UL; // Change this +char call[7] = "N0CALL"; // Change this +char loc[5] = "AB12"; // Change this +uint8_t dbm = 10; +uint8_t tx_buffer[SYMBOL_COUNT]; + +// Global variables used in ISRs +volatile bool proceed = false; + +// Timer interrupt vector. This toggles the variable we use to gate +// each column of output to ensure accurate timing. Called whenever +// Timer1 hits the count set below in setup(). +ISR(TIMER1_COMPA_vect) +{ + proceed = true; +} + +// Loop through the string, transmitting one character at a time. +void encode() +{ + uint8_t i; + + jtencode.wspr_encode(call, loc, dbm, tx_buffer); + + // Reset the tone to 0 and turn on the output + si5351.set_clock_pwr(SI5351_CLK0, 1); + digitalWrite(TX_LED_PIN, HIGH); + + // Now do the rest of the message + for(i = 0; i < SYMBOL_COUNT; i++) + { + si5351.set_freq((freq * 100) + (tx_buffer[i] * TONE_SPACING), 0, SI5351_CLK0); + proceed = false; + while(!proceed); + } + + // Turn off the output + si5351.set_clock_pwr(SI5351_CLK0, 0); + digitalWrite(TX_LED_PIN, LOW); +} + +void processSyncMessage() +{ + unsigned long pctime; + const unsigned long DEFAULT_TIME = 1357041600; // Jan 1 2013 + + if(Serial.find(TIME_HEADER)) + { + pctime = Serial.parseInt(); + if( pctime >= DEFAULT_TIME) + { // check the integer is a valid time (greater than Jan 1 2013) + setTime(pctime); // Sync Arduino clock to the time received on the serial port + } + } +} + +time_t requestSync() +{ + Serial.write(TIME_REQUEST); + return 0; // the time will be sent later in response to serial mesg +} + +void setup() +{ + // Use the Arduino's on-board LED as a keying indicator. + pinMode(TX_LED_PIN, OUTPUT); + pinMode(SYNC_LED_PIN, OUTPUT); + + digitalWrite(TX_LED_PIN, LOW); + digitalWrite(SYNC_LED_PIN, LOW); + Serial.begin(9600); + + // Set time sync provider + setSyncProvider(requestSync); //set function to call when sync required + + // Initialize the Si5351 + // Change the 2nd parameter in init if using a ref osc other + // than 25 MHz + si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0); + + // Set CLK0 output + si5351.set_correction(CORRECTION); + si5351.set_freq(freq * 100, 0, SI5351_CLK0); + si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA); // Set for max power + si5351.set_clock_pwr(SI5351_CLK0, 0); // Disable the clock initially + + // Set up Timer1 for interrupts every symbol period. + noInterrupts(); // Turn off interrupts. + TCCR1A = 0; // Set entire TCCR1A register to 0; disconnects + // interrupt output pins, sets normal waveform + // mode. We're just using Timer1 as a counter. + TCNT1 = 0; // Initialize counter value to 0. + TCCR1B = (1 << CS12) | // Set CS12 and CS10 bit to set prescale + (1 << CS10) | // to /1024 + (1 << WGM12); // turn on CTC + // which gives, 64 us ticks + TIMSK1 = (1 << OCIE1A); // Enable timer compare interrupt. + OCR1A = SUBMODE_A; // Set up interrupt trigger count; + interrupts(); // Re-enable interrupts. +} + +void loop() +{ + if(Serial.available()) + { + processSyncMessage(); + } + + if(timeStatus() == timeSet) + { + digitalWrite(SYNC_LED_PIN, HIGH); // LED on if synced + } + else + { + digitalWrite(SYNC_LED_PIN, LOW); // LED off if needs refresh + } + + // Trigger every 10th minute + // WSPR should start on the 1st second of the minute, but there's a slight delay + // in this code because it is limited to 1 second resolution. + if(timeStatus() == timeSet && minute() % 10 == 0 && second() == 0) + { + encode(); + delay(1000); + } + + //delay(100); +} \ No newline at end of file diff --git a/ntpserial.py b/ntpserial.py new file mode 100644 index 0000000..10fc80f --- /dev/null +++ b/ntpserial.py @@ -0,0 +1,20 @@ +import serial +import sys +import time + +# Arduino serial dev paramaters +DEVICE = '/dev/ttyUSB0' # Change this as necessary +BAUD = 9600 + +# Open serial port +try: + ser = serial.Serial(port=DEVICE, baudrate=BAUD, timeout=1, writeTimeout=1) +except: + print('Cannot open serial port') + sys.exit(0) + +#Wait for ASCII bell, then send the Unix time string +while True: + ser_in = ser.read() + if('\a' in ser_in): + ser.write('T' + str(int(time.time()))) \ No newline at end of file