Monday, 9 November 2015

Arduino Sidereal Clock Using GPS module and real time clock

The previous post dealt with how to calculate Sidereal time and my inability to get it to work on an Arduino. If you are interested that post is here:

arduino sidereal clock with rtc module

This post is going to discuss how the electronics for the sidereal clock is going to be implemented with the electronic components modules and the firmware for the controlling the arduino microcontroller.

We are going to use a GPS module to receive the current accurate UTC (Universal Co-ordinate Time) time, latitude and longitude of our location.  The UTC time will be used to set the RTC clock (real time clock) module with the value of UTC.  With the value for UTC and latitude and longitude we are then going to calculate and display the Local Sidereal Time.  To compare lets also display the UTC time and the latitude and longitude as well.

So in order to do this we need:

1x Arduino Uno
1x GPS receiver module
1x RTC Clock module
1x 20x4 LCD display

Here is a connection diagram of the connections:
Once we have all of those items we can get down to coding the microcontroller (arduino).  Let draw a flow diagram to make this easier to code and easier to understand:

Here is the code for performing this:

Full Code for an Arduino Sidereal Clock with GPS

I'm going to discuss the code in sections to make it easier to see and hopefully understand:

// Alex's sidereal clock
// 3rd implementation
// 08/11/2015

//get time from GPS
//display time from GPS
//update RTC with time from GPS
//Display time on RTC
//Get Latitude and Longitude
//calculate sidereal time

// Libraries required
#include <LiquidCrystal_I2C.h>
#include <DS1307RTC.h>
#include <Time.h>
#include <Wire.h>               
#include <TinyGPS.h>           // http://arduiniana.org/libraries/TinyGPS/
#include <SoftwareSerial.h>
// TinyGPS and SoftwareSerial libraries are the work of Mikal Hart

// GPS receiver attached to pins 18 and 19
//SoftwareSerial SerialGPS = SoftwareSerial(18, 19);  // receive on pin 18

// To use a hardware serial port, which is far more efficient than
// SoftwareSerial, uncomment this line and remove SoftwareSerial
#define SerialGPS Serial1

//start GPS
TinyGPS gps; 

// Offset hours from gps time (UTC)
const int offset = 0;     // GMT Time

LiquidCrystal_I2C lcd(0x27, 20, 4); // set the LCD address to 0x27 for the 20x4 LCD display

//Store previous known good value for time.
time_t prevDisplay = 0;   // when the digital clock was displayed
time_t RTCHasBeenSet = 0; // when RTC was set

// sidereal calculation constants
#define dc 0.0657098244
#define tc 1.00273791
#define gc 6.648605
#define g2000 6.5988098
#define lc 0.0497958000000001
#define nc -0.0159140999999998
#define fudge -0.013922               // fudge factor (unnecessary?)
#define LONGITUDE -2.24               // longitude for Manchester, UK
#define LATITUDE  53.53               // latitude for Manchester, UK
#define siderealday 23.9344699        // length of sidereal day (23:56:04)

double GST,LST;                       // greenwich & local apparent sidereal time
int dh,dm,ds;                         // local sidereal time
int nd;                               // number of days

int g = 0;
int leap = 0;                         // number of leap years since 2000
int nleap = 0;                        // number of non-leap years since 2000   
double G = 0;                          

float UTCTime=0;                      // decomal UTC time

float UTCYear;                        // store the Year
float UTCMonth;                       // store the Month
float UTCDay;                         // store the Day

float flon = -2.24;                   // store Longitude from GPS
float flat = 53.53;                   // store Latitude from GPS

float longLCD;
float latLCD;

void setup()  {
  
  lcd.init();       // initialize the LCD
  lcd.backlight();  // Turn on LCD Backlight
  
  Serial.begin(9600);
  
  SerialGPS.begin(9600);
  Serial.println("Waiting for GPS time ... ");    

  //check if RTC has been set
      RTCHasBeenSet = now();  //set the RTC to the current time
  if (RTCHasBeenSet == 0 || RTCHasBeenSet < prevDisplay)    
   {
     getGPSTime(); 
     setRTCWithGPSTime();
   }  
  

}

void loop()
{
  getGPSTime();               // Receive GPS Packet and get UTC time
  getLatLong();               // Recieve Latitude and Longitude
  digitalClockDisplayRTC();   // Display RTC Time
  calcUTC();                  // Calculate decimal UTC 
  calcSidereal();             // Calculate local Sidereal Time
  displayOnLCD();
  delay(500);                 // wait 500 milliseconds
}

--------------------------------------------------------------------------------------------------
The first lines above are comments to let me know what the code does and provide a bit of reference to what the code 'should' perform.  I like to write some comments as it helps focus my mind when I'm coding.

The next lines tell the compiler to include various header files needed to access the associated hardware:

#include <LiquidCrystal_I2C.h> - Control the I2C 20x4 display
#include <DS1307RTC.h>         - Control the DS1307 Real time Clock      
#include <Time.h>              - Control the RTC
#include <Wire.h>              - Control I2C devices  
#include <TinyGPS.h>           - Control GPS devices
#include <SoftwareSerial.h>    - Add RS232 serial port communications in software

The next lines are commands to implement the I2C controlled display and the serial controlled GPS modules, I had trouble using software serial so I've upgraded the arduino to a Mega for testing.  The I2C display address is added and the GPS module is added and connected to pins 18 and 19 (Hardware Serial1 on a mega)  and an integer constant is defined for storing the GMT offset and a couple of structures are implemented to store the values for the current time and date.

// GPS receiver attached to pins 18 and 19
//SoftwareSerial SerialGPS = SoftwareSerial(18, 19);  // receive on pin 18

// To use a hardware serial port, which is far more efficient than
// SoftwareSerial, uncomment this line and remove SoftwareSerial
#define SerialGPS Serial1

//start GPS
TinyGPS gps; 

// Offset hours from gps time (UTC)
const int offset = 0;     // GMT Time

LiquidCrystal_I2C lcd(0x27, 20, 4); // set the LCD address to 0x27 for the 20x4 LCD display

//Store previous known good value for time.
time_t prevDisplay = 0;   // when the digital clock was displayed
time_t RTCHasBeenSet = 0; // when RTC was set

The next few lines are all of the constants and variables needed to calculate sidereal time and there are a lot of them....The constants are taken from the Equatio sidereal clock implementation by Adrian Jones (who did an excellent Job) using the method described here:

http://www.astro.umd.edu/~jph/GST_eqn.pdf

My implementation of the actual code differs slightly - more on this later but essentially the constants are numbers needed to calculate Greenwich mean sidereal time and local mean sidereal time.

// sidereal calculation constants
#define dc 0.0657098244
#define tc 1.00273791
#define gc 6.648605
#define g2000 6.5988098
#define lc 0.0497958000000001
#define nc -0.0159140999999998
#define fudge -0.013922               // fudge factor (unnecessary?)
#define LONGITUDE -2.24               // longitude for Manchester, UK
#define LATITUDE  53.53               // latitude for Manchester, UK
#define siderealday 23.9344699        // length of sidereal day (23:56:04)

double GST,LST;                       // greenwich & local apparent sidereal time
int dh,dm,ds;                         // local sidereal time
int nd;                               // number of days

int g = 0;
int leap = 0;                         // number of leap years since 2000
int nleap = 0;                        // number of non-leap years since 2000   
double G = 0;                          

float UTCTime=0;                      // decomal UTC time

float UTCYear;                        // store the Year
float UTCMonth;                       // store the Month
float UTCDay;                         // store the Day

float flon = -2.24;                   // store Longitude from GPS
float flat = 53.53;                   // store Latitude from GPS

float longLCD;
float latLCD;

Finally we have the setup function and the loop functions.  The setup function initialises the I2C display and turns on the backlight. Then the serial communications betweent the arduino and the PC are started and then a check to see if the real-time clock has been set.  The setting of the real-time clock doesn't actually work as intended.  It's on my to work out list.  What I wanted to do was check if the RTC has been set.  If it hasn't then set it to the time the compiler was run.  The later the idea was to update the RTC with the time obtained from the GPS module.  However the whole code does sort of work so I've left the function in for now.  After the setup function we have the loop function which runs continuously.  The function calls all the other functions in turn.

void setup()  {
  
  lcd.init();       // initialize the LCD
  lcd.backlight();  // Turn on LCD Backlight
  
  Serial.begin(9600);
  
  SerialGPS.begin(9600);
  Serial.println("Waiting for GPS time ... ");    

  //check if RTC has been set
      RTCHasBeenSet = now();  //set the RTC to the current time
  if (RTCHasBeenSet == 0 || RTCHasBeenSet < prevDisplay)    
   {
     getGPSTime(); 
     setRTCWithGPSTime();
   }  

}

void loop()
{
  getGPSTime();               // Receive GPS Packet and get UTC time
  getLatLong();               // Recieve Latitude and Longitude
  digitalClockDisplayRTC();   // Display RTC Time
  calcUTC();                  // Calculate decimal UTC 
  calcSidereal();             // Calculate local Sidereal Time
  displayOnLCD();
  delay(500);                 // wait 500 milliseconds
}

The getGPSTime function does exactly what its name suggests:  It looks to see if a GPS signal is available via the serial port and then if a signal has been received and if it has it is decoded to extract current time and Date.  These values are stored for later use and to update the RTC.  If the time changes the values are updated.

void getGPSTime()
// Receive GPS packet and decode it to extract time and date

{
  while (SerialGPS.available()) {
    if (gps.encode(SerialGPS.read())) { // process gps messages
      // when TinyGPS reports new data...
      Serial.println("GPS Signal Received!");
      unsigned long age;
      int Year;
      byte Month, Day, Hour, Minute, Second;
      gps.crack_datetime(&Year, &Month, &Day, &Hour, &Minute, &Second, NULL, &age);
      if (age < 500) {
        // set the Time to the latest GPS reading
        setTime(Hour, Minute, Second, Day, Month, Year);
        adjustTime(offset * SECS_PER_HOUR);
      }
    }
  }
  if (timeStatus()!= timeNotSet) {
    if (now() != prevDisplay) { //update the display only if the time has changed
      prevDisplay = now();
      //digitalClockDisplayGPS();  
    }
  }

}

The getLatLong function as it's name suggests decodes the latitude and longitude from a GPS packet and stores them for later use with the Sidereal Time calculations.  The results are printed to the serial communications to aid debugging etc.

void getLatLong()
{
  // receive GPS packet and extract latitude and longitude
  
  gps.f_get_position(&flat, &flon);

  longLCD = flon;
  latLCD = flat;

  Serial.print("Latitude: ");
  Serial.print(latLCD);
  Serial.println();
  Serial.print("Longitude: ");
  Serial.print(longLCD);
  Serial.println();
 
}

The next function digitalClockDisplayRTC displays the current value for UTC time.  The information is sent to the serial listening port and to the LCD display.

void digitalClockDisplayRTC(){
  // digital clock display of the RTC time
  
  Serial.print("RTC Time: ");
  Serial.print(hour());
  
  lcd.setCursor(0,0);       // set LCD to 1st line
  lcd.print(" UTC Time: ");       // display UTC time
  lcd.print(hour());        // print hour to LCD
    
  printDigits(minute());
  printDigits(second());
  Serial.print(" ");
  Serial.print(day());
  Serial.print(" ");
  Serial.print(month());
  Serial.print(" ");
  Serial.print(year()); 
  Serial.println(); 
 } 

The next function - calcUTC as it's name suggest does some calculations with the current UTC value for the time and date.  The time is converted to decimal time for use in the sidereal calculation and the current date is stored in specific UTC prefixed variables.

void calcUTC(){

  // Calculate and display the values for decimal UTC
  
  float UTCHour;
  float UTCMinute;
  float UTCSecond;
    
  UTCHour = hour();
  UTCMinute = minute(); 
  UTCSecond = second(); 

  UTCMinute = UTCMinute / 60;
  UTCSecond = UTCSecond /3600;

  UTCYear = year();
  UTCMonth = month();
  UTCDay = day(); 
   
  UTCTime = UTCHour + UTCMinute + UTCSecond;
  Serial.print("UTC: ");
  Serial.print(UTCTime,4);
  Serial.println(); 
   

The next function calcSidereal is the key function for this project.  It takes the current value of the time and date and uses this information to calculate Greenwich Mean Sidereal time and then local sidereal time using the current longitude value.  The first part of the code calculates the number of leap years that have occurred since the year 2000 and then the number of non leap years.  These numbers are then used to calculate the number of days that have occurred since the year 2000 to the current date.  After that Greenwich Mean Sidereal time is calculated and then finally Local Sidereal Time is calculated in decimal form.  The decimal LST is then converted into Hours, Minutes and seconds and then displayed on the LCD display.

void calcSidereal(){ 
  // calculate G (based on extrapolation)
   g = (year() - 2000);
   leap = int((g+1.0)/4.0);                              // number of leap years since 2000
   nleap = g-leap;                                       // number of non-leap years since 2000   
   G = g2000 + leap*lc + nleap*nc;  // number of days
   
  // calculate nd
  nd = doNumDays(UTCYear, UTCMonth, UTCDay);
   
  // calculate GST and Local Sidereal Time (LST)
  GST = G + (dc*nd) + (tc*UTCTime); //+ fudge;               // Grenwich Sidereal Time removed fudge
  LST = GST + 24.0 + (flon/360*siderealday);          // adjust for longitude (longitude portion of siderail day
  while(LST>24.0) {  LST -= 24.0; }                          // adjust to bring into 0-24 hours
  dh = int( LST );                                           // translate into hours, ...
  dm = int( (LST - (float)dh)*60.0 );                        // ... mins and ...
  ds = int( (LST - (float)dh - (float)dm/60.0)*3600.0 );     // ... seconds

  digitalClockDisplaySidereal();                             // Display the LST on the Serial Monitor and LCD display.
}

The final function displays the latitude and longitude values on the LCD display and the last line then is a delay for 500 ms and then the entire process is repeated.

void displayOnLCD()
{
  lcd.setCursor(0,2); 
  lcd.print(" Latitude: ");
  lcd.print(latLCD);
  lcd.print("  ");
  
  lcd.setCursor(0,3); 
  lcd.print("Longitude: ");
  lcd.print(longLCD); 
  lcd.print("  ");
}

Now that I've explained the code here are some pictures of the electronics in action:

Sidereal Clock Prototype using Arduino Mega
A close up of the Display

I also did a short video of the prototype in operation which is obviously true cinematic brilliance!


There are some issues with the circuit. It didn't work particularly well with the arduino uno clone - I believe there was not enough memory available so the Sidereal calculation does not show up properly if the longitude and latitude were displayed on the LCD.  I have no idea why that was happening but I believe it was due to memory issues.  I may be able to resolve that with use of the ProgMEM library and shift some of the constants to program memory instead of RAM.  

It sometimes takes forever for the GPS time to be displayed but the latitude and longitude values come through almost immediately.  The idea was for the RTC to keep the time and be augmented by the GPS time - this isn't working at the moment so I may well investigate further.  

For the most part this works though so I'm happy enough.  The next job is to design the battery power section and charging regime and then design an enclosure.

Cheers for now - Langster!