Compare commits

..

16 commits

Author SHA1 Message Date
Jeff Epler
3b81e8348d Only handle most characters in cmdstate 0
Otherwise, "T1s" didn't set time (period) of 1 second, because the "s"
was interpreted to mean "save".
2019-03-09 13:57:26 -06:00
5bb9409426 offsets: a small note 2019-03-09 13:57:26 -06:00
541e313bca Allow setting period in addition to frequency
Some values can be set by period which can't be set by frequency (and
vice versa).  For instance, 6.25Hz (cannot be set by frequency) is
160000us (can be set by period); 300Hz (can be set by frequency) is
3333.33...us (can't be set by period).
2019-03-09 13:57:26 -06:00
815d006787 eeprom_data: introduce a structure to load/save settings to eeprom 2019-03-09 13:57:14 -06:00
Harry
f1acb908c0
Update README.md 2018-09-26 00:38:37 +02:00
Harry
5d09726d1a
Hardware Schema 2018-08-14 20:42:01 +02:00
Harry
22576b34fa
Added serial command to save startup frequency.
Added a serial command "S" to save current frequency as startup frequency.
If no startup frequency is found in EEPROM at boot time the default value of 1MHz will be used.
Introduced "fixstate" state machine to keep traxk of if fix is accuired or not, this makes the serial output quiter.
Added a "H" serial command to print Help text.
2018-07-12 21:49:23 +02:00
Harry
7b19239c44
Merge pull request #3 from jepler/passthrough-and-runtime-frequency
[RFC] Passthrough and runtime frequency
2018-07-09 17:57:00 +02:00
Jeff Epler
c86e552d7f Allow a jumper to enable booting directly to passthrough mode
Connect the SDA/SCL pins with a jumper or low value (e.g., 1K)
resistor to enable passthrough mode.  Disconnect the jumper to return
to normal mode.  These are pins 7/8 on the 10-pin connector, so they
are convenient to short with a standard .100 jumper block.
2018-07-06 20:36:44 -05:00
Jeff Epler
16df1ad584 Support setting frequency via serial
To set the frequency, connect via serial and enter e.g.,:
    F1M
followed by enter/return to set the frequency to 1000000Hz (1MHz)

Frequency can be set as a whole number, a whole number of kHz, or a
whole number of MHz.  The commands are case insensitive.  So all
the following set the frequency to 1MHz:
    F1000000
    F1000k
    F1M

No validation is performed and nonsensical things can be requested,
so for example if you enter the nonsensical "F9MMM", you get
"GPS Initialized to output RF at 3800301568"  This specific number
results from overflow of integer arithmetic and is also way out of
spec for the board, since the actual value requested is around 3.8GHz.
2018-07-06 17:21:37 -05:00
Jeff Epler
c241ed1444 Use the same checksum computing code in ACK 2018-07-06 17:21:37 -05:00
5f5ca8af1c Support setting arbitrary frequencies 2018-07-06 17:21:37 -05:00
0284dc50b5 Consistently use F-strings for serial prints 2018-07-06 07:11:54 -05:00
Jeff Epler
70c9003b5e Implement 'switch to passthrough' mode 2018-07-06 07:11:54 -05:00
Zachtek
4806e86c09
Merge pull request #2 from jepler/travis
travis-ci: build the latest version & (if tagged) upload hex to github
2018-07-03 08:23:05 +02:00
8191e4b027 travis-ci: build the latest version & (if tagged) upload hex to github 2018-07-02 22:21:43 -05:00
4 changed files with 663 additions and 91 deletions

35
.travis.yml Normal file
View file

@ -0,0 +1,35 @@
dist: trusty
language: bash
cache:
directories:
- "${HOME}/persist"
addons:
apt:
packages:
- build-essential
- curl
- xvfb
install:
- /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_1.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :1 -ac -screen 0 1280x1024x16
- sleep 3
- export DISPLAY=:1.0
- test -e "${HOME}/persist/arduino.tar.xz" || curl -o "${HOME}/persist/arduino.tar.xz" https://downloads.arduino.cc/arduino-1.8.5-linux64.tar.xz
- mkdir $HOME/arduino_ide
- tar -C $HOME/arduino_ide --strip-components=1 -xaf "${HOME}/persist/arduino.tar.xz"
- export PATH="$HOME/arduino_ide:$PATH"
- arduino --install-library "NeoGPS" || arduino --install-library "NeoGPS"
- mkdir -p artifacts
script:
- arduino --pref "build.path=$(pwd)/artifacts" --pref "arduino:avr:pro:cpu=8MHzatmega328" --pref "compiler.warning_level=all" --preserve-temp-files --verbose-build --verify "$(pwd)/RFout1MHzV1_05/RFout1MHzV1_05.ino"
deploy:
provider: releases
api_key:
secure: eUTHqMZp7e4eASS4GXx/qbmO71MNp1KbaQU9kQeFgXFxbOL60bOQJu7KHJUlLsmpipNZs098XRGpEcIIR6EhQ5A+28F1k8X/QClrScV2sn4BiXoXpHCQHPmhzySaLN1U5u/nTAQvZeUX5E6Or5N9NpvPtADoKKVn00bBuStM/ItH15/10RFUmJ8MMpczTvcFb+LHpFZksGJaQPxDVqXiVnyP+v9dnASdGWtqWPMUt6IaVhH13lE0eoY6AyOYozINPzsORb8JLOC8a3m7v5o1I9hlhpr3/oWhdqy0eDu+5QAOtBmZi7udr0gutnHlq/AkQkLFZvHZotRgMMqCBXQm3OAubDoxZ1aHX8KzKvjnFLvCjX8+gLDiDkiF3RQMJ21yIl1x+C6Dy0FAb/OvkWPQW1TqZPSdoMeNv6L/M4r9OdMHxqx38DuewhLkGfnVpGffo1PmK12uJ9z3qWYW2X/d5VyLzJgdU1tCOc9VjKT5QquZS5Qcy+gbIkbuLmWISN2TWZ4wPnbsm/OX8hfnODg4MbZhXDJoXpWXXDy3jVaiWqUji4MQmxBeIeWZ/vkygXhL0ez506qxC6meZedDcuqC87JhuSqFF/ou/IybfOpoOip+ovaWFwddkkm/RvOIyAKPnjyFD17e8IGzF0cYJ6mhYH3fOVwqe83+8rpJbrbWrWw=
file_glob: true
file: "artifacts/*.ino.hex"
on:
repo: jepler/1009-GPS_Derived_RF_Generator
tags: true
skip_cleanup: true

View file

@ -1,3 +1,3 @@
# 1009-GPS_Derived_RF_Generator
Arduino code for the ZachTek product "GPS Referenced RF generator"
See https://www.tindie.com/products/11033/
Arduino code for GPS Referenced RF generator

View file

@ -14,12 +14,25 @@ SoftwareSerial gpsPort(2, 3); // RX, TX
#define LEDIndicator1 5 //LED indicator for GPS Lock on pin A3
#define FIXOut 4 //Pin Out at IDC. Indicator for GPS Lock on pin 4
#define LDO_Enable A3 //GPS Voltage regulator Enable on pin A3
boolean GPSOK;
const char softwareversion[] = "1.05" ; //Version of this program, sent to serialport at startup
boolean GPSOK, passthrough;
int state; // serial command state machine
uint32_t freq; // Frequency in Hz commanded via serial
#define softwareversion "1.05" //Version of this program, sent to serialport at startup
NMEAGPS gps; // This parses the GPS characters
gps_fix fix; // This holds on to the latest values
constexpr auto MHz = 1000000lu;
constexpr auto KHz = 1000lu;
constexpr auto Hz = 1lu;
// This is a little bit arcane but it just lets you keep the user string
// and the actual value in synch automatically
#define SPEED 1
#define UNITS MHz
#define STRINGIFY1(x) #x
#define STRINGIFY(x) STRINGIFY1(x)
#define SPEED_STR STRINGIFY(SPEED) STRINGIFY(UNITS)
#define SPEED_ARG (UINT32_C(SPEED) * UNITS)
//-------------------------- SETUP -----------------------
@ -27,9 +40,18 @@ void setup()
{
Serial.begin(9600);
while (!Serial);
Serial.println("");
Serial.print(F("Zachtek GPS referenced RF, Software version: "));
Serial.println(softwareversion);
Serial.println(F(""));
Serial.println(F("Zachtek GPS referenced RF, Software version: " softwareversion));
if(strapped_for_passthrough()) {
pinMode(LDO_Enable, OUTPUT); // Set Voltage Regulator Enable pin as output.
digitalWrite(LDO_Enable, HIGH); //Turn on 3.1V Power supply for the Ublox GPS module
gpsPort.begin(9600);
passthrough = 1;
Serial.println(F("Strapped for passthrough. Disconnect jumper between SDA/SCL for normal operation"));
return;
}
pinMode(LDO_Enable, OUTPUT); // Set Voltage Regulator Enable pin as output.
@ -58,15 +80,15 @@ void setup()
delay(500);//Wait for GPSmodule to complete it's power on.
//Program GPS to output RF
if (setGPS_OutputFreq1MHz()) {
Serial.println ("GPS Initialized to output RF at 1MHz");
if (setGPS_OutputFreq(SPEED_ARG)) {
Serial.println ("GPS Initialized to output RF at " SPEED_STR);
Serial.println ("Initialization is complete.");
Serial.println ("");
GPSOK = true;
}
else
{
Serial.println ("Error! Could not program GPS!");
Serial.println (F("Error! Could not program GPS!"));
GPSOK = false;
}
}
@ -74,11 +96,59 @@ void setup()
//--------------------------
// Handle serial commands
void handle_serial() {
int c = Serial.read();
if(c == 'P') {
Serial.println(F("Entering passthrough mode -- reset microcontroller to return to normal mode\n"));
passthrough = true;
}
else if(c == 'F' || c == 'f') {
Serial.print(F("Frequency?"));
state = 1; freq = 0;
}
else if(state == 1) {
if(c >= '0' && c <= '9') {
Serial.write(c);
freq = freq * 10 + c - '0';
} else if (c == 'M' || c == 'm') {
Serial.write(c);
freq *= 1000000lu;
} else if (c == 'K' || c == 'k') {
Serial.write(c);
freq *= 1000lu;
} else if (c == '\n' || c == '\r') {
Serial.println(F(""));
if (setGPS_OutputFreq(freq)) {
Serial.print ("GPS Initialized to output RF at " );
Serial.println (freq);
Serial.println ("Initialization is complete.");
Serial.println ("");
GPSOK = true;
}
else
{
Serial.println (F("Error! Could not program GPS!"));
GPSOK = false;
}
state = 0;
}
}
}
//-------------------------- Main loop -----------------------
void loop()
{
if(passthrough) {
if(gpsPort.available()) Serial.write(gpsPort.read());
if(Serial.available()) gpsPort.write(Serial.read());
return;
}
if(Serial.available()) {
handle_serial();
}
if(state != 0) return;
while (gps.available( gpsPort )) {
fix = gps.read();
if (fix.valid.location && fix.valid.date && fix.valid.time)
@ -108,81 +178,68 @@ void loop()
//--------------------------
bool setGPS_OutputFreq100kHz()
{
int gps_set_sucess = 0;
uint8_t setOutputFreq[] = {
0xB5, 0x62, 0x06, 0x31, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0xA0, 0x86, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
0x00, 0x00, 0xEF, 0x00, 0x00, 0x00, 0x20, 0x1B
};
// For details of the UBX protocol, see
// https://www.u-blox.com/sites/default/files/products/documents/u-blox7-V14_ReceiverDescriptionProtocolSpec_%28GPS.G7-SW-12001%29_Public.pdf
// Documentation of this packet is under the heading CFG-TP5 (35.19.2) in the current documentation.
uint8_t setOutputFreq[] = {
0xB5, 0x62, 0x06, 0x31, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0xA0, 0x86, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
0x00, 0x00, 0xEF, 0x00, 0x00, 0x00, 0x20, 0x1B
};
#define OFFSET_FREQUENCY_LOCKED (18)
#define OFFSET_CKSUM (38)
// Note: Normally payload_start is 2 bytes past the start of buf, because the first two bytes
// are the message type and are not checksummed.
void ubx_compute_checksum(uint8_t *payload_start, uint8_t *payload_end, uint8_t *cksum) {
uint8_t ck_a=0, ck_b=0;
for(const uint8_t *p = payload_start; p != payload_end; p++)
{
ck_a += *p;
ck_b += ck_a;
}
cksum[0] = ck_a;
cksum[1] = ck_b;
}
bool setGPS_OutputFreq(uint32_t freq) {
for(int i=0; i<4; i++) {
setOutputFreq[OFFSET_FREQUENCY_LOCKED+i] = freq & 0xff;
freq >>= 8;
}
ubx_compute_checksum(setOutputFreq+2, setOutputFreq+38, setOutputFreq+38);
sendUBX(setOutputFreq, sizeof(setOutputFreq) / sizeof(uint8_t));
gps_set_sucess = getUBX_ACK(setOutputFreq);
//Serial.println("Set output Freq Done");
bool gps_set_sucess = getUBX_ACK(setOutputFreq);
// Serial.println(F("Set output Freq Done"));
return gps_set_sucess;
}
bool setGPS_OutputFreq1Kz()
{
return setGPS_OutputFreq(1*KHz);
}
bool setGPS_OutputFreq1MHz()
{
int gps_set_sucess = 0;
uint8_t setOutputFreq[] = {
0xB5, 0x62, 0x06, 0x31, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x40, 0x42, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
0x00, 0x00, 0xEF, 0x00, 0x00, 0x00, 0x8A, 0x8B
};
sendUBX(setOutputFreq, sizeof(setOutputFreq) / sizeof(uint8_t));
gps_set_sucess = getUBX_ACK(setOutputFreq);
//Serial.println("Set output Freq Done");
return gps_set_sucess;
return setGPS_OutputFreq(1*MHz);
}
bool setGPS_OutputFreq2MHz()
{
int gps_set_sucess = 0;
uint8_t setOutputFreq[] = {
0xB5, 0x62, 0x06, 0x31, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x80, 0x84, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
0x00, 0x00, 0xEF, 0x00, 0x00, 0x00, 0x1B, 0x7F
};
sendUBX(setOutputFreq, sizeof(setOutputFreq) / sizeof(uint8_t));
gps_set_sucess = getUBX_ACK(setOutputFreq);
//Serial.println("Set output Freq Done");
return gps_set_sucess;
return setGPS_OutputFreq(2*MHz);
}
bool setGPS_OutputFreq4MHz()
{
int gps_set_sucess = 0;
uint8_t setOutputFreq[] = {
0xB5, 0x62, 0x06, 0x31, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x00, 0x09, 0x3D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
0x00, 0x00, 0xEF, 0x00, 0x00, 0x00, 0x3F, 0x8C
};
sendUBX(setOutputFreq, sizeof(setOutputFreq) / sizeof(uint8_t));
gps_set_sucess = getUBX_ACK(setOutputFreq);
//Serial.println("Set output Freq Done");
return gps_set_sucess;
return setGPS_OutputFreq(4*MHz);
}
//8MHz is the highest low-jitter frequency possible
bool setGPS_OutputFreq8MHz()
{
int gps_set_sucess = 0;
uint8_t setOutputFreq[] = {
0xB5, 0x62, 0x06, 0x31, 0x20, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x00, 0x12, 0x7A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
0x00, 0x00, 0x6F, 0x00, 0x00, 0x00, 0xD4, 0x28
};
sendUBX(setOutputFreq, sizeof(setOutputFreq) / sizeof(uint8_t));
gps_set_sucess = getUBX_ACK(setOutputFreq);
//Serial.println("Set output Freq Done");
return gps_set_sucess;
return setGPS_OutputFreq(8*MHz);
}
//10 MHz is very jittery. Numbers that can be done with an integer division from 48MHz will produce
@ -190,34 +247,14 @@ bool setGPS_OutputFreq8MHz()
//If 10MHz low jitter is needed then one option is to output 2MHz and then filter out the 5th overtone arriving at 10MHz in that way.
bool setGPS_OutputFreq10MHz()
{
int gps_set_sucess = 0;
uint8_t setOutputFreq[] = {
0xB5, 0x62, 0x06, 0x31, 0x20, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x80, 0x96, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
0x00, 0x00, 0x6F, 0x00, 0x00, 0x00, 0xF6, 0x10
};
sendUBX(setOutputFreq, sizeof(setOutputFreq) / sizeof(uint8_t));
gps_set_sucess = getUBX_ACK(setOutputFreq);
//Serial.println("Set output Freq Done");
return gps_set_sucess;
return setGPS_OutputFreq(10*MHz);
}
//16MHz is above the specs for lUblox Neo-6, only included for experiments.
//This will not produce as clean Square wave.
bool setGPS_OutputFreq16MHz()
{
int gps_set_sucess = 0;
uint8_t setOutputFreq[] = {
0xB5, 0x62, 0x06, 0x31, 0x20, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x00, 0x24, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
0x00, 0x00, 0x6F, 0x00, 0x00, 0x00, 0x60, 0x12
};
sendUBX(setOutputFreq, sizeof(setOutputFreq) / sizeof(uint8_t));
gps_set_sucess = getUBX_ACK(setOutputFreq);
//Serial.println("Set output Freq Done");
return gps_set_sucess;
return setGPS_OutputFreq(16*MHz);
}
@ -249,10 +286,7 @@ boolean getUBX_ACK(uint8_t *MSG) {
ackPacket[9] = 0; // CK_B
// Calculate the checksums
for (uint8_t ubxi = 2; ubxi < 8; ubxi++) {
ackPacket[8] = ackPacket[8] + ackPacket[ubxi];
ackPacket[9] = ackPacket[9] + ackPacket[8];
}
ubx_compute_checksum(ackPacket+2, ackPacket+8, ackPacket+8);
while (1) { // Test for success
if (ackByteID > 9) {
@ -277,4 +311,26 @@ boolean getUBX_ACK(uint8_t *MSG) {
}//else
}//If
}//While
}//getUBX_ACK
}//getUBX_ACK
// If pins 7/8 on the 10-pin header are bridged, this is "strapped for passthrough"
// Detect this by driving one pin high and checking the other pin, then driving low and repeating
// set pins back to inputs before returning
bool strapped_for_passthrough() {
bool result = true;
pinMode(A4, OUTPUT);
digitalWrite(A4, HIGH);
delay(1);
if(!digitalRead(A5)) result = false;
digitalWrite(A4, LOW);
pinMode(A5, INPUT_PULLUP);
delay(1);
if(digitalRead(A5)) result = false;
pinMode(A4, INPUT);
pinMode(A5, INPUT);
return result;
}

View file

@ -0,0 +1,481 @@
//Software for Zachtek "GPS Referenced RF generator 1MHz Out" with a 8MHz Mini Pro Arduno
//This will program an NEO-6 Gps module at startup to output an RF signal when locked.
//The NEO-6 has an internal 48MHz PLL that can be diveded down to any freq below 10MHz.
//Divide to a frequency that is an integer fraction of 48MHz to get the lowest jitter.
//Examples off low jitter freqencies is 1, 2, 4 and 8 MHz
//To compile you need to install the Library "NeoGps by SlashDevin", you can download it using The Library manager in the Arduino IDE
//ZachTec 2017-2018
//The Version of this software is stored in the constant "softwareversion" and is displayed on the Serialport att startup
//For Arduino Pro Mini.
#include <EEPROM.h>
#include <NMEAGPS.h>
#include <SoftwareSerial.h>
SoftwareSerial gpsPort(2, 3); // RX, TX
#define LEDIndicator1 5 //LED indicator for GPS Lock on pin A3
#define FIXOut 4 //Pin Out at IDC. Indicator for GPS Lock on pin 4
#define LDO_Enable A3 //GPS Voltage regulator Enable on pin A3
boolean GPSOK, passthrough;
int cmdstate; // serial command state machine
int fixstate; //GPS Fix state machine 0=Init, 1=wating for fix,2=fix accuired
uint32_t freq; // Frequency in Hz commanded via serial
uint32_t period; // Period in Hz commanded via serial
#define softwareversion "1.06" //Version of this program, sent to serialport at startup
NMEAGPS gps; // This parses the GPS characters
gps_fix fix; // This holds on to the latest values
constexpr auto MHz = 1000000lu;
constexpr auto KHz = 1000lu;
constexpr auto Hz = 1lu;
// This is a little bit arcane but it just lets you keep the user string
// and the actual value in synch automatically
#define SPEED 1
#define UNITS MHz
#define STRINGIFY1(x) #x
#define STRINGIFY(x) STRINGIFY1(x)
#define SPEED_STR STRINGIFY(SPEED) STRINGIFY(UNITS)
#define SPEED_ARG (UINT32_C(SPEED) * UNITS)
//-------------------------- SETUP -----------------------
void setup()
{
Serial.begin(9600);
while (!Serial);
Serial.println(F(""));
Serial.println(F("Zachtek GPS referenced RF, Software version: " softwareversion));
if(strapped_for_passthrough()) {
pinMode(LDO_Enable, OUTPUT); // Set Voltage Regulator Enable pin as output.
digitalWrite(LDO_Enable, HIGH); //Turn on 3.1V Power supply for the Ublox GPS module
gpsPort.begin(9600);
passthrough = 1;
Serial.println(F("Strapped for passthrough. Disconnect jumper between SDA/SCL for normal operation"));
return;
}
pinMode(LDO_Enable, OUTPUT); // Set Voltage Regulator Enable pin as output.
//Blink the Lock led
Serial.println (F("Blinking Lock LED"));
pinMode(LEDIndicator1, OUTPUT); // Set GPS Lock LED pin as output.
for (int i = 0; i <= 17; i++) {
digitalWrite(LEDIndicator1, HIGH); //Turn on Lock LED
delay (60);
digitalWrite(LEDIndicator1, LOW); //Turn off Lock LED
delay (60);
}
//Turn on Power to GPS
digitalWrite(LDO_Enable, HIGH); //Turn on 3.1V Power supply for the Ublox GPS module
Serial.println (F("Turning on Voltage Regulator for GPS module"));
pinMode(FIXOut, OUTPUT); // Set GPS Lock line as output.
digitalWrite(FIXOut, LOW); //Go low on Lock line
//Read startup frequency from EEPROM
if (LoadFromEPROM()==false)
{
Serial.println ("No startup frequency data was found, using 1MHz");
freq = 1000000;
period = 0;
}
//Program GPS
Serial.println (F("Programming GPS"));
delay(500);//Wait for GPSmodule to complete it's power on.
gpsPort.begin(9600);
GPSOK = true;
delay(500);//Wait for GPSmodule to complete it's power on.
//Program GPS to output RF
if (freq && setGPS_OutputFreq(freq)) {
Serial.print (F("GPS Initialized to output RF at "));
Serial.print (freq);
Serial.println (F("Hz"));
Serial.println ("Initialization is complete.");
Serial.println ("");
GPSOK = true;
} else if (period && setGPS_OutputPeriod(period)) {
Serial.print (F("GPS reprogramed to output RF at "));
Serial.print(period);
Serial.print(F("us, approximately "));
Serial.print(1.e6 / period);
Serial.println (F("Hz"));
GPSOK = true;
}
else
{
Serial.println (F("Error! Could not program GPS!"));
GPSOK = false;
}
HelpText ();
fixstate=0; //Initial State for the fixstate that helps only output teh GPS postion once after accuring fix
}
//--------------------------
// Handle serial commands
void handle_serial() {
int c = Serial.read();
if(cmdstate == 0) {
if(c == 'P' || c=='p') {
Serial.println(F("Entering passthrough mode -- reset microcontroller to return to normal mode\n"));
passthrough = true;
}
else if(c == 'S' || c == 's') {
SaveToEPROM ();
Serial.println(F("Time pulse setting was saved"));
}
else if(c == 'H' || c == 'h') {
HelpText ();
}
else if(c == 'F' || c == 'f') {
Serial.print(F("Frequency?"));
cmdstate = 1; freq = period = 0;
}
else if(c == 'T' || c == 't') {
Serial.print(F("Period?"));
cmdstate = 2; freq = period = 0;
}
} else if(cmdstate == 1) {
if(c >= '0' && c <= '9') {
Serial.write(c);
freq = freq * 10 + c - '0';
} else if (c == 'M' || c == 'm') {
Serial.write(c);
freq *= 1000000lu;
} else if (c == 'K' || c == 'k') {
Serial.write(c);
freq *= 1000lu;
} else if (c == '\n' || c == '\r') {
Serial.println(F(""));
if (setGPS_OutputFreq(freq)) {
Serial.print (F("GPS reprogramed to output RF at "));
Serial.print(freq);
Serial.println (F("Hz"));
GPSOK = true;
}
else
{
Serial.println (F("Error! Could not reprogram GPS!"));
GPSOK = false;
}
cmdstate = 0;
}
}
else if(cmdstate == 2) {
if(c >= '0' && c <= '9') {
Serial.write(c);
period = period * 10 + c - '0';
} else if (c == 'S' || c == 's') {
Serial.write(c);
period *= 1000000lu;
} else if (c == 'M' || c == 'm') {
Serial.write(c);
period *= 1000lu;
} else if (c == '\n' || c == '\r') {
Serial.println(F(""));
if (setGPS_OutputPeriod(period)) {
Serial.print (F("GPS reprogramed to output RF at "));
Serial.print(period);
Serial.print(F("us, approximately "));
Serial.print(1.e6 / period);
Serial.println (F("Hz"));
GPSOK = true;
}
else
{
Serial.println (F("Error! Could not reprogram GPS!"));
GPSOK = false;
}
cmdstate = 0;
}
}
}
//-------------------------- Main loop -----------------------
void loop()
{
if(passthrough) {
if(gpsPort.available()) Serial.write(gpsPort.read());
if(Serial.available()) gpsPort.write(Serial.read());
return;
}
if(Serial.available()) {
handle_serial();
}
if(cmdstate != 0) return;
while (gps.available( gpsPort )) {
fix = gps.read();
if (fix.valid.location && fix.valid.date && fix.valid.time )
{
digitalWrite(LEDIndicator1, HIGH); // turn the LED on
digitalWrite(FIXOut, HIGH); // Set Lock Line high
if (fixstate != 2) {
Serial.print( F("Fix accuired, RF turned ON. (Location is: ") );
Serial.print( fix.latitude(), 6 );
Serial.print( ',' );
Serial.print( fix.longitude(), 6 );
Serial.println(")");
fixstate=2;// Fix accuired
}
}
else
{
if (GPSOK) { //If the GPS is connected but not locked then short blink
digitalWrite(LEDIndicator1, HIGH); // turn the LED on
digitalWrite(FIXOut, LOW); // Set Lock Line low
delay(100);
digitalWrite(LEDIndicator1, LOW); // turn the LED off
if (fixstate !=1) {
Serial.println(F("Waiting for GPS location fix, RF turned OFF"));
fixstate=1; //Waiting for fix
}
}
}
}
}
//--------------------------
// For details of the UBX protocol, see
// https://www.u-blox.com/sites/default/files/products/documents/u-blox7-V14_ReceiverDescriptionProtocolSpec_%28GPS.G7-SW-12001%29_Public.pdf
// Documentation of this packet is under the heading CFG-TP5 (35.19.2) in the current documentation.
uint8_t setOutputFreq[] = {
0xB5, 0x62, 0x06, 0x31, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0xA0, 0x86, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
0x00, 0x00, 0xEF, 0x00, 0x00, 0x00, 0x20, 0x1B
};
// Note: these offsets are from the beginning of the packet header, not the payload; as such, they differ by 6 from the payload byte offsets in the PDF
#define OFFSET_FREQUENCY_LOCKED (18)
#define OFFSET_FLAGS (34)
#define OFFSET_CKSUM (38)
// Note: Normally payload_start is 2 bytes past the start of buf, because the first two bytes
// are the message type and are not checksummed.
void ubx_compute_checksum(uint8_t *payload_start, uint8_t *payload_end, uint8_t *cksum) {
uint8_t ck_a=0, ck_b=0;
for(const uint8_t *p = payload_start; p != payload_end; p++)
{
ck_a += *p;
ck_b += ck_a;
}
cksum[0] = ck_a;
cksum[1] = ck_b;
}
bool setGPS_OutputFreq(uint32_t freq) {
setOutputFreq[OFFSET_FLAGS] = 0xef;
for(int i=0; i<4; i++) {
setOutputFreq[OFFSET_FREQUENCY_LOCKED+i] = freq & 0xff;
freq >>= 8;
}
ubx_compute_checksum(setOutputFreq+2, setOutputFreq+38, setOutputFreq+38);
sendUBX(setOutputFreq, sizeof(setOutputFreq) / sizeof(uint8_t));
bool gps_set_sucess = getUBX_ACK(setOutputFreq);
// Serial.println(F("Set output Freq Done"));
return gps_set_sucess;
}
bool setGPS_OutputPeriod(uint32_t period) {
setOutputFreq[OFFSET_FLAGS] = 0xe7;
for(int i=0; i<4; i++) {
setOutputFreq[OFFSET_FREQUENCY_LOCKED+i] = period & 0xff;
period >>= 8;
}
ubx_compute_checksum(setOutputFreq+2, setOutputFreq+38, setOutputFreq+38);
sendUBX(setOutputFreq, sizeof(setOutputFreq) / sizeof(uint8_t));
bool gps_set_sucess = getUBX_ACK(setOutputFreq);
// Serial.println(F("Set output Freq Done"));
return gps_set_sucess;
}
bool setGPS_OutputFreq1Kz()
{
return setGPS_OutputFreq(1*KHz);
}
bool setGPS_OutputFreq1MHz()
{
return setGPS_OutputFreq(1*MHz);
}
bool setGPS_OutputFreq2MHz()
{
return setGPS_OutputFreq(2*MHz);
}
bool setGPS_OutputFreq4MHz()
{
return setGPS_OutputFreq(4*MHz);
}
//8MHz is the highest low-jitter frequency possible
bool setGPS_OutputFreq8MHz()
{
return setGPS_OutputFreq(8*MHz);
}
//10 MHz is very jittery. Numbers that can be done with an integer division from 48MHz will produce
//the lowest jitter so 16 ,12 ,8 ,6 ,4 ,2 and 1 MHz is low jitter but 10MHz is not
//If 10MHz low jitter is needed then one option is to output 2MHz and then filter out the 5th overtone arriving at 10MHz in that way.
bool setGPS_OutputFreq10MHz()
{
return setGPS_OutputFreq(10*MHz);
}
//16MHz is above the specs for lUblox Neo-6, only included for experiments.
//This will not produce as clean Square wave.
bool setGPS_OutputFreq16MHz()
{
return setGPS_OutputFreq(16*MHz);
}
void sendUBX(uint8_t *MSG, uint8_t len) {
gpsPort.flush();
gpsPort.write(0xFF);
_delay_ms(500);
for (int i = 0; i < len; i++) {
gpsPort.write(MSG[i]);
}
}
boolean getUBX_ACK(uint8_t *MSG) {
uint8_t b;
uint8_t ackByteID = 0;
uint8_t ackPacket[10];
unsigned long startTime = millis();
// Construct the expected ACK packet
ackPacket[0] = 0xB5; // header
ackPacket[1] = 0x62; // header
ackPacket[2] = 0x05; // class
ackPacket[3] = 0x01; // id
ackPacket[4] = 0x02; // length
ackPacket[5] = 0x00;
ackPacket[6] = MSG[2]; // ACK class
ackPacket[7] = MSG[3]; // ACK id
ackPacket[8] = 0; // CK_A
ackPacket[9] = 0; // CK_B
// Calculate the checksums
ubx_compute_checksum(ackPacket+2, ackPacket+8, ackPacket+8);
while (1) { // Test for success
if (ackByteID > 9) {
// All packets in order!
return true;
}
// Timeout if no valid response in 3 seconds
if (millis() - startTime > 3000) {
return false;
}
// Make sure data is available to read
if (gpsPort.available()) {
b = gpsPort.read();
// Check that bytes arrive in sequence as per expected ACK packet
if (b == ackPacket[ackByteID]) {
ackByteID++;
}
else {
ackByteID = 0; // Reset and look again, invalid order
}//else
}//If
}//While
}//getUBX_ACK
// If pins 7/8 on the 10-pin header are bridged, this is "strapped for passthrough"
// Detect this by driving one pin high and checking the other pin, then driving low and repeating
// set pins back to inputs before returning
bool strapped_for_passthrough() {
bool result = true;
pinMode(A4, OUTPUT);
digitalWrite(A4, HIGH);
delay(1);
if(!digitalRead(A5)) result = false;
digitalWrite(A4, LOW);
pinMode(A5, INPUT_PULLUP);
delay(1);
if(digitalRead(A5)) result = false;
pinMode(A4, INPUT);
pinMode(A5, INPUT);
return result;
}
struct eeprom_data {
uint32_t value;
};
unsigned long GetEEPROM_CRC(void) {
const unsigned long crc_table[16] = {
0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c,
0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c
};
unsigned long crc = ~0L;
for (int index = 0 ; index < sizeof(eeprom_data) ; ++index) {
crc = crc_table[(crc ^ EEPROM[index]) & 0x0f] ^ (crc >> 4);
crc = crc_table[(crc ^ (EEPROM[index] >> 4)) & 0x0f] ^ (crc >> 4);
crc = ~crc;
}
return crc;
}
void SaveToEPROM ()
{
unsigned long CRCFromEEPROM;
eeprom_data e = {period ? period | 0x80000000lu : freq};
EEPROM.put(0, e); //Save the Freqency to EEPROM at address0
CRCFromEEPROM=GetEEPROM_CRC (); //Calculate CRC on the saved data
EEPROM.put(sizeof(eeprom_data), CRCFromEEPROM); //Save the CRC after the data
}
bool LoadFromEPROM (void)
{
unsigned long CRCFromEEPROM,CalculatedCRC;
eeprom_data e;
EEPROM.get(0, e); //Load the Frequency from EEPROM address 0
EEPROM.get(sizeof(eeprom_data), CRCFromEEPROM); //Load the CRC value that is stored in EEPROM
CalculatedCRC=GetEEPROM_CRC(); //Calculate the CRC of the Frequency
if(e.value & 0x80000000lu) {
period = e.value & 0x7fffffff; freq = 0;
} else {
period = 0; freq = e.value;
}
return (CRCFromEEPROM==CalculatedCRC); //If Stored and Calculated CRC are the same then return true
}
void HelpText () {
Serial.println(F("Type one of the following serial commands to control the GPS Module: "));
Serial.println(F(" : F ... ,(FREQUENCY) sets a new output frequency, value in Hz or use k or M suffix for kHz and MHz"));
Serial.println(F(" : T ... ,(TIME) sets a new output period, value in us or use m or s suffix for milliseconds and seconds"));
Serial.println(F(" : S ,(SAVE) Saves the current frequency to be used as startup frequency"));
Serial.println(F(" : P ,(PASSTHROUGH) Enters passtrough mode, GPS serial data will be passed to and from this Serial port. Reset the module to go back to normal mode"));
Serial.println(F(" : H ,(HELP) prints this information"));
Serial.println("");
}