

/* This is an amp limit controller for the Midnite Classic Charge Controller equipped with a WhizBangJr. It controls the amps flowing to
the batteries (as measured by the WBJr) by altering the Classic amps out setpoint limit. The WBJr and Classic amps limits are both 
constrained High and Low. The maximum WBJr amps to the batteries and to the Classic are both adjusted by their setpoints. The Modbusmaster
writes to the Classic only when the PID is turned on which allows manual adjustment of the Classic amp limiter if desired. The manual 
limit will be overriden by the controller when it turns on.The WBJr amp flow is smoothed with an array which is adjustable for the 
number of readings desired to be averaged.*/

#include <PID_v1.h>
#include <Wire.h>
#include <SimpleModbusMaster.h>
#include <LiquidCrystal_I2C.h>

// SCL Mega pin 21, Uno pin D16 ; SDA Mega pin 20 , Uno pin D17

double  inputAmpsToBatt;
double  aveInputAmpsToBatt = 30;                   // smoothed wbjr average input amps to battery

int  actualAmpLimitSP;
int inUseAmpLimitSetPoint;
float ampOutputCalc = 0;                           // variable for amp PID output % calculation
int spAmpLimit;                                    // variable for display removes decimal
double ClassicAmpSetPoint;                         // variable setpoint value written to Classic
boolean pidAmpMode;
double WBJrAmpLimitSP = 0;
int ClassicHiAmpLimit = 0;

//-------------------------------------------------------------------------smoothing wbjr amps to battery

#define  numReadings                     25
double  wbjrReadings[numReadings];                 // the array for readings from wbjr amps to battery
int     index  =  0;                               // the index of the current reading
double  total  =  0;                               // the running total
double  average  =  0;                             // the average

//--------------------------------------------------------------------------------------------------control panel

double  WBJRAmpLimitSP = 70;                       // Hi amp limit to batteries (WBJr) setpoint
int constrainWBJrLimitLo = 10;                     // constrains WBJr amp limit setpoint to low value
int constrainWBJrLimitHi = 75;                     // constrains WBJr amp limit setpoint to high value
int ClassicHIAmpLimitSP = 75;                      // Hi amp limit to Classic setpoint
int constrainClscLimitLo = 5;                      // constrains Classic amp limit setpoint to low value
int constrainClscLimitHi = 80;                     // constrains Classic amp limit setpoint to high value

//--------------------------------------------------------------------------------------------------PID define Variables 
                                                    
double ampInput;                                   //pid input
double ampOutput;                                  //pid output

//--------------------------------------------------------------------------------------------------PID tuning parameters

#define Kp       2
#define Ki     .30
#define Kd     .03

PID ampPID(&ampInput, &ampOutput, &WBJrAmpLimitSP, Kp, Ki, Kd, REVERSE);

//--------------------------------------------------------------------------------------------------rs232

#define baud                          19200
#define timeout                        1500
#define polling                         200        // the scan rate
#define retry_count                      20
#define TxEnablePin                       2        // used to toggle the receive/transmit pin on the driver     

enum
{
  PACKET1,
  PACKET2,
  PACKET3,
  PACKET4,
  PACKET5,
  TOTAL_NO_OF_PACKETS                             // leave this last entry
};

Packet packets[TOTAL_NO_OF_PACKETS];              // Create an array of Packets to be configured
packetPointer packet1 = &packets[PACKET1];        // Create a packetPointer to access each packet
packetPointer packet2 = &packets[PACKET2];
packetPointer packet3 = &packets[PACKET3];
packetPointer packet4 = &packets[PACKET4];
packetPointer packet5 = &packets[PACKET5];
unsigned int readRegsd[3];
unsigned int readRegse[3];
unsigned int writeRegse[3];
unsigned int readRegsc[3];
unsigned int readRegsa[3];

//-----------------------------------------------------------------------------------------------------------

LiquidCrystal_I2C lcd1(0X20, 20, 4);              // set the LCD address to 0X20 for a 20 chars and 4 line display for lcd 1

//----------------------------------------------------------------------------------------------------------setup

void setup() {

  lcd1.init ();                                   // lcd setUp LCD1 display
  lcd1.backlight ();
  lcd1.display ();

  //-------------------------------------------------------------------------------------------------------------

  modbus_construct(packet1, 10, READ_HOLDING_REGISTERS, 4370, 1, readRegsd);        // read 1 register wbjr amps to batt 4371

  modbus_construct(packet2, 10, READ_HOLDING_REGISTERS, 4147, 1, readRegse);        // read 1 register Classic battery amp in load limit setpoint 4148

  modbus_construct(packet3, 10, PRESET_MULTIPLE_REGISTERS, 4147, 1, writeRegse);    // write Classic amp in load limit register reg 4148

  modbus_construct(packet4, 10, READ_HOLDING_REGISTERS, 4119, 1, readRegsc);        // read 1 register Classic Charge State 4120
  
  modbus_construct(packet5, 10, READ_HOLDING_REGISTERS, 4129, 1, readRegsa);        // read 1 register Classic Charge State 4120

  modbus_configure(&Serial, baud, SERIAL_8N1, timeout, polling, retry_count, TxEnablePin, packets, TOTAL_NO_OF_PACKETS);      // Initialize communication settings:

  packets[PACKET3].connection = 0;                                                  // turns packet3 write connection off

  //----------------------------------------------------------------------------------------------------------
  //initialize the PID in off state - 0 output
  ampPID.SetMode(MANUAL);
  ampPID.SetOutputLimits(0, 255);
  ampOutput = 0;
  pidAmpMode = false;
  lcd1.setCursor (16, 0);
  lcd1.print ("Off");

  //-------------------------------------------------------------------------------------------------------
  // constrains Classic and WBJr amp limits; displays limits with constrained values
  WBJrAmpLimitSP = WBJRAmpLimitSP;
  spAmpLimit = (int) WBJrAmpLimitSP;                              // for display - cast as int to remove decimal points

  WBJrAmpLimitSP = constrain (WBJrAmpLimitSP, constrainWBJrLimitLo, constrainWBJrLimitHi);

  if (WBJRAmpLimitSP > constrainWBJrLimitHi) {                    // for display - prints actual limit values in use
    lcd1.setCursor (7, 2);
    lcd1.print (constrainWBJrLimitHi);
  } else if (WBJRAmpLimitSP < constrainWBJrLimitLo) {
    lcd1.setCursor (7, 2);
    lcd1.print (constrainWBJrLimitLo);
  } else {
    lcd1.setCursor (7, 2);
    lcd1.print (spAmpLimit);
  }
  ClassicHiAmpLimit = ClassicHIAmpLimitSP;

  ClassicHiAmpLimit = constrain (ClassicHiAmpLimit, constrainClscLimitLo, constrainClscLimitHi);

  if (ClassicHIAmpLimitSP > constrainClscLimitHi) {                    // for display - prints actual limit values in use
    lcd1.setCursor (7, 3);
    lcd1.print (constrainClscLimitHi);
  } else if (ClassicHIAmpLimitSP < constrainClscLimitLo) {
    lcd1.setCursor (7, 3);
    lcd1.print (constrainClscLimitLo);
  } else {
    lcd1.setCursor (7, 3);
    lcd1.print (ClassicHiAmpLimit);
  }

}

//--------------------------------------------------------------------------------------------------------loop

void loop() {

  int state ;
  int ampLimitState;
  modbus_update();                                           // rs 232 read the Classic's modbus registers

  state = (unsigned int)readRegsc[0] >> 8;                   // high byte contains charge state code
  
  ampLimitState = (unsigned int)readRegsa[0] <<9;            // 10th bit to the right has`amp limit reached bit

  actualAmpLimitSP = readRegse[0];
  inUseAmpLimitSetPoint = actualAmpLimitSP;
  inUseAmpLimitSetPoint /= 10;

  //-----------------------------------------------------------------------------------------------------------------rs232 retry checking

  /*errorRegs[0] = packet1-> retries;                       // retry count from packet1
     lcd1.setCursor (0,3);
     lcd1.print("Retries");
     lcd1.setCursor (9,3);
     lcd1.print (errorRegs[0]);
  */

  //-----------------------------------------------------------------------------------------------------------------wbjr amps to battery with smoothing

  inputAmpsToBatt = (signed int)readRegsd[0];            //wbjr amps to battery   reg  4371
  inputAmpsToBatt /= 10 ;

  total =  total  - wbjrReadings[index];
  wbjrReadings[index]  =  inputAmpsToBatt;               // read from the wbjr
  total =  total  + wbjrReadings[index];                 // add the reading to the total:
  index  =  index + 1;                                    // increment by 1

  if (index  >=  numReadings)                            // if array full
    index  =  0;                                         // restart
  average  = (double) total  /  numReadings;             // calculate the average:
  aveInputAmpsToBatt = average;
  delay (2);                                             //delay between reads for stability

  ampInput = aveInputAmpsToBatt;                         // input to PID is WBJr average amps to battery

  //-------------------------------------------------------------------------------------

  lcd1.setCursor(0, 0);                                  // top line of lcd1

  switch (state)
  {
    case 0:
      lcd1.print ("Resting  ");
      ampPID.SetMode(MANUAL);                           //Manual when in resting
      ampOutput = 0;                                   // Output"locks" at existing when on manual. This line sets output to zero
      lcd1.setCursor (16, 0);
      lcd1.print ("Off");
      break;
    case 3:
      lcd1.print ("Absorb   ");
      break;
    case 4:
      lcd1.print ("BulkMppt ");
      break;
    case 5:
      lcd1.print ("Float    ");
      break;
    case 6:
      lcd1.print ("FloatMppt");
      break;
    case 7:
      lcd1.print ("Equalize ");
      break;
    case 10:
      lcd1.print ("HyperVoc ");
      ampPID.SetMode(MANUAL);
      ampOutput = 0;
      lcd1.setCursor (17, 0);
      lcd1.print ("Off");
      break;
    case 18:
      lcd1.print ("EqMppt   ");
      break;
  }
  
  lcd1.setCursor (10, 0);                                               //lcd1 lines 1,2,3&4
  lcd1.print(ampLimitState);
  
  lcd1.setCursor (0, 1);                                               //lcd1 lines 1,2,3&4
  lcd1.print("WBJr BAmp");
  lcd1.setCursor (15, 1);
  lcd1.print (aveInputAmpsToBatt);
  lcd1.setCursor (0, 2);
  lcd1.print ("WBJrSP");
  lcd1.setCursor (12, 2);
  lcd1.print("%");
  lcd1.setCursor(15, 2);
  lcd1.print(ampOutputCalc);
  lcd1.setCursor (0, 3);
  lcd1.print ("ClscSP");
  lcd1.setCursor (11, 3);
  lcd1.print ("PidSP");
  lcd1.setCursor (17, 3);
  lcd1.print (inUseAmpLimitSetPoint);

  //--------------------------------------------------------------------------------------------------PID computation & output % calculation

  ampPID.Compute();

  ampOutputCalc = ampOutput;
  ampOutputCalc = ampOutputCalc /= 255;                                              // convert  controller output to %
  ampOutputCalc *= 100;                                                              // for display % output

  ClassicAmpSetPoint = ampOutputCalc;                                                // output setpoint calc for Classic
  ClassicAmpSetPoint = ClassicAmpSetPoint /= 100;
  ClassicAmpSetPoint = 1 - ClassicAmpSetPoint;
  ClassicAmpSetPoint = ClassicAmpSetPoint * ClassicHiAmpLimit;                       // sets hi value for amps to Classic
  ClassicAmpSetPoint = ClassicAmpSetPoint *= 10;
  ClassicAmpSetPoint = (unsigned int) ClassicAmpSetPoint;

  if ((ampOutputCalc >= 1) && (pidAmpMode == true)) {
    lcd1.setCursor (16, 0);
    lcd1.print (" On");
    packets[PACKET3].connection = 1;                                                  // turns packet3 write connection on
    writeRegse[0] = ClassicAmpSetPoint;
    modbus_update();
    delay (5);
  }

  if ((ampInput < spAmpLimit) && ((inUseAmpLimitSetPoint + 1) == ClassicHiAmpLimit) && (pidAmpMode == true)) {
    packets[PACKET3].connection = 0;                                                  // turns packet3 write connection off
    modbus_update();
    ampPID.SetMode(MANUAL);
    ampOutput = 0;
    lcd1.setCursor (16, 0);
    lcd1.print ("Off");
    pidAmpMode = false;
  }

  if  ((!(state == 0)) && (!(state == 10))) {                                         // turns PID on if state is not resting or hypervoc
    ampPID.SetMode(AUTOMATIC);
    pidAmpMode = true;
  }
}




