DougalPlummerorionplummer
Published © GPL3+

Non Optical Solar Tracker (East Tower 2.4KW)

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

AdvancedWork in progress22,216
Non Optical Solar Tracker (East Tower 2.4KW)

Things used in this project

Hardware components

Arduino UNO
Arduino 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
Rotary Encoder with Push-Button
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
HC-05 Bluetooth Module
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

Software apps and online services

Arduino IDE
Arduino IDE
Code compiled in here
Visual Studio 2015
Microsoft Visual Studio 2015
Used for much of the proof of concept software.
Android Studio
Android Studio
Used to develop the HMI for bluetooth

Hand tools and fabrication 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...

Story

Read more

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

Code

TRACKER_ALL_IN_ONE_BOTTOM.ino

C/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.bas

VBScript
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.ino

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

C/C++
Been asked a few times for these library files
No preview (download only).

Modified I2C library

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

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

C/C++
This is an older copy of this lib that works with this project
No preview (download only).

Credits

DougalPlummer

DougalPlummer

14 projects • 87 followers
I write code for a living, on everything from PLC's to web and accounting systems. I'm also very talented at farming prickles :)
orionplummer

orionplummer

0 projects • 8 followers
Thanks to Jim Andrews, Orion Plummer, and Sally the Dog.

Comments