From 22576b34faf00aceddbc34d253e268b5a9d4bd17 Mon Sep 17 00:00:00 2001 From: Harry <30804618+HarrydeBug@users.noreply.github.com> Date: Thu, 12 Jul 2018 21:49:23 +0200 Subject: [PATCH] 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. --- RFout1MHzV1_06/RFout1MHzV1_06.ino | 411 ++++++++++++++++++++++++++++++ 1 file changed, 411 insertions(+) create mode 100644 RFout1MHzV1_06/RFout1MHzV1_06.ino diff --git a/RFout1MHzV1_06/RFout1MHzV1_06.ino b/RFout1MHzV1_06/RFout1MHzV1_06.ino new file mode 100644 index 0000000..50beaea --- /dev/null +++ b/RFout1MHzV1_06/RFout1MHzV1_06.ino @@ -0,0 +1,411 @@ +//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 +#include +#include +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 +#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; + } + + //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 (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 + { + 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(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("Frequency was saved")); + } + else if(c == 'H' || c == 'h') { + HelpText (); + } + else if(c == 'F' || c == 'f') { + Serial.print(F("Frequency?")); + cmdstate = 1; freq = 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; + } + } +} + + +//-------------------------- 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 +}; + +#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)); + 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; +} + + +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(freq) ; ++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.put(0, freq); //Save the Freqency to EEPROM at address0 + CRCFromEEPROM=GetEEPROM_CRC (); //Calculate CRC on the saved data + EEPROM.put(sizeof(freq), CRCFromEEPROM); //Save the CRC after the data +} + + +bool LoadFromEPROM (void) +{ +unsigned long CRCFromEEPROM,CalculatedCRC; + + EEPROM.get(0, freq); //Load the Frequency from EEPROM address 0 + EEPROM.get(sizeof(freq), CRCFromEEPROM); //Load the CRC value that is stored in EEPROM + CalculatedCRC=GetEEPROM_CRC(); //Calculate the CRC of the Frequency + 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(" : 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(""); + }