Simple Lunar cLOCK

Every astronomical fact of interest about the moon and its appearance from Earth can be calculated to nearly arbitrary accuracy. The results depend on the observer's position on Earth as well as the date and time of day, to a fraction of a second.

See the offerings at Moonrise and Moonset Calculator and various astronomy buff websites for more information.

Or better, buy a copy of Jean Meeus' book "Astronomical Formula for Calculators". The moon phase calculation (more accurately, the illuminated fraction of the moon's disk) is presented and explained in Chapter 31 (4th Ed.).

Many of the Meeus algorithms were coded in C and are available in the source for the "moontool" program, which displays moon phases using a 64x64 bitmap. See the bitmap in action with the Windows version of "moontool".

moon_phase.gif

/*
 * $Id: moon.xbm,v 1.1.1.1 1993/08/17 09:42:33 alm Exp $
 */
/* converted from Sun Icon format - originally distributed with moontool */
#define moon_width 64
#define moon_height 64
static char moon_bits[] = {
 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
 0xff,0xff,0xff,0xff,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0x10,0xf8,
 0xff,0xff,0xff,0xff,0x07,0x82,0xc4,0xe0,0xff,0xff,0xff,0xff,0x01,0x10,0x04,
 0x80,0xff,0xff,0xff,0xff,0x10,0x47,0x50,0x0d,0xff,0xff,0xff,0x7f,0xc0,0x21,
 0x02,0x15,0xfe,0xff,0xff,0x1f,0x20,0x36,0x44,0x31,0xf8,0xff,0xff,0x0f,0x69,
 0x8b,0xd8,0x9a,0xf0,0xff,0xff,0x07,0xec,0x1a,0xa0,0xe3,0xe0,0xff,0xff,0x27,
 0xa0,0x85,0x04,0x8f,0xe2,0xff,0xff,0x03,0x55,0x0b,0xf5,0x28,0xc5,0xff,0xff,
 0x41,0xa2,0x20,0x02,0x67,0x81,0xff,0xff,0x90,0x89,0x84,0x20,0x2d,0x10,0xff,
 0xff,0x04,0xb1,0x80,0x04,0x0a,0x04,0xff,0x7f,0x40,0x5a,0x52,0x10,0x40,0x01,
 0xfe,0x7f,0x14,0x81,0x48,0x04,0x99,0x48,0xfe,0x3f,0xa0,0x62,0x52,0x52,0x06,
 0x08,0xfc,0x3f,0x2a,0x02,0x89,0x00,0x22,0x42,0xfc,0x3f,0x16,0xa4,0x2d,0x42,
 0x82,0x04,0xfc,0x1f,0x81,0x44,0x06,0xdd,0x0d,0x20,0xf8,0x1f,0x14,0x82,0x30,
 0x20,0x10,0x04,0xf8,0x9f,0x00,0x9f,0x0a,0x33,0x65,0x91,0xf8,0x1f,0xc9,0x28,
 0x00,0x0c,0x80,0x00,0xf8,0x1f,0x50,0x64,0x12,0x54,0x04,0x01,0xf9,0x1f,0xc9,
 0xa0,0x00,0x08,0x10,0x03,0xf8,0x9f,0x84,0x19,0x04,0x28,0x81,0x00,0xf8,0x1f,
 0x08,0xee,0x10,0x11,0x40,0x92,0xf8,0x1f,0xb5,0x50,0x01,0x62,0x38,0x00,0xf8,
 0x1f,0x56,0x08,0x02,0x48,0x87,0x00,0xf8,0x1f,0xa9,0x09,0x41,0x00,0x00,0x80,
 0xf8,0x1f,0x56,0x40,0x00,0x00,0x00,0x00,0xf8,0x9f,0x50,0x12,0x00,0x00,0x00,
 0x40,0xf8,0x1f,0xaa,0x20,0x11,0x00,0x40,0x0e,0xf8,0x1f,0x0c,0x05,0x00,0x20,
 0x90,0x11,0xf8,0x3f,0x90,0x02,0x00,0x00,0x40,0x10,0xfc,0x3f,0xc1,0x52,0x02,
 0x04,0x20,0x94,0xfc,0x3f,0x20,0x8c,0x00,0x00,0x21,0x0a,0xfc,0x7f,0x92,0x08,
 0xf2,0x21,0xa0,0x04,0xfe,0x7f,0x90,0x16,0x0c,0x02,0xc0,0x03,0xfe,0xff,0x48,
 0xa1,0x06,0x08,0x00,0x00,0xff,0xff,0x10,0x5a,0x01,0x30,0x00,0x08,0xff,0xff,
 0xb1,0xce,0x40,0x28,0x80,0x80,0xff,0xff,0xc3,0x93,0xe0,0x41,0x00,0xc0,0xff,
 0xff,0x47,0xc1,0x70,0x2c,0x20,0xe0,0xff,0xff,0x07,0x8a,0x80,0x28,0x00,0xe2,
 0xff,0xff,0x0f,0x0a,0x49,0x06,0x12,0xf0,0xff,0xff,0x1f,0x00,0x73,0x31,0x40,
 0xf8,0xff,0xff,0x7f,0x90,0x9e,0x87,0x00,0xfe,0xff,0xff,0xff,0x10,0x70,0x48,
 0x00,0xff,0xff,0xff,0xff,0x81,0x40,0x02,0x84,0xff,0xff,0xff,0xff,0x07,0x04,
 0x90,0xe0,0xff,0xff,0xff,0xff,0x1f,0x00,0x00,0xf8,0xff,0xff,0xff,0xff,0xff,
 0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
 0xff,0xff};

jremington:
I just used a triangle wave to represent the cos() term. Please feel free to modify the program, check the result against accurate calculations and let us know how that improves the result.

Note that not only does the length of the lunar cycle vary from 29.5309 days, the actual illumination depends on the position of the Sun relative to the observer's position, similar to the calculations in the following short, but not very precise program:

(code redacted)

What's the point of just ripping code from some random place on the Internet?

Again, remember, we don't have a whole lot of digits to work with. So why reckon time from a base date several millennia in the past?

I can hardly emphasize enough: we don't have a whole lot of digits to work with. Think about that before you copy code like this

    do {
	dl=e-.016718*sin(e)-x;
	e=e-dl/(1-.016718*cos(e));
    } while (fabs(dl)>=SMALL_FLOAT);

with SMALL_FLOAT defined as (1e-12).

What's the point of just ripping code from some random place on the Internet?

Thanks for asking, as there is quite a good point.

That program is short, more correct in its approach and is somewhat more accurate than the Arduino program I posted, as it uses double precision floats. We therefore have about 15 digits to work with, so there is no problem making a comparison with 1e-12.

Naturally, it won't run properly on an Arduino, but is instructive anyway. I've edited the above post to hopefully make that clear.

For rather more accurate calculations use the moontool program linked above, and for even more accurate calculations there are several other code libraries. Again, not for Arduino.

Posting slightly improved lunar phase calculation for Arduino below.

The moon illumination fraction is not a simple cosine function of the lunar cycle, because it depends on the sun position relative to the observer during the cycle.

In reply #8 I used a triangle function to approximate it, but as suggested by odometer above, a cosine function is a somewhat better fit to the actual curve (as calculated by the U.S. Navy Astronomical Applications Department). See plot below. Some other function fit would significantly increase the accuracy, but that may be of no practical value.

moon_illum.gif

For the code below, over a lunar cycle, the averall accuracy in the illumination curve is about +/- 3%.

// Moon phase, takes three parameters: the year (4 digits), the month and the day.
// The function returns fraction full, float 0-1 (e.g. 0 for new, .25 for crescent, .5 for quarter, .75 for gibbous and 1 for full).
// Also calculates phase and age in days. THIS VERSION uses a cosine function to model the illumination fraction.
// Calculated at noon, based on new moon Jan 6, 2000 @18:00, illumination accurate to about 5%, at least for years 1900-2100.
// Modified from post at http://www.nano-reef.com/topic/217305-a-lunar-phase-function-for-the-arduino/
// S. J. Remington 11/2016

float MoonPhase(int nYear, int nMonth, int nDay) // calculate the current phase of the moon
{
  float age, phase, frac, days_since;
  long YY, MM, K1, K2, K3, JD;
  YY = nYear - floor((12 - nMonth) / 10);
  MM = nMonth + 9;
  if (MM >= 12)
  {
    MM = MM - 12;
  }
  K1 = floor(365.25 * (YY + 4712));
  K2 = floor(30.6 * MM + 0.5);
  K3 = floor(floor((YY / 100) + 49) * 0.75) - 38;
  JD = K1 + K2 + nDay + 59;  //Julian day
  if (JD > 2299160) //1582, Gregorian calendar
  {
    JD = JD - K3;
  }

  // Serial.print(" JD ");  //Julian Day, checked OK
  // Serial.print(JD);
  // Serial.print(" ");

  days_since = JD - 2451550L; //since noon on Jan. 6, 2000 (new moon @18:00)
  phase = (days_since - 0.25) / 29.53059; //0.25 = correct for 6 pm that day
  phase -= floor(phase);  //phase in cycle
  age = phase * 29.53059;

  // calculate fraction full
  frac = (1.0 - cos(phase * 2 * PI)) * 0.5;

  // Serial.print("Moon Age ");
  // Serial.print(age);

  return frac; //phase or age or frac, as desired
}

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  for (int i = 1; i < 31; i++) {  // lunar calendar for month
    Serial.print("2016 Nov ");
    Serial.print(i);
    float frac = MoonPhase(2016, 11, i);
    Serial.print(" Moon phase ");
    Serial.println(frac);
  }
}

void loop() {
  // put your main code here, to run repeatedly:

}
1 Like

jremington:
Some other function fit would significantly increase the accuracy, but that may be of no practical value.

Indeed. Ultimately, it comes down to the ability to display any difference. Hence my previous.

@jremington

I understand the getPhase - function return - if statement process in the nano-reef example. Could you kindly explain the best way to implement your function to provide current day and appropriate fraction.

You provide the year, month and day to the function, and the function returns the approximate fraction of the moon illuminated, as shown in the example code that I posted. What don't you understand about this?

What I am not clear about is how to input the current date from an RTC into the function.

Please show us the code you have to read the RTC (post with code tags, "</>" button).

Thankyou....

void loop () {
    
    DateTime now = rtc.now();
    y = now.year();
    m = now.month();
    d = now.day();
    h = now.hour();
    
    Serial.print(y);
    Serial.print('/');
    Serial.print(m);
    Serial.print('/');
    Serial.print(d);
    Serial.print(' ');
    Serial.print(h);
    Serial.println();
    
        if (h < 10 || h >24)
    {
    lightLeds (0,0,0,0,0,0);
    delay(3000);
    } 
    else 
    { 
    getPhase (y , m, d);
    delay(3000);
    }
}

void lightLeds (int L6, int L5, int L4, int L3, int L2, int L1) {
  
  analogWrite (led1, L1);
  analogWrite (led2, L2);
  analogWrite (led3, L3);
  analogWrite (led4, L4);
  analogWrite (led5, L5);
  analogWrite (led6, L6);
}

void getPhase(int Y, int M, int D) {  // calculate the current phase of the moon
  double AG, IP;                      // based on the current date
  byte phase;                         // algorithm adapted from Stephen R. Schmitt's
                                      // Lunar Phase Computation program, originally
  long YY, MM, K1, K2, K3, JD;        // written in the Zeno programming language
                                      // http://home.att.net/~srschmitt/lunarphasecalc.html
  // calculate julian date
  YY = Y - floor((12 - M) / 10);
  MM = M + 9;
  if(MM >= 12)
    MM = MM - 12;
  
  K1 = floor(365.25 * (YY + 4712));
  K2 = floor(30.6 * MM + 0.5);
  K3 = floor(floor((YY / 100) + 49) * 0.75) - 38;

  JD = K1 + K2 + D + 59;
  if(JD > 2299160)
    JD = JD -K3;
  
  IP = normalize((JD - 2451550.1) / 29.530588853);
  AG = IP*29.53;
    Serial.print(AG);
    Serial.println();
  if(AG < 1.20369)
    //phase = B00000000;
     lightLeds (0,0,0,0,0,0);
     
  else if(AG < 3.61108)
    //phase = B00000001;
    lightLeds (0,0,0,0,0,I);
    
  else if(AG < 6.01846)
    //phase = B00000011;
    lightLeds (0,0,0,0,I,I);
    
  else if(AG < 8.42595)
    //phase = B00000111;
    lightLeds (0,0,0,I,I,I);
   
  else if(AG < 10.83323)
    //phase = B00001111;
    lightLeds (0,0,I,I,I,I);
  
  else if(AG < 13.24062)
    //phase = B00011111;
    lightLeds (0,I,I,I,I,I);
   
  else if(AG < 15.64800)
    //phase = B00111111;
    lightLeds (I,I,I,I,I,I);
  
  else if(AG < 18.05539)
    //phase = B00111110;
    lightLeds (I,I,I,I,I,0);
 
  else if(AG < 20.46277)
    //phase = B00111100;
    lightLeds (I,I,I,I,0,0);
   
  else if(AG < 22.87016)
    //phase = B00111000;
    lightLeds (I,I,I,0,0,0);
   
  else if(AG < 25.27754)
    //phase = B00110000;
    lightLeds (I,I,0,0,0,0);
  
  else if(AG < 27.68493)
    //phase = B00100000;
    lightLeds (I,0,0,0,0,0);
   
  else
    phase = 0;
    lightLeds (0,0,0,0,0,0);
}

double normalize(double v) {           // normalize moon calculation between 0-1
    v = v - floor(v);
    if (v < 0)
        v = v + 1;
    return v;
}
getPhase (y , m, d);

Edit: I see you are using the old code. getPhase calls a function to light some LEDS. Is there a problem with it?

In the code I posted, MoonPhase() returns the fraction illumination. Save it, print it out or do something useful with that value, as shown in the example that I posted.

   float frac = MoonPhase(2016, 11, i);

The original code works fine I was trying to implement the same result (lighting the phases - LED's) using your improved algorithm.

Being a novice it's not entirely clear to me how to get the fraction illumination on a current daily basis.

Being a novice it's not entirely clear to me how to get the fraction illumination on a current daily basis.

Every day you call the function MoonPhase with the day's date, and it returns that day's fraction illumination, closely similar to the actual fraction of the moon illuminated on that day.

I'm having trouble understanding why this is so difficult. Doesn't the simple lunar calendar program posted in reply #23 make sense to you?

No to an average person who has never coded before it's not clear, I may be a bit simple but I am trying to learn and understand.

What I am trying to understand is how you "give it the date"...

What I am trying to understand is how you "give it the date"...

The key is as follows:

   float frac = MoonPhase(2016, 11, i);

The "2016" above stands for the year 2016.
The "11" above stands for the month (November). January = 1, February =2, etc.
The "i" stands for the day of the month, and in the program I posted in reply #23 "i" takes on values ranging from 1 to 30.

To find the fraction of moon illuminated on February 12, 1929:

   float frac = MoonPhase(1929, 2, 12);

Forum members strongly recommend that beginners work through the simple examples provided with the Arduino program development package (the IDE), to help in learning the programming language and about the capabilities of the Arduino boards.

Output from RTC into float frac..
Thank You!

I hope I have implemented everything correctly so far, there are a few issues that I would appreciate some help with.

Are lines 95 - 101 correct?

int y = (year());
int m = (month());
int d = (day());
float frac = MoonPhase(y, m, d);

Today GMT 08/12/2016 it is returning 0.61

The display of the moon has 8 segments - waxing and waning (total 16)

I have taken jremingtons frac output 0-1 and divided it by 16 to give 0.0625 increments.

I do have an issue here as the programme is sticking on 0.125 (help needed)

Is there a simpler or more efficient way of doing this?

Is there a way of implementing a fade of 0-4096 on the leading or tailing LED between the phases?

Many thanks.....

/ Moon phase, takes three parameters: the year (4 digits), the month and the day.
// The function returns fraction full, float 0-1 (e.g. 0 for new, .25 for crescent, .5 for quarter, .75 for gibbous and 1 for full).
// Also calculates phase and age in days.
// Calculated at noon UTC, based on new moon Jan 6, 2000 @18:00, illumination accurate to about 5%, at least for years 1900-2100.
// Modified from post at http://www.nano-reef.com/topic/217305-a-lunar-phase-function-for-the-arduino/
// S. J. Remington 11/2016

#include <Wire.h>
#include <DS3231.h>
#include <TimeLib.h>
#include <DS1307RTC.h>
#include <Tlc5940.h>

// Init the DS3231 using the hardware interface
DS3231  rtc(SDA, SCL);

float MoonPhase(int nYear, int nMonth, int nDay) // calculate the current phase of the moon
{
  float age, phase, frac, days_since;
  long YY, MM, K1, K2, K3, JD;
  YY = nYear - floor((12 - nMonth) / 10);
  MM = nMonth + 9;
  if (MM >= 12)
  {
    MM = MM - 12;
  }
  K1 = floor(365.25 * (YY + 4712));
  K2 = floor(30.6 * MM + 0.5);
  K3 = floor(floor((YY / 100) + 49) * 0.75) - 38;
  JD = K1 + K2 + nDay + 59;  //Julian day
  if (JD > 2299160) //1582, Gregorian calendar
  {
    JD = JD - K3;
  }

  // Serial.print(" JD ");  //Julian Day, checked OK
  // Serial.print(JD);
  // Serial.print(" ");

  days_since = JD - 2451550L; //since new moon on Jan. 6, 2000 (@18:00)
  phase = (days_since - 0.25) / 29.53059; //0.25 = correct for 6 pm that day
  phase -= floor(phase);  //phase in cycle
  age = phase * 29.53059;

  // calculate fraction full
  frac = 2.0 * phase;
  if (frac > 1.0) frac = 2.0 - frac;  //illumination, accurate to about 5%

  // Serial.print("Moon Age ");
  // Serial.print(age);

  //return frac; //phase or age or frac, as desired
  return frac;
  //return phase;
}
/////////////////////////////////////////////////////////////////////////////
void setup()
{
  rtc.begin();

  Tlc.init(0);    //initalizes all LEDs to off at first
  Serial.begin(9600);
  while (!Serial) ; // wait until Arduino Serial Monitor opens
  setSyncProvider(RTC.get);   // the function to get the time from the RTC
  if (timeStatus() != timeSet)
    Serial.println("Unable to sync with the RTC");
  else
    Serial.println("RTC has set the system time");

  // Uncommented to set the date and time
  //rtc.setDOW(WEDNESDAY);     // Set Day-of-Week to SUNDAY
  //rtc.setTime(12, 0, 0);     // Set the time to 12:00:00 (24hr format)
  //rtc.setDate(12, 8, 2016);   // Set the date to January 1st, 2014


  //Serial.begin(9600);
  //for (int i = 1; i < 31; i++) {  // lunar calendar for month
  //Serial.print("2016 Nov ");
  //Serial.print(i);
  //float x = MoonPhase(2016, 12, 5);
  //Serial.print(" Moon phase ");
  //Serial.println(x);
}

void loop() {


  Serial.print(year());
  Serial.print(" ");
  Serial.print(month());
  Serial.print(" ");
  Serial.print(day());
  Serial.println();

  int y = (year());
  int m = (month());
  int d = (day());



  float frac = MoonPhase(y, m, d);

  Serial.print (frac);
  Serial.println();
  delay (3000);

  //LED Test = passed
  //Tlc.set(1, 4095);
  //Tlc.set(2, 4095);
  //Tlc.set(3, 4095);
  //Tlc.set(4, 4095);
  //Tlc.set(5, 4095);
  //Tlc.set(6, 4095);
  //Tlc.set(7, 4095);
  //Tlc.set(8, 4095);
  //Tlc.update();//Reminder---Executes pin states

  if (frac = 0)
    //lightLeds (0,0,0,0,0,0);
  { Tlc.set (1, 0);
    Tlc.set (2, 0);
    Tlc.set (3, 0);
    Tlc.set (4, 0);
    Tlc.set (5, 0);
    Tlc.set (6, 0);
    Tlc.set (7, 0);
    Tlc.set (8, 0);
    Tlc.update();
  }

  else if (frac > 0.0625)
    //lightLeds (0,0,0,0,0,0,0,I);
  { Tlc.set (1, 0);
    Tlc.set (2, 0);
    Tlc.set (3, 0);
    Tlc.set (4, 0);
    Tlc.set (5, 0);
    Tlc.set (6, 0);
    Tlc.set (7, 0);
    Tlc.set (8, 4095);
    Tlc.update();

  }

  else if (frac < 0.125)
    //lightLeds (0,0,0,0,0,0,I,I);
  { Tlc.set (1, 0);
    Tlc.set (2, 0);
    Tlc.set (3, 0);
    Tlc.set (4, 0);
    Tlc.set (5, 0);
    Tlc.set (6, 0);
    Tlc.set (7, 4095);
    Tlc.set (8, 4095);
    Tlc.update();

  }

  else if (frac < 0.1875)
    //lightLeds (0,0,0,0,0,I,I,I);
  { Tlc.set (1, 0);
    Tlc.set (2, 0);
    Tlc.set (3, 0);
    Tlc.set (4, 0);
    Tlc.set (5, 0);
    Tlc.set (6, 4095);
    Tlc.set (7, 4095);
    Tlc.set (8, 4095);
    Tlc.update();
  }

  else if (frac < 0.25)
    //lightLeds (0,0,0,0,I,I,I,I);

  { Tlc.set (1, 0);
    Tlc.set (2, 0);
    Tlc.set (3, 0);
    Tlc.set (4, 0);
    Tlc.set (5, 4095);
    Tlc.set (6, 4095);
    Tlc.set (7, 4095);
    Tlc.set (8, 4095);
    Tlc.update();
  }

  else if (frac < 0.3125)
    //lightLeds (0,0,0,I,I,I,I,I);

  { Tlc.set (1, 0);
    Tlc.set (2, 0);
    Tlc.set (3, 0);
    Tlc.set (4, 4095);
    Tlc.set (5, 4095);
    Tlc.set (6, 4095);
    Tlc.set (7, 4095);
    Tlc.set (8, 4095);
    Tlc.update();
  }

  else if (frac < 0.375)
    //lightLeds (0,0,I,I,I,I,I,I);

  { Tlc.set (1, 0);
    Tlc.set (2, 0);
    Tlc.set (3, 4095);
    Tlc.set (4, 4095);
    Tlc.set (5, 4095);
    Tlc.set (6, 4095);
    Tlc.set (7, 4095);
    Tlc.set (8, 4095);
    Tlc.update();
  }

  else if (frac < 0.4375)
    //lightLeds (0,I,I,I,I,I,I,I);

  { Tlc.set (1, 0);
    Tlc.set (2, 4095);
    Tlc.set (3, 4095);
    Tlc.set (4, 4095);
    Tlc.set (5, 4095);
    Tlc.set (6, 4095);
    Tlc.set (7, 4095);
    Tlc.set (8, 4095);
    Tlc.update();
  }

  else if (frac < 0.5)
    //lightLeds (I,I,I,I,I,I,I,I);

  { Tlc.set (1, 4095);
    Tlc.set (2, 4095);
    Tlc.set (3, 4095);
    Tlc.set (4, 4095);
    Tlc.set (5, 4095);
    Tlc.set (6, 4095);
    Tlc.set (7, 4095);
    Tlc.set (8, 4095);
    Tlc.update();
  }
//////////////////////Waning////////////////////////////////
  else if (frac > 0.5625)
    //lightLeds (0,I,I,I,I,I,I,I);
  { Tlc.set (1, 0);
    Tlc.set (2, 4095);
    Tlc.set (3, 4095);
    Tlc.set (4, 4095);
    Tlc.set (5, 4095);
    Tlc.set (6, 4095);
    Tlc.set (7, 4095);
    Tlc.set (8, 4095);
    Tlc.update();
  }

  else if (frac > 0.625)
    //lightLeds (0,0,I,I,I,I,I,I);
  { Tlc.set (1, 0);
    Tlc.set (2, 0);
    Tlc.set (3, 4095);
    Tlc.set (4, 4095);
    Tlc.set (5, 4095);
    Tlc.set (6, 4095);
    Tlc.set (7, 4095);
    Tlc.set (8, 4095);
    Tlc.update();
  }

  else if (frac > 0.6875)
    //lightLeds (0,0,0,I,I,I,I,I);
  { Tlc.set (1, 0);
    Tlc.set (2, 0);
    Tlc.set (3, 0);
    Tlc.set (4, 4095);
    Tlc.set (5, 4095);
    Tlc.set (6, 4095);
    Tlc.set (7, 4095);
    Tlc.set (8, 4095);
    Tlc.update();
  }

  else if (frac > 0.75)
    //lightLeds (0,0,0,0,I,I,I,I);
  { Tlc.set (1, 0);
    Tlc.set (2, 0);
    Tlc.set (3, 0);
    Tlc.set (4, 0);
    Tlc.set (5, 4095);
    Tlc.set (6, 4095);
    Tlc.set (7, 4095);
    Tlc.set (8, 4095);
    Tlc.update();
  }

  else if (frac > 0.8125)
    //lightLeds (0,0,0,0,,I,I,I);
  { Tlc.set (1, 0);
    Tlc.set (2, 0);
    Tlc.set (3, 0);
    Tlc.set (4, 0);
    Tlc.set (5, 0);
    Tlc.set (6, 4095);
    Tlc.set (7, 4095);
    Tlc.set (8, 4095);
    Tlc.update();
  }

  else if (frac > 0.875)
    //lightLeds (0,0,0,0,0,0,I,I);
  { Tlc.set (1, 0);
    Tlc.set (2, 0);
    Tlc.set (3, 0);
    Tlc.set (4, 0);
    Tlc.set (5, 0);
    Tlc.set (6, 0);
    Tlc.set (7, 4095);
    Tlc.set (8, 4095);
    Tlc.update();
  }

  else if (frac > 0.9375)
    //lightLeds (0,0,0,0,0,0,0,I);
  { Tlc.set (1, 0);
    Tlc.set (2, 0);
    Tlc.set (3, 0);
    Tlc.set (4, 0);
    Tlc.set (5, 0);
    Tlc.set (6, 0);
    Tlc.set (7, 0);
    Tlc.set (8, 4095);
    Tlc.update();
  }

  else if (frac > 1.0)
    //lightLeds (0,0,0,0,0,0,0,0);
  { Tlc.set (1, 0);
    Tlc.set (2, 0);
    Tlc.set (3, 0);
    Tlc.set (4, 0);
    Tlc.set (5, 0);
    Tlc.set (6, 0);
    Tlc.set (7, 0);
    Tlc.set (8, 0);
    Tlc.update();
  }
  
}
  // calculate fraction full
  frac = 2.0 * phase;
  if (frac > 1.0) frac = 2.0 - frac;  //illumination, accurate to about 5%

For better accuracy, replace the above lines with:

  // calculate fraction full
  frac = (1.0 - cos(phase * 2 * PI)) * 0.5;

This is a problem, and why it gets "stuck".

  else if (frac > 0.0625)

Thank you again @jremington for your input and help it has been invaluable.

After I posted I realised my mistake not using your improved calc.

Just have to sort out the fading and possibly automatically updating the time via GPS / Serial, the I can start cutting, engraving and enamelling.

It's still stuck on

else if (frac < 0.0625)

I can't see where the error is, I have checked as much as i can...

if   (frac = 0)
    //lightLeds (0,0,0,0,0,0);
  { Tlc.set (1, 0);
    Tlc.set (2, 0);
    Tlc.set (3, 0);
    Tlc.set (4, 0);
    Tlc.set (5, 0);
    Tlc.set (6, 0);
    Tlc.set (7, 0);
    Tlc.set (8, 0);
    Tlc.update();
  }


  else if (frac < 0.0625)
    //lightLeds (0,0,0,0,0,0,0,I);
  { Tlc.set (1, 0);
    Tlc.set (2, 0);
    Tlc.set (3, 0);
    Tlc.set (4, 0);
    Tlc.set (5, 0);
    Tlc.set (6, 0);
    Tlc.set (7, 0);
    Tlc.set (8, 4095);
    Tlc.update();

  }

  else if (frac < 0.125)
    //lightLeds (0,0,0,0,0,0,I,I);
  { Tlc.set (1, 0);
    Tlc.set (2, 0);
    Tlc.set (3, 0);
    Tlc.set (4, 0);
    Tlc.set (5, 0);
    Tlc.set (6, 0);
    Tlc.set (7, 4095);
    Tlc.set (8, 4095);
    Tlc.update();

  }

  else if (frac < 0.1875)
    //lightLeds (0,0,0,0,0,I,I,I);
  { Tlc.set (1, 0);
    Tlc.set (2, 0);
    Tlc.set (3, 0);
    Tlc.set (4, 0);
    Tlc.set (5, 0);
    Tlc.set (6, 4095);
    Tlc.set (7, 4095);
    Tlc.set (8, 4095);
    Tlc.update();
  }

  else if (frac < 0.25)
    //lightLeds (0,0,0,0,I,I,I,I);
  { Tlc.set (1, 0);
    Tlc.set (2, 0);
    Tlc.set (3, 0);
    Tlc.set (4, 0);
    Tlc.set (5, 4095);
    Tlc.set (6, 4095);
    Tlc.set (7, 4095);
    Tlc.set (8, 4095);
    Tlc.update();
  }

  else if (frac < 0.3125)
    //lightLeds (0,0,0,I,I,I,I,I);
  { Tlc.set (1, 0);
    Tlc.set (2, 0);
    Tlc.set (3, 0);
    Tlc.set (4, 4095);
    Tlc.set (5, 4095);
    Tlc.set (6, 4095);
    Tlc.set (7, 4095);
    Tlc.set (8, 4095);
    Tlc.update();
  }

  else if  (frac < 0.375)
    //lightLeds (0,0,I,I,I,I,I,I);
  { Tlc.set (1, 0);
    Tlc.set (2, 0);
    Tlc.set (3, 4095);
    Tlc.set (4, 4095);
    Tlc.set (5, 4095);
    Tlc.set (6, 4095);
    Tlc.set (7, 4095);
    Tlc.set (8, 4095);
    Tlc.update();
  }

  else if  (frac < 0.4375)
    //lightLeds (0,I,I,I,I,I,I,I);
  { Tlc.set (1, 0);
    Tlc.set (2, 4095);
    Tlc.set (3, 4095);
    Tlc.set (4, 4095);
    Tlc.set (5, 4095);
    Tlc.set (6, 4095);
    Tlc.set (7, 4095);
    Tlc.set (8, 4095);
    Tlc.update();
  }

  else if  (frac < 0.5)
    //lightLeds (I,I,I,I,I,I,I,I);
  { Tlc.set (1, 4095);
    Tlc.set (2, 4095);
    Tlc.set (3, 4095);
    Tlc.set (4, 4095);
    Tlc.set (5, 4095);
    Tlc.set (6, 4095);
    Tlc.set (7, 4095);
    Tlc.set (8, 4095);
    Tlc.update();
  }
  //////////////////////Waning////////////////////////////////
  else if (frac < 0.5625)
    //lightLeds (0,I,I,I,I,I,I,I);
  { Tlc.set (1, 0);
    Tlc.set (2, 4095);
    Tlc.set (3, 4095);
    Tlc.set (4, 4095);
    Tlc.set (5, 4095);
    Tlc.set (6, 4095);
    Tlc.set (7, 4095);
    Tlc.set (8, 4095);
    Tlc.update();
  }

  else if (frac < 0.625)   
  //lightLeds (0,0,I,I,I,I,I,I);
  { Tlc.set (1, 0);
    Tlc.set (2, 0);
    Tlc.set (3, 4095);
    Tlc.set (4, 4095);
    Tlc.set (5, 4095);
    Tlc.set (6, 4095);
    Tlc.set (7, 4095);
    Tlc.set (8, 4095);
    Tlc.update();
  }

  else if (frac < 0.6875)
    //lightLeds (0,0,0,I,I,I,I,I);
  { Tlc.set (1, 0);
    Tlc.set (2, 0);
    Tlc.set (3, 0);
    Tlc.set (4, 4095);
    Tlc.set (5, 4095);
    Tlc.set (6, 4095);
    Tlc.set (7, 4095);
    Tlc.set (8, 4095);
    Tlc.update();
  }

  else if (frac < 0.75)
    //lightLeds (0,0,0,0,I,I,I,I);
  { Tlc.set (1, 0);
    Tlc.set (2, 0);
    Tlc.set (3, 0);
    Tlc.set (4, 0);
    Tlc.set (5, 4095);
    Tlc.set (6, 4095);
    Tlc.set (7, 4095);
    Tlc.set (8, 4095);
    Tlc.update();
  }

  else if (frac < 0.8125)
    //lightLeds (0,0,0,0,,I,I,I);
  { Tlc.set (1, 0);
    Tlc.set (2, 0);
    Tlc.set (3, 0);
    Tlc.set (4, 0);
    Tlc.set (5, 0);
    Tlc.set (6, 4095);
    Tlc.set (7, 4095);
    Tlc.set (8, 4095);
    Tlc.update();
  }

  else if (frac < 0.875)
    //lightLeds (0,0,0,0,0,0,I,I);
  { Tlc.set (1, 0);
    Tlc.set (2, 0);
    Tlc.set (3, 0);
    Tlc.set (4, 0);
    Tlc.set (5, 0);
    Tlc.set (6, 0);
    Tlc.set (7, 4095);
    Tlc.set (8, 4095);
    Tlc.update();
  }

  else if (frac < 0.9375)
    //lightLeds (0,0,0,0,0,0,0,I);
  { Tlc.set (1, 0);
    Tlc.set (2, 0);
    Tlc.set (3, 0);
    Tlc.set (4, 0);
    Tlc.set (5, 0);
    Tlc.set (6, 0);
    Tlc.set (7, 0);
    Tlc.set (8, 4095);
    Tlc.update();
  }

  else if (frac < 1.0)
    //lightLeds (0,0,0,0,0,0,0,0);
  { Tlc.set (1, 0);
    Tlc.set (2, 0);
    Tlc.set (3, 0);
    Tlc.set (4, 0);
    Tlc.set (5, 0);
    Tlc.set (6, 0);
    Tlc.set (7, 0);
    Tlc.set (8, 0);
    Tlc.update();
  }