Showing posts with label gps module. Show all posts
Showing posts with label gps module. Show all posts

Sunday, October 27, 2019

Si5351 GPS disciplined oscillator with NEO7M module

This marks my 500th post in the blog. Not bad, considering most of the posts are projects, that is a lot of soldering.

Carrying on, I had been experimenting with GPS disciplined oscillators, most using a control loop for a 10Mhz crystal oscillator using 10Khz output from a GPS module PPS. I was not happy with results so decided to go on a different approach.
The NEO7M gps module can output multiple frequencies, the problem is how clean the output is, eventually figure out that a 24Mhz output is enough clean to drive the Si5352 module XO input, this module according to specs can take a crystal of 25 or 27, I tried using external 24Mhz as reference and worked brilliant.


The simplified schematic:


There's also an LED to Arduino pin 10 to indicate satellite lock

Since there are multiple outputs on the Si5351 I used another output (CLK0) for a 1Mhz out besides the 10Mhz one (CLK1).

I didn't used an LCD display on my project to keep consumption as low as possible.

 The 10Mhz output and the GPS antenna input are on the back:
All powered by a 9V battery for portable use.

Inside view:


You need to remove the inboard oscillator crystal from the Si5351 module and connect XA pin to the 24Mhz source from the GPS module.


From datasheet


XB is not used.


The lib's for the Si5351, when programing/compiling the code, need to reflect the new 24Mhz clock input.

Library file:
 Si5351/si5351.h

Change from 25Mhz to 24 as bellow:
 #define SI5351_XTAL_FREQ                                                24000000

The 1Mhz output from the Si5351 is not filtered so plenty of harmonics so it can use as marker.




The final 10Mhz output: is filtered trough a low pass filter to clear some noise and this is the result:



Checking the accuracy of my frequency counter (since I know the GPS is correct):

It's counting 10000000.3 Hz so 0.3 Hz off, for a counter with 20 Year of age  (or so) is not bad.



The code:
It basically programs the u-blox 7m to out a 24Mhz signal on PPS output.
It display all those steps on i2c LCD if connected and just for monitoring
lock LED indication is on when enough satelites are in sync, never the less from the boot it starts output the 24Mhz that might not be stable enough before lock/satelites avaiable but still usable.

// programing of the u-blox 7m module from Arduino for timepulse TP-5
// hex was taken from u-center software configuration view
//
//
// plus, flashing LED connected to pin 10 indicates when enough sat's in view
// u-blox module to connect o 3 and 4 for using soft serial of Arduino
// CT2GQV 2019 mixing multiple libs and examples.

#include <SoftwareSerial.h>
#include <TinyGPS.h>
#include "si5351.h"
Si5351 si5351; // i2c 0x60
#include                           
#include  
LiquidCrystal_I2C lcd(0x27,2,1,0,4,5,6,7);

const char UBLOX_INIT[] PROGMEM = {   
 
// the actual programing string, uncoment for the one needed. 10Mhz, 2.5Mhz, 24Mhz or 2Mhz
// any frequency not integer divide of 48Mhz will have some jitter since module reference is 48. Best use is for 24 or 2 Mhz

/*
 // CFG-TP5 1Hz / 10Mhz sync
  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, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x6F, 0x08, 0x00, 0x00, 0x7E, 0xA8,
*/

/*
  // CFG-TP5 1Hz / 10Mhz no sync 50ms cable delay
  0xB5, 0x62, 0x06, 0x31, 0x20, 0x00, 0x00, 0x01, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80, 0x96, 0x98, 0x00,
  0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x6F, 0x00, 0x00, 0x00, 0xA8, 0x08,
*/
/*
  // CFG-TP5 1Hz / 24 Mhz no sync 0ms cable delay
  0xB5, 0x62, 0x06, 0x31, 0x20, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x36, 0x6E, 0x01,
  0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x6F, 0x00, 0x00, 0x00, 0x6D, 0x8D,
*/

 // CFG-TP5 24MHz / 24 Mhz - allways outputing 24Mhz either in sync with GPS or not. LED on D10 will indicate GPS lock.
  0xB5, 0x62, 0x06, 0x31, 0x20, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x6E, 0x01, 0x00, 0x36,
  0x6E, 0x01, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x6F, 0x00, 0x00, 0x00, 0x11, 0xD8,

/*
  // CFG-TP5 1Hz / 2.5Mhz no sync 50ms cable delay
  0xB5, 0x62, 0x06, 0x31, 0x20, 0x00, 0x00, 0x01, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xA0, 0x25, 0x26, 0x00,
  0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x6F, 0x00, 0x00, 0x00, 0xE5, 0x21,
*/

/*
  // CFG-TP5 1Hz / 2 Mhz no sync 50ms cable delay
  0xB5, 0x62, 0x06, 0x31, 0x20, 0x00, 0x00, 0x01, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80, 0x84, 0x1E, 0x00,
  0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x6F, 0x00, 0x00, 0x00, 0x1C, 0x1E,
*/

/* 
  // UBX-CFG-TP5
  0xB5, 0x62, // header
  0x06, 0x31, // time pulse get/set
  0x20,  // lenght 32
  0x00, // tpIdx time pulse selection = 0 = timepulse, 1 = timepulse2  (U1 Char)
  0x00,  // reserved0 U1
  0x01, 0x00, // reserved1 U2
  0x00, 0x32, // antCableDelay ns
  0x00, 0x00, // rf group delay I2
  0x00, 0x90, 0xD0, 0x03, // freqPeriod
  0x00, 0x40, 0x42, 0x0F, // freqPeriodLoc
  0x00, 0xF0, 0x49, 0x02, // pulselenRatio
  0x00, 0x60, 0xAE, 0x0A, // pulselenRatio
  0x00, 0x00, 0x00, 0x00, // userConfigDelay ns
  0x00, 0x77, 0x00, 0x00, // flags - page 135 u-blox 7 Receiver Description Including Protocol Specification V14.pdf
  0x00, 0x48, 0x65,
*/ 

};

int SATLED = 10;  // for showing we have statelites and in sync
int havefix = 0; // to keep track if we have fix for at least 3 seconds
float start_freq = 2000000000;

TinyGPS gps;
// SoftwareSerial ss(4, 3);
SoftwareSerial ss(3, 4);
int sats = 0 ;

void setup()

  lcd.begin(16, 2);                           // LCD set for 16 by 2 display
  lcd.setBacklightPin(3,POSITIVE);            // (BL, BL_POL)
  lcd.setBacklight(HIGH);                     // LCD backlight turned ON
 
  lcd.setCursor(0, 0);                        //
  lcd.print("Booting...");    
  ss.begin(9600);

  pinMode(SATLED, OUTPUT); // to indicate we have enough satelites
  digitalWrite(SATLED, HIGH);
  delay(2000);
  digitalWrite(SATLED, LOW);

   // actual u-blox 7m programing 
   for(int i = 0; i < sizeof(UBLOX_INIT); i++) {                       
    ss.write( pgm_read_byte(UBLOX_INIT+i) );
    delay(5); // simulating a 38400baud pace (or less), otherwise commands are not accepted by the device.
  }
// ends here  
  
  Serial.begin(9600);
  Serial.println("= sent init string to GPS =");
  lcd.setCursor(0, 0); lcd.print("NO DATA   ");
 
   // on the library Si5351/si5351.h chang it to 24Mhz 
   // #define SI5351_XTAL_FREQ                                                25000000
   si5351.set_pll(SI5351_PLL_FIXED, SI5351_PLLA);
   si5351.set_freq(10000000, 0, SI5351_CLK1); // 10.000 Mhz for the ext 10Mhz of counter
   si5351.set_freq( 1000000, 0, SI5351_CLK0); //  1.000 Mhz for the marker

}

void loop()
{
  bool newData = false;
  unsigned long chars;
  unsigned short sentences, failed;

  // For one second we parse GPS data and report some key values
  for (unsigned long start = millis(); millis() - start < 1000;)
  {
    while (ss.available())
    {
      char c = ss.read();
      Serial.write(c); // uncomment this line if you want to see the GPS data flowing
      if (gps.encode(c)) // Did a new valid sentence come in?
        newData = true;
    }
  }

  if (newData)
  {
    float flat, flon;
    unsigned long age;
    gps.f_get_position(&flat, &flon, &age);
    Serial.print("LAT=");
    Serial.print(flat == TinyGPS::GPS_INVALID_F_ANGLE ? 0.0 : flat, 6);
    Serial.print(" LON=");
    Serial.print(flon == TinyGPS::GPS_INVALID_F_ANGLE ? 0.0 : flon, 6);
    Serial.print(" SAT=");
    sats = gps.satellites();
    Serial.println(sats);
    // lcd.setCursor(0, 0); lcd.print("Satelites:");lcd.print(sats);
   
    if (sats >= 3){ // we have sats in view, we should be good
      digitalWrite(SATLED, HIGH); // delay(200);digitalWrite(SATLED, LOW );
      havefix=2;
      havefix++; // increments every time we have fix until 3, if we don't have fix/sats then after 3 cycles it will go zero and we shutt the indicator led.
      if (havefix >= 3)havefix=3; // never goes over 3
      Serial.println(); Serial.print("SAT_HAVE_FIX: "); Serial.println(havefix); 
    };   
  } // end new data
 
  if (havefix >= 1) havefix--; // we had allready 1 fix in the loop
  if (havefix==0) { digitalWrite(SATLED, LOW ); }; // we don't have fix so power off the fix LED 
  Serial.print("MAIN_HAVE_FIX: "); Serial.println(havefix);
 
  gps.stats(&chars, &sentences, &failed);
  if (chars == 0)
    Serial.println("** No characters received from GPS: check wiring **");

}

//=end code==============

I've seen other type of approach for similar problem using just a 1 Hz PPS and then integrating and correcting in software the "error" from one of the clock outputs of the Si5351, this is also a valid approach if your GPS module can only do 1Hz PPS out, like on this ARRL article:
 http://www.arrl.org/files/file/QEX_Next_Issue/2015/Jul-Aug_2015/Marcus.pdf

P.S.
I was not having great reception with the external antenna near a window, only on the clear, looks like that both antennas in parallel (internal/external) will cause some performance issues. For use of the external antenna only, remove C2 from the board:

From the schematic at: https://wiki.keyestudio.com/KS0319_keyestudio_GPS_Module



Much faster now on gps lock acquire.

Have a nice day!


Sunday, October 07, 2018

Skylab SKM52 - C&Q 84 - GPS module

Had this Skylab SKM52 GPS module for some time, in the past I tested it but nothing special made with it.

 


 Another ref/name for the module is: C&Q 84


Data-sheet here: http://www.skylab.com.cn/uploadfile/Download/201508011706307884.pdf

Original idea was to use the pps output to discipline another oscillator but no need for the moment.

This is the pps out:



Lat/Lon display:

Time display:

Interesting enough I could never see the $GPZDA (date and time) ever coming out of the module (during debug output) as on the spec data-sheet, this display of data is just taken from the normal output string. ZDA string would give the microseconds.... probably not implemented.

The code made will scroll alternately between the the values of position and time.


Connections and module view:



Vcc   - Arduino 3v3 output
RX    - Arduino D4 (softserial)
TX    - Arduino D3 (softserial)
GND - Arduino GND
PPS out: connect to a LED with a resistor. PPS only outputs when there's a stable GPS signal.
 
Pin 10 on Arduino to a 1k resistor then anode of LED, cathode to ground and this will indicate a GPS fix



Code:

////////////////// code here bellow
 // read the SKM52 Skylabe GPS module (C&Q 84) via softserial and dumps lat/lon/time on the Arduino nano USB
//
//
// pin D3 on Arduino to TX and D4 to RX on the GPS module
// power to module from the 3v3 output on arduino.
//
// CT2GQV 20180922

#include
#include
// initialize the library with the numbers of the interface pins
#define I2C_ADDR    0x27
#define BACKLIGHT_PIN     3
#define En_pin  2
#define Rw_pin  1
#define Rs_pin  0
#define D4_pin  4
#define D5_pin  5
#define D6_pin  6
#define D7_pin  7
LiquidCrystal_I2C       lcd(I2C_ADDR,En_pin,Rw_pin,Rs_pin,D4_pin,D5_pin,D6_pin,D7_pin);

#include
#include

unsigned long fix_age;

SoftwareSerial GPS(3,4);
TinyGPS gps;
void gpsdump(TinyGPS &gps);
bool feedgps();
void getGPS();
long lat, lon;
float LAT, LON;

int fix_detected=0; // no fix yet

int year;
byte month, day, hour, minutes, second, hundredths;

//For the dual display
long previousMillis = 0;
long interval = 3000; // 3 seconds
int a = 60;


void setup(){
  pinMode(10, OUTPUT);
  GPS.begin(9600);
  Serial.begin(9600);
  lcd.begin(16, 2);
  lcd.setBacklightPin(BACKLIGHT_PIN,POSITIVE); // init the backlight
  lcd.setBacklight(1);        // Backlight on

}

void loop(){
  long lat, lon;
  unsigned long fix_age, time, date, speed, course;
  unsigned long chars;
  unsigned short sentences, failed_checksum;

  gps.get_position(&lat, &lon, &fix_age);
  gps.get_datetime(&date, &time, &fix_age);

  getGPS();
 
  // do we have a fix or not
  if (fix_age == TinyGPS::GPS_INVALID_AGE) {
    Serial.println("No fix detected");fix_detected=0;
    digitalWrite(10, HIGH); delay (1); digitalWrite(10, LOW);
  } else if (fix_age > 5000) {
     Serial.println("Warning: possible stale data!");fix_detected=1;
    digitalWrite(10, HIGH); delay (10); digitalWrite(10, LOW);
   }
  else  fix_detected=2; //Serial.println("Data is current.");
  // lcd.setCursor(0,1); lcd.print("fix: "); lcd.print(fix_detected);

  if (fix_detected==0 ||  fix_detected==1) {lcd.setCursor(0,0); lcd.print("WAITING FIX!!");};

// fix_detected=2; // for debug only to see output even without fix

  if (fix_detected==2) { // let's print gps data
    digitalWrite(10, HIGH); // set the green LED to allways on
 
 
   Serial.print("Lat : ");      Serial.print(LAT);
   Serial.print(" :: Lon : ");  Serial.print(LON);
 
   gps.crack_datetime(&year, &month, &day, &hour, &minutes, &second, &hundredths, &fix_age);
   Serial.print("::"); Serial.print(year);
   Serial.print("-"); Serial.print(month);
   Serial.print("-"); Serial.print(day);
 
   Serial.print("T"); Serial.print(hour);
   Serial.print(":"); Serial.print(minutes);
   Serial.print(":"); Serial.print(second);


   // every interval print the lat
   unsigned long currentMillis = millis();
   if (currentMillis - previousMillis > interval) {
    previousMillis = currentMillis;
    lcd.setCursor(0,0); lcd.print("Lat:"); lcd.print(LAT);
    lcd.setCursor(0,1); lcd.print("Lon:"); lcd.print(LON);
    delay(2000);
   }
 
   lcd.setCursor(0,0); lcd.print("                ");
   lcd.setCursor(0,1); lcd.print("                ");
   lcd.setCursor(0,0); lcd.print(year); lcd.print("-"); lcd.print(month); lcd.print("-"); lcd.print(day);
   lcd.setCursor(0,1); lcd.print(hour); lcd.print(":"); lcd.print(minutes); lcd.print(":"); lcd.print(second);
 
   // no NMEA sentences see where it displays the hundreths...
   // probably needs module programing, says on the manual that outputs the $GPZDA date and time...
   // manual http://www.skylab.com.cn/uploadfile/Download/201508011706307884.pdf
   //  Serial.print("."); Serial.print(hundredths);
   Serial.println();
  }; // endif fix detected
 
 
}

void getGPS(){
  bool newdata = false;
  unsigned long start = millis();
  // Every 1 seconds we print an update
  while (millis() - start < 1000)
  {
    if (feedgps ()){
      newdata = true;
    }
  }
  if (newdata)
  {
    gpsdump(gps);
  }
}

bool feedgps(){
  while (GPS.available())
  {
    if (gps.encode(GPS.read()))
      return true;
  }
  return 0;
}

void gpsdump(TinyGPS &gps)
{
  gps.get_position(&lat, &lon);
  LAT = lat;
  LON = lon;
  {
    feedgps(); // If we don't feed the gps during this long routine, we may drop characters and get checksum errors
  }
}



//////////// Have a nice week!