




// Connect Uno  via rs232 shield to Midnite Classic's rj11 MNGP/Slave rs232 port.
// uses Modbus RTU to retrieve modbus registers. UNO is master, Classic is slave.
// David Dix Jan 2015, dgd@kc.net.nz
// 4X20lcd PID Timer1 Will Eert July2015, willeert@gmail.com


      #include <TimerOne.h>           //pwm frequency control
      #include <LiquidCrystal_I2C.h>  //lcd
      #include <Wire.h>               //lcd
      #include <SimpleModbusMaster.h> //rs232
      #include <PID_v1.h>             //pid
      
      int controlPin = 10;           //pwm pin

      double divSpt=0;      //pid storage for diversion setpoint for pid SP
      double battInput=0;   //pid storage for battery volts for pid input
      float outputCalc = 0;  // pid storage for output % calculation 

                            //pid Define Variables we'll be connecting to
      double Setpoint;
      double Input;
      double Output;

                            //pid Specify the links and initial tuning parameters
      double Kp=400, Ki=75, Kd=.5;                                   // change Kp,Ki&Kd to tune controller response
      PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, REVERSE);



                         //rs232 Port information 
      #define baud 19200
      #define timeout 1000
      #define polling 200 // the scan rate
      #define retry_count 10
      #define TxEnablePin 2 // used to toggle the receive/transmit pin on the driver
      #define LED 9


      enum
{
      PACKET1,
      PACKET2,
      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];
      packetPointer packet2 = &packets[PACKET2];// Create a packetPointer to access each packet
      unsigned int readRegsa[21];
      unsigned int readRegsb[3];
      LiquidCrystal_I2C lcd1(0X20,20,4); // set the LCD address to 0X20 for a 20 chars and 4 line display for lcd 1
      LiquidCrystal_I2C lcd2(0X21,20,4); // set the LCD address to 0X20 for a 20 chars and 4 line display for lcd 2
 
 
 

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

      lcd2.init ();   //lcd setUp LCD2 display
      lcd2.backlight  (); 
      
                  // rs 232 read 20 register starting at address 4114  
      modbus_construct(packet1, 10, READ_HOLDING_REGISTERS, 4114, 20, readRegsa);
                  //rs232 read 2 register starting at address 4243
      modbus_construct(packet2, 10, READ_HOLDING_REGISTERS, 4243, 2, readRegsb);
  
 
  
                  // Initialize communication settings:
       
       modbus_configure(&Serial, baud, SERIAL_8N1, timeout, polling, retry_count, TxEnablePin, packets, TOTAL_NO_OF_PACKETS);
  
 {
                

                    //pwm pin

      pinMode(controlPin,OUTPUT);    
     

                     //pid turn the PID on
                 
      myPID.SetMode(MANUAL);
      myPID.SetOutputLimits(0,1023);  //Timer1 allows the output limit to be 1023
      Output = 0;

      Timer1.initialize(100000);    //pwm frequency = 1000000/100000 = 10 hz. change () to alter frequency
      Timer1.pwm(controlPin,OUTPUT);
      
}

}


      void loop()
{
      double batt_volts;
      int state ;
      double bat_v_spt,div_spt;

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

                     // pid calculate rs232 battery voltage input value
     battInput = readRegsa[0];
     battInput /=10;
     
                    // pid calculate rs232 diversion voltage setpoint value
                    
     divSpt = readRegsb[0];
     divSpt /=10;
     divSpt -=.3;   // pid change this number to adjust diversion setpoint offset from battery voltage setpoint

                    //pid initialize the variables we're linked to

      Input = (battInput);
      Setpoint = (divSpt) ;      
  
                     // rs 232to lcd make Classic register values ready for LCD and diversion control
      
      state = (unsigned int)readRegsa[5] >> 8;  // high byte contains charge state code
      bat_v_spt = readRegsb[0];
      bat_v_spt /=10;
      
 
                  // top line of lcd1
                  
      lcd1.setCursor(0,0);   
                     
         
            //lcd displays charge controller state on lcd1 
     switch (state)   
  {
       case 0:
          lcd1.print ("Resting  ");
          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 ");
          break;
       case 18:
          lcd1.print ("EqMppt   ");
          break;
   }
    lcd2.setCursor (0,0);                  // 1st line lcd2
    lcd2.print ("Divt Ctl State");
    lcd2.setCursor (16,0);

        switch (state)                     // pid turns controller on/off in relation to Classic states manual = off automatic = on
  {
       case 0:       //resting state
       {
          myPID.SetMode(MANUAL);           //Manual when in resting
          Output = 0;                      // Output "locks" at existing when on manual. This drives output to zero 
          lcd2.print ("Off");              // displays divt state
       }
          break;
       case 3:                             // absorb state
       {
          myPID.SetMode(AUTOMATIC);        // Auto when in absorb
          lcd2.print (" On");
       }
          break;
       case 4:                             //bulk state
       {
          myPID.SetMode(AUTOMATIC);          //Auto when in bulk
          Output = 0;
          lcd2.print (" On");
       }
         break;
       case 5:                            //float state
       {
          myPID.SetMode(AUTOMATIC);       //Auto when in float
          lcd2.print (" On");
       }
          break;
       case 6:                            //float mppt state
       {
          myPID.SetMode(AUTOMATIC);       //Auto when in float mppt
          lcd2.print (" On");
       }
          break;
       case 7:                            //equalize state
       {
          myPID.SetMode(AUTOMATIC);       //Auto when in equaliize
          lcd2.print (" On");
       }
          break;
       case 10:                           // hypervoc state                      
       {
          myPID.SetMode(MANUAL);          //Manual when in hypervoc
          Output = 0;
          lcd2.print ("Off");
       }
          break;
       case 18:                           //equalize mppt state
       {
          myPID.SetMode(AUTOMATIC);       //Auto when in equalize mppt
          lcd2.print (" On");
       }
          break;
   }
  
                                         //lcd1 lines 2,3&4
      lcd1.setCursor (0,1);
      lcd1.print("Batt Volts    ") ; 
      lcd1.print((battInput));  
      lcd1.setCursor(0,2);
      lcd1.print("Batt V SetPt  ");
      lcd1.print(bat_v_spt);
      lcd1.setCursor(0,3);
      lcd1.print("Divt V SetPt  ");
      lcd1.print((divSpt));

      lcd2.setCursor (0,1);            // 2nd line lcd2
      lcd2.print("Divt Output % ");   
      lcd2.setCursor(14,1);
      lcd2.print(outputCalc);
      
      delay (300);          // delay to stop LCD flickering, refreshes display 300ms

  
      
  
      Input = (battInput);
      myPID.Compute();
      Timer1.setPwmDuty(controlPin,(int) Output);
      
      outputCalc =(Output) /=1023;  // convert controller output to %
      outputCalc *=100;

     
     
      
 }
     

  
   






 







