Simple Lunar cLOCK

Hi Folks,

First may I say I am a relative a beginner at programming so excuse any dumb questions.
My partner has asked me to make her a Lunar clock.

Ok I thought,

Actions.
• Run a clock via I2C DS3231.
• Display clock time on I2C PCF8574/16X2 LCD.
• Set clock by button press.
• Use calendar dates to interpret Moon phase calculation.
with the algorithm adapted from Stephen R. Schmitt's by Tim Farley.
• Turn a specific row of LED's on via a TLC5940-nt to show incremental moon phases, max 60ma per row, 6 rows.]

I can run the LCD and DS3231 ok, but I would really appreciate any advice and tips on how to set up the algorithms and I presume an 'if' statement to trigger the relevant LED's

Regards
Mark

A relative simple approach is to Google "arduino lunar clock", which will point you to a number of examples.

MarkS1964:
• Display clock time on I2C PCF8574/16X2 LCD.
•...........
• Turn a specific row of LED's on via a TLC5940-nt to show incremental moon phases, max 60ma per row, 6 rows.]

What you propose seems to leave Arduino grossly under-employed. The only moon phase clock I have seen worthy of the name is that by Galilea. It has been around for years. Its simple enough, and its own advantage is that they bit the bullet and it is truly three dimensional. This should be easily copied with Arduino and a stepper motor, but I submit something just as good but solid state can be done with Arduino by drawing the phases on an LCD. I bet you can do this with a proper photograph of the moon as background. This would be updatable every hour, instead of that clunky LED setup, using a proper lunar clock running off the DS3231 square wave output. I'm sure you will find the mathematics you need in Meeus, Duffet-Smith et al. You can probably then determine moonrise and set, observed orientation, and God knows what all else - including eclipses. This should seriously impress your partner.

Lunar phase function for Arduino
Moon rise and set

jremington:
Lunar phase function for Arduino

More or less that in the original post

More or less that in the original post

?

The links point to actual code. Perhaps you were thinking of this implementation.

After looking more closely, the moon rise and set algorithm will unfortunately not run properly on an Arduino. I can fix it if there is interest.

For that matter, the lunar phase calculation is inaccurate on the single precision Arduino, because of sloppy calculations like this, which require double precision math.

IP = MyNormalize((JD - 2451550.1) / 29.530588853);

According to an accurate calculation for my location, the actual moon phase is 0.028, but the above code reports 0.25.

It is better just to print out the variable "AG", which is the moon age in days. AG is off by only a day or two in my location, at this time.

I thought the actual code is essentially the same as that you allude to. The reason why the code reports 0.25 is that that is about all it is good for, as admitted by the author. Further, that degree of competence is all that is needed with the hacked nightlight project. I imagine Arduino can be persuaded to do a lot better than that, hence my comment.

Hi Guy's
Messaged you both.....
Thanks for your input

Mark

I fixed a couple of problems with the lunar phase calculation and added a "fraction full" option.

It now appears to be accurate to a maximum of about 5% with regard to phase or illumination fraction (not timing of new and old moons) for dates between 1900 and 2100, as compared to professional level calculations that take into account the sun position.

// 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

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
}

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 x = MoonPhase(2016, 11, i);
    Serial.print(" Moon phase ");
    Serial.println(x);
  }
}

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

}

Thank you jremington.
Very much appreciated.

This project is being built into a champlevé enamel and gilded case (my hobby) with a clock face and matching lunar display, it would be kind of pointless if it was inaccurate.
....................... would you be willing to maybe offer a little hand holding with the coding etc?

I guess perhaps I need to withdraw my comment about using a graphic LCD display. I find it a bit hard to reconcile that approach with champlevé enamel and guilded case, and maybe the Galilea type is better suited for such craftsmanship. A fair bit of calculation might be involved with the LCD approach, but the Galilea probably just means 1 rev per 29.53 days - at least to start with.

I haven't done much of this since about 1983 using a Casio PB100 for marine navigation with the moon but, as I said, Jean Meeus and Duffet-Smith have all you need - and more. I think somebody said Arduino is not up to the job but I cannot find the post. I rather think it is, or can be persuaded, anyway. If Arduino's maths aren't as good as a thirty-year-old handheld, it can surely do the job when coupled to a calculator chip, and I imagine somebody has done that before now. As things are, it looks like reply#8 has enough for you to get on with for that approach.

The question remains of how you are going to display the result.

If you are inclined, the LCD display is obviously a goer but, while I think I can see what is needed, it is going to take somebody a lot younger and smarter to actually do it. At its simplest, I think the representation involves projecting a tilted arc of fixed radius onto a flat disc. At new moon that projection has a radius the same as the moon image, while at half moon it has infinite radius. Once you have done that, the actual screen drawing is easy(!)

Hi Nick,
I agree champlevé enamel and LCD's do not mix, well on an aesthetic basis anyway.

The Galilea approach, funny you mention that, I live in Glastonbury and at the Abbey in the fourteenth century a monk by the name of Lightfoot built the first clocks with a Lunar dial. They can still be seen working today and exactly as you mention 1 rev per 29.53 days - give or take a bit of "blacksmith's tolerances.

I certainly hope the Arduino is up to the job, if not I will have to switch approach and use something with a bit more under the bonnet.

As far as the display goes, I am happy with an eight segment display behind a glass fascia. I can position the light source (LED'S) in a way as to give enough blending and diffusion between the lunar phases, suitable design choice of deflector and glass is important. I think it will be enough for most, it's after all not a "scientific" instrument. The mock up looks good so far.

@MarkS: Please post pics of the completed project!

The Arduino is certainly up to the task of doing accurate astronomical calculations, but unfortunately the C/C++ compiler does not support the 64 bit floats employed by virtually all actually useful code.

It is quite a task to rewrite that code for accurate 32 bit float/32 bit integer calculations and for that reason, most of the Arduino code you find is worthless.

Recently, however accurate code for sun position and rise/set calculations, taken from Meeus' book, was presented here: http://www.instesre.org/ Hopefully someone will do that for lunar position calculations, which are quite a bit more demanding.

This is one of the more interesting Arduino capers. I had a think about this while down the hill surfing, and I now realise the graphic display is a lot simpler than I first thought.

To draw an LCD equivalent of Galilea, what is needed is a 29.53 day sine wave that traces the height of a segment on a nominal circle

This will enable the radius of the apparent arc of shadow to be determined by.

(height/2)+(diameter of moon(sq)/8*height).

I might have a go at this. I can draw a moon 240 pixels in diameter. The astronomical calculations may be simply used to correct the cycle at the end of of the calendar month.

@Nick_Pryner

Definitely give it a go and please share the results, the resolution will be important unless a 1980's

pixalated retro look is desired. The higher def screens typically 480x700 are still comparably poor

compared to what most people are used to these days with their Hi Def mobile devices. Surfing, yeah

that's the life. The Somerset levels here are flooding as they do from time to time, no waves just a

beautiful view.

@jremington

Thanks for the link, it made interesting reading and as you explained:

"The precision of floating point numbers on the Arduino Uno is no better than 7 digits, so the fractional part of a Julian Date is essentially lost in Arduino Uno floating point math. The solution is to separate the integer and fractional parts, because type long integers will handle values up to 2,147,483,647:"

I don't know if this is of any use GitHub - mmoller2k/Math64: Double precision 64-bit floating point math library for Arduino and whether anyone could comment.

There is definitely a challenge on the table for anyone with an in depth understanding of the mathematics involved.

The current error rate of 3% equates to 10.92 days with an inaccurate reading, 5% - 18.2 days. It could be ignored but that seems a bit defeatist in approach, a bit like a clock that won't work on 18 days of the week.

Options.

  1. Re write the algorithms for 64bit (way beyond my capabilities).
  2. Accept the error rate (not really an option)
  3. Adopt a 64 bit platform (simple but kind of spoils the fun)
  4. Acquire the moon phase date from an external source (hassle with licenses etc)

Of course I will post pic's of the final result but I think there is a way to go yet.....
Thank's for your input.

The current error rate of 3% equates to 10.92 days with an inaccurate reading, 5% - 18.2 days. It could be ignored but that seems a bit defeatist in approach, a bit like a clock that won't work on 18 days of the week.

The stated error is in the amount of the moon predicted to be illuminated, not in the timing. The program is very accurate in the timing of new and full moons for hundreds of years in either direction from the year 2000.

If you don't understand the point, compare the program output with the predictions found on this site: Moon Phases 2022 – Lunar Calendar for any date of your choosing.

For today (Nov. 29, 2016) the program predicts 0.01 illumination, the site predicts 0.016.

I doubt that a human would be able to discern the error in a picture of a moon crescent, calculated by the program, compared to the real thing.

Thank you @jremington, that helps to make things clearer,

MarkS1964:
@jremington

Thanks for the link, it made interesting reading and as you explained:

"The precision of floating point numbers on the Arduino Uno is no better than 7 digits, so the fractional part of a Julian Date is essentially lost in Arduino Uno floating point math. The solution is to separate the integer and fractional parts, because type long integers will handle values up to 2,147,483,647:"

I don't know if this is of any use GitHub - mmoller2k/Math64: Double precision 64-bit floating point math library for Arduino and whether anyone could comment.

There is definitely a challenge on the table for anyone with an in depth understanding of the mathematics involved.

The current error rate of 3% equates to 10.92 days with an inaccurate reading, 5% - 18.2 days. It could be ignored but that seems a bit defeatist in approach, a bit like a clock that won't work on 18 days of the week.

Options.

  1. Re write the algorithms for 64bit (way beyond my capabilities).
  2. Accept the error rate (not really an option)
  3. Adopt a 64 bit platform (simple but kind of spoils the fun)
  4. Acquire the moon phase date from an external source (hassle with licenses etc)

Of course I will post pic's of the final result but I think there is a way to go yet.....
Thank's for your input.

Instead of being intimidated by this problem, let's try to solve it.

Look again at the code in reply #8, especially this part here:

  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

So what is this trying to do, really? In plain English:
Step 1: Figure out how many days it has been since Jan. 6, 2000. There was a new moon at around 6 PM on that date.
Step 2: Figure out how many complete moon phase cycles, and what fraction of a cycle, are in that number of days. The length of a cycle is taken to be 29.53059 days.
Step 3: Get rid of the whole number from Step 2, leaving only the fraction. The fraction corresponds to the moon phase: .0 for new moon, .25 for first quarter, .5 for full moon, .75 for last quarter, .999 for just before the next new moon.

A complete lunar cycle is not always exactly 29.53059 days: it can vary by a few hours either way. For the moment, though, let's ignore this inaccuracy.

Counting days (our Step 1) can be done in a number of ways. Here is one of my functions to do the job:

uint16_t ymdToDays(uint8_t y, uint8_t m, uint8_t d) {
  // Converts date (year, month, day) to day number.
  // Input the year as a number from 0 to 99. (0 means AD 2000)
  
  // some basic sanity checks
  if (y>99) return 0xFFFF;
  if ((m<1)||(m>12)) return 0xFFFF;
  if ((d<1)||(d>31)) return 0xFFFF;
  
  y+=4; // so that the next line can't make it go negative
  if (m<=2) {y--; m+=12;} // treat Jan. and Feb. as part of preceding year
  
  uint16_t n=365*y; // whole years (not counting leap days)
  n+=(y>>2); // add in the leap days
  n+=(31*(uint16_t)m); // take care of months (assuming 31 days per month)
  
  // the next few lines compensate for short months
  if     (m>11) n-=4;
  else if (m>9) n-=3;
  else if (m>6) n-=2;
  else if (m>4) n-=1;
  
  n+=d; // take care of the day of the month
  
  return (n-1495); // make 2000-01-01(Sat) be day 0
}

Step 2 and Step 3 are just simple arithmetic. Even with the Arduino's limited ability to deal with floats, it would be more than accurate enough for this purpose over your and your partner's lifetime, even if the two of you live to be very old.

Once you know the phase of the moon, the next step is calculating how much of the moon's disk is illuminated. This involves a bit of trigonometry. I believe that the formula you want is:

frac = (1.0 - cos(phase * 6.28318)) * 0.5;

The constant 6.28318 is (2 * pi). This is because Arduino trig calculations are in radians, and (2 * pi) radians equals 360 degrees.

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 more complicated, somewhat more precise program EDIT: (not for Arduino):

/* standalone moon phase calculation */ 
 // accurate to about 2% of "fraction illuminated" as compared to the U.S. Navy Astronomical
 // Applications Department web page http://aa.usno.navy.mil/data/docs/MoonFraction.php
#include <stdio.h>
#include <math.h>

#define		PI	3.1415926535897932384626433832795
#define		RAD	(PI/180.0)
#define         SMALL_FLOAT	(1e-12)

typedef struct {
    int year,month,day;
    double hour;
} TimePlace;

void JulianToDate(TimePlace* now, double jd)
{
    long jdi, b;
    long c,d,e,g,g1;

    jd += 0.5;
    jdi = jd;
    if (jdi > 2299160) {
        long a = (jdi - 1867216.25)/36524.25;
        b = jdi + 1 + a - a/4;
    }
    else b = jdi;

    c = b + 1524;
    d = (c - 122.1)/365.25;
    e = 365.25 * d;
    g = (c - e)/30.6001;
    g1 = 30.6001 * g;
    now->day = c - e - g1;
    now->hour = (jd - jdi) * 24.0;
    if (g <= 13) now->month = g - 1;
    else now->month = g - 13;
    if (now->month > 2) now->year = d - 4716;
    else now->year = d - 4715;
}

double 
Julian(int year,int month,double day)
{
    /*
      Returns the number of julian days for the specified day.
      */
    
    int a,b,c,e;
    if (month < 3) {
	year--;
	month += 12;
    }
    if (year > 1582 || (year == 1582 && month>10) ||
	(year == 1582 && month==10 && day > 15)) {
	a=year/100;
	b=2-a+a/4;
    }
    c = 365.25*year;
    e = 30.6001*(month+1);
    return b+c+e+day+1720994.5;
}

double sun_position(double j)
{
    double n,x,e,l,dl,v;
    double m2;
    int i;

    n=360/365.2422*j;
    i=n/360;
    n=n-i*360.0;
    x=n-3.762863;
    if (x<0) x += 360;
    x *= RAD;
    e=x;
    do {
	dl=e-.016718*sin(e)-x;
	e=e-dl/(1-.016718*cos(e));
    } while (fabs(dl)>=SMALL_FLOAT);
    v=360/PI*atan(1.01686011182*tan(e/2));
    l=v+282.596403;
    i=l/360;
    l=l-i*360.0;
    return l;
}

double moon_position(double j, double ls)
{
    
    double ms,l,mm,n,ev,sms,z,x,lm,bm,ae,ec;
    double d;
    double ds, as, dm;
    int i;
    
    /* ls = sun_position(j) */
    ms = 0.985647332099*j - 3.762863;
    if (ms < 0) ms += 360.0;
    l = 13.176396*j + 64.975464;
    i = l/360;
    l = l - i*360.0;
    if (l < 0) l += 360.0;
    mm = l-0.1114041*j-349.383063;
    i = mm/360;
    mm -= i*360.0;
    n = 151.950429 - 0.0529539*j;
    i = n/360;
    n -= i*360.0;
    ev = 1.2739*sin((2*(l-ls)-mm)*RAD);
    sms = sin(ms*RAD);
    ae = 0.1858*sms;
    mm += ev-ae- 0.37*sms;
    ec = 6.2886*sin(mm*RAD);
    l += ev+ec-ae+ 0.214*sin(2*mm*RAD);
    l= 0.6583*sin(2*(l-ls)*RAD)+l;
    return l;
}

double moon_phase(int year,int month,int day, double hour, int* ip)
{
    /*
      Calculates the phase of the moon at
      the given epoch.
      returns the moon phase as a real number (0-1)
      */

    double j= Julian(year,month,(double)day+hour/24.0)-2444238.5;
    double ls = sun_position(j);
    double lm = moon_position(j, ls);

    double t = lm - ls;
    if (t < 0) t += 360;
    *ip = (int)((t + 22.5)/45) & 0x7;
    return (1.0 - cos((lm - ls)*RAD))/2;
}

static void nextDay(int* y, int* m, int* d, double dd)
{
    TimePlace tp;
    double jd = Julian(*y, *m, (double)*d);
    
    jd += dd;
    JulianToDate(&tp, jd);
    
    *y = tp.year;
    *m = tp.month;
    *d = tp.day;
}

main()
{
    int y, m, d;
    int m0;
    int h;
    int i;
    double step = 1;
    int begun = 0;

    double pmax = 0;
    double pmin = 1;
    int ymax, mmax, dmax, hmax;
    int ymin, mmin, dmin, hmin;
    double plast = 0;

    printf("tabulation of the phase of the moon for one month\n\n");

    printf("year: "); fflush(stdout);
    scanf("%d", &y);
    
    printf("month: "); fflush(stdout);
    scanf("%d", &m);    

    d = 1;
    m0 = m;

    printf("\nDate       Time   Phase Segment\n");
    for (;;) {
        double p;
        int ip;
        
        for (h = 0; h < 24; h += step) {
            
            p = moon_phase(y, m, d, h, &ip);

            if (begun) {
                if (p > plast && p > pmax) {
                    pmax = p;
                    ymax = y;
                    mmax = m;
                    dmax = d;
                    hmax = h;
                }
                else if (pmax) {
                    printf("%04d/%02d/%02d %02d:00          (fullest)\n",
                           ymax, mmax, dmax, hmax);
                    pmax = 0;
                }

                if (p < plast && p < pmin) {
                    pmin = p;
                    ymin = y;
                    mmin = m;
                    dmin = d;
                    hmin = h;
                }
                else if (pmin < 1) {
                    printf("%04d/%02d/%02d %02d:00          (newest)\n",
                           ymin, mmin, dmin, hmin);
                    pmin = 1.0;
                }
            
                if (h == 16) {
                    printf("%04d/%02d/%02d %02d:00 %5.1f%%   (%d)\n",
                           y, m, d, h, floor(p*1000+0.5)/10, ip);
                }
            }
            else begun = 1;

            plast = p;

        }        
        nextDay(&y, &m, &d, 1.0);
        if (m != m0) break;
    }
    return 0;
}

An interesting point came up today in conversation, that of "Blood Moon's" -Tetrads, "Harvest Moon's", Super Moon's and of course the Eclipses.

The point being if you are interested in the Moon Phases then all the above are the highlights of the Lunar calendar. A device showing the phases is nice, but a bit like a calendar that only shows the days of the week and not when grandmas birthday is.

So looking past my project (the programming is a steep learning curve but an enjoyable one!) My question is could the above be calculated mathematically? Or would it be best dealt with by some form of Calendar function.

Could the calendar be populated with all the data from above via an excel spreadsheet and functions executed from that data? 100 years ahead......
Well I am at the point now after two weeks where I confidently connect devices, configure them, react to sensors and turn things on and off.

My next step is to implement @jremingtons updated Algorithm code.

  1. Configure it with an RTC (DS3231)
  2. Understand a bit more about how to manage the output of the code to control analogWrite etc

I know this has all been done but there is no fun in just copying code without understanding how it functions.

@odometer & @jremington, thanks for your input, I must confess to only understanding a little of your complex maths but it's certainly fascinating and hopefully all the effort will result in something a bit special.