Project in progress
Non Optical Solar Tracker (East Tower 2.4KW)

Non Optical Solar Tracker (East Tower 2.4KW) © GPL3+

Calculates the position of the sun relative to the trackers position on the earth and points the array at the sun.

  • 5,479 views
  • 11 comments
  • 35 respects

Components and supplies

A000066 iso both
Arduino UNO & Genuino UNO
Well actually I only use the genuine one for development, the final version run a Chinese version http://www.ebay.com.au/itm/400995324988?_trksid=p2060353.m1438.l2649&ssPageName=STRK%3AMEBIDX%3AIT
×1
Real Time clock Modual
These are great and have a temp sensor in them as a byproduct
×1
2004 LCD with I2C backpack
I built mine from two parts but easier to buy as one
×1
LED matrix display
This has an I2C interface , used for the "meatball" in the gyro enclosure
×1
GYRO / MAG / ACC / TEMP / PRES sensor
These are great supported nativly by libraries
×1
Bourns pec11r 4215f s0024 image 75px
Rotary Encoder with Push-Button
Wanted something simple --- mistake to much UI code
×1
Satellite Jacks - Actuator to move the array 1 x 36" HD 1 x 24" STD
Heaver the better, don't skimp they do break in high winds
×1
Automotive fuse holders
Need to fuse both motor legs as driven from a H-Switch and the Positive of the regulator is grounded
×1
Solar Regulator- Powers motors and Arduino Board
Was hoping to use the inbuilt 5V power supply to run arduino from USB but alass
×1
Battery 12V 7 AH - The ubiquitous UPS battery
Actually these are recycled from other tasks - only needs to store 1 or 2 AH
×1
12 to 30V step up power supply
Neat device but but startup current can be a issue for the solar regulator
×1
61pby065esl  sx679  tnr8syww5d
HC-05 Bluetooth Module
Garden variety Bluetooth modem
×1
Texas Instruments P82B96 Data Buffer
Data Buffer for I2C
×1
Veroboard
You can not make really UGLY circuits without some of this ;-)
×1

Necessary tools and machines

Fabrication Equipment - Welder / Cutters / Drill
The frame is all welded using gas-less MIG. The Gimble parts and other heavy stuff is done with a tranformerless stick welder.
Machining - Lathe is needed to machine shafts to bearings
See cad drawings - this is very basic machining - doesn't require CNC
Hand Drill
Used for Everything from securing Teck screws to drilling holes and then some...

Apps and online services

Ide web
Arduino IDE
Code compiled in here
Vs2015logo
Microsoft Visual Studio 2015
Used for much of the proof of concept software.
0bitge6r 400x400
Android Studio
Used to develop the HMI for bluetooth

About this project

Background

I have always been fascinated with solar trackers since childhood, having seen one in the James Bond film “The Man with the Golden Gun.” So fast forward 35 years and with an engineering degree under my belt, I say “Why not build what I have been dreaming about for years – minus the death ray maybe?"

So the first two (twin) towers where built. They are, like most twins, a bit of a mistake but it all worked out the best, sort of. I designed/built these myself as there was no ready made tracker kits for small to medium size arrays. Tracking a panel or two is child’s play but 8 panels with a sail area of over 9m square is a different proposition especially at wind speeds of over 80km/hr .

These towers a located on a working farm so the arrays are positioned 5.8m above the ground in order that tractors and other equipment not collide with the trackers. If you’re an astute gardener you’ll have worked out they are positioned between avocado trees which do seem to grow a little better under them. The big panels are connected to a grid inverter and the small “pony” panels drive the computers and electronics. There is no parasitic power from the mains to drive the positioning motors, this is largely due to needing power to move the arrays in storms when you are least likely to have mains available.

Approximately one sixth of the tower is buried underground in 1.2 cubic meters of concrete configured as a “top hat” foundation. There is two layers of re-enforcement mesh in the upper section and this is welded to steel ties to prevent the post exceeding the 40Mpa limit of the concrete at it’s interface. The bottom of the foundation is keyed through a rock shelf which happened to lie 1.2 meters below the surface.

So fast forward we have two nice mechanisms (north and south towers). How so we point them at the sun? Optical that sounds logical and easy. I was happy at first but then frustration grew as it was obvious that cloudy days (SCT20 and above) where a real issue for the controllers. They would frantically sweep the heavens in search of the bright spot, wearing out motors and depleting batteries in the process.

Walking into my office, inspiration was right on the floor. Over the previous 2 years it had become adorned with sundial like markings in felt tip pen which in ancient times where used to mark the passage of the seasons. You open the office door at solar noon and read off how far from the equinox / solstice and the exact angle that arrays should be set to. It was so clear you can calculate the suns position and simply “be there” with the solar array. So in an attempt to correct the mistakes of the twins I decided to embark on the “East tower project” which would be nearly 50% bigger than either of the first 2 trackers and would have a computer that would allow it to calculate and point to the sun.

Picking the Board

Having just helped my son with a school project involving an Arduino I was amazed with the ease of use and sheer volume of libraries available, there be one of everything! A far cry from my early days of embedded work, writing all my own libraries in machine code. So I picked the Uno for this job as a bit of a challenge, it’s nice to have lots of everything but it really “hot” when you shoe horn truckloads into a tiny package. I ran out of space many times, this resulted in me recoding more efficiently and not bringing my lazy “windows” habits into the project. In the end it all fitted into the tiny wee chip.

Sensors

So obviously the main sensor is the 3 axis accelerometer which is used to work out which way is up. This is compared to the calculated value of the position of the sun with a hysteresis band of course. The motors then activate to move the array so the target angles are within tolerance of the actual measured values. A relay block is used to switch the motors, I did toy with using PWM. But simplicity and electrical noise where on my mind. The whole system is driven by a single 7AH 12V battery with a DC/DC step up used to power the 36VDC motors (at about 20 something volts).

Position of the Sun

Position of the sun? At this point I admit I’m not an astronomer and didn’t just dream up the equations, I downloaded the algorithm from the NOAA web site. I did a proof of concept program in MS windows which was actually driven from a GPS NEMA stream, once that worked I ported it to the Arduino platform. The GPS was replaced by a simple RTC for initial trials, it was intended that this unit be able to “self calibrate” and point to the sun without setup anywhere in the world .

The algorithm returns values in term of Alt and Az however my tracker/mount type requires Declination and hour angle. This was an easy thing to fix as they are a natural by-product of the Alt/Az calculations. It also means that the program can be used on any tracker type as the sensor used also included a 3 axis magnetometer to measure out Az if necessary. There was a model built which demonstrated the code and outputted to two servos configured as an Alt/Az mount, this had the mandatory “death ray laser” mounted 90 degrees to the plane of the array to project the suns position on the roof of the room. My son suggested we increase the laser power to leave sun trails on the roof of his school class room, fortunately mother stepped in at this point.

GUI for a Wee Computer

Now having got sensors and equations, a GUI was needed. A 2004 lcd display with i2c backpack provides this. The program ended up with 25 screens for settings and diagnostics, everything from setting the time to adjusting the tracking mode and angles etc etc. For input I choose a rotary encoder with integrated push button, in hindsight this was not the best choice. I think a LCD shield with touch input would have been simpler and more suited for the task. There is an “meat-ball” display in the gyro enclosure that allows you to see how far off target the array is and if the tracker is moving to correct the issue. This was sort of a luxury and further evidence of my ever growing Arduino/eBay addiction.

I2C Problems

I2C. A confession here, I knew I would have trouble with the cable length but being way too much of a “red neck” I did it anyhow. The tests run in the workshop looked fine but in the real environment there are lots of electrical cables emitting all frequencies of radiation. I built an active terminator for the I2C but this didn’t solve the lockup problem nor did adding ferrite beads or messing with earth configurations on the solar charger. Tired not talking on the I2C buss for a while after motor starts and fitted snubbers to all the actuator motors. The funny part about this process was that for several weeks I had a solar tracking computer that would only run at night, yep as useful as a bull with tits. So the final solution, some buffer chips from Texas Instruments (P82B96). These allow you to drive i2c over large distances with relatively high noise immunity, worked a treat just like they say on the box.

And of course final version of what the sensor box looks like

More Problems obvious in hindsight

Tower movement in the wind was somewhat under estimated as it turned out to be as high as 3 degrees. The original 2 degree hysteresis was quickly adjusted to 4 degrees. Full stop was implemented for night hours so the system would not react to winds etc at night… no reason to move after the system has parked and gone to sleep of the night (unless it get windy). Changed the program so one axis can move at a time and to favor E/W motion of N/S.

Low and high angle limits where implemented, along with different tracking methods and parking combinations. There are selectable park angles for both axes. As well as the ability to change target angles provided it is in "tracking none” mode.

All this data is made non-volatile by being stored in CPU internal EEPROM memory along with routines to initialize a blank or senile CPU to safe and sane values.

Given the lockup issues I needed a a hardware watchdog, found the one in the chip and implemented that at 4 second intervals, also programmed a spare pin just in case I needed and external one. Deployment of this had to wait till after I fixed the hardware issues or I risked masking the I2C issues I was having.

The programming contains a Modbus interface that allows you to read and write all of the major memory locations so that I could write a smart phone GUI via Bluetooth and be able to access the unit without the need to use the GUI in the enclosure. A surprise for me was that the memory map worked backwards if simply declared as variables but forward if in an array. It didn’t take long to work this out so all those years working on both Intel and Motorola CPU’s paid off. The Bluetooth is via the Arduino serial port and a HC-05 Bluetooth module.

So did it all work

Yes on sunny days the unit does produce at least as much energy as the optical trackers after you compensate for panel output. However on cloudy days it's a winner, on track all the time and I am expecting less maintenance on the jacks as they move the absolute minimum amount necessary to follow the sun each day. So refit of North and South towers coming up next.

But before we go - Parking

So you track the sun during the day but where should you park the array at night?

This is one of those “How long is a piece of string” questions. It is a very complex question, the more I observe and record issues the more I’m convinced it requires and “active/adaptive solution”. Obviously wind forces can be minimised when the array is flat BUT if your vortex size is about that of the array it can flutter depending on the relative position of the centre of pressure and centre of gravity. This is BAD in a storm. I’d much rather do the old sailors trick of “point her into the wind” in spite of the increased wind loading than have an array flutter. If your battery runs out in the night best to be pointed towards the sun in the morning so you can get moving as soon as possible. So all said and done things like battery voltage and wind speeds as well as the variation in angles (tower wobble) should be taken into account) , this be the brief for another program/project I think !

UPDATE (August 2017)

I still had a lot of trouble with “random” reboots which would crash the I2C. The kicker was, it would never do this on a sunny clear day. It was battery and seemingly wind dependent. Anyhow 6 weeks ago I caught a lucky break and saw it actually reset. Could have kicked myself it was definitely the solar regulator at fault. I assume that a motor start against wind or with a flatish battery pushed the unit into shutdown/brownout briefly but not long enough to fully reset all the electronics. So a new 30A solar regulator unit replaced the 10A and I have yet to see it reset once in the last 4 weeks including stormy weather. It’s been offline a few times with depleted battery but always seems to start fine when the sun returns (case closed).

You know how they say “inside every problem is another waiting to get out”. Well, once the main computer is 100% reliable you start to notice other things. Mechanically stuck relays arrrhhh, horrid little blighters! The boards I bought must only have the relays that “John West Rejected” soldered on them. They look the same as the genuine quality brand but as with all things cheap on Ebay “buyer beware”. The only real solution, buy an H switch board (I knew it would come to this). So I’ve done the programming for the Uno to implement plain old on/off H switch, the program even fitted in the same number of bytes. I’m just waiting for the board to arrive.

But how can I leave it there? I mean I have an H switch and arduino that naively does PWM. How can I not do soft start on the motors?

OK...That's cool it I cant believe all fitted in the UNO... we now got soft start and recycle counters. The motors glide off to full speed now sweet...

UPDATE (June 2018)

Computer and software running fine, the RTC losses time at an annoying rate. Just not quite enough for me to be bothered to fix it :)

PWM soft start is awesome, really does take the stain off the mechanics. No cracks in the steel work at the last inspection so very happy with this aspect.

Jack may be nimble jack may be quick but jack not be strong enough!

This problem would appear to be a combination of many things. However the most obvious is stay away from "flyable" angles ( 0 to 16 Deg ) in high winds. The preceding weeks had see the clamp slip numerous times, however I never caught it in the act. It had crossed my mind that it was a limit switch failure however that would mean both hard and soft limits failed. The distance between centers of the failed jack is quite a bit less than the hard limit distance so I think it was just simple overloading. It has now been replaced with the same heavy duty jack as used in the east/west axis. All seems good and the clamp has not slipped yet.

A wee bit of documentation on the GUI

In general rotate the encoder to change menu .. push in and rotate to change the value of the setting. When your on a save menu (8 & 18 ) press and hold till the system shows it has saved the data

The menu items all require you to do a save if you want the new settings to flash to eeprom (menu 18 - press button ).. This is so you can test ... if you like it you save... if you don't then just reboot.

Time setting is menus 1->7 Menu 8 is a time set/update - press to save back to RTC and arduino. The display will take a snapshot of the current time to edit as you enter Menu 1 or 7. That way you can set the time and wait at menu 8 for the exact moment to press and synchronize, if your that way inclined.

X and Y min / max angle i.e. soft limit switches ( 4 menus 19->22)

X and Y hysteresis i.e. how accurately you track. ( 2 Menus 16->17)

Tracking mode ( 1 menu 13 )

X and Y Parking angles (2 menus 11->12 )

X and Y angle offsets (2 menus 14->15 ) these are to trim out misalignment's of the circuit board to the final mounting if you need it.

X and Y Set angle ( 2 menus 9->10) If you switch tracking to off you can dial up the angle here (helps with maintenance) otherwise this shows the target angle coming from the calculations and is effectively read only.

There three menu screens for display only.

Menu 23 - GPS coordinates / tome zone - need to set these in code or change via Modbus and then saved either via modbus or menu 18

Menu 24 - Angles - Sensor , Target and Difference

Menu 0 - Time Date , Sunrise , Sunset , Dec / AH and Alt / AZ

Obviously you set the clock to standard time for your time zone. Or you could set it to GMT and use time zone 0 ... whatever floats your boat.

Code

TRACKER_ALL_IN_ONE_BOTTOM.inoC/C++
This for a "Uno" Board using the described hardware
There are 24 ish screens of data and diagnostics .... uses the chips internal eeprom for non-volatile storage.
Good coding exercise as I has do be more efficient as the program grew...It been a long time since I have had only 32K to program in .
Don't stress about the modified lib's - they are only patches to faults in the coding. If you need just ask....
//#include <SFE_BMP180.h>
#include <avr/wdt.h>
#include <Wire.h>
#include <LSM303.h>       // modified ... fixed a couple of bugs
#include <LiquidCrystal_I2C.h>
//#include <L3G.h>
#include "ds3231.h"
#include "ht16k33.h"
#include <ModbusRtu.h>   // Modified ... this no longer a stock lib - Slave supports register translation/mapping
#include <EEPROM.h>
#include <math.h>

#define ID   1

#define BUFF_MAX 32
#define PARK_EAST 1
#define PARK_WEST 2
#define PARK_NORTH 3
#define PARK_SOUTH 4
#define PARK_FLAT 5


#define MAX_MODBUS_DATA  60

#define HT16K33_DSP_NOBLINK 0   // constants for the half arsed cheapo display
#define HT16K33_DSP_BLINK1HZ 4
#define HT16K33_DSP_BLINK2HZ 2
#define HT16K33_DSP_BLINK05HZ 6


const byte ENCODER_PINA = 2;  // encoder connects - interupt pin
const byte ENCODER_PINB = 3;  // non interupt pin
const byte ENCODER_PB = 8;

const byte RELAY_XZ_F = 4; //   X+  Relay 1  North
const byte RELAY_XZ_R = 5; //   X-  Relay 2  South
const byte RELAY_YZ_F = 6; //   Y+  Relay 3  West
const byte RELAY_YZ_R = 7; //   Y-  Relay 4  East

const byte UNUSED09 =  9;
const byte UNUSED10 = 10;
const byte UNUSED11 = 11;
const byte WATCHDOG = 12;


//L3G gyro;
LSM303 compass;
LiquidCrystal_I2C lcd(0x27);  // Set the LCD I2C address I modified the lib for default wiring

//SFE_BMP180 pressure;
HT16K33 HT;

Modbus slave1(ID, 1, 0); // this is slave ID and RS-232 or USB-FTDI

uint8_t time[8];

char recv[BUFF_MAX];
unsigned int recv_size = 0;
unsigned long prev_millis;
uint8_t u8state; //!< machine state
uint8_t u8query; //!< pointer to message query

struct ts t;             // MODBUS MAP
struct ts tc;            // 44 
int iSave = 0 ;          // 43    
int iDoSave = 0 ;        // 42
float T;                 // 40 temperature of board
float xzTarget ;         // 38 target for angles
float yzTarget ;         // 36
float xzH ;              // 34 hyserisis zone
float yzH ;              // 32
float xzAng;             // 30 current angles
float yzAng;             // 28
float xzOffset;          // 26 offset xz
float yzOffset;          // 24 offset yz
float dyPark;            // 22 parking position
float dxPark;            // 20 
float xMinVal ;          // 18 Min and Max values  X - N/S
float xMaxVal ;          // 16 
float yMinVal ;          // 14  Y  -- E/W
float yMaxVal ;          // 12 
float latitude;          // 10
float longitude;         // 8
int timezone;            // 7
int iDayNight ;          // 6
float solar_az_deg;      // 4
float solar_el_deg;      // 2
int iTrackMode ;         // 1 
int iMode ;              // 0

int iCycle ;
int iPMode;

float baseline; // baseline pressure
float gT ;
unsigned long tempus;
int8_t state1 = 0;


void ActivateRelays(int iAllStop) {
  if ( iAllStop == 0 ) {
    digitalWrite(RELAY_YZ_R, HIGH) ;
    digitalWrite(RELAY_YZ_F, HIGH) ;
    digitalWrite(RELAY_XZ_R, HIGH) ;
    digitalWrite(RELAY_XZ_F, HIGH) ;
  } else {
    if ((tc.sec % 5) == 0 ){
      if (((yzAng  ) < ( yzTarget - yzH )) && ( digitalRead(RELAY_YZ_F) == HIGH )) {   // do Y ie E/W before N/S
        digitalWrite(RELAY_YZ_F, LOW) ;
        digitalWrite(RELAY_YZ_R, HIGH) ;
      }
      if (((yzAng ) > (yzTarget + yzH )) && ( digitalRead(RELAY_YZ_R) == HIGH )) {
        digitalWrite(RELAY_YZ_F, HIGH) ;
        digitalWrite(RELAY_YZ_R, LOW) ;
      }
    }
    if ((yzAng > yzTarget) && ( digitalRead(RELAY_YZ_F) == LOW )) {
      digitalWrite(RELAY_YZ_F, HIGH) ;
      digitalWrite(RELAY_YZ_R, HIGH) ;
    }
    if ((yzAng < yzTarget) && ( digitalRead(RELAY_YZ_R) == LOW )) {
      digitalWrite(RELAY_YZ_F, HIGH) ;
      digitalWrite(RELAY_YZ_R, HIGH) ;
    }

    if (( digitalRead(RELAY_YZ_F) == HIGH ) && ( digitalRead(RELAY_YZ_R) == HIGH )) {  // if finished on E/W you can do N/S
      if ((tc.sec % 5) == 0 ){
        if ((xzAng < ( xzTarget - xzH )) && ( digitalRead(RELAY_XZ_F) == HIGH ))  { // turn on if not in tolerance
          digitalWrite(RELAY_XZ_F, LOW) ;
          digitalWrite(RELAY_XZ_R, HIGH) ;
        }
        if ((xzAng > ( xzTarget + xzH ))&& ( digitalRead(RELAY_XZ_R) == HIGH )) { // turn on if not in tolerance
          digitalWrite(RELAY_XZ_F, HIGH) ;
          digitalWrite(RELAY_XZ_R, LOW) ;
        }
      }  
    }else{
      if ((digitalRead(RELAY_XZ_F) == LOW ) || (digitalRead(RELAY_XZ_R) == LOW )){
        digitalWrite(RELAY_XZ_F, HIGH) ; // switch off if on
        digitalWrite(RELAY_XZ_R, HIGH) ;      
      }
    }
    if ((xzAng > xzTarget ) && ( digitalRead(RELAY_XZ_F) == LOW ))  { // if on turn off
      digitalWrite(RELAY_XZ_F, HIGH) ;
      digitalWrite(RELAY_XZ_R, HIGH) ;
    }
    if ((xzAng < xzTarget ) && ( digitalRead(RELAY_XZ_R) == LOW ))  { // if on turn off
      digitalWrite(RELAY_XZ_F, HIGH) ;
      digitalWrite(RELAY_XZ_R, HIGH) ;
    }
  }
}

void FloatToModbusWords(float src_value , uint16_t * dest_lo , uint16_t * dest_hi ) {
  uint16_t tempdata[2] ;
  float *tf ;
  tf = (float * )&tempdata[0]  ;
  *tf = src_value ;
  *dest_lo = tempdata[1] ;
  *dest_hi = tempdata[0] ;
}
float FloatFromModbusWords( uint16_t dest_lo , uint16_t dest_hi ) {
  uint16_t tempdata[2] ;
  float *tf ;
  tf = (float * )&tempdata[0]  ;
  tempdata[1] = dest_lo ;
  tempdata[0] = dest_hi  ;
  return (*tf) ;
}

// Arduino doesnt have these to we define from a sandard libruary
float arcsin(float x) {
  return (atan(x / sqrt(-x * x + 1)));
}
float arccos(float x) {
  return (atan(x / sqrt(-x * x + 1)) + (2 * atan(1)));
}
// fractional orbital rotation in radians
float gama(struct ts *tm) {
  return ((2 * PI / 365 ) *  DayOfYear(tm->year , tm->mon , tm->mday , tm->hour , tm->min ));
}
// equation of rime
float eqTime(float g) {
  return (229.18 * ( 0.000075 + ( 0.001868 * cos(g)) - (0.032077 * sin(g)) - (0.014615 * cos (2 * g)) - (0.040849 * sin(2 * g))));
}
// declination of sun in radians
float Decl(float g) {
  return ( 0.006918 - (0.399912 * cos(g)) + (0.070257 * sin(g)) - (0.006758 * cos(2 * g)) + ( 0.000907 * sin(2 * g)) - ( 0.002697 * cos(3 * g)) + (0.00148 * sin(3 * g)) );
}
float TimeOffset(float longitude , struct ts *tm ,  int timezone ) {
  float dTmp ;
  dTmp = (-4.0 * longitude ) + (60 * timezone) - eqTime(gama(tm)) ;
  return (dTmp);
}

float TrueSolarTime(float longitude , struct ts *tm ,  int timezone ) {
  float dTmp ;
  dTmp = ( 60.0 * tm->hour ) + (1.0 * tm->min) + (1.0 * tm->sec / 60) - TimeOffset(longitude, tm, timezone) ;
  return (dTmp);
}
float HourAngle(float longitude , struct  ts *tm ,  int timezone) {
  float dTmp;
  dTmp = (TrueSolarTime(longitude, tm, timezone) / 4 ) - 180 ; // 720 minutes is solar noon -- div 4 is 180
  return (dTmp);
}
// Hour angle for sunrise and sunset only
float HA (float lat , struct ts *tm ) {
  float latRad ;
  latRad = lat * 2 * PI / 360 ;
  return ( acos((cos(90.833 * PI / 180 ) / ( cos(latRad) * cos(Decl(gama(tm)))) - (tan(latRad) * tan(Decl(gama(tm)))))) / PI * 180  );
}

float Sunrise(float longitude , float lat , struct ts *tm , int timezone) {
  return (720 - ( 4.0 * (longitude + HA(lat, tm))) + (60 * timezone) - eqTime(gama(tm))  ) ;
}
float Sunset(float longitude , float lat , struct ts *tm , int timezone) {
  return (720 - ( 4.0 * (longitude - HA(lat, tm))) + (60 * timezone) - eqTime(gama(tm))  ) ;
}
float SNoon(float longitude , float lat , struct ts *tm , int timezone) {
  return (720 - ( 4.0 * (longitude  + (60 * timezone) - eqTime(gama(tm))))  ) ;
}

float SolarZenithRad(float longitude , float lat , struct ts *tm , int timezone) {
  float latRad ;
  float decRad ;
  float HourAngleRad ;
  float dTmp ;

  latRad = lat * 2 * PI / 360 ;
  decRad = Decl(gama(tm));
  HourAngleRad = HourAngle (longitude , tm , timezone ) * PI / 180 ;
  dTmp = acos((sin(latRad) * sin(decRad)) + (cos(latRad) * cos(decRad) * cos(HourAngleRad)));
  return (dTmp) ;

}
float SolarElevationRad(float longitude , float lat , struct ts *tm ,  int timezone ) {
  return ((PI / 2) - SolarZenithRad(longitude , lat , tm , timezone )) ;
}

float SolarAzimouthRad(float longitude , float lat , struct ts *tm ,  int timezone) {
  float latRad ;
  float decRad ;
  float solarzenRad ;
  float HourAngleRad ;
  float dTmp ;
  latRad = lat * 2 * PI / 360 ;
  decRad = Decl(gama(tm));
  solarzenRad = SolarZenithRad ( longitude , lat , tm , timezone ) ;
  HourAngleRad = HourAngle (longitude , tm , timezone ) * PI / 180 ;
  dTmp = acos(((sin(decRad) * cos(latRad)) - (cos(HourAngleRad) * cos(decRad) * sin(latRad))) / sin(solarzenRad)) ;
  if ( HourAngleRad < 0 ) {
    return (dTmp) ;
  } else {
    return ((2 * PI) - dTmp) ;
  }
}

float LoadFloatFromEEPROM(int address,float minval,float maxval, float defaultval){
float tmp ;  
  EEPROM.get(0 + (address * sizeof(float)) , tmp );
  if (( tmp < minval ) || ( tmp > maxval )|| (NumberOK(tmp) == 1)) {
    tmp = defaultval ;
    EEPROM.put(0 + (address * sizeof(float)) , tmp );
  }
  return(tmp);  
}
int LoadIntFromEEPROM(int address,int minval,int maxval, int defaultval){
int tmp ;  
  EEPROM.get(0 + (address * sizeof(float)) , tmp );
  if (( tmp < minval ) || ( tmp > maxval )) {
    tmp = defaultval ;
    EEPROM.put(0 + (address * sizeof(float)) , tmp );
  }
  return(tmp);  
}

int NumberOK (float target) {
  int tmp = 0 ;
  tmp = isnan(target);
  if ( tmp != 1 ) {
    tmp = isinf(target);
  }
  return (tmp);
}

void IncFloat(float * target, boolean bInc, float increment, float minval, float maxval , boolean bWrap){
    if( bInc  ){
      *target += increment ;  
    }else{
      *target -= increment ;  
    }
    if ( *target > maxval ){
      if ( bWrap  ){
        *target = minval ;
      }else{
        *target = maxval ;
      }
    }
    if ( *target < minval ){
      if ( bWrap  ){
        *target = maxval ;
      }else{
        *target = minval ;
      }
    }
}
void IncInt(int * target, boolean bInc, int increment, int minval, int maxval , boolean bWrap){
    if( bInc  ){
      *target += increment ;  
    }else{
      *target -= increment ;  
    }
    if ( *target > maxval ){
      if ( bWrap  ){
        *target = minval ;
      }else{
        *target = maxval ;
      }
    }
    if ( *target < minval ){
      if ( bWrap  ){
        *target = maxval ;
      }else{
        *target = minval ;
      }
    }  
}
void IncUint8_t (uint8_t * target , boolean bInc, int increment, int minval, int maxval , boolean bWrap){
    if( bInc  ){
      *target += increment ;  
    }else{
      *target -= increment ;  
    }
    if ( *target > maxval ){
      if ( bWrap  ){
        *target = minval ;
      }else{
        *target = maxval ;
      }
    }
    if ( *target < minval ){
      if ( bWrap  ){
        *target = maxval ;
      }else{
        *target = minval ;
      }
    }  
}
void StdFlloatValueDisplay(float target, char message[]){
  lcd.setCursor ( 0, 1 );       // line 1
  lcd.print(message) ;
  lcd.print(target) ;
  lcd.setCursor ( 0, 2 );       // line 2
  lcd.print( "PB to set ") ;
}
 

void setup() {
  int led ;
  
  Wire.begin();
  lcd.begin(20, 4);
  lcd.home();
  lcd.setBacklightPin(3, NEGATIVE);
  lcd.noCursor(); 

  MCUSR &= ~_BV(WDRF);
  wdt_disable();
  
  compass.init();
  compass.enableDefault();
  compass.setTimeout(1000);
  
  pinMode(RELAY_XZ_F, OUTPUT); // relay
  pinMode(RELAY_XZ_R, OUTPUT); // relay
  pinMode(RELAY_YZ_F, OUTPUT); // relay
  pinMode(RELAY_YZ_R, OUTPUT); // relay
  pinMode(13, OUTPUT); //
  digitalWrite(13, HIGH );
  ActivateRelays(0); // call an all stop first

  Serial.begin(9600) ;  // use as a diagnostic port
  slave1.begin( 9600 ); // RS-232 to base of tower
  tempus = millis() + 100;

  pinMode(ENCODER_PINA, INPUT_PULLUP);
  pinMode(ENCODER_PINB, INPUT_PULLUP);
  pinMode(ENCODER_PB, INPUT_PULLUP);
  attachInterrupt(0, counter, FALLING);

  pinMode(UNUSED09, OUTPUT);  // unused so dont leave floating set as output
  pinMode(UNUSED10, OUTPUT);  // 
  pinMode(UNUSED11, OUTPUT);  // 
  pinMode(WATCHDOG, OUTPUT);  // 
  digitalWrite(WATCHDOG,HIGH);

  compass.m_min = (LSM303::vector<int16_t>) {-3848, -1822, -1551 };   // calibration figures are empirical
  compass.m_max = (LSM303::vector<int16_t>) { +3353, +5127, +5300};

  xzH = LoadFloatFromEEPROM(0,0.1,20.0,4.0);  // hysterisis NS
  yzH = LoadFloatFromEEPROM(1,0.1,20.0,4.0);  //    ""      EW

  dyPark = LoadFloatFromEEPROM(2,-70.0,50.0,0);  
  dxPark = LoadFloatFromEEPROM(3,-5.0,50.0,0.0);  
  
  xzOffset = LoadFloatFromEEPROM(4,-90.0,90.0,0);  // NS
  yzOffset = LoadFloatFromEEPROM(5,-90.0,90.0,0);  // EW

  xzTarget = LoadFloatFromEEPROM(6,-90.0,90.0,0);  // NS
  yzTarget = LoadFloatFromEEPROM(7,-90.0,90.0,0);  // EW

  xMinVal = LoadFloatFromEEPROM(8,-10.0,60.0,0.0);   // NS
  xMaxVal = LoadFloatFromEEPROM(9,-10.0,60.0,45);

  yMinVal = LoadFloatFromEEPROM(10,-70.0,50.0,-65);  // EW
  yMaxVal = LoadFloatFromEEPROM(11,-70.0,50.0,45);

  iMode = 0 ;
  iTrackMode = LoadIntFromEEPROM(12,-1,4,0);
  
//  latitude = -34.051219 ;
//  longitude = 142.013618 ;
//  timezone = 10 ;
   
  latitude = LoadFloatFromEEPROM(13,-90.0,90.0,-34.051219);
  longitude = LoadFloatFromEEPROM(14,-180.0,180.0,142.013618);
  timezone = LoadIntFromEEPROM(15,0,23,10);
   
  DS3231_init(DS3231_INTCN);
  DS3231_get(&tc);
  DS3231_get(&t);
  lcd.clear();
  HT.begin(0x00);
  for (led = 0; led < 127; led++) {
    HT.clearLedNow(led);
  }
  wdt_enable(WDTO_4S);
}

void loop() {
  float P;
  float sunInc;
  float sunAng;
  float xzRatio;
  float yzRatio;
  char buff[BUFF_MAX];
  float decl ;
  float eqtime ;
  float dTmp ;
  float ha ;
  float heading ;
  float sunrise ;
  float sunset ;
  float tst ;
  float sunX ;

  compass.read();  // this reads all 6 channels
//  compass.readAcc();
//  compass.readMag();
  digitalWrite(UNUSED09,!digitalRead(UNUSED09));   // toggle this output so I can measure the cycle time with a scope
  
  T = DS3231_get_treg();
  heading = compass.heading((LSM303::vector<int>) { 1, 0, 0 });
  
  if (( compass.a.z != 0) && (!compass.timeoutOccurred() ))  {
    xzRatio = (float)compass.a.y / abs((float)compass.a.z) ;   //  yep if your sharp you can see I swapped axis orientation late in the programming
    yzRatio = (float)compass.a.x / abs((float)compass.a.z) ;
    xzAng = ((float)atan(xzRatio) / PI * -180 ) + xzOffset ;   // good old offsets or fudge factors
    yzAng = ((float)atan(yzRatio) / PI * 180 ) + yzOffset ;
  }else{                                                       // try restarting the compass/accelerometer modual - cos he gone walkabout...
    compass.init();
    compass.enableDefault();
    compass.setTimeout(1000);                                  // BTW I fixed up the int / long issue in the time out function in the LM303 lib I was using
    compass.m_min = (LSM303::vector<int16_t>) {-3848, -1822, -1551 };   // calibration figures are empirical (just whirl it around a bit and records the min max !!)
    compass.m_max = (LSM303::vector<int16_t>) { +3353, +5127, +5300 };
  }

  DS3231_get(&tc);

  solar_az_deg = SolarAzimouthRad(longitude, latitude, &tc, timezone) * 180 / PI ;
  solar_el_deg = SolarElevationRad(longitude, latitude, &tc, timezone) * 180 / PI ;

  decl = Decl(gama(&tc)) * 180 / PI ;
  ha = HourAngle (longitude , &tc , timezone )  ;
  sunrise = Sunrise(longitude, latitude, &tc, timezone) ;
  sunset = Sunset(longitude, latitude, &tc, timezone);
  tst = TrueSolarTime(longitude, &tc, timezone);
  sunX = abs(latitude) + decl ;
  if (solar_el_deg >= 0 ){           // day
    iDayNight = 1 ;
  }else{                             // night
    iDayNight = 0 ;
  }

  switch (iTrackMode) {
    case 4: // both axis to park
      yzTarget = dyPark ;  // night park position  E/W
      xzTarget = dxPark ;  // night park position  N/S
      break ;
    case 3: // both axis off no tracking
      break ;
    case 2: // xz tracking  NS
      if ( iDayNight == 1 ) {
        xzTarget = sunX ; // need to map the coordinate system correctly
      } else {
        xzTarget = dxPark ;  // night park position
      }
      break;
    case 1:  // yz tracking   EW
      if (iDayNight == 1) {
        yzTarget = ha ;
      } else {
        yzTarget = dyPark ;  // night park position
      }
      break;
    case -1: // set target to tracking and park both at nigh
      if (iDayNight == 1) {
        yzTarget = ha ;
        xzTarget = sunX ; // need to map the coordinate system correctly
      } else {
        yzTarget = dyPark ;  // night park position  E/W
        xzTarget = dxPark ;  // night park position  N/S
      }
      break;
    default: // set target to tracking
      if (iDayNight == 1) {
        yzTarget = ha ;
        xzTarget = sunX ; // need to map the coordinate system correctly
      } else {
        yzTarget = dyPark ;  // night park position (dont park the other - leave till morning)
      }
      break;
  }
  xzTarget = constrain(xzTarget,xMinVal,xMaxVal);   // constain function... very cool - dont leave home without it !
  yzTarget = constrain(yzTarget,yMinVal,yMaxVal);
  
  if ( iPMode != iMode ) { // clear screen after first change of mode
    lcd.clear();
    if ((( iPMode < 1 ) ||  ( iPMode > 8 )) && ( iMode > 0 ) && (iMode < 9 )) {
      DS3231_get(&t);  // when going into time edit mode update the time - then leave it allone to be worked on by the editor
    }
    iPMode = iMode ;
  }
  if ( iCycle != tc.sec )  {     //only update once a second
    digitalWrite(WATCHDOG,LOW);  // external watch dog pin
    wdt_reset();                 // reset internal watchdog - good puppy
    //   lcd.clear();
    if ( iMode > 0 ){
      lcd.setCursor ( 0, 3 );       // line 3
      lcd.print( "Mode ") ;
      lcd.print(iMode) ;
      lcd.print( " " ) ;        
      switch (tc.sec % 2 ){
        case 1:
          lcd.print( "|" ) ;        
        break;
        default:
          lcd.print( "-" ) ;        
        break;
      }
    }
    lcd.setCursor ( 0, 0 );      // line 0
    switch (iMode) {
      case 22:
        StdFlloatValueDisplay(yMaxVal,"Y Max ");
      break;
      case 21:
        StdFlloatValueDisplay(yMinVal,"Y Min ");
      break;
      case 20:
        StdFlloatValueDisplay(xMaxVal,"X Max ");
      break;
      case 19:
        StdFlloatValueDisplay(xMinVal,"X Min ");
      break;
      case 23:
        lcd.setCursor ( 0, 0 );
        lcd.print("Lat  ");
        lcd.print(latitude,6);
        lcd.setCursor ( 0, 1 );
        lcd.print("Long ");
        lcd.print(longitude,6);
        lcd.setCursor ( 0, 2 );
        lcd.print("Time Zone ");
        lcd.print(timezone);
        break;
      case 24:
        //        lcd.clear();
        lcd.setCursor ( 0, 0 );
        lcd.print("X/Z ");
        if ( xzAng > 0 ) {
          lcd.print("+");
        }
        lcd.print(xzAng);
        lcd.setCursor ( 10, 0 );
        lcd.print("Y/Z ");
        if ( yzAng > 0 ) {
          lcd.print("+");
        }
        lcd.print(yzAng);
        lcd.setCursor ( 0, 1 );
        lcd.print("TX ");
        if (( xzTarget) > 0 ) {
          lcd.print("+");
        }
        lcd.print(( xzTarget));
        lcd.setCursor ( 10, 1 );
        lcd.print("TY ");
        if ((  yzTarget) > 0 ) {
          lcd.print("+");
        }
        lcd.print((  yzTarget));
        lcd.setCursor ( 0, 2 );
        lcd.print("DX ");
        dTmp = ( xzAng - xzTarget) ;
        if (dTmp > 0 ) {
          lcd.print("+");
        }
        lcd.print(dTmp);
        lcd.setCursor ( 10, 2 );
        lcd.print("DY ");
        dTmp = ( yzAng - yzTarget) ;
        if (dTmp > 0 ) {
          lcd.print("+");
        }
        lcd.print(dTmp);

        lcd.setCursor ( 10, 3 );       // line 2
        lcd.print( "T ") ;
        lcd.print(T) ;

        lcd.setCursor ( 18, 3 );       // line 2
        if (( digitalRead(RELAY_YZ_F) == LOW )) {
          lcd.print( "W") ;
        }else{
          if (( digitalRead(RELAY_YZ_R) == LOW )) {
            lcd.print( "E") ;
          }else{
            lcd.print( " ") ;            
          }
        }
        lcd.setCursor ( 19, 3 );       // line 2
        if (( digitalRead(RELAY_XZ_F) == LOW )) {
          lcd.print( "N") ;
        }else{
          if (( digitalRead(RELAY_XZ_R) == LOW )) {
            lcd.print( "S") ;
          }else{
            lcd.print( " ") ;            
          }
        }
        
        break;
      case 18: // SAVE ALL
        lcd.setCursor ( 0, 1 );       // line 1
        if ( iSave != 0 ) {
          lcd.print( "SAVE REQUIRED") ;
        } else {
          lcd.print( "### SAVED ###") ;
        }
        if ( iDoSave == 2 ) {
          EEPROM.put( 0 , xzH );
          EEPROM.put(0 + (1 * sizeof(float)) , yzH );
          EEPROM.put(0 + (2 * sizeof(float)) , dyPark );
          EEPROM.put(0 + (3 * sizeof(float)) , dxPark );
          EEPROM.put(0 + (4 * sizeof(float)) , xzOffset );
          EEPROM.put(0 + (5 * sizeof(float)) , yzOffset );
          EEPROM.put(0 + (6 * sizeof(float)) , xzTarget );
          EEPROM.put(0 + (7 * sizeof(float)) , yzTarget );
          EEPROM.put(0 + (8 * sizeof(float)) , xMinVal );
          EEPROM.put(0 + (9 * sizeof(float)) , xMaxVal );
          EEPROM.put(0 + (10 * sizeof(float)) , yMinVal );
          EEPROM.put(0 + (11 * sizeof(float)) , yMaxVal );
          EEPROM.put(0 + (12 * sizeof(float)) , iTrackMode );
          EEPROM.put(0 + (13 * sizeof(float)) , latitude );
          EEPROM.put(0 + (14 * sizeof(float)) , longitude );
          EEPROM.put(0 + (15 * sizeof(float)) , timezone );
          iDoSave = 0 ;
          iSave = 0 ;
        }
        lcd.setCursor ( 0, 2 );       // line 2
        lcd.print( "PB to Save ALL") ;
      break ;
      case 17:     // yzH hysterisis
        StdFlloatValueDisplay(yzH,"Y Hysteresis ");
      break;
      case 16:     // xzH  hysterisis
        StdFlloatValueDisplay(xzH,"X Hysteresis ");
      break;
      case 15:     // yOffset
        StdFlloatValueDisplay(yzOffset,"Y Offset ");
      break;
      case 14:     // xOffset
        StdFlloatValueDisplay(xzOffset,"X Offset ");
      break;
      case 13:
        lcd.setCursor ( 0, 1 );       // line 1
        lcd.print(iTrackMode) ;
        lcd.print( " - ") ;
        PrintTrackerMode(iTrackMode);
        lcd.setCursor ( 0, 2 );       // line 2
        lcd.print( "PB change track mode") ;
        break;
      case 12:
        StdFlloatValueDisplay(dxPark,"X Park ");

        break;
      case 11:
        StdFlloatValueDisplay(dyPark,"Y Park ");

        break;
      case 10:
        StdFlloatValueDisplay(yzTarget,"Y ");
        lcd.print( " WE angle") ;
        break;
      case 9:
        StdFlloatValueDisplay(xzTarget,"X ");
        lcd.print( " NS angle") ;
        break;
      case 8:
        snprintf(buff, BUFF_MAX, "%d/%02d/%02d %02d:%02d:%02d", tc.year, tc.mon, tc.mday , tc.hour, tc.min, tc.sec);
        lcd.print(buff) ;
        lcd.setCursor ( 0, 1 );       // line 2
        snprintf(buff, BUFF_MAX, "%d/%02d/%02d %02d:%02d:%02d", t.year, t.mon, t.mday , t.hour, t.min, t.sec);
        lcd.print(buff) ;
        lcd.setCursor ( 0, 2 );       // line 2
        lcd.print( "Press PB to set time ") ;
        if ( not  digitalRead(ENCODER_PB) ) {
          DS3231_set(t);
        }
        break;
      case 1:
      case 2:
      case 3:
      case 4:
      case 5:
      case 6:
      case 7:
        snprintf(buff, BUFF_MAX, "%d/%02d/%02d %02d:%02d:%02d", t.year, t.mon, t.mday , t.hour, t.min, t.sec);
        lcd.print(buff) ;
        if (iMode == 7) {
          lcd.setCursor ( 0, 1 );
          lcd.print( "Day - > ") ;
          PrintDay(t.wday);
        }
        lcd.setCursor ( 0, 2 );
        lcd.print( "Hold in set") ;
        lcd.setCursor ( 10, 3 ) ;
        switch (iMode) {
          case 1:
            lcd.print( "Year") ;
            break;
          case 2:
            lcd.print( "Month") ;
            break;
          case 3:
            lcd.print( "Day") ;
            break;
          case 4:
            lcd.print( "Hour") ;
            break;
          case 5:
            lcd.print( "Min") ;
            break;
          case 6:
            lcd.print( "Sec") ;
            break;
          case 7:
            lcd.print( "WDay") ;
            break;
        }
        //    lcd.noBlink();
        break;
      default:   // 0
        DS3231_get(&tc);                                                     // update the time registers
        snprintf(buff, BUFF_MAX, "%d/%02d/%02d %02d:%02d:%02d", tc.year, tc.mon, tc.mday , tc.hour, tc.min, tc.sec);
        lcd.print(buff) ;
        lcd.setCursor ( 0, 1 );       // line 2 has the Az and El
        lcd.print( "Az ") ;
        lcd.print(solar_az_deg) ;
        lcd.setCursor ( 10, 1 );
        lcd.print( "El ") ;
        lcd.print(solar_el_deg) ;

        lcd.setCursor ( 0, 2 );
        lcd.print("D ");
        if ( decl > 0 ) {
          lcd.print("+");
        }
        lcd.print(decl);
        lcd.setCursor ( 10, 2 );
        lcd.print("H ");
        if ( ha > 0 ) {
          lcd.print("+");
        }
        lcd.print(ha);
        lcd.setCursor ( 0, 3 );   // third line is sunset and sunrise
        snprintf(buff, BUFF_MAX, "%02d:%02d", HrsSolarTime(sunrise), MinSolarTime(sunrise));
        lcd.print(buff) ;
        if (iDayNight == 1) {
          lcd.setCursor ( 8, 3 );
          lcd.print(" DAY ") ;
        } else {
          lcd.setCursor ( 7, 3 );
          lcd.print("NIGHT") ;
        }
        lcd.setCursor ( 15, 3 );
        snprintf(buff, BUFF_MAX, "%02d:%02d", HrsSolarTime(sunset), MinSolarTime(sunset));
        lcd.print(buff) ;
        break;
    }

    iCycle = tc.sec ;
    DisplayMeatBall() ;
    if (tc.sec % 2 == 0 ) {
      digitalWrite(13, HIGH);
    } else {
      digitalWrite(13, LOW);
    }
  }

  if (((tc.hour > 19 ) || ( tc.hour < 5 )) && (iTrackMode < 3)) {
    ActivateRelays(0) ;  // power down at night if in tracking mode
  }else{
    ActivateRelays(1) ;    
  }
  digitalWrite(WATCHDOG,HIGH); // nice doggy
  
  state1 = slave1.poll( (uint16_t*)&iMode, MAX_MODBUS_DATA );

  switch (state1) {
    case EXC_ADDR_RANGE:
      //      Serial.println("EXC_ADDR_RANGE PORT 1");
      break;
    case EXC_FUNC_CODE:
      //    Serial.println("EXC_FUNC_CODE PORT 1");
      break;
    case EXC_REGS_QUANT:
      //      Serial.println("EXC_REGS_QUANT PORT 1");
      break;
  }

}






float DayOfYear(uint16_t iYear , uint8_t iMon , uint8_t iDay , uint8_t iHour , uint8_t iMin ) {
  int i ;
  float iTDay ;

  iTDay = iDay - 1 ;  // this is zero referenced
  for ( i = 1 ; i < iMon ; i++ ) {
    switch (i) {
      case 1:
      case 3:
      case 5:
      case 7:
      case 8:
      case 10:
      case 12:
        iTDay += 31 ;
        break;
      case 4:
      case 6:
      case 9:
      case 11:
        iTDay += 30 ;
        break;
      case 2 :
        if ((iYear % 4) == 0 ) {
          iTDay += 29 ;
        } else {
          iTDay += 28 ;
        }
        break;
    }
  }
  iTDay += (( 1.0 * iHour - 12 ) / 24 ) ;
  //  iDay +=  1.0 * iMin  / 1440 ;
  return (iTDay);
}

void PrintTrackerMode(int iTarget) {
  switch (iTarget) {
    case 1:
      lcd.print( "EW Only") ;
      break;
    case 2:
      lcd.print( "NS Only") ;
      break;
    case 3:
      lcd.print( "None") ;
      break;
    case 4:
      lcd.print( "Both Park") ;
      break;
    case -1:   
      lcd.print( "2P ") ;
    default:
      lcd.print( "Both Track") ;
      break;
  }
}


void PrintDay(int iTarget) {
  switch (iTarget) {
    case 1:
      lcd.print( "Sun") ;
      break;
    case 2:
      lcd.print( "Mon") ;
      break;
    case 3:
      lcd.print( "Tue") ;
      break;
    case 4:
      lcd.print( "Wed") ;
      break;
    case 5:
      lcd.print( "Thr") ;
      break;
    case 6:
      lcd.print( "Fri") ;
      break;
    case 7:
      lcd.print( "Sat") ;
      break;
  }
}

int HrsSolarTime(float target) {
  int i ;
  i = target ;
  return (  i / 60 );
}
int MinSolarTime(float target) {
  int i ;
  i = target  ;
  return (   i % 60 );
}

void counter()
{

  if ( ( prev_millis + 100 ) < millis() ) {
    if ( digitalRead(ENCODER_PB) ) {
      IncInt(&iMode,digitalRead(ENCODER_PINB),1,0,24,true);
    } else {
      if ((iMode > 8 ) && (iMode < 23) && (iMode != 18)) {
        iSave = 1;
      }
      switch (iMode) {  // setting the time and date etc
        case 1:   // year
          IncInt(&t.year ,digitalRead(ENCODER_PINB),1,2016,3116,false);
          break;
        case 2:   // month
          IncUint8_t(&t.mon ,digitalRead(ENCODER_PINB),1,1,12,true);
          break;
        case 3:  // day
          IncUint8_t(&t.mday ,digitalRead(ENCODER_PINB),1,1,31,true);
          break;
        case 4:  // hour
          IncUint8_t(&t.hour ,digitalRead(ENCODER_PINB),1,0,23,true);
          break;
        case 5:
          IncUint8_t(&t.min ,digitalRead(ENCODER_PINB),1,0,59,true);
          break;
        case 6:
          IncUint8_t(&t.sec ,digitalRead(ENCODER_PINB),1,0,59,true);
          break;
        case 7:
          IncUint8_t(&t.wday ,digitalRead(ENCODER_PINB),1,1,7,true);
          break;
        case 9:
          IncFloat(&xzTarget,digitalRead(ENCODER_PINB),1,0,90,false);
          break;
        case 10:
          IncFloat(&yzTarget,digitalRead(ENCODER_PINB),1,-80,80,false);
          break;
        case 11:
          IncFloat(&dyPark,digitalRead(ENCODER_PINB),1,-80,80,false);
          break;
        case 12:
          IncFloat(&dxPark,digitalRead(ENCODER_PINB),1,-80,80,false);
          break;
        case 13:
          IncInt(&iTrackMode,digitalRead(ENCODER_PINB),1,-1,4,true);
          break;
        case 14:
          IncFloat(&xzOffset,digitalRead(ENCODER_PINB),0.5,-20,20,false);
          break;
        case 15:
          IncFloat(&yzOffset,digitalRead(ENCODER_PINB),0.5,-20,20,false);
          break;
        case 16:
          IncFloat(&xzH,digitalRead(ENCODER_PINB),0.25,-20,20,false);
...

This file has been truncated, please download it to see its full contents.
Solar_Calcs.basVBScript
Early proof of concept code....
Attribute VB_Name = "SolarStuff"
Option Explicit

Global gdblLat As Double
Global gdblLong As Double

Global Const PI = 3.14159265358979

Function arccos(x)
    arccos = Atn(-x / Sqr(-x * x + 1)) + 2 * Atn(1)
End Function

Function arcsin(x)
    arcsin = Atn(x / Sqr(-x * x + 1))
End Function

'
' Fraction of a year
'
Function gama(Optional MyDate) As Double
Dim dtNow As Date
Dim dt1st As Date
    If IsMissing(MyDate) Then
        MyDate = Now()
    Else
        MyDate = CDate(MyDate)
    End If
    dt1st = CDate("1/1/" & Year(MyDate))
    gama = (2 * PI / 365) * (DateDiff("y", dt1st, MyDate) + ((Hour(MyDate) - 12) / 24))
'    gama = (2 * PI / 364) * ((DateDiff("y", dt1st, MyDate) + ((hour(MyDate) - 12) * 60 + Minute(MyDate)) / 1440))
'    gama = (2 * PI / 365) * (DateDiff("y", dt1st, MyDate) + ((hour(MyDate)) / 24))
End Function
'
'
' Equation of time
' In Minutes
Function eqTime(g As Double) As Double
    eqTime = 229.18 * (0.000075 + (0.001868 * Cos(g)) - (0.032077 * Sin(g)) - (0.014615 * Cos(2 * g)) - (0.040849 * Sin(2 * g)))
        
'     eqTime = (229.18 * (0.000075 + 0.001568 * Cos(g) - 0.032077 * Sin(g) - 0.014615 * Cos(2 * g) - 0.040849 * Sin(2 * g)))

End Function
'
'
'   Declination of the sun
'   In Radians
Function Decl(g As Double) As Double
    Decl = 0.006918 - (0.399912 * Cos(g)) + (0.070257 * Sin(g)) - (0.006758 * Cos(2 * g)) + (0.000907 * Sin(2 * g)) - (0.002697 * Cos(3 * g)) + (0.00148 * Sin(3 * g))
'    Decl = (0.006918 - 0.399912 * Cos(g) + 0.070257 * Sin(g) - 0.006758 * Cos(2 * g) + 0.000907 * Sin(2 * g))
End Function
'
'
'  Eq time in minutes
'
Function time_offset(Longitude As Double, MyTime As Date, TimeZone As Long) As Double
    time_offset = (-4 * Longitude) + (60 * TimeZone) - eqTime(gama(MyTime))
End Function

'
'  tst
'  In minutes
'
Function TrueSolarTime(Longitude As Double, MyTime As Date, TimeZone As Long) As Double
    TrueSolarTime = (Hour(MyTime) * 60) + Minute(MyTime) + (Second(MyTime) / 60) - time_offset(Longitude, MyTime, TimeZone)
End Function
'
'
' in degrees
' -180 to 180
Function hourAngle(Longitude As Double, MyTime As Date, TimeZone As Long) As Double
    hourAngle = (TrueSolarTime(Longitude, MyTime, TimeZone) / 4) - 180   ' 720 minutes is solar noon  -- div 4 gives 180
End Function


' Hour Angle at sunset or sunrise only !!!!
' in degrees  (ie solar zenith is 90.833 deg)
Function ha(lat As Double, MyDate As Date) As Double
    Dim latRad As Double
    latRad = lat * 2 * PI / 360
    ha = arccos((Cos(90.833 * PI / 180) / (Cos(latRad) * Cos(Decl(gama(MyDate)))) - (Tan(latRad) * Tan(Decl(gama(MyDate)))))) / PI * 180
End Function


Function Sunrise(Longitude As Double, lat As Double, MyTime As Date, TimeZone As Long) As Double
    Sunrise = 720 - (4 * (Longitude + ha(lat, MyTime))) + (60 * TimeZone) - eqTime(gama(MyTime))
End Function
Function SunriseTime(Longitude As Double, lat As Double, MyTime As Date, TimeZone As Long) As Date
Dim dblTmp As Double
    dblTmp = Sunrise(Longitude, lat, MyTime, TimeZone)
    SunriseTime = DateAdd("n", CInt(dblTmp), DateSerial(Year(MyTime), Month(MyTime), Day(MyTime)))
End Function

Function Sunset(Longitude As Double, lat As Double, MyTime As Date, TimeZone As Long) As Double
    Sunset = 720 - (4 * (Longitude - ha(lat, MyTime))) + (60 * TimeZone) - eqTime(gama(MyTime))
End Function
Function SunsetTime(Longitude As Double, lat As Double, MyTime As Date, TimeZone As Long) As Date
Dim dblTmp As Double
    dblTmp = Sunset(Longitude, lat, MyTime, TimeZone)
    SunsetTime = DateAdd("n", CInt(dblTmp), DateSerial(Year(MyTime), Month(MyTime), Day(MyTime)))
End Function

Function Snoon(Longitude As Double, lat As Double, MyTime As Date, TimeZone As Long) As Double
    Snoon = 720 - (4 * Longitude) + (60 * TimeZone) - eqTime(gama(MyTime))
End Function
Function SnoonTime(Longitude As Double, lat As Double, MyTime As Date, TimeZone As Long) As Date
Dim dblTmp As Double
    dblTmp = Snoon(Longitude, lat, MyTime, TimeZone)
    SnoonTime = DateAdd("n", CInt(dblTmp), DateSerial(Year(MyTime), Month(MyTime), Day(MyTime)))
End Function

Function SolarZenithRad(Longitude As Double, lat As Double, MyTime As Date, TimeZone As Long) As Double
Dim dTmp As Double
Dim latRad As Double
Dim decrad As Double
Dim HourAngleRad As Double
    
    latRad = lat * 2 * PI / 360
    decrad = Decl(gama(MyTime))
    HourAngleRad = hourAngle(Longitude, MyTime, TimeZone) * PI / 180
    
    dTmp = arccos((Sin(latRad) * Sin(decrad)) + (Cos(latRad) * Cos(decrad) * Cos(HourAngleRad)))
    SolarZenithRad = dTmp
End Function

Function SolarElevationRad(Longitude As Double, lat As Double, MyTime As Date, TimeZone As Long) As Double
Dim dTmp As Double
Dim latRad As Double
Dim decrad As Double
Dim HourAngleRad As Double
    
    latRad = lat * 2 * PI / 360
    decrad = Decl(gama(MyTime))
    HourAngleRad = hourAngle(Longitude, MyTime, TimeZone) * PI / 180
    
    dTmp = arccos((Sin(latRad) * Sin(decrad)) + (Cos(latRad) * Cos(decrad) * Cos(HourAngleRad)))
    SolarElevationRad = (PI / 2) - dTmp
End Function


Function SolarAzimuthRad(Longitude As Double, lat As Double, MyTime As Date, TimeZone As Long) As Double
Dim dTmp As Double
Dim latRad As Double
Dim decrad As Double
Dim solzenrad As Double
Dim HourAngleRad As Double

    latRad = lat * 2 * PI / 360
    decrad = Decl(gama(MyTime))
    solzenrad = SolarZenithRad(Longitude, lat, MyTime, TimeZone)
    HourAngleRad = hourAngle(Longitude, MyTime, TimeZone) * PI / 180

'    dTmp = arccos(-1 * (Cos(solzenrad) * Sin(latrad) - Sin(decrad)) / (Sin(solzenrad) * Cos(latrad)))
'    dTmp = arccos((Sin(decrad) - (Sin(decrad) * Sin(latrad))) / (Sin(solzenrad) * Cos(latrad)))
    dTmp = arccos(((Sin(decrad) * Cos(latRad)) - (Cos(HourAngleRad) * Cos(decrad) * Sin(latRad))) / Sin(solzenrad))
    If HourAngleRad < 0 Then
        SolarAzimuthRad = dTmp
    Else
        SolarAzimuthRad = (2 * PI) - dTmp
    End If
    
End Function

'
'  ????????   nor working ?????  from winkipedia
'
Function SolarDeclinationRad(MyDate As Date) As Double
Dim dTmp As Double
Dim N As Double

    N = DateDiff("y", CDate("1/1/" & CStr(Year(MyDate))), MyDate) + (((Hour(MyDate) - 12) * 60 + Minute(MyDate)) / 1440) ' days since 1/1 in year
    dTmp = -arcsin(0.39779 * Cos(0.0172029 * (N + 10) + 0.0334 * Sin(0.0172029 * (N - 2))))  ' constants converted to radians (was degrees in winkipedia)
    SolarDeclinationRad = dTmp '  * 180 / PI  ' converter to degrees
End Function


Function JulianDay(MyDate As Date) As Double

End Function
Function JulianCentury(MyDate As Date) As Double

End Function
TRACKER_ALL_IN_ONE_BOTTOM_PWM_SOFT.inoC/C++
PWM soft starter variation on the original design ... wiring changes described in top of the file
//#include <SFE_BMP180.h>
#include <avr/wdt.h>
#include <Wire.h>
#include <LSM303.h>       // modified ... fixed a couple of bugs
#include <LiquidCrystal_I2C.h>
//#include <L3G.h>
#include "ds3231.h"
#include "ht16k33.h"
#include <ModbusRtu.h>   // Modified ... this no longer a stock lib - Slave supports register translation/mapping
#include <EEPROM.h>
#include <math.h>

#define ID   1

#define BUFF_MAX 32
#define PARK_EAST 1
#define PARK_WEST 2
#define PARK_NORTH 3
#define PARK_SOUTH 4
#define PARK_FLAT 5

#define MOTOR_DWELL 100

#define MAX_MODBUS_DATA  60

#define HT16K33_DSP_NOBLINK 0   // constants for the half arsed cheapo display
#define HT16K33_DSP_BLINK1HZ 4
#define HT16K33_DSP_BLINK2HZ 2
#define HT16K33_DSP_BLINK05HZ 6


const byte ENCODER_PINA = 2;  // encoder connects - interupt pin
const byte ENCODER_PINB = 3;  // non interupt pin
const byte ENCODER_PB = 8;

const byte RELAY_XZ_DIR = 4; // DIR 1    X+ X-  North / South    Was the X+ N relay
const byte RELAY_XZ_PWM = 5; // PWM 1    Speed North / South     Was the X- S relay
const byte RELAY_YZ_PWM = 6; // PWM 2    Speed East / West       Was the Y+ W relay
const byte RELAY_YZ_DIR = 7; // DIR 2    Y+ Y- East / West       Was the Y- E relay

const byte UNUSED09 =  9;  // cycle timer 26 Hz  38ms period
const byte UNUSED10 = 10;
const byte UNUSED11 = 11;
const byte WATCHDOG = 12;


//L3G gyro;
LSM303 compass;
LiquidCrystal_I2C lcd(0x27);  // Set the LCD I2C address I modified the lib for default wiring

//SFE_BMP180 pressure;
HT16K33 HT;

Modbus slave1(ID, 1, 0); // this is slave ID and RS-232 or USB-FTDI

uint8_t time[8];
int motor_recycle = 0 ;
char recv[BUFF_MAX];
unsigned int recv_size = 0;
unsigned long prev_millis;
uint8_t u8state; //!< machine state
uint8_t u8query; //!< pointer to message query

struct ts t;             // MODBUS MAP
struct ts tc;            // 44 
int iSave = 0 ;          // 43    
int iDoSave = 0 ;        // 42
float T;                 // 40 temperature of board
float xzTarget ;         // 38 target for angles
float yzTarget ;         // 36
float xzH ;              // 34 hyserisis zone
float yzH ;              // 32
float xzAng;             // 30 current angles
float yzAng;             // 28
float xzOffset;          // 26 offset xz
float yzOffset;          // 24 offset yz
float dyPark;            // 22 parking position
float dxPark;            // 20 
float xMinVal ;          // 18 Min and Max values  X - N/S
float xMaxVal ;          // 16 
float yMinVal ;          // 14  Y  -- E/W
float yMaxVal ;          // 12 
float latitude;          // 10
float longitude;         // 8
int timezone;            // 7
int iDayNight ;          // 6
float solar_az_deg;      // 4
float solar_el_deg;      // 2
int iTrackMode ;         // 1 
int iMode ;              // 0

int iCycle ;
int iPMode;
int iPWM_YZ ;
int iPWM_XZ ;

unsigned long tempus;
int8_t state1 = 0;

void StopYZ(){
  iPWM_YZ=0 ;
  motor_recycle = MOTOR_DWELL ;
}
void StopXZ(){
  iPWM_XZ=0 ;
  motor_recycle = MOTOR_DWELL ;
}

void ActivateRelays(int iAllStop) {
  if (motor_recycle > 0 ){
    motor_recycle-- ;
  }
  if ( iAllStop == 0 ) {
    StopYZ() ;
    StopXZ() ;
  } else {
    if (( iPWM_YZ==0 ) && (motor_recycle == 0 )){
      if (((yzAng  ) < ( yzTarget - yzH )) ) {   // do Y ie E/W before N/S
        digitalWrite(RELAY_YZ_DIR, LOW) ;
        iPWM_YZ=128 ;
      }
      if (((yzAng ) > (yzTarget + yzH )) ) {
        digitalWrite(RELAY_YZ_DIR, HIGH) ;
        iPWM_YZ=128 ;
      }
    }
    if ( iPWM_YZ>0 ){
      if ((yzAng > yzTarget) && ( digitalRead(RELAY_YZ_DIR)==LOW )) {
        StopYZ() ;
      }
      if ((yzAng < yzTarget) && ( digitalRead(RELAY_YZ_DIR)==HIGH )) {
        StopYZ() ;
      }
    }

    if (( iPWM_YZ==0)) {  // if finished on E/W you can do N/S
        if (( iPWM_XZ==0 ) && (motor_recycle == 0 )){
          if ((xzAng < ( xzTarget - xzH ))  )  { // turn on if not in tolerance
            digitalWrite(RELAY_XZ_DIR, LOW) ;
            iPWM_XZ=128 ;
          }
          if ((xzAng > ( xzTarget + xzH )) ) { // turn on if not in tolerance
            digitalWrite(RELAY_XZ_DIR, HIGH) ;
            iPWM_XZ=128 ;
          }
        }
    }else{
      if ((iPWM_XZ>0 )){
        StopXZ() ;
      }
    }
    if ( iPWM_XZ>0 ){
      if ((xzAng > xzTarget ) && ( digitalRead(RELAY_XZ_DIR)==LOW ))  { // if on turn off
        StopXZ() ;
      }
      if ((xzAng < xzTarget ) && ( digitalRead(RELAY_XZ_DIR)==HIGH ))  { // if on turn off
        StopXZ() ;
      }
    }
  }
  if (iPWM_XZ>0){
    iPWM_XZ += 3 ;
  }
  if (iPWM_YZ>0){
    iPWM_YZ += 3 ;
  }
  iPWM_XZ = constrain(iPWM_XZ,0,254);  
  iPWM_YZ = constrain(iPWM_YZ,0,254);  
  analogWrite(RELAY_XZ_PWM,iPWM_XZ);
  analogWrite(RELAY_YZ_PWM,iPWM_YZ);
}

void FloatToModbusWords(float src_value , uint16_t * dest_lo , uint16_t * dest_hi ) {
  uint16_t tempdata[2] ;
  float *tf ;
  tf = (float * )&tempdata[0]  ;
  *tf = src_value ;
  *dest_lo = tempdata[1] ;
  *dest_hi = tempdata[0] ;
}
float FloatFromModbusWords( uint16_t dest_lo , uint16_t dest_hi ) {
  uint16_t tempdata[2] ;
  float *tf ;
  tf = (float * )&tempdata[0]  ;
  tempdata[1] = dest_lo ;
  tempdata[0] = dest_hi  ;
  return (*tf) ;
}

// Arduino doesnt have these to we define from a sandard libruary
float arcsin(float x) {
  return (atan(x / sqrt(-x * x + 1)));
}
float arccos(float x) {
  return (atan(x / sqrt(-x * x + 1)) + (2 * atan(1)));
}
// fractional orbital rotation in radians
float gama(struct ts *tm) {
  return ((2 * PI / 365 ) *  DayOfYear(tm->year , tm->mon , tm->mday , tm->hour , tm->min ));
}
// equation of rime
float eqTime(float g) {
  return (229.18 * ( 0.000075 + ( 0.001868 * cos(g)) - (0.032077 * sin(g)) - (0.014615 * cos (2 * g)) - (0.040849 * sin(2 * g))));
}
// declination of sun in radians
float Decl(float g) {
  return ( 0.006918 - (0.399912 * cos(g)) + (0.070257 * sin(g)) - (0.006758 * cos(2 * g)) + ( 0.000907 * sin(2 * g)) - ( 0.002697 * cos(3 * g)) + (0.00148 * sin(3 * g)) );
}
float TimeOffset(float longitude , struct ts *tm ,  int timezone ) {
  float dTmp ;
  dTmp = (-4.0 * longitude ) + (60 * timezone) - eqTime(gama(tm)) ;
  return (dTmp);
}

float TrueSolarTime(float longitude , struct ts *tm ,  int timezone ) {
  float dTmp ;
  dTmp = ( 60.0 * tm->hour ) + (1.0 * tm->min) + (1.0 * tm->sec / 60) - TimeOffset(longitude, tm, timezone) ;
  return (dTmp);
}
float HourAngle(float longitude , struct  ts *tm ,  int timezone) {
  float dTmp;
  dTmp = (TrueSolarTime(longitude, tm, timezone) / 4 ) - 180 ; // 720 minutes is solar noon -- div 4 is 180
  return (dTmp);
}
// Hour angle for sunrise and sunset only
float HA (float lat , struct ts *tm ) {
  float latRad ;
  latRad = lat * 2 * PI / 360 ;
  return ( acos((cos(90.833 * PI / 180 ) / ( cos(latRad) * cos(Decl(gama(tm)))) - (tan(latRad) * tan(Decl(gama(tm)))))) / PI * 180  );
}

float Sunrise(float longitude , float lat , struct ts *tm , int timezone) {
  return (720 - ( 4.0 * (longitude + HA(lat, tm))) + (60 * timezone) - eqTime(gama(tm))  ) ;
}
float Sunset(float longitude , float lat , struct ts *tm , int timezone) {
  return (720 - ( 4.0 * (longitude - HA(lat, tm))) + (60 * timezone) - eqTime(gama(tm))  ) ;
}
float SNoon(float longitude , float lat , struct ts *tm , int timezone) {
  return (720 - ( 4.0 * (longitude  + (60 * timezone) - eqTime(gama(tm))))  ) ;
}

float SolarZenithRad(float longitude , float lat , struct ts *tm , int timezone) {
  float latRad ;
  float decRad ;
  float HourAngleRad ;
  float dTmp ;

  latRad = lat * 2 * PI / 360 ;
  decRad = Decl(gama(tm));
  HourAngleRad = HourAngle (longitude , tm , timezone ) * PI / 180 ;
  dTmp = acos((sin(latRad) * sin(decRad)) + (cos(latRad) * cos(decRad) * cos(HourAngleRad)));
  return (dTmp) ;

}
float SolarElevationRad(float longitude , float lat , struct ts *tm ,  int timezone ) {
  return ((PI / 2) - SolarZenithRad(longitude , lat , tm , timezone )) ;
}

float SolarAzimouthRad(float longitude , float lat , struct ts *tm ,  int timezone) {
  float latRad ;
  float decRad ;
  float solarzenRad ;
  float HourAngleRad ;
  float dTmp ;
  latRad = lat * 2 * PI / 360 ;
  decRad = Decl(gama(tm));
  solarzenRad = SolarZenithRad ( longitude , lat , tm , timezone ) ;
  HourAngleRad = HourAngle (longitude , tm , timezone ) * PI / 180 ;
  dTmp = acos(((sin(decRad) * cos(latRad)) - (cos(HourAngleRad) * cos(decRad) * sin(latRad))) / sin(solarzenRad)) ;
  if ( HourAngleRad < 0 ) {
    return (dTmp) ;
  } else {
    return ((2 * PI) - dTmp) ;
  }
}

float LoadFloatFromEEPROM(int address,float minval,float maxval, float defaultval){
float tmp ;  
  EEPROM.get(0 + (address * sizeof(float)) , tmp );
  if (( tmp < minval ) || ( tmp > maxval )|| (NumberOK(tmp) == 1)) {
    tmp = defaultval ;
    EEPROM.put(0 + (address * sizeof(float)) , tmp );
  }
  return(tmp);  
}
int LoadIntFromEEPROM(int address,int minval,int maxval, int defaultval){
int tmp ;  
  EEPROM.get(0 + (address * sizeof(float)) , tmp );
  if (( tmp < minval ) || ( tmp > maxval )) {
    tmp = defaultval ;
    EEPROM.put(0 + (address * sizeof(float)) , tmp );
  }
  return(tmp);  
}

int NumberOK (float target) {
  int tmp = 0 ;
  tmp = isnan(target);
  if ( tmp != 1 ) {
    tmp = isinf(target);
  }
  return (tmp);
}

void IncFloat(float * target, boolean bInc, float increment, float minval, float maxval , boolean bWrap){
    if( bInc  ){
      *target += increment ;  
    }else{
      *target -= increment ;  
    }
    if ( *target > maxval ){
      if ( bWrap  ){
        *target = minval ;
      }else{
        *target = maxval ;
      }
    }
    if ( *target < minval ){
      if ( bWrap  ){
        *target = maxval ;
      }else{
        *target = minval ;
      }
    }
}
void IncInt(int * target, boolean bInc, int increment, int minval, int maxval , boolean bWrap){
    if( bInc  ){
      *target += increment ;  
    }else{
      *target -= increment ;  
    }
    if ( *target > maxval ){
      if ( bWrap  ){
        *target = minval ;
      }else{
        *target = maxval ;
      }
    }
    if ( *target < minval ){
      if ( bWrap  ){
        *target = maxval ;
      }else{
        *target = minval ;
      }
    }  
}
void IncUint8_t (uint8_t * target , boolean bInc, int increment, int minval, int maxval , boolean bWrap){
    if( bInc  ){
      *target += increment ;  
    }else{
      *target -= increment ;  
    }
    if ( *target > maxval ){
      if ( bWrap  ){
        *target = minval ;
      }else{
        *target = maxval ;
      }
    }
    if ( *target < minval ){
      if ( bWrap  ){
        *target = maxval ;
      }else{
        *target = minval ;
      }
    }  
}
void StdFlloatValueDisplay(float target, char message[]){
  lcd.setCursor ( 0, 1 );       // line 1
  lcd.print(message) ;
  lcd.print(target) ;
  lcd.setCursor ( 0, 2 );       // line 2
  lcd.print( "PB to set ") ;
}
 

void setup() {
  int led ;
  
  Wire.begin();
  lcd.begin(20, 4);
  lcd.home();
  lcd.setBacklightPin(3, NEGATIVE);
  lcd.noCursor(); 

  MCUSR &= ~_BV(WDRF);
  wdt_disable();
  
  compass.init();
  compass.enableDefault();
  compass.setTimeout(1000);
  
  pinMode(RELAY_XZ_DIR, OUTPUT); // Outputs for PWM motor control
  pinMode(RELAY_XZ_PWM, OUTPUT); // 
  pinMode(RELAY_YZ_PWM, OUTPUT); //
  pinMode(RELAY_YZ_DIR, OUTPUT); // 
  iPWM_YZ = 0 ;
  iPWM_XZ = 0 ;
  pinMode(13, OUTPUT); //
  digitalWrite(13, HIGH );
  ActivateRelays(0); // call an all stop first

  Serial.begin(9600) ;  // use as a diagnostic port
  slave1.begin( 9600 ); // RS-232 to base of tower
  tempus = millis() + 100;

  pinMode(ENCODER_PINA, INPUT_PULLUP);
  pinMode(ENCODER_PINB, INPUT_PULLUP);
  pinMode(ENCODER_PB, INPUT_PULLUP);
  attachInterrupt(0, counter, FALLING);

  pinMode(UNUSED09, OUTPUT);  // unused so dont leave floating set as output
  pinMode(UNUSED10, OUTPUT);  // 
  pinMode(UNUSED11, OUTPUT);  // 
  pinMode(WATCHDOG, OUTPUT);  // 
  digitalWrite(WATCHDOG,HIGH);

  compass.m_min = (LSM303::vector<int16_t>) {-3848, -1822, -1551 };   // calibration figures are empirical
  compass.m_max = (LSM303::vector<int16_t>) { +3353, +5127, +5300};

  xzH = LoadFloatFromEEPROM(0,0.1,20.0,4.0);  // hysterisis NS
  yzH = LoadFloatFromEEPROM(1,0.1,20.0,4.0);  //    ""      EW

  dyPark = LoadFloatFromEEPROM(2,-70.0,50.0,0);  
  dxPark = LoadFloatFromEEPROM(3,-5.0,50.0,0.0);  
  
  xzOffset = LoadFloatFromEEPROM(4,-90.0,90.0,0);  // NS
  yzOffset = LoadFloatFromEEPROM(5,-90.0,90.0,0);  // EW

  xzTarget = LoadFloatFromEEPROM(6,-90.0,90.0,0);  // NS
  yzTarget = LoadFloatFromEEPROM(7,-90.0,90.0,0);  // EW

  xMinVal = LoadFloatFromEEPROM(8,-10.0,60.0,0.0);   // NS
  xMaxVal = LoadFloatFromEEPROM(9,-10.0,60.0,45);

  yMinVal = LoadFloatFromEEPROM(10,-70.0,50.0,-65);  // EW
  yMaxVal = LoadFloatFromEEPROM(11,-70.0,50.0,45);

  iMode = 0 ;
  iTrackMode = LoadIntFromEEPROM(12,-1,4,0);
  
//  latitude = -34.051219 ;
//  longitude = 142.013618 ;
//  timezone = 10 ;
   
  latitude = LoadFloatFromEEPROM(13,-90.0,90.0,-34.051219);
  longitude = LoadFloatFromEEPROM(14,-180.0,180.0,142.013618);
  timezone = LoadIntFromEEPROM(15,0,23,10);
   
  DS3231_init(DS3231_INTCN);
  DS3231_get(&tc);
  DS3231_get(&t);
  lcd.clear();
  HT.begin(0x00);
  for (led = 0; led < 127; led++) {
    HT.clearLedNow(led);
  }
  wdt_enable(WDTO_4S);
}

void loop() {
  float P;
  float sunInc;
  float sunAng;
  float xzRatio;
  float yzRatio;
  char buff[BUFF_MAX];
  float decl ;
  float eqtime ;
  float dTmp ;
  float ha ;
  float heading ;
  float sunrise ;
  float sunset ;
  float tst ;
  float sunX ;

  compass.read();  // this reads all 6 channels
//  compass.readAcc();
//  compass.readMag();
  digitalWrite(UNUSED09,!digitalRead(UNUSED09));   // toggle this output so I can measure the cycle time with a scope
  
  T = DS3231_get_treg();
  heading = compass.heading((LSM303::vector<int>) { 1, 0, 0 });
  
  if (( compass.a.z != 0) && (!compass.timeoutOccurred() ))  {
    xzRatio = (float)compass.a.y / abs((float)compass.a.z) ;   //  yep if your sharp you can see I swapped axis orientation late in the programming
    yzRatio = (float)compass.a.x / abs((float)compass.a.z) ;
    xzAng = ((float)atan(xzRatio) / PI * -180 ) + xzOffset ;   // good old offsets or fudge factors
    yzAng = ((float)atan(yzRatio) / PI * 180 ) + yzOffset ;
    digitalWrite(13, LOW);
  }else{                                                       // try restarting the compass/accelerometer modual - cos he gone walkabout...
    Wire.begin();   // reset the I2C
    compass.init();
    compass.enableDefault();
    compass.setTimeout(1000);                                  // BTW I fixed up the int / long issue in the time out function in the LM303 lib I was using
    compass.m_min = (LSM303::vector<int16_t>) {-3848, -1822, -1551 };   // calibration figures are empirical (just whirl it around a bit and records the min max !!)
    compass.m_max = (LSM303::vector<int16_t>) { +3353, +5127, +5300 };
    if (tc.sec % 2 == 0 ) {
      digitalWrite(13, HIGH);
    } else {
      digitalWrite(13, LOW);
    }
    HT.begin(0x00);
  }

  DS3231_get(&tc);

  solar_az_deg = SolarAzimouthRad(longitude, latitude, &tc, timezone) * 180 / PI ;
  solar_el_deg = SolarElevationRad(longitude, latitude, &tc, timezone) * 180 / PI ;

  decl = Decl(gama(&tc)) * 180 / PI ;
  ha = HourAngle (longitude , &tc , timezone )  ;
  sunrise = Sunrise(longitude, latitude, &tc, timezone) ;
  sunset = Sunset(longitude, latitude, &tc, timezone);
  tst = TrueSolarTime(longitude, &tc, timezone);
  sunX = abs(latitude) + decl ;
  if (solar_el_deg >= 0 ){           // day
    iDayNight = 1 ;
  }else{                             // night
    iDayNight = 0 ;
  }

  switch (iTrackMode) {
    case 4: // both axis to park
      yzTarget = dyPark ;  // night park position  E/W
      xzTarget = dxPark ;  // night park position  N/S
      break ;
    case 3: // both axis off no tracking
      break ;
    case 2: // xz tracking  NS
      if ( iDayNight == 1 ) {
        xzTarget = sunX ; // need to map the coordinate system correctly
      } else {
        xzTarget = dxPark ;  // night park position
      }
      break;
    case 1:  // yz tracking   EW
      if (iDayNight == 1) {
        yzTarget = ha ;
      } else {
        yzTarget = dyPark ;  // night park position
      }
      break;
    case -1: // set target to tracking and park both at nigh
      if (iDayNight == 1) {
        yzTarget = ha ;
        xzTarget = sunX ; // need to map the coordinate system correctly
      } else {
        yzTarget = dyPark ;  // night park position  E/W
        xzTarget = dxPark ;  // night park position  N/S
      }
      break;
    default: // set target to tracking
      if (iDayNight == 1) {
        yzTarget = ha ;
        xzTarget = sunX ; // need to map the coordinate system correctly
      } else {
        yzTarget = dyPark ;  // night park position (dont park the other - leave till morning)
      }
      break;
  }
  xzTarget = constrain(xzTarget,xMinVal,xMaxVal);   // constain function... very cool - dont leave home without it !
  yzTarget = constrain(yzTarget,yMinVal,yMaxVal);
  
  if ( iPMode != iMode ) { // clear screen after first change of mode
    lcd.clear();
    if ((( iPMode < 1 ) ||  ( iPMode > 8 )) && ( iMode > 0 ) && (iMode < 9 )) {
      DS3231_get(&t);  // when going into time edit mode update the time - then leave it allone to be worked on by the editor
    }
    iPMode = iMode ;
  }
  if ( iCycle != tc.sec )  {     //only update once a second
    digitalWrite(WATCHDOG,LOW);  // external watch dog pin
    wdt_reset();                 // reset internal watchdog - good puppy
    //   lcd.clear();
    if ( iMode > 0 ){
      lcd.setCursor ( 0, 3 );       // line 3
      lcd.print( "Mode ") ;
      lcd.print(iMode) ;
      lcd.print( " " ) ;        
      switch (tc.sec % 2 ){
        case 1:
          lcd.print( "|" ) ;        
        break;
        default:
          lcd.print( "-" ) ;        
        break;
      }
    }
    lcd.setCursor ( 0, 0 );      // line 0
    switch (iMode) {
      case 22:
        StdFlloatValueDisplay(yMaxVal,"Y Max ");
      break;
      case 21:
        StdFlloatValueDisplay(yMinVal,"Y Min ");
      break;
      case 20:
        StdFlloatValueDisplay(xMaxVal,"X Max ");
      break;
      case 19:
        StdFlloatValueDisplay(xMinVal,"X Min ");
      break;
      case 23:
        lcd.setCursor ( 0, 0 );
        lcd.print("Lat  ");
        lcd.print(latitude,6);
        lcd.setCursor ( 0, 1 );
        lcd.print("Long ");
        lcd.print(longitude,6);
        lcd.setCursor ( 0, 2 );
        lcd.print("Time Zone ");
        lcd.print(timezone);
        break;
      case 24:
        //        lcd.clear();
        lcd.setCursor ( 0, 0 );
        lcd.print("X/Z ");
        if ( xzAng > 0 ) {
          lcd.print("+");
        }
        lcd.print(xzAng);
        lcd.setCursor ( 10, 0 );
        lcd.print("Y/Z ");
        if ( yzAng > 0 ) {
          lcd.print("+");
        }
        lcd.print(yzAng);
        lcd.setCursor ( 0, 1 );
        lcd.print("TX ");
        if (( xzTarget) > 0 ) {
          lcd.print("+");
        }
        lcd.print(( xzTarget));
        lcd.setCursor ( 10, 1 );
        lcd.print("TY ");
        if ((  yzTarget) > 0 ) {
          lcd.print("+");
        }
        lcd.print((  yzTarget));
        lcd.setCursor ( 0, 2 );
        lcd.print("DX ");
        dTmp = ( xzAng - xzTarget) ;
        if (dTmp > 0 ) {
          lcd.print("+");
        }
        lcd.print(dTmp);
        lcd.setCursor ( 10, 2 );
        lcd.print("DY ");
        dTmp = ( yzAng - yzTarget) ;
        if (dTmp > 0 ) {
          lcd.print("+");
        }
        lcd.print(dTmp);

        lcd.setCursor ( 10, 3 );       // line 2
        lcd.print( "T ") ;
        lcd.print(T) ;

        lcd.setCursor ( 18, 3 );       // line 2
        if ( iPWM_YZ == 0 ) {
          lcd.print( " ") ;
        }else{
          if (( digitalRead(RELAY_YZ_DIR) == LOW )) {
            lcd.print( "W") ;
          }else{
            lcd.print( "E") ;            
          }
        }
        lcd.setCursor ( 19, 3 );       // line 2
        if ( iPWM_XZ == 0 ) {
          lcd.print( " ") ;
        }else{
          if (( digitalRead(RELAY_XZ_DIR) == LOW )) {
            lcd.print( "N") ;
          }else{
            lcd.print( "S") ;            
          }
        }
        
        break;
      case 18: // SAVE ALL
        lcd.setCursor ( 0, 1 );       // line 1
        if ( iSave != 0 ) {
          lcd.print( "SAVE REQUIRED") ;
        } else {
          lcd.print( "### SAVED ###") ;
        }
        if ( iDoSave == 2 ) {
          EEPROM.put( 0 , xzH );
          EEPROM.put(0 + (1 * sizeof(float)) , yzH );
          EEPROM.put(0 + (2 * sizeof(float)) , dyPark );
          EEPROM.put(0 + (3 * sizeof(float)) , dxPark );
          EEPROM.put(0 + (4 * sizeof(float)) , xzOffset );
          EEPROM.put(0 + (5 * sizeof(float)) , yzOffset );
          EEPROM.put(0 + (6 * sizeof(float)) , xzTarget );
          EEPROM.put(0 + (7 * sizeof(float)) , yzTarget );
          EEPROM.put(0 + (8 * sizeof(float)) , xMinVal );
          EEPROM.put(0 + (9 * sizeof(float)) , xMaxVal );
          EEPROM.put(0 + (10 * sizeof(float)) , yMinVal );
          EEPROM.put(0 + (11 * sizeof(float)) , yMaxVal );
          EEPROM.put(0 + (12 * sizeof(float)) , iTrackMode );
          EEPROM.put(0 + (13 * sizeof(float)) , latitude );
          EEPROM.put(0 + (14 * sizeof(float)) , longitude );
          EEPROM.put(0 + (15 * sizeof(float)) , timezone );
          iDoSave = 0 ;
          iSave = 0 ;
        }
        lcd.setCursor ( 0, 2 );       // line 2
        lcd.print( "PB to Save ALL") ;
      break ;
      case 17:     // yzH hysterisis
        StdFlloatValueDisplay(yzH,"Y Hysteresis ");
      break;
      case 16:     // xzH  hysterisis
        StdFlloatValueDisplay(xzH,"X Hysteresis ");
      break;
      case 15:     // yOffset
        StdFlloatValueDisplay(yzOffset,"Y Offset ");
      break;
      case 14:     // xOffset
        StdFlloatValueDisplay(xzOffset,"X Offset ");
      break;
      case 13:
        lcd.setCursor ( 0, 1 );       // line 1
        lcd.print(iTrackMode) ;
        lcd.print( " - ") ;
        PrintTrackerMode(iTrackMode);
        lcd.setCursor ( 0, 2 );       // line 2
        lcd.print( "PB change track mode") ;
        break;
      case 12:
        StdFlloatValueDisplay(dxPark,"X Park ");

        break;
      case 11:
        StdFlloatValueDisplay(dyPark,"Y Park ");

        break;
      case 10:
        StdFlloatValueDisplay(yzTarget,"Y ");
        lcd.print( " WE angle") ;
        break;
      case 9:
        StdFlloatValueDisplay(xzTarget,"X ");
        lcd.print( " NS angle") ;
        break;
      case 8:
        snprintf(buff, BUFF_MAX, "%d/%02d/%02d %02d:%02d:%02d", tc.year, tc.mon, tc.mday , tc.hour, tc.min, tc.sec);
        lcd.print(buff) ;
        lcd.setCursor ( 0, 1 );       // line 2
        snprintf(buff, BUFF_MAX, "%d/%02d/%02d %02d:%02d:%02d", t.year, t.mon, t.mday , t.hour, t.min, t.sec);
        lcd.print(buff) ;
        lcd.setCursor ( 0, 2 );       // line 2
        lcd.print( "Press PB to set time ") ;
        if ( not  digitalRead(ENCODER_PB) ) {
          DS3231_set(t);
        }
        break;
      case 1:
      case 2:
      case 3:
      case 4:
      case 5:
      case 6:
      case 7:
        snprintf(buff, BUFF_MAX, "%d/%02d/%02d %02d:%02d:%02d", t.year, t.mon, t.mday , t.hour, t.min, t.sec);
        lcd.print(buff) ;
        if (iMode == 7) {
          lcd.setCursor ( 0, 1 );
          lcd.print( "Day - > ") ;
          PrintDay(t.wday);
        }
        lcd.setCursor ( 0, 2 );
        lcd.print( "Hold in set") ;
        lcd.setCursor ( 10, 3 ) ;
        switch (iMode) {
          case 1:
            lcd.print( "Year") ;
            break;
          case 2:
            lcd.print( "Month") ;
            break;
          case 3:
            lcd.print( "Day") ;
            break;
          case 4:
            lcd.print( "Hour") ;
            break;
          case 5:
            lcd.print( "Min") ;
            break;
          case 6:
            lcd.print( "Sec") ;
            break;
          case 7:
            lcd.print( "WDay") ;
            break;
        }
        //    lcd.noBlink();
        break;
      default:   // 0
        DS3231_get(&tc);                                                     // update the time registers
        snprintf(buff, BUFF_MAX, "%d/%02d/%02d %02d:%02d:%02d", tc.year, tc.mon, tc.mday , tc.hour, tc.min, tc.sec);
        lcd.print(buff) ;
        lcd.setCursor ( 0, 1 );       // line 2 has the Az and El
        lcd.print( "Az ") ;
        lcd.print(solar_az_deg) ;
        lcd.setCursor ( 10, 1 );
        lcd.print( "El ") ;
        lcd.print(solar_el_deg) ;

        lcd.setCursor ( 0, 2 );
        lcd.print("D ");
        if ( decl > 0 ) {
          lcd.print("+");
        }
        lcd.print(decl);
        lcd.setCursor ( 10, 2 );
        lcd.print("H ");
        if ( ha > 0 ) {
          lcd.print("+");
        }
        lcd.print(ha);
        lcd.setCursor ( 0, 3 );   // third line is sunset and sunrise
        snprintf(buff, BUFF_MAX, "%02d:%02d", HrsSolarTime(sunrise), MinSolarTime(sunrise));
        lcd.print(buff) ;
        if (iDayNight == 1) {
          lcd.setCursor ( 8, 3 );
          lcd.print(" DAY ") ;
        } else {
          lcd.setCursor ( 7, 3 );
          lcd.print("NIGHT") ;
        }
        lcd.setCursor ( 15, 3 );
        snprintf(buff, BUFF_MAX, "%02d:%02d", HrsSolarTime(sunset), MinSolarTime(sunset));
        lcd.print(buff) ;
        break;
    }

    iCycle = tc.sec ;
    DisplayMeatBall() ;
  }

  if (((tc.hour > 19 ) || ( tc.hour < 5 )) && (iTrackMode < 3)) {
    ActivateRelays(0) ;  // power down at night if in tracking mode
  }else{
    ActivateRelays(1) ;    
  }
  digitalWrite(WATCHDOG,HIGH); // nice doggy
  
  state1 = slave1.poll( (uint16_t*)&iMode, MAX_MODBUS_DATA );

  switch (state1) {
    case EXC_ADDR_RANGE:
      //      Serial.println("EXC_ADDR_RANGE PORT 1");
      break;
    case EXC_FUNC_CODE:
      //    Serial.println("EXC_FUNC_CODE PORT 1");
      break;
    case EXC_REGS_QUANT:
      //      Serial.println("EXC_REGS_QUANT PORT 1");
      break;
  }

}






float DayOfYear(uint16_t iYear , uint8_t iMon , uint8_t iDay , uint8_t iHour , uint8_t iMin ) {
  int i ;
  float iTDay ;

  iTDay = iDay - 1 ;  // this is zero referenced
  for ( i = 1 ; i < iMon ; i++ ) {
    switch (i) {
      case 1:
      case 3:
      case 5:
      case 7:
      case 8:
      case 10:
      case 12:
        iTDay += 31 ;
        break;
      case 4:
      case 6:
      case 9:
      case 11:
        iTDay += 30 ;
        break;
      case 2 :
        if ((iYear % 4) == 0 ) {
          iTDay += 29 ;
        } else {
          iTDay += 28 ;
        }
        break;
    }
  }
  iTDay += (( 1.0 * iHour - 12 ) / 24 ) ;
  //  iDay +=  1.0 * iMin  / 1440 ;
  return (iTDay);
}

void PrintTrackerMode(int iTarget) {
  switch (iTarget) {
    case 1:
      lcd.print( "EW Only") ;
      break;
    case 2:
      lcd.print( "NS Only") ;
      break;
    case 3:
      lcd.print( "None") ;
      break;
    case 4:
      lcd.print( "Both Park") ;
      break;
    case -1:   
      lcd.print( "2P ") ;
    default:
      lcd.print( "Both Track") ;
      break;
  }
}


void PrintDay(int iTarget) {
  switch (iTarget) {
    case 1:
      lcd.print( "Sun") ;
      break;
    case 2:
      lcd.print( "Mon") ;
      break;
    case 3:
      lcd.print( "Tue") ;
      break;
    case 4:
      lcd.print( "Wed") ;
      break;
    case 5:
      lcd.print( "Thr") ;
      break;
    case 6:
      lcd.print( "Fri") ;
      break;
    case 7:
      lcd.print( "Sat") ;
      break;
  }
}

int HrsSolarTime(float target) {
  int i ;
  i = target ;
  return (  i / 60 );
}
int MinSolarTime(float target) {
  int i ;
  i = target  ;
  return (   i % 60 );
}

void counter()
{

  if ( ( prev_millis + 100 ) < millis() ) {
    if ( digitalRead(ENCODER_PB) ) {
      IncInt(&iMode,digitalRead(ENCODER_PINB),1,0,24,true);
    } else {
      if ((iMode > 8 ) && (iMode < 23) && (iMode != 18)) {
        iSave = 1;
      }
      switch (iMode) {  // setting the time and date etc
        case 1:   // year
          IncInt(&t.year ,digitalRead(ENCODER_PINB),1,2016,3116,false);
          break;
        case 2:   // month
          IncUint8_t(&t.mon ,digitalRead(ENCODER_PINB),1,1,12,true);
          break;
        case 3:  // day
          IncUint8_t(&t.mday ,digitalRead(ENCODER_PINB),1,1,31,true);
          break;
        case 4:  // hour
          IncUint8_t(&t.hour ,digitalRead(ENCODER_PINB),1,0,23,true);
          break;
        case 5:
          IncUint8_t(&t.min ,digitalRead(ENCODER_PINB),1,0,59,true);
          break;
        case 6:
          IncUint8_t(&t.sec ,digitalRead(ENCODER_PINB),1,0,59,true);
          break;
        case 7:
          IncUint8_t(&t.wday ,digitalRead(ENCODER_PINB),1,1,7,true);
...

This file has been truncated, please download it to see its full contents.
Modified Modbus Library C/C++
Been asked a few times for my tweaked library files
No preview (download only).
ht16k33 lib for matrix displayC/C++
Been asked a few times for these library files
No preview (download only).
Modified I2C libraryC/C++
Been asked a few times for my tweaked library files.. Small changes to suit LCD's and boards in my "kit"
Thanks to the original author...
From this you should find the rest...
// ---------------------------------------------------------------------------
// Created by Francisco Malpartida on 20/08/11.
// Copyright 2011 - Under creative commons license 3.0:
//        Attribution-ShareAlike CC BY-SA
//
// This software is furnished "as is", without technical support, and with no 
// warranty, express or implied, as to its usefulness for any purpose.
//
// Thread Safe: No
// Extendable: Yes
//
// @file LiquidCrystal_I2C.c
// This file implements a basic liquid crystal library that comes as standard
// in the Arduino SDK but using an I2C IO extension board.
// 
// @brief 
// This is a basic implementation of the LiquidCrystal library of the
// Arduino SDK. The original library has been reworked in such a way that 
// this class implements the all methods to command an LCD based
// on the Hitachi HD44780 and compatible chipsets using I2C extension
// backpacks such as the I2CLCDextraIO with the PCF8574* I2C IO Expander ASIC.
//
// The functionality provided by this class and its base class is identical
// to the original functionality of the Arduino LiquidCrystal library.
//
//
//
// @author F. Malpartida - fmalpartida@gmail.com
// ---------------------------------------------------------------------------
#if (ARDUINO <  100)
#include <WProgram.h>
#else
#include <Arduino.h>
#endif
#include <inttypes.h>
#include "I2CIO.h"
#include "LiquidCrystal_I2C.h"

// CONSTANT  definitions
// ---------------------------------------------------------------------------

// flags for backlight control
/*!
 @defined 
 @abstract   LCD_NOBACKLIGHT
 @discussion NO BACKLIGHT MASK
 */
#define LCD_NOBACKLIGHT 0x00

/*!
 @defined 
 @abstract   LCD_BACKLIGHT
 @discussion BACKLIGHT MASK used when backlight is on
 */
#define LCD_BACKLIGHT   0xFF


// Default library configuration parameters used by class constructor with
// only the I2C address field.
// ---------------------------------------------------------------------------
/*!
 @defined 
 @abstract   Enable bit of the LCD
 @discussion Defines the IO of the expander connected to the LCD Enable
 */
#define EN 2  // Enable bit

/*!
 @defined 
 @abstract   Read/Write bit of the LCD
 @discussion Defines the IO of the expander connected to the LCD Rw pin
 */
#define RW 1  // Read/Write bit

/*!
 @defined 
 @abstract   Register bit of the LCD
 @discussion Defines the IO of the expander connected to the LCD Register select pin
 */
#define RS 0  // Register select bit

/*!
 @defined 
 @abstract   LCD dataline allocation this library only supports 4 bit LCD control
 mode.
 @discussion D4, D5, D6, D7 LCD data lines pin mapping of the extender module
 */
#define D4 4
#define D5 5
#define D6 6
#define D7 7


// CONSTRUCTORS
// ---------------------------------------------------------------------------
LiquidCrystal_I2C::LiquidCrystal_I2C( uint8_t lcd_Addr )
{
   config(lcd_Addr, EN, RW, RS, D4, D5, D6, D7);
}


LiquidCrystal_I2C::LiquidCrystal_I2C(uint8_t lcd_Addr, uint8_t backlighPin, 
                                     t_backlighPol pol = POSITIVE)
{
   config(lcd_Addr, EN, RW, RS, D4, D5, D6, D7);
   setBacklightPin(backlighPin, pol);
}

LiquidCrystal_I2C::LiquidCrystal_I2C(uint8_t lcd_Addr, uint8_t En, uint8_t Rw,
                                     uint8_t Rs)
{
   config(lcd_Addr, En, Rw, Rs, D4, D5, D6, D7);
}

LiquidCrystal_I2C::LiquidCrystal_I2C(uint8_t lcd_Addr, uint8_t En, uint8_t Rw,
                                     uint8_t Rs, uint8_t backlighPin, 
                                     t_backlighPol pol = POSITIVE)
{
   config(lcd_Addr, En, Rw, Rs, D4, D5, D6, D7);
   setBacklightPin(backlighPin, pol);
}

LiquidCrystal_I2C::LiquidCrystal_I2C(uint8_t lcd_Addr, uint8_t En, uint8_t Rw,
                                     uint8_t Rs, uint8_t d4, uint8_t d5,
                                     uint8_t d6, uint8_t d7 )
{
   config(lcd_Addr, En, Rw, Rs, d4, d5, d6, d7);
}

LiquidCrystal_I2C::LiquidCrystal_I2C(uint8_t lcd_Addr, uint8_t En, uint8_t Rw,
                                     uint8_t Rs, uint8_t d4, uint8_t d5,
                                     uint8_t d6, uint8_t d7, uint8_t backlighPin, 
                                     t_backlighPol pol = POSITIVE )
{
   config(lcd_Addr, En, Rw, Rs, d4, d5, d6, d7);
   setBacklightPin(backlighPin, pol);
}

// PUBLIC METHODS
// ---------------------------------------------------------------------------

//
// begin
void LiquidCrystal_I2C::begin(uint8_t cols, uint8_t lines, uint8_t dotsize) 
{
   
   init();     // Initialise the I2C expander interface
   LCD::begin ( cols, lines, dotsize );   
}


// User commands - users can expand this section
//----------------------------------------------------------------------------
// Turn the (optional) backlight off/on

//
// setBacklightPin
void LiquidCrystal_I2C::setBacklightPin ( uint8_t value, t_backlighPol pol = POSITIVE )
{
   _backlightPinMask = ( 1 << value );
   _polarity = pol;
   setBacklight(BACKLIGHT_OFF);
}

//
// setBacklight
void LiquidCrystal_I2C::setBacklight( uint8_t value ) 
{
   // Check if backlight is available
   // ----------------------------------------------------
   if ( _backlightPinMask != 0x0 )
   {
      // Check for polarity to configure mask accordingly
      // ----------------------------------------------------------
      if  (((_polarity == POSITIVE) && (value > 0)) || 
           ((_polarity == NEGATIVE ) && ( value == 0 )))
      {
         _backlightStsMask = _backlightPinMask & LCD_BACKLIGHT;
      }
      else 
      {
         _backlightStsMask = _backlightPinMask & LCD_NOBACKLIGHT;
      }
      _i2cio.write( _backlightStsMask );
   }
}


// PRIVATE METHODS
// ---------------------------------------------------------------------------

//
// init
int LiquidCrystal_I2C::init()
{
   int status = 0;
   
   // initialize the backpack IO expander
   // and display functions.
   // ------------------------------------------------------------------------
   if ( _i2cio.begin ( _Addr ) == 1 )
   {
      _i2cio.portMode ( OUTPUT );  // Set the entire IO extender to OUTPUT
      _displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS;
      status = 1;
      _i2cio.write(0);  // Set the entire port to LOW
   }
   return ( status );
}

//
// config
void LiquidCrystal_I2C::config (uint8_t lcd_Addr, uint8_t En, uint8_t Rw, uint8_t Rs, 
                                uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7 )
{
   _Addr = lcd_Addr;
   
   _backlightPinMask = 0x08;
   _backlightStsMask = LCD_BACKLIGHT;
   _polarity = POSITIVE;
   
   _En = ( 1 << En );
   _Rw = ( 1 << Rw );
   _Rs = ( 1 << Rs );
   
   // Initialise pin mapping
   _data_pins[0] = ( 1 << d4 );
   _data_pins[1] = ( 1 << d5 );
   _data_pins[2] = ( 1 << d6 );
   _data_pins[3] = ( 1 << d7 );   
}



// low level data pushing commands
//----------------------------------------------------------------------------

//
// send - write either command or data
void LiquidCrystal_I2C::send(uint8_t value, uint8_t mode) 
{
   // No need to use the delay routines since the time taken to write takes
   // longer that what is needed both for toggling and enable pin an to execute
   // the command.
   
   if ( mode == FOUR_BITS )
   {
      write4bits( (value & 0x0F), COMMAND );
   }
   else 
   {
      write4bits( (value >> 4), mode );
      write4bits( (value & 0x0F), mode);
   }
   delay(1);	
}

//
// write4bits
void LiquidCrystal_I2C::write4bits ( uint8_t value, uint8_t mode ) 
{
   uint8_t pinMapValue = 0;
   
   // Map the value to LCD pin mapping
   // --------------------------------
   for ( uint8_t i = 0; i < 4; i++ )
   {
      if ( ( value & 0x1 ) == 1 )
      {
         pinMapValue |= _data_pins[i];
      }
      value = ( value >> 1 );
   }
   
   // Is it a command or data
   // -----------------------
   if ( mode == DATA )
   {
      mode = _Rs;
   }
   
   pinMapValue |= mode | _backlightStsMask;
   pulseEnable ( pinMapValue );
}

//
// pulseEnable
void LiquidCrystal_I2C::pulseEnable (uint8_t data)
{
   _i2cio.write (data | _En);   // En HIGH
   _i2cio.write (data & ~_En);  // En LOW
}
modified LM303 libC/C++
very small changes aimed at roll over detection of the millis function which is used to detect time outs
No preview (download only).
DS3231 libC/C++
This is an older copy of this lib that works with this project
No preview (download only).

Custom parts and enclosures

Post top section
This is the business end of the machine. This is then aligned north and welded to the support tower The frame then locks into this via two bearings.
Frame and Piviots
This shows the gimble piece and its relation to the frame and ost
Panels Location
This shows the relationship of the panels to the frame and the relative dimesions
Tracker Frame
This is the support frame so the panels do not flex to much in the wind

Schematics

Mud map of circuits
Suggest you follow the TI PDF on the buffer chips
Need to look at photos as well , but its not rocket science ... very basic stuff

Comments

Similar projects you might like

Solar Tracker with Live Data Feed - Windows IoT

Project tutorial by Jed Hodson

  • 10,563 views
  • 1 comment
  • 19 respects

Arduino Sun Tracker Turret

Project tutorial by RobotGeek Projects Team

  • 32,839 views
  • 1 comment
  • 157 respects

Solar Panel Sun Tracker - Phone Charger

Project tutorial by FIELDING

  • 27,305 views
  • 11 comments
  • 118 respects

Mega Solar Tracker

Project in progress by Team Trouble

  • 4,426 views
  • 9 comments
  • 34 respects

Integrated Solar ChargeController, Inverter, PowerBank, Lamp

Project tutorial by Shahariar

  • 7,733 views
  • 16 comments
  • 30 respects

Smart Wardrobe

Project in progress by Tan See Youu

  • 18,858 views
  • 8 comments
  • 46 respects
Add projectSign up / Login