DougalPlummer
Published © GPL3+

ESP Solar Tracker - Revolutions

Take 3 - Single ESP8266 CPU. Maybe the mix is right this time.

AdvancedWork in progress16 hours13,300
ESP Solar Tracker - Revolutions

Things used in this project

Hardware components

ESP8266 NodeMCU with inbuild OLED display
×1
Dallas RTC board 3.3V compatible
×1
10DOF Multi Sesor board from ebay
×1
Dual H-Bride Board
https://www.ebay.com.au/itm/Dual-Motor-Driver-Module-H-bridge-DC-MOSFET-IRF3205-10A-15A-30A-3-36V-L9110S/192290964866?hash=item2cc56f4582:m:mcdfOZizFbHYzblLtQlGq9w:rk:1:pf:1&frcectupt=true
×1
plastic box
×1
Level Shifter Board
SparkFun Level Shifter Board
Not exact one but you get the picture
×1
i2c led matrix display
×1
30V to 5V buck power supply
×1
ESP8266 ESP-12E
Espressif ESP8266 ESP-12E
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Custom parts and enclosures

Solar

CAD files for the hardware

Schematics

Rough circuit diagram

Code

OLD - ESP8266 Tracker Second Cut - OTA Updates working

C/C++
Works on Nodemcu, Wemos D1 R2 , WeMos D1 mini etc
This code is circa Sep 2018 much newer code available on GitHub Site
#include <ESP8266WiFi.h>
#include <WiFiUDP.h> 
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <ESP8266HTTPUpdateServer.h>
#include <ESP8266httpUpdate.h>
//#include <DNSServer.h>
#include <TimeLib.h>
#include <Wire.h>
//#include <SPI.h>
#include <EEPROM.h>
#include <stdio.h>
#include <LSM303.h>       // modified ... fixed a couple of bugs
#include <L3G.h>
#include <SFE_BMP180.h>
#include <TinyGPS.h>
#include "ht16k33.h"

#include "SSD1306.h"
#include "SH1106.h"
#include "SH1106Wire.h"
#include "ds3231.h"


#define BUFF_MAX 32
/* Display settings */
#define minRow       0              /* default =   0 */
#define maxRow     127              /* default = 127 */
#define minLine      0              /* default =   0 */
#define maxLine     63              /* default =  63 */

#define LineText     0
#define Line        12

const byte SETPMODE_PIN = D0 ; 
const byte FLASH_BTN = D3 ;    // GPIO 0 = FLASH BUTTON 
const byte SCOPE_PIN = D3 ;
const byte FACTORY_RESET = D0 ;
const byte LED = BUILTIN_LED ;  // = D4 ;

SSD1306 display(0x3c, 5, 4);   // GPIO 5 = D1, GPIO 4 = D2
//SH1106Wire display(0x3c, 4, 5);   // arse about ??? GPIO 5 = D1, GPIO 4 = D2

#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 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 RELAY_XZ_PWM = D5; // PWM 1    Speed North / South     Was the X- S relay  Orange 
const byte RELAY_YZ_PWM = D6; // PWM 2    Speed East / West       Was the Y+ W relay  Blue
const byte RELAY_YZ_DIR = D7; // DIR 2    Y+ Y- East / West       Was the Y- E relay  Yellow
const byte RELAY_XZ_DIR = D8; // DIR 1    X+ X-  North / South    Was the X+ N relay  Brown



static bool hasSD = false;
static bool hasNet = false;
static bool hasGyro = false;
static bool hasRTC = false;
static bool hasPres = false ;

char dayarray[8] = {'S','M','T','W','T','F','S','E'} ;

char NodeName[16] ={"South_Tower\0"} ;
char nssid[20] ;
char npassword[20] ;
char timeServer[40] = {"au.pool.ntp.org\0"};

char buff[BUFF_MAX]; 

IPAddress MyIP(192,168,2,110) ;
IPAddress RCIP(192,168,2,255) ;

const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message
byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets

byte rtc_sec ;
byte rtc_min ;
byte rtc_hour ;
byte rtc_fert_hour ;
float rtc_temp ;
unsigned int localPort = 2390;      // local port to listen for NTP UDP packets
unsigned int localPortCtrl = 8666;      // local port to listen for Control UDP packets
unsigned int RemotePortCtrl = 8664;      // local port to listen for Control UDP packets

L3G gyro;
LSM303 compass;
SFE_BMP180 pressure;
TinyGPS gps;
HT16K33 HT;

int motor_recycle_x = 0 ;
int motor_recycle_y = 0 ;
char trackername[18] ;
unsigned long  gpschars ; 
float heading ;          // MODBUS MAP
struct ts tb;             // 
struct ts tn;             // 
struct ts td;            //   
struct ts tg;            // 
struct ts tc;            //
float ha ;
float sunX ;
float sunrise ;
float sunset ;
int iNightShutdown ;     //
int iMultiDrive   ;      // 69    do the axis drives run together 
time_t setchiptime ;     // 68    if set to non zero this will trigger a time set event
float zAng ;             // 66
float xMul = 1.0 ;       // 64
float yMul = 1.0 ;       // 62
float zMul = 1.0 ;       // 60
int iXYS = 0 ;           // 59
int iSave = 0 ;          // 58    
int iDoSave = 0 ;        // 57
int iGPSLock = 0  ;      // 56 
unsigned long  fixage ;  // 54
float xRoll = 0.0 ;      // 52
float yRoll = 0.0 ;      // 50
float zRoll = 0.0 ;      // 48
float gT ;               // 46  temp from sensor
float Pr ;               // 44  presure sensor
float alt ;              // 42  altitude from GPS
float T;                 // 40  temperature of board (if has RTC)
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 iPMode;
int iPWM_YZ ;
int iPWM_XZ ;
int iPowerUp = 0 ;

int iUseGPS = 0 ;

long lTimeZone ;
long lScanCtr = 0 ;
long lScanLast = 0 ;
time_t AutoOff_t ;         // auto off until time > this date
bool bConfig = false ;
uint8_t rtc_status ;


bool bDoTimeUpdate = false ;
long lTimePrev ;
long lTimePrev2 ;

WiFiUDP ntpudp;
WiFiUDP ctrludp;

ESP8266WebServer server(80);
ESP8266WebServer OTAWebServer(81);
ESP8266HTTPUpdateServer OTAWebUpdater;
//DNSServer dnsServer;



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

void ActivateRelays(int iAllStop) {
  if (motor_recycle_y > 0 ){
    motor_recycle_y-- ;
  }
  if (motor_recycle_x > 0 ){
    motor_recycle_x-- ;
  }
  if ( iAllStop == 0 ) {
    StopYZ() ;
    StopXZ() ;
  } else {
    if (( iPWM_YZ==0 ) && (motor_recycle_y == 0 )){
      if ((( yzAng  ) < ( yzTarget - yzH )) ) {   // do Y ie E/W before N/S
        digitalWrite(RELAY_YZ_DIR, LOW) ;
        iPWM_YZ=2 ;
      }
      if ((( yzAng ) > ( yzTarget + yzH )) ) {
        digitalWrite(RELAY_YZ_DIR, HIGH) ;
        iPWM_YZ=2 ;
      }
    }
    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) || ( iMultiDrive == 1 )) {  // if finished on E/W you can do N/S  or if we are doing multidrive
        if (( iPWM_XZ==0 ) && (motor_recycle_x == 0 )){
          if ((xzAng < ( xzTarget - xzH ))  )  { // turn on if not in tolerance
            digitalWrite(RELAY_XZ_DIR, LOW) ;
            iPWM_XZ=2 ;
          }
          if ((xzAng > ( xzTarget + xzH )) ) { // turn on if not in tolerance
            digitalWrite(RELAY_XZ_DIR, HIGH) ;
            iPWM_XZ=2 ;
          }
        }
    }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 += 1 ;
  }
  if (iPWM_YZ>0){
    iPWM_YZ += 1 ;
  }
  iPWM_XZ = constrain(iPWM_XZ,0,1023);  // 254 in atmel - arduino land 
  iPWM_YZ = constrain(iPWM_YZ,0,1023);  //   
  analogWrite(RELAY_XZ_PWM,iPWM_XZ);
  analogWrite(RELAY_YZ_PWM,iPWM_YZ);
}

// 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) ;
  }
}

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




unsigned long sendCTRLpacket(IPAddress address){
int j ;  
byte packetBuffer[50];           //buffer to hold outgoing packets  
  Serial.println("sending CTRL packet...");
                    
  memset(packetBuffer, 0, sizeof(packetBuffer[50]));    // set all bytes in the buffer to 0
  packetBuffer[0] = 0xff;                       // broadcast as all stations
  packetBuffer[1] = 0xff;  // 
  packetBuffer[2] = 0xff;  // 
  packetBuffer[3] = 0xff;  // 
  
  ctrludp.beginPacket(address, RemotePortCtrl);      // Send control data to the remote port - Broadcast ???
  ctrludp.write(packetBuffer, sizeof(packetBuffer[50]));
  ctrludp.endPacket();
}



unsigned long sendNTPpacket(char* address)
{
byte packetBuffer[ NTP_PACKET_SIZE];     //buffer to hold incoming and outgoing packets  
  Serial.println("sending NTP packet...");
                    
  memset(packetBuffer, 0, NTP_PACKET_SIZE);    // set all bytes in the buffer to 0
  // Initialize values needed to form NTP request
  // (see URL above for details on the packets)
  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum, or type of clock
  packetBuffer[2] = 6;     // Polling Interval
  packetBuffer[3] = 0xEC;  // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  packetBuffer[12]  = 49;
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;

  // all NTP fields have been given values, now
  // you can send a packet requesting a timestamp:
  ntpudp.beginPacket(address, 123); //NTP requests are to port 123
  ntpudp.write(packetBuffer, NTP_PACKET_SIZE);
  ntpudp.endPacket();
}


 

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) ;
}

float LoadFloatFromEEPROM(int address,float minval,float maxval, float defaultval){
float tmp ;  
int i ;
byte  *ba ;  

  ba =(byte *)&tmp ; // set the byte array to point at the long
  for ( i = 0 ; i < 4 ; i++ ){
    ba[i] = EEPROM.read((address*4)+i);  // read the 4 bytes
  }
  if (( tmp < minval ) || ( tmp > maxval )) {
    tmp = defaultval ;
    for ( i = 0 ; i < 4 ; i++ ){
      EEPROM.write((address*4)+i , ba[i] );
      EEPROM.commit();  // save changes in one go ???    
    }  
  }
  return(tmp);  
}

int LoadIntFromEEPROM(int address,int minval,int maxval, int defaultval){
int dummy1 = 0 ; // belt and braces ... dont know which way the stack works
int tmp ;  
int dummy2 = 0 ; // yep write this one as well ... maybe
int i ;
byte  *ba ;  

  ba =(byte *)&tmp ; // set the byte array to point at the long
  for ( i = 0 ; i < 4 ; i++ ){
    ba[i] = EEPROM.read((address*4)+i);  // read the 4 bytes
  }
  if (( tmp < minval ) || ( tmp > maxval )) {
    tmp = defaultval ;
    for ( i = 0 ; i < 4 ; i++ ){
      EEPROM.write((address*4)+i , ba[i] );
      EEPROM.commit();  // save changes in one go ???    
    }  
  }
  return(tmp);  
}
int LoadLongFromEEPROM(int address,long minval,long maxval, long defaultval){
long tmp ;
int i ;
byte  *ba ;  

  ba =(byte *)&tmp ; // set the byte array to point at the long
  for ( i = 0 ; i < 4 ; i++ ){
    ba[i] = EEPROM.read((address*4)+i);  // read the 4 bytes
  }
  if (( tmp < minval ) || ( tmp > maxval )) {
    tmp = defaultval ;
    for ( i = 0 ; i < 4 ; i++ ){
      EEPROM.write((address*4)+i , ba[i] );
      EEPROM.commit();  // save changes in one go ???    
    }  
  }
  return(tmp);  
}

time_t LoadTimeFromEEPROM(int address, time_t defaultval){
time_t tmp ;
int i ;
byte  *ba ;  

  ba =(byte *)&tmp ; // set the byte array to point at the long
  for ( i = 0 ; i < 4 ; i++ ){
    ba[i] = EEPROM.read((address*4)+i);  // read the 4 bytes
  }
  if ( year(tmp) < 2000 ) {
    tmp = defaultval ;
    for ( i = 0 ; i < 4 ; i++ ){
      EEPROM.write((address*4)+i , ba[i] );
      EEPROM.commit();  // save changes in one go ???    
    }  
  }
  return(tmp);  
}

void LoadCharFromEEPROM(int address , char * target , int targetsize ){
  for ( int i = 0 ; i < targetsize ; i++ ){
    target[i] = EEPROM.read((address*4)+i);  
  }
}
void SaveCharToEEPROM(int address , char * target , int targetsize ){
  for ( int i = 0 ; i < targetsize ; i++ ){
    EEPROM.write((address*4)+i,target[i]);  
  }
}

void LoadIPFromEEPROM(int address , IPAddress * target ){
  for ( int i = 0 ; i < 4 ; i++ ){
    target[i] = EEPROM.read((address*4)+i);  
  }  
}
void SaveIPToEEPROM(int address , IPAddress * target ){
  for ( int i = 0 ; i < 4 ; i++ ){
    EEPROM.write((address*4)+i,target[i]);  
  }  
}
byte LoadDayByteFromEEPROM(int address, int ofs){
byte tmp ;  
  ofs %= 4 ;
  tmp = EEPROM.read((address*4)+ofs);  // read the 4 bytes
  return(tmp);  
}

void SaveDayByteToEEPROM(int address, int ofs,byte val){
byte tmp ;  
  ofs %= 4 ;
  EEPROM.write((address*4)+ofs,val);  // read the 4 bytes
}

void SaveFloatToEEPROM(int address,float val){
float tmp ;  
int i ;
byte  *ba ;  
  tmp = val ;
  ba =(byte *)&tmp ; // set the byte array to point at the long
  for ( i = 0 ; i < 4 ; i++ ){
    EEPROM.write((address*4)+i , ba[i] );
  }  
}
void SaveLongToEEPROM(int address,long val){
long tmp ;  
int i ;
byte  *ba ;  
  tmp = val ;
  ba =(byte *)&tmp ; // set the byte array to point at the long
  for ( i = 0 ; i < 4 ; i++ ){
    EEPROM.write((address*4)+i , ba[i] );
  }  
}
void SaveTimeToEEPROM(int address,time_t val){
time_t tmp ;  
int i ;
byte  *ba ;  
  tmp = val ;
  ba =(byte *)&tmp ; // set the byte array to point at the long
  for ( i = 0 ; i < 4 ; i++ ){
    EEPROM.write((address*4)+i , ba[i] );
  }  
}
void SaveIntToEEPROM(int address,int val){
int dummy1 = 0 ;
int tmp ;  
int dummy2 = 0 ;
int i ;
byte  *ba ;  
  tmp = val ;
  ba =(byte *)&tmp ; // set the byte array to point at the long
  for ( i = 0 ; i < 4 ; i++ ){
    EEPROM.write((address*4)+i , ba[i] );
  }  
}

void LoadParamsFromEEPROM(bool bLoad){
  if ( bLoad ) {
    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);
  
    iTrackMode = LoadIntFromEEPROM(12,-1,4,0);
      
    latitude = LoadFloatFromEEPROM(13,-90.0,90.0,-34.051219);
    longitude = LoadFloatFromEEPROM(14,-180.0,180.0,142.013618);
    timezone = LoadIntFromEEPROM(15,0,23,10);  
    xMul = LoadFloatFromEEPROM(16,-10,10,1);  
    yMul = LoadFloatFromEEPROM(17,-10,10,1);  
    zMul = LoadFloatFromEEPROM(18,-10,10,1);  
    iXYS = LoadIntFromEEPROM(19,0,1,0);
    if ( xMul == 0.0 )  // zero is rubbish value so take 1.0 as the default
      xMul = 1.0 ;
    if ( yMul == 0.0 )
      yMul = 1.0 ;
    if ( zMul == 0.0 )
      zMul = 1.0 ;
    iNightShutdown = LoadIntFromEEPROM(20,0,1,1);
    iMultiDrive = LoadIntFromEEPROM(21,0,1,0);
    EEPROM.get((23 * sizeof(float)) , iUseGPS ) ;
    if (digitalRead(FACTORY_RESET)== LOW) {
        MyIP = IPAddress(192,168,42,1);
        sprintf(trackername,"Most Excellent\0");
        sprintf(nssid , "Configure\0") ;
        sprintf(npassword, "\0");      
    }else{
      EEPROM.get((22 * sizeof(float)) , MyIP );
      if ((( MyIP[0] == 255 ) && ( MyIP[1] == 255 ))) {
        MyIP = IPAddress(192,168,42,1);
      }
      EEPROM.get((30 * sizeof(float)) , trackername );
      if ( String(trackername).length() < 2 ){
        sprintf(trackername,"Most Excellent\0");
      }
      EEPROM.get((40 * sizeof(float)) , nssid );
      if ( String(nssid).length() < 2 ) {
        sprintf(nssid , "Configure\0") ;
        sprintf(npassword, "\0");
      }else{
        EEPROM.get((45 * sizeof(float)) , npassword );
      }
      EEPROM.get((50 * sizeof(float)) , timeServer );
    }
  }else{
    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 );  
    EEPROM.put(0 + (16 * sizeof(float)) , xMul );
    EEPROM.put(0 + (17 * sizeof(float)) , yMul );
    EEPROM.put(0 + (18 * sizeof(float)) , zMul );
    EEPROM.put(0 + (19 * sizeof(float)) , iXYS );  
    EEPROM.put(0 + (20 * sizeof(float)) , iNightShutdown );  
    EEPROM.put(0 + (21 * sizeof(float)) , iMultiDrive );  
    EEPROM.put(0 + (22 * sizeof(float)) , MyIP );  
    EEPROM.put(0 + (23 * sizeof(float)) , iUseGPS );      
    EEPROM.put(0 + (30 * sizeof(float)) , trackername);
    EEPROM.put(0 + (40 * sizeof(float)) , nssid);
    EEPROM.put(0 + (45 * sizeof(float)) , npassword);
    EEPROM.put(0 + (50 * sizeof(float)) , timeServer);
    EEPROM.commit();                                                       // save changes in one go ???
  }
}



unsigned long processNTPpacket(void){
int oldyear ;
    oldyear = year() ;  
    ntpudp.read(packetBuffer, NTP_PACKET_SIZE);                                         // the timestamp starts at byte 40 of the received packet and is four bytes, or two words, long. First, esxtract the two words:
    unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);                  // combine the four bytes (two words) into a long integer
    unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
    unsigned long secsSince1900 = highWord << 16 | lowWord;                             // this is NTP time (seconds since Jan 1 1900):
    const unsigned long seventyYears = 2208988800UL;                                    // now convert NTP time into everyday time:     Unix time starts on Jan 1 1970. In seconds, that's 2208988800:   
    unsigned long epoch = secsSince1900 - seventyYears + (timezone * SECS_PER_HOUR);   // subtract seventy years:
    setTime((time_t)epoch);                                                             // update the clock
    Serial.print(F("Unix time = "));
    Serial.println(epoch);                                                              // print Unix time:
    tn.year = year();   // record the last NTP time set
    tn.mon = month() ;
    tn.mday = day();
    tn.hour = hour();
    tn.min = minute();
    tn.sec = second();
    if (( !hasRTC ) && ( oldyear < 2000 )){
      tb = tn ;
    }
}

int SetTimeFromGPS(){
  byte hundredths ;
  time_t chiptime ;
  
  gps.crack_datetime((int *)&tg.year,(byte *)&tg.mon,(byte *) &tg.mday,(byte *) &tg.hour,(byte *) &tg.min,(byte *) &tg.sec , &hundredths, &fixage);
  setTime((int)tg.hour,(int)tg.min,(int)tg.sec,(int)tg.mday,(int)tg.mon,(int)tg.year ) ; // set the internal RTC from last GPS time  
  chiptime = now() ;                          // get it back again
  chiptime += (( timezone * SECS_PER_HOUR ) + ( fixage / 1000 )) ; // add the offset plus the fix age
  setTime(chiptime);                         // set it again  
   if (hasRTC) {
      tg.year = year();
      tg.mon = month() ;
      tg.mday = day();
      tg.hour = hour() ;
      tg.min = minute();
      tg.sec = second();
      DS3231_set(tg);                          //should also update this
    }
  return(0);
}


//  ##############################  SETUP   #############################
void setup() {
int i , j = 0; 
String host ;
 
  pinMode(LED,OUTPUT);  //  builtin LED
  pinMode(SETPMODE_PIN,INPUT_PULLUP);
  
  display.init();
  display.flipScreenVertically();

  /* show start screen */
  display.clear();
  display.setFont(ArialMT_Plain_16);
  display.drawString(0, 0, "ESP Solar");
  display.drawString(0, 16, "Tracker");
  display.setFont(ArialMT_Plain_10);
  display.drawString(0, 40, "Copyright (c) 2019");
  display.drawString(0, 50, "Dougal Plummer");
  display.display();

  compass.init();
  compass.enableDefault();
  compass.setTimeout(1000);
  
  if (gyro.init()) {
    gyro.enableDefault();
    hasGyro = true ;
  }
  if (pressure.begin()){
    Serial.println("BMP180 init success");
    hasPres = true ;
  }      
//  pinMode(FACTORY_RESET,INPUT_PULLUP);
  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 ;
  ActivateRelays(0); // call an all stop first

  EEPROM.begin(1024);
  LoadParamsFromEEPROM(true);

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

  
  delay(1000);
  if (iUseGPS==0){
    Serial.begin(115200);
    Serial.println("Warp Speed no GPS !");
  }else{
    Serial.begin(9600);    
  }
  Serial.setDebugOutput(true);  
  Serial.println("Chip ID " + String(ESP.getChipId(), HEX));
  Serial.println("Configuring WiFi...");

  display.clear();
  display.setFont(ArialMT_Plain_10);
  display.drawString(0, 11, "Chip ID " + String(ESP.getChipId(), HEX) );
  display.display();

  sprintf(nssid,"*********\0"); // put you credentials in here
  sprintf(npassword,"********\0");

  if ((digitalRead(SETPMODE_PIN) == HIGH) ){
    bConfig = true ;
    IPAddress localIp(192, 168, 5 +(ESP.getChipId() & 0x7f ) , 1);
    IPAddress MaskIp(255, 255, 255 , 0);
    WiFi.softAPConfig(localIp,localIp,MaskIp);
    WiFi.softAP(nssid); // configure mode no password
    MyIP = WiFi.softAPIP();
    Serial.print("Soft AP IP address: ");
    Serial.println(MyIP);
    display.drawString(0, 22, "Soft AP IP address: "+String(MyIP) );
    display.display();
  }else{
    bConfig = false ;   // are we in factory configuratin mode
    Serial.println(String(nssid));
    Serial.println(String(npassword));
    display.drawString(0, 22, String(nssid) );
    display.drawString(0, 33, String(npassword) );
    display.display();
    if ( npassword[0] == 0 ){
      WiFi.begin((char*)nssid);                    // connect to unencrypted access point      
    }else{
      WiFi.begin((char*)nssid, (char*)npassword);  // connect to access point with encryption
    }
    while (( WiFi.status() != WL_CONNECTED ) && ( j < 20 )) {
     j = j + 1 ;
     delay(500);
     Serial.print("+");
    } 
    if ( j >= 20 ) {
       bConfig = true ;
       WiFi.disconnect();
       IPAddress localIp(192, 168, 5 +(ESP.getChipId() & 0x7f ) , 1);
       IPAddress MaskIp(255, 255, 255 , 0);
       WiFi.softAPConfig(localIp,localIp,MaskIp);
       WiFi.softAP(nssid); // configure mode no password
       MyIP = WiFi.softAPIP();
       Serial.print("Soft AP IP address: ");
       Serial.println(MyIP);
       display.drawString(0, 22, "Soft AP IP address: "+String(MyIP) );
       display.display();
    }else{
       Serial.println("");
       Serial.println("WiFi connected");  
       Serial.print("IP address: ");
       MyIP =  WiFi.localIP() ;
       Serial.println(MyIP) ;
       display.drawString(0, 53, "IP "+String(MyIP) );
       display.display();
       hasNet = true ;
    }
    if (localPortCtrl == localPort ){             // bump the NTP port up if they ar the same
      localPort++ ;
    }
    Serial.println("Starting UDP");
    ntpudp.begin(localPort);                      // this is the recieve on NTP port
    display.drawString(0, 44, "NTP UDP " );
    display.display();
    Serial.print("NTP Local UDP port: ");
    Serial.println(ntpudp.localPort());
    ctrludp.begin(localPortCtrl);                 // recieve on the control port
    display.drawString(64, 44, "CTRL UDP " );
    display.display();
    Serial.print("Control Local UDP port: ");
    Serial.println(ctrludp.localPort());
  }                                              // end of the normal setup
  host = trackername ;
  String(host).replace(" ","_");
  String(host).toCharArray(buff,sizeof(buff));
  if (MDNS.begin(buff)) {
    MDNS.addService("http", "tcp", 80);
    Serial.println("MDNS responder started");
    Serial.print("You can now connect to http://");
    Serial.print(host);
    Serial.println(".local");
  }

  server.on("/", handleRoot);
  server.on("/setup", handleRoot);
  server.on("/scan", i2cScan);
  server.on("/stime", handleRoot);
  server.onNotFound(handleNotFound);  
  server.begin();
  Serial.println("HTTP server started");
 
//  dnsServer.setTTL(300);
//  dnsServer.setErrorReplyCode(DNSReplyCode::ServerFailure);
//  dnsServer.start(53,"injector.local",myIP);
  tc.mon = 0 ;
  tc.wday = 0 ;
  DS3231_init(DS3231_INTCN); // look for a rtc
  DS3231_get(&tc);
  rtc_status = DS3231_get_sreg();
  if (((tc.mon < 1 )|| (tc.mon > 12 ))&& (tc.wday>8)){  // no rtc to load off
    Serial.println("NO RTC ?");
  }else{
    setTime((int)tc.hour,(int)tc.min,(int)tc.sec,(int)tc.mday,(int)tc.mon,(int)tc.year ) ; // set the internal RTC
    hasRTC = true ;
    Serial.println("Has RTC ?");
    rtc_temp = DS3231_get_treg(); 
    DS3231_get(&tb);
  }
  rtc_min = minute();
  rtc_sec = second();


  HT.begin(0x00);
  for (int led = 0; led < 127; led++) {
    HT.clearLed(led);
  } 
  HT.sendLed();  
  Serial.println("OTA startup");  
  OTAWebUpdater.setup(&OTAWebServer);
  OTAWebServer.begin();  
  Serial.println("End of Setup");

}

//  ##############################  LOOP   #############################
void loop() {
long lTime ;  
long lRet ;
int i , j , k  ;
float P;
float sunInc;
float sunAng;
float xzRatio;
float yzRatio;
float decl ;
float eqtime ;
float dTmp ;
float heading ;
float tst ;
float flat, flon;
unsigned short goodsent;
unsigned short failcs;
String msg ;

bool bSendCtrlPacket = false ;

  server.handleClient();
  OTAWebServer.handleClient();
  
  lTime = millis() ;

  compass.read();  // this reads all 6 channels

  if (( compass.a.z != 0) && (!compass.timeoutOccurred() ))  {
    zAng = (float)compass.a.z ;
    if (iXYS == 0 ){                                            // Proper Job make it configurable 
      xzRatio = (float)compass.a.x * xMul / abs(zAng) ;         // Normal
      yzRatio = (float)compass.a.y * yMul / abs(zAng) ;
    }else{
      xzRatio = (float)compass.a.y * xMul / abs(zAng) ;         // Swapped
      yzRatio = (float)compass.a.x * yMul / abs(zAng) ;      
    }
    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...
    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 };
  }
   
//  digitalWrite(SCOPE_PIN,!digitalRead(SCOPE_PIN));  // my scope says we are doing this loop at an unreasonable speed except when we do web stuff
//  pwmtest++ ;
//  if ( pwmtest > 1024 ) {
//    pwmtest = 0 ;
//  }
//  analogWrite(SCOPE_PIN,pwmtest); 

  delay(1);                  // limmit to 1000 cyclces a second max
  
  if (digitalRead(FLASH_BTN) == LOW) { // what to do if the button be pressed
  }
      
  lScanCtr++ ;
  bSendCtrlPacket = false ;
  if ( rtc_sec != second()){
//    digitalWrite(LED,!digitalRead(LED));  // my scope says we are doing this loop at an unreasonable speed except when we do web stuff
    rtc_sec = second() ;

    display.clear();
//      display.drawLine(minRow, 63, maxRow, 63);
    display.setTextAlignment(TEXT_ALIGN_LEFT);
    snprintf(buff, BUFF_MAX, "%d/%02d/%02d %02d:%02d:%02d", year(), month(), day() , hour(), minute(), second());
    display.drawString(0 , LineText, String(buff) );
    display.setTextAlignment(TEXT_ALIGN_RIGHT);
    display.drawString(128 , LineText, String(WiFi.RSSI()));
    display.setTextAlignment(TEXT_ALIGN_LEFT);
//    snprintf(buff, BUFF_MAX, "TX %d TY %d", year(), month() );
    msg = "X  " + String(xzAng,2)  ;
    display.drawString(0 , 11, msg ) ;
    msg = "Y  " + String(yzAng,2)  ;
    display.drawString(56 , 11, msg ) ;
    msg = "TX " + String(xzTarget,2) ;
    display.drawString(0 , 22, msg ) ;
    msg = "TY " + String(yzTarget,2) ;
    display.drawString(56 , 22, msg ) ;
    msg = "DX " + String((xzAng-xzTarget),2) ;
    display.drawString(0 , 33, msg ) ;
    msg = "DY " + String((yzAng-yzTarget),2) ;
    display.drawString(56 , 33, msg ) ;

    display.setTextAlignment(TEXT_ALIGN_RIGHT);
    msg = "" ;
    if ( iPWM_YZ != 0 ) {
      if (( digitalRead(RELAY_YZ_DIR) == LOW )) {
        msg = "W" ;
      }else{
        msg = "E" ;            
...

This file has been truncated, please download it to see its full contents.

Matrix Display Lib (modified)

C/C++
/*
 * ht16k33.cpp - used to talk to the htk1633 chip to do things like turn on LEDs or scan keys
 * Copyright:  Peter Sjoberg <peters-alib AT techwiz.ca>
 * License: GPLv3
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License version 3 as 
    published by the Free Software Foundation.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses></http:>.
 *
 *
 * History:
 * 2015-10-04  Peter Sjoberg <peters-alib AT techwiz.ca>
 *             Created using https://www.arduino.cc/en/Hacking/LibraryTutorial and ht16k33 datasheet
 * 2015-11-25  Peter Sjoberg <peters-alib AT techwiz DOT ca>
 *	       first check in to github
 *
 *
 *
 *
 *
 */

#include "Arduino.h"
#include "ht16k33.h"
#include <Wire.h>

// "address" is base address 0-7 which becomes 11100xxx = E0-E7
#define BASEHTADDR 0x70

//Commands
#define HT16K33_DDAP          B00000000 // Display data address pointer: 0000xxxx
#define HT16K33_SS            B00100000 // System setup register
#define HT16K33_SS_STANDBY    B00000000 // System setup - oscillator in standby mode
#define HT16K33_SS_NORMAL     B00000001 // System setup - oscillator in normal mode
#define HT16K33_KDAP          B01000000 // Key Address Data Pointer
#define HT16K33_IFAP          B01100000 // Read status of INT flag
#define HT16K33_DSP           B10000000 // Display setup
#define HT16K33_DSP_OFF       B00000000 // Display setup - display off
#define HT16K33_DSP_ON        B00000001 // Display setup - display on
#define HT16K33_DSP_NOBLINK   B00000000 // Display setup - no blink
#define HT16K33_DSP_BLINK2HZ  B00000010 // Display setup - 2hz blink
#define HT16K33_DSP_BLINK1HZ  B00000100 // Display setup - 1hz blink
#define HT16K33_DSP_BLINK05HZ B00000110 // Display setup - 0.5hz blink
#define HT16K33_RIS           B10100000 // ROW/INT Set
#define HT16K33_RIS_OUT       B00000000 // Set INT as row driver output
#define HT16K33_RIS_INTL      B00000001 // Set INT as int active low
#define HT16K33_RIS_INTH      B00000011 // Set INT as int active high
#define HT16K33_DIM           B11100000 // Dimming set
#define HT16K33_DIM_1         B00000000 // Dimming set - 1/16
#define HT16K33_DIM_2         B00000001 // Dimming set - 2/16
#define HT16K33_DIM_3         B00000010 // Dimming set - 3/16
#define HT16K33_DIM_4         B00000011 // Dimming set - 4/16
#define HT16K33_DIM_5         B00000100 // Dimming set - 5/16
#define HT16K33_DIM_6         B00000101 // Dimming set - 6/16
#define HT16K33_DIM_7         B00000110 // Dimming set - 7/16
#define HT16K33_DIM_8         B00000111 // Dimming set - 8/16
#define HT16K33_DIM_9         B00001000 // Dimming set - 9/16
#define HT16K33_DIM_10        B00001001 // Dimming set - 10/16
#define HT16K33_DIM_11        B00001010 // Dimming set - 11/16
#define HT16K33_DIM_12        B00001011 // Dimming set - 12/16
#define HT16K33_DIM_13        B00001100 // Dimming set - 13/16
#define HT16K33_DIM_14        B00001101 // Dimming set - 14/16
#define HT16K33_DIM_15        B00001110 // Dimming set - 15/16
#define HT16K33_DIM_16        B00001111 // Dimming set - 16/16

// Constructor
HT16K33::HT16K33(){
}

/****************************************************************/
// Setup the env
//
void HT16K33::begin(uint8_t address){
  uint8_t i;
  _address=address | BASEHTADDR;
  Wire.begin();
  _i2c_write(HT16K33_SS  | HT16K33_SS_NORMAL); // Wakeup
  _i2c_write(HT16K33_DSP | HT16K33_DSP_ON | HT16K33_DSP_NOBLINK); // Display on and no blinking
  _i2c_write(HT16K33_RIS | HT16K33_RIS_OUT); // INT pin works as row output 
  _i2c_write(HT16K33_DIM | HT16K33_DIM_16);  // Brightness set to max
  //Clear all lights
  for (i = 0 ; i < 16 ; i++){
	displayRam[i] = 0 ;
  }
//  memcpy(displayRam,0,sizeof(displayRam));  // this was the problem ???
  _i2c_write(HT16K33_DDAP, displayRam,sizeof(displayRam),true);
} // begin

/****************************************************************/
// internal function - Write a single byte
//
uint8_t HT16K33::_i2c_write(uint8_t val){
  Wire.beginTransmission(_address);
  Wire.write(val);
  return Wire.endTransmission();
} // _i2c_write

/****************************************************************/
// internal function - Write several bytes
// "size" is amount of data to send excluding the first command byte
// if LSB is true then swap high and low byte to send LSB MSB
// NOTE: Don't send odd amount of data if using LSB, then it will send one to much
//
uint8_t HT16K33::_i2c_write(uint8_t cmd,uint8_t *data,uint8_t size,boolean LSB){
  uint8_t i;
  Wire.beginTransmission(_address);
  Wire.write(cmd);
  i=0;
  while (i<size){
    if (LSB){
      Wire.write(data[i+1]);
      Wire.write(data[i++]);
      i++;
    } else {
      Wire.write(data[i++]);
    }
  }
  return Wire.endTransmission(); // Send out the data
} // _i2c_write

/****************************************************************/
// internal function - read a byte from specific address (send one byte(address to read) and read a byte)
//
uint8_t HT16K33::_i2c_read(uint8_t addr){
  _i2c_write(addr);
  Wire.requestFrom(_address,(uint8_t) 1);
  return Wire.read();    // read one byte
} // _i2c_read

/****************************************************************/
// read an array from specific address (send a byte and read several bytes back)
// return value is how many bytes that where really read
//
uint8_t HT16K33::_i2c_read(uint8_t addr,uint8_t *data,uint8_t size){
  uint8_t i,retcnt,val;
  
  _i2c_write(addr);
  retcnt=Wire.requestFrom(_address, size);
  i=0;
  while(Wire.available() && i<size)    // slave may send less than requested
  {
    data[i++] = Wire.read();    // receive a byte as character
  }

  return retcnt;
} // _i2c_read

/****************************************************************/
// Put the chip to sleep
//
uint8_t HT16K33::sleep(){
  return _i2c_write(HT16K33_SS|HT16K33_SS_STANDBY); // Stop oscillator
} // sleep

/****************************************************************/
// Wake up the chip (after it been a sleep )
//
uint8_t HT16K33::normal(){
  return _i2c_write(HT16K33_SS|HT16K33_SS_NORMAL); // Start oscillator
} // normal

/****************************************************************/
// Turn off one led but only in memory
// To do it on chip a call to "sendLed" is needed
//
uint8_t HT16K33::clearLed(uint8_t ledno){ // 16x8 = 128 LEDs to turn on, 0-127
  if (ledno>=0 && ledno<128){
    bitClear(displayRam[int(ledno/8)],(ledno % 8));
    return 0;
  } else {
    return 1;
  }
} // clearLed

/****************************************************************/
// Turn on one led but only in memory
// To do it on chip a call to "sendLed" is needed
//
uint8_t HT16K33::setLed(uint8_t ledno){ // 16x8 = 128 LEDs to turn on, 0-127
  if (ledno>=0 && ledno<128){
    bitSet(displayRam[int(ledno/8)],(ledno % 8));
    return 0;
  } else {
    return 1;
  }
} // setLed

/****************************************************************/
// check if a specific led is on(true) or off(false)
//
boolean HT16K33::getLed(uint8_t ledno,boolean Fresh){ 

  // get the current state from chip
  if (Fresh) {
    _i2c_read(HT16K33_DDAP, displayRam,sizeof(displayRam));
  }

  if (ledno>=0 && ledno<128){
    return bitRead(displayRam[int(ledno/8)],ledno % 8) != 0;
  }
} // getLed

/****************************************************************/
uint8_t HT16K33::setDisplayRaw(uint8_t pos, uint8_t val) {
  if (pos < sizeof(displayRam)) {
    displayRam[pos] = val;
    return 0;
  } else {
    return 1;
  }
} // setDisplayRaw

/****************************************************************/
// Send the display ram info to chip - kind of commit all changes to the outside world
//
uint8_t HT16K33::sendLed(){
  return _i2c_write(HT16K33_DDAP, displayRam,sizeof(displayRam));
} // sendLed

/****************************************************************/
// set a single LED and update NOW
//
uint8_t HT16K33::setLedNow(uint8_t ledno){
  uint8_t rc;
  rc=setLed(ledno);
  if (rc==0){
    return sendLed();
  } else {
    return rc;
  }
} // setLedNow

/****************************************************************/
// clear a single LED and update NOW
//
uint8_t HT16K33::clearLedNow(uint8_t ledno){
  uint8_t rc;
  rc=clearLed(ledno);
  if (rc==0){
    return sendLed();
  } else {
    return rc;
  }
} // clearLedNow

/****************************************************************/
// Change brightness of the whole display
// level 0-15, 0 means display off
//
uint8_t HT16K33::setBrightness(uint8_t level){
  if (HT16K33_DIM_1>=0 && level <HT16K33_DIM_16){
    return _i2c_write(HT16K33_DIM|level);
  } else {
    return 1;
  }
} // setBrightness

/****************************************************************/
// Check the chips interrupt flag
// 0 if no new key is pressed
// !0 if some key is pressed and not yet read
//
uint8_t HT16K33::keyINTflag(){ 
  return _i2c_read(HT16K33_IFAP);
} // keyINTflag

/****************************************************************/
// Check if any key is pressed
// returns how many keys that are currently pressed
// 

//From http://stackoverflow.com/questions/109023/how-to-count-the-number-of-set-bits-in-a-32-bit-integer
#ifdef __GNUC__
  uint16_t _popcount(uint16_t x) {
    return __builtin_popcount(x);
  }
#else
  uint16_t _popcount(uint16_t i) {
    i = i - ((i >> 1) & 0x55555555);
    i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
    return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
  }
#endif

uint8_t HT16K33::keysPressed(){ 
  //  Serial.println(_keyram[0]|_keyram[1]|_keyram[2],HEX);
  return (_popcount(_keyram[0])+_popcount(_keyram[1])+_popcount(_keyram[2]));
} // keysPressed

/****************************************************************/
// Internal function - update cached key array
//
void HT16K33::_updateKeyram(){
  uint8_t curkeyram[6];

  _i2c_read(HT16K33_KDAP, curkeyram, 6);
  _keyram[0]=curkeyram[1]<<8 | curkeyram[0]; // datasheet page 21, 41H is high 40H is low
  _keyram[1]=curkeyram[3]<<8 | curkeyram[2]; // or LSB MSB
  _keyram[2]=curkeyram[5]<<8 | curkeyram[4];
  return;
} // _updateKeyram

/****************************************************************/
// return the key status
//
void HT16K33::readKeyRaw(HT16K33::KEYDATA keydata,boolean Fresh){
  int8_t i;

  // get the current state
  if (Fresh) {_updateKeyram();}

  for (i=0;i<3;i++){
    keydata[i]=_keyram[i];
  }

  return;
} // readKeyRaw

/****************************************************************
 * read the keys and return the key that changed state
 * if more than one is pressed (compared to last scan) 
 * only one is returned, the first one found
 * 0 means no key pressed.
 * "1" means the key #1 is pressed
 * "-1" means the key #1 is released 
 * "clear"=true means it will only look keys currently pressed down.
 *     this is so you can detect what key is still pressed down after
 *     several keys are pressed down and then all but one is released
 *     (without keeping track of up/down separately)
 *
 *Observations:
 * As long as the key is pressed the keyram bit is set
 * the flag is set when key is pressed down but then cleared at first
 * read of key ram.
 * When released the key corresponding bit is cleared but the flag is NOT set
 * This means that the only way a key release can be detected is
 * by only polling readKey and ignoring flag
 * 
 */

int8_t HT16K33::readKey(boolean clear){
  static HT16K33::KEYDATA oldKeyData;
  uint16_t diff;
  uint8_t key;
  int8_t i,j;

    // save the current state
  for (i=0;i<3;i++){
    if (clear){
      oldKeyData[i]=0;
    } else {
      oldKeyData[i]=_keyram[i];
    }
  }
    
  _updateKeyram();

  key=0; //the key that changed state
  for (i=0;i<3;i++){
    diff=_keyram[i] ^ oldKeyData[i]; //XOR old and new, any changed bit is set.
    if ( diff !=0 ){ // something did change
      for (j=0;j<13;j++){
	key++;
	if (((diff >> j) & 1) == 1){
	  if (((_keyram[i] >> j) & 1)==0){
	    return -key;
	  }else{
	    return key;
	  }
	} // if keyram differs
      } // for j in bits
    } else {
      key+=13;
    } // if diff
  } // for i
  return 0; //apperently no new key was pressed - old might still be held down, pass clear=true to see it
} // readKey

/****************************************************************/
// Make the display blink
//
uint8_t HT16K33::setBlinkRate(uint8_t rate){
  switch (rate) {
    case HT16K33_DSP_NOBLINK:
    case HT16K33_DSP_BLINK2HZ:
    case HT16K33_DSP_BLINK1HZ:
    case HT16K33_DSP_BLINK05HZ:
      _i2c_write(HT16K33_DSP | HT16K33_DSP_ON |rate);
      return 0;
      ;;
  default:
    return 1;
  }
} //setBlinkRate

/****************************************************************/
// turn on the display
//
void HT16K33::displayOn(){
  _i2c_write(HT16K33_DSP |HT16K33_DSP_ON);
} // displayOn

/****************************************************************/
// turn off the display
//
void HT16K33::displayOff(){
  _i2c_write(HT16K33_DSP |HT16K33_DSP_OFF);
} // displayOff

The matching .H for HT16K33

C/C++
/*
  ht16k33.h - used to talk to the htk1633 chip to do things like turn on LEDs or scan keys
 * Copyright:  Peter Sjoberg <peters-alib AT techwiz.ca>
 * License: GPLv3
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License version 3 as 
    published by the Free Software Foundation.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses></http:>.

 *
 *
 * History:
 * 2015-10-04  Peter Sjoberg <peters-alib AT techwiz.ca>
 *             Created using https://www.arduino.cc/en/Hacking/LibraryTutorial and ht16k33 datasheet
 * 2015-11-25  Peter Sjoberg <peters-alib AT techwiz DOT ca>
 *	       first check in to github
 * 2015-12-05  Peter Sjoberg <peters-alib AT techwiz.ca>
 *	       moved displayram to public section
 */



#ifndef ht16k33_h
#define ht16k33_h

#include "Arduino.h"

class HT16K33
{
 public:
  typedef uint16_t KEYDATA[3];
  typedef uint8_t  DisplayRam_t[16];

  DisplayRam_t displayRam;

  HT16K33(); // the class itself

  void    begin(uint8_t address);
  void    end();
  uint8_t sleep();  // stop oscillator to put the chip to sleep
  uint8_t normal(); // wake up chip and start ocillator
  uint8_t clearLed(uint8_t ledno); // 16x8 = 128 LEDs to turn on, 0-127
  uint8_t setLed(uint8_t ledno); // 16x8 = 128 LEDs to turn on, 0-127
  boolean getLed(uint8_t ledno,boolean Fresh=false); // check if a specific led is on(true) or off(false)
  uint8_t setDisplayRaw(uint8_t pos, uint8_t val); // load byte "pos" with value "val"
  uint8_t sendLed(); // send whatever led patter you set
  uint8_t setLedNow(uint8_t ledno); //Set a single led and send led in one function
  uint8_t clearLedNow(uint8_t ledno); //Clear a single led and send led in one function
  uint8_t setBrightness(uint8_t level); // level 0-16, 0 means display off
  uint8_t keyINTflag(); // INTerrupt flag value, set when a key is pressed
  uint8_t keysPressed(); // report how many keys that are pressed, clear means report as if new
  int8_t  readKey(boolean clear=false);  // read what key was pressed, Fresh=false to go from cache
  void    readKeyRaw(KEYDATA keydata,boolean Fresh=true); //read the raw key info, bitmapped info of all key(s) pressed
  uint8_t setBlinkRate(uint8_t rate); // HT16K33_DSP_{NOBLINK,BLINK2HZ,BLINK1HZ,BLINK05HZ}
  void    displayOn();
  void    displayOff();

 private:
  void _updateKeyram();
  uint8_t _i2c_write(uint8_t val);
  uint8_t _i2c_write(uint8_t cmd,uint8_t *data,uint8_t size,boolean LSB=false);
  uint8_t _i2c_read(uint8_t addr);
  uint8_t _i2c_read(uint8_t addr,uint8_t *data,uint8_t size);

  KEYDATA _keyram;
  uint8_t _address;
};


#endif

Current - Solar Tracker Files

Software and Hardware is here

Credits

DougalPlummer

DougalPlummer

14 projects • 89 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 :)

Comments