#include "TimerOne.h" // using Timer1 library from http://www.arduino.cc/playground/Code/Timer1
#include <LiquidCrystal_I2C.h>
#include <Wire.h>
//------------------------------------------------------------------------------------------------------
// definitions
#define SOL_VOLTS_CHAN 0 // the adc channel to read solar volts
#define SOL_AMPS_CHAN 1 // the adc channel to read solar amps
#define BAT_VOLTS_CHAN 2 // the adc channel to read battery volts
#define AVG_NUM 8 // number of iterations of the adc routine to average the adc readings
#define SOL_AMPS_SCALE 0.026393581 // the scaling value for raw adc reading to get solar amps // 5/(1024*0.185)
#define SOL_VOLTS_SCALE (4.25/1023)*(120/20) // the scaling value for raw adc reading to get solar volts // (5/1024)*(R1+R2)/R2 // R1=100k and R2=20k
#define BAT_VOLTS_SCALE (4.25/1023)*(120/20) // the scaling value for raw adc reading to get battery volts
#define PWM_PIN 9 // the output pin for the pwm
#define PWM_ENABLE_PIN 8 // pin used to control shutoff function of the IR2104 MOSFET driver
#define PWM_FULL 1023 // the actual value used by the Timer1 routines for 100% pwm duty cycle
#define PWM_MAX 99 // the value for pwm duty cyle 0-100%
#define PWM_MIN 50 // the value for pwm duty cyle 0-100%
#define PWM_START 90 // the value for pwm duty cyle 0-100%
#define PWM_INC 1 // the value the increment to the pwm value for the ppt algorithm
#define TRUE 1
#define FALSE 0
#define ON TRUE
#define OFF FALSE
#define TURN_ON_MOSFETS digitalWrite(PWM_ENABLE_PIN, HIGH) // enable MOSFET driver
#define TURN_OFF_MOSFETS digitalWrite(PWM_ENABLE_PIN, LOW) // disable MOSFET driver
#define ONE_SECOND 50000 // count for number of interrupt in 1 second on interrupt period of 20us
#define LOW_SOL_WATTS 5.00 // value of solar watts // this is 5.00 watts
#define MIN_SOL_WATTS 1.00 // value of solar watts // this is 1.00 watts
#define MIN_BAT_VOLTS 11.00 // value of battery voltage // this is 11.00 volts
#define MAX_BAT_VOLTS 14.10 // value of battery voltage// this is 14.10 volts
#define BATT_FLOAT 13.60 // battery voltage we want to stop charging at
#define HIGH_BAT_VOLTS 13.00 // value of battery voltage // this is 13.00 volts
#define LVD 11.5 // low voltage disconnect setting for a 12V system
#define OFF_NUM 9 // number of iterations of off charger state
//------------------------------------------------------------------------------------------------------
// global variables
int ledPin = 13; // LED connected to digital pin 13
int ledPin_jc2 = 6; // LED connected to JC2
int digPin = 4; // pin for button arduino shield
int count = 0;
int pwm = 0; // pwm duty cycle 0-100%
float sol_amps; // solar amps // scaled by 100
float sol_volts; // solar volts // scaled by 100
float bat_volts; // battery volts // scaled by 100
float sol_watts; // solar watts // scaled by 100
int old_sol_watts = 0; // solar watts from previous time through ppt routine // scaled by 100
unsigned int seconds = 0; // seconds from timer routine
unsigned int prev_seconds = 0; // seconds value from previous pass
unsigned int interrupt_counter = 0; // counter for 20us interrrupt
boolean led_on = TRUE;
int led_counter = 0;
int delta = PWM_INC; // variable used to modify pwm duty cycle for the ppt algorithm
enum charger_mode {off, on, bulk, bat_float} charger_state; // enumerated variable that holds state for charger state machine
const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
LiquidCrystal_I2C lcd(0x27, 4, 20);
//------------------------------------------------------------------------------------------------------
//bitmap aray
//-------------------------------------------------------------------------------------------------------
byte solar[8] = //icon for solar panel
{
0b11111,
0b10101,
0b11111,
0b10101,
0b11111,
0b10101,
0b11111,
0b00000
};
byte battery[8]=
{
0b01110,
0b11011,
0b10001,
0b10001,
0b11111,
0b11111,
0b11111,
0b11111,
};
byte _PWM [8]=
{
0b11101,
0b10101,
0b10101,
0b10101,
0b10101,
0b10101,
0b10101,
0b10111,
};
//------------------------------------------------------------------------------------------------------
// This routine is automatically called at powerup/reset
//------------------------------------------------------------------------------------------------------
void setup() // run once, when the sketch starts
{
lcd.begin(20,4); // initialize the lcd for 20 chars 4 lines, turn on backlight
lcd.setCursor(0, 0);
lcd.print(" MNG proSystems "); // Welcome screen
lcd.setCursor(0, 1);
lcd.print("Maximum Power Point ");
lcd.setCursor(0, 2);
lcd.print("-System-V2.1 Rev.1b-");
lcd.setCursor(0, 3);
lcd.print("********************");
lcd.backlight();
delay(3500);
lcd.clear();
lcd.createChar(1,solar); // turn the bitmap into a character
lcd.createChar(2,battery); // turn the bitmap into a character
lcd.createChar(3,_PWM); // turn the bitmap into a character
pinMode(ledPin, OUTPUT); // sets the digital pin as output
pinMode(ledPin_jc2, OUTPUT); // sets the digital pin as output
pinMode(PWM_ENABLE_PIN, OUTPUT); // sets the digital pin as output
Timer1.initialize(20); // initialize timer1, and set a 20uS period
Timer1.pwm(PWM_PIN, 0); // setup pwm on pin 9, 0% duty cycle
TURN_ON_MOSFETS; // turn on MOSFET driver chip
Timer1.attachInterrupt(callback); // attaches callback() as a timer overflow interrupt
Serial.begin(9600); // open the serial port at 38400 bps:
pwm = PWM_START; // starting value for pwm
charger_state = on; // start with charger state as on
}
//------------------------------------------------------------------------------------------------------
// This is interrupt service routine for Timer1 that occurs every 20uS.
// It is only used to incremtent the seconds counter.
// Timer1 is also used to generate the pwm output.
//------------------------------------------------------------------------------------------------------
void callback()
{
if (interrupt_counter++ > ONE_SECOND) { //increment interrupt_counter until one second has passed
interrupt_counter = 0;
seconds++; //then increment seconds counter
}
}
//------------------------------------------------------------------------------------------------------
// This routine reads and averages the analog inputs for this system, solar volts, solar amps and
// battery volts. It is called with the adc channel number (pin number) and returns the average adc
// value as an integer.
//------------------------------------------------------------------------------------------------------
int read_adc(int channel)
{
int sum = 0;
int temp;
int i;
for (i=0; i < AVG_NUM; i++) // loop through reading raw adc values AVG_NUM number of times
{
temp = analogRead(channel); // read the input pin
sum += temp; // store sum for averaging
delayMicroseconds(50); // pauses for 50 microseconds
}
return(sum / AVG_NUM); // divide sum by AVG_NUM to get average and return it
}
//------------------------------------------------------------------------------------------------------
// This routine uses the Timer1.pwm function to set the pwm duty cycle. The routine takes the value in
// the variable pwm as 0-100 duty cycle and scales it to get 0-1034 for the Timer1 routine.
// There is a special case for 100% duty cycle. Normally this would be have the top MOSFET on all the time
// but the MOSFET driver IR2104 uses a charge pump to generate the gate voltage so it has to keep running
// all the time. So for 100% duty cycle I set the pwm value to 1023 - 1 so it is on 99.9% almost full on
// but is switches enough to keep the charge pump on IR2104 working.
//------------------------------------------------------------------------------------------------------
void set_pwm_duty(void)
{
if (pwm < PWM_MAX) // check limits of PWM duty cyle and set to PWM_MAX
{
pwm = PWM_MAX;
}
else if (pwm > PWM_MIN) // if pwm is less than PWM_MIN then set it to PWM_MIN
{
pwm = PWM_MIN;
}
if (pwm > PWM_MAX)
{
Timer1.pwm(PWM_PIN,(PWM_FULL * (long)pwm / 100), 20); // use Timer1 routine to set pwm duty cycle at 20uS period
//Timer1.pwm(PWM_PIN,(PWM_FULL * (long)pwm / 100));
}
else if (pwm == PWM_MAX)
{ // if pwm set to 100% it will be on full but we have
Timer1.pwm(PWM_PIN,(PWM_FULL - 1), 1000); // keep switching so set duty cycle at 99.9% and slow down to 1000uS period
//Timer1.pwm(PWM_PIN,(PWM_FULL - 1));
}
}
//------------------------------------------------------------------------------------------------------
// This routine prints all the data out to the serial port.
//------------------------------------------------------------------------------------------------------
void print_data(void)
{
Serial.print("s_volts = ");
Serial.print(sol_volts);
Serial.print(" ");
Serial.print("s_amps = ");
Serial.print(sol_amps);
Serial.print(" ");
Serial.print("s_watts = ");
Serial.print(sol_watts);
Serial.print(" ");
Serial.print("b_volts = ");
Serial.print(bat_volts);
Serial.print(" ");
Serial.print("charger = ");
if (charger_state == on) Serial.print("on ");
else if (charger_state == off) Serial.print("off ");
else if (charger_state == bulk) Serial.print("bulk ");
else if (charger_state == bat_float) Serial.print("float");
Serial.print(" ");
Serial.print("pwm = ");
Serial.print(pwm,DEC);
Serial.print(" ");
Serial.print("\n\r");
}
//------------------------------------------------------------------------------------------------------
// This routine reads all the analog input values for the system. Then it multiplies them by the scale
// factor to get actual value in volts or amps. Then it adds on a rounding value before dividing to get
// the result scaled by 100 to give a fractional value of two decimal places. It also calculates the input
// watts from the solar amps times the solar voltage and rounds and scales that by 100 (2 decimal places) also.
//------------------------------------------------------------------------------------------------------
void read_data(void)
{
sol_volts = read_adc(SOL_VOLTS_CHAN) * SOL_VOLTS_SCALE; //input of solar volts
sol_amps = (read_adc(SOL_AMPS_CHAN) * SOL_AMPS_SCALE -12.01); //input of solar amps
sol_watts = sol_amps * sol_volts ; //calculations of solar watts
bat_volts = read_adc(BAT_VOLTS_CHAN) * BAT_VOLTS_SCALE; //input of battery volts
}
//------------------------------------------------------------------------------------------------------
// This routine blinks the jc2 LED.
//------------------------------------------------------------------------------------------------------
void blink_leds(void)
{
static boolean led_on = TRUE;
static int led_counter = 0;
if (!(led_counter++ % 4))
{
if (led_on)
{
led_on = FALSE;
digitalWrite(ledPin_jc2, HIGH); // sets the LED on
}
else
{
led_on = TRUE;
digitalWrite(ledPin_jc2, LOW); // sets the LED off
}
}
}
//------------------------------------------------------------------------------------------------------
// This routine is the charger state machine. It has four states on, off, bulk and float.
// It's called once each time through the main loop to see what state the charger should be in.
// The battery charger can be in one of the following four states:
//
// On State - this is charger state for MIN_SOL_WATTS &lt; solar watts &lt; LOW_SOL_WATTS. This state is probably
// happening at dawn and dusk when the solar watts input is too low for the bulk charging state but not
// low enough to go into the off state. In this state we just set the pwm = 100% to get the most of low
// amount of power available.
// Bulk State - this is charger state for solar watts &gt; MIN_SOL_WATTS. This is where we do the bulk of the battery
// charging and where we run the Peak Power Tracking alogorithm. In this state we try and run the maximum amount
// of current that the solar panels are generating into the battery.
// Float State - As the battery charges it's voltage rises. When it gets to the MAX_BAT_VOLTS we are done with the
// bulk battery charging and enter the battery float state. In this state we try and keep the battery voltage
// at MAX_BAT_VOLTS by adjusting the pwm value. If we get to pwm = 100% it means we can't keep the battery
// voltage at MAX_BAT_VOLTS which probably means the battery is being drawn down by some load so we need to back
// into the bulk charging mode.
// Off State - This is state that the charger enters when solar watts &lt; MIN_SOL_WATTS. The charger goes into this
// state when it gets dark and there is no more power being generated by the solar panels. The MOSFETs are turned
// off in this state so that power from the battery doesn't leak back into the solar panel. When the charger off
// state is first entered all it does is decrement off_count for OFF_NUM times. This is done because if the battery
// is disconnected (or battery fuse is blown) it takes some time before the battery voltage changes enough so we can tell
// that the battery is no longer connected. This off_count gives some time for battery voltage to change so we can
// tell this.
//------------------------------------------------------------------------------------------------------
void run_charger(void) {
static int off_count = OFF_NUM;
switch (charger_state) {
case on:
if (sol_watts < MIN_SOL_WATTS) { //if watts input from the solar panel is less than
charger_state = off; //the minimum solar watts then it is getting dark so
off_count = OFF_NUM; //go to the charger off state
TURN_OFF_MOSFETS;
}
else if (bat_volts > MAX_BAT_VOLTS) { //else if the battery voltage has gotten above the float
charger_state = bat_float; //battery float voltage go to the charger battery float state
}
else if (sol_watts < LOW_SOL_WATTS) { //else if the solar input watts is less than low solar watts
pwm = PWM_MAX; //it means there is not much power being generated by the solar panel
set_pwm_duty(); //so we just set the pwm = 100% so we can get as much of this power as possible
} //and stay in the charger on state
else {
pwm = ((bat_volts * 10) / (sol_volts / 10)) + 5; //else if we are making more power than low solar watts figure out what the pwm
charger_state = bulk; //value should be and change the charger to bulk state
}
break;
case bulk:
if (sol_watts < MIN_SOL_WATTS) { //if watts input from the solar panel is less than
charger_state = off; //the minimum solar watts then it is getting dark so
off_count = OFF_NUM; //go to the charger off state
TURN_OFF_MOSFETS;
}
else if (bat_volts > MAX_BAT_VOLTS) { //else if the battery voltage has gotten above the float
charger_state = bat_float; //battery float voltage go to the charger battery float state
}
else if (sol_watts < LOW_SOL_WATTS) { //else if the solar input watts is less than low solar watts
charger_state = on; //it means there is not much power being generated by the solar panel
TURN_ON_MOSFETS; //so go to charger on state
}
else { // this is where we do the Peak Power Tracking ro Maximum Power Point algorithm
if (old_sol_watts >= sol_watts) { // if previous watts are greater change the value of
delta = -delta; // delta to make pwm increase or decrease to maximize watts
}
pwm += delta; // add delta to change PWM duty cycle for PPT algorythm
old_sol_watts = sol_watts; // load old_watts with current watts value for next time
set_pwm_duty(); // set pwm duty cycle to pwm value
}
break;
case bat_float:
if (sol_watts < MIN_SOL_WATTS) { //if watts input from the solar panel is less than
charger_state = off; //the minimum solar watts then it is getting dark so
off_count = OFF_NUM; //go to the charger off state
set_pwm_duty();
TURN_OFF_MOSFETS;
}
else if (bat_volts > MAX_BAT_VOLTS) { //since we're in the battery float state if the battery voltage
pwm -= 1; //is above the float voltage back off the pwm to lower it
set_pwm_duty();
}
else if (bat_volts < MAX_BAT_VOLTS) { //else if the battery voltage is less than the float voltage
pwm += 1; //increment the pwm to get it back up to the float voltage
set_pwm_duty();
if (pwm >= 100) { //if pwm gets up to 100 it means we can't keep the battery at
charger_state = bulk; //float voltage so jump to charger bulk state to charge the battery
}
}
break;
case off: //when we jump into the charger off state, off_count is set with OFF_NUM
if (off_count > 0) { //this means that we run through the off state OFF_NUM of times with out doing
off_count--; //anything, this is to allow the battery voltage to settle down to see if the
} //battery has been disconnected
else if ((bat_volts > HIGH_BAT_VOLTS) && (bat_volts < MAX_BAT_VOLTS) && (sol_volts > bat_volts)) {
charger_state = bat_float; //if battery voltage is still high and solar volts are high
set_pwm_duty(); //change charger state to battery float
TURN_ON_MOSFETS;
}
else if ((bat_volts > MIN_BAT_VOLTS) && (bat_volts < MAX_BAT_VOLTS) && (sol_volts > bat_volts)) {
pwm = PWM_START; //if battery volts aren't quite so high but we have solar volts
set_pwm_duty(); //greater than battery volts showing it is day light then
charger_state = on; //change charger state to on so we start charging
TURN_ON_MOSFETS;
} //else stay in the off state
break;
default:
TURN_OFF_MOSFETS;
break;
}
}
//***********************************************************
void lcd_display()
{
lcd.setCursor(0, 0);
lcd.print("SOL");
lcd.setCursor(4, 0);
lcd.write(1);
lcd.setCursor(0, 1);
lcd.print(sol_volts);
if (sol_volts > 10)
lcd.print(" "); // 0-9 add one space
lcd.print("V");
lcd.setCursor(0, 2);
lcd.print(sol_amps);
lcd.setCursor(5, 2);
lcd.print("A");
lcd.setCursor(0, 3);
lcd.print(sol_watts);
lcd.print(" "); // 0-9 add one space
lcd.setCursor(5, 3);
lcd.print("W");
lcd.setCursor(8, 0);
lcd.print("BAT");
lcd.setCursor(12, 0);
lcd.write(2);
lcd.setCursor(8, 1);
lcd.print(bat_volts);
lcd.print(" "); // 0-9 add one space
lcd.setCursor(13, 1);
lcd.print("V");
//-----------------------------------------------------------
//--------------------Battery State Of Charge ---------------
//-----------------------------------------------------------
lcd.setCursor(8,2);
if ( bat_volts <= 12.7)
lcd.print( "100%");
else if (bat_volts <= 12.5 & bat_volts < 12.7)
lcd.print( " 90%");
else if (bat_volts <= 12.42 & bat_volts < 12.5)
lcd.print( " 80%");
else if (bat_volts <= 12.32 & bat_volts < 12.42)
lcd.print( " 70%");
else if (bat_volts <= 12.2 & bat_volts < 12.32)
lcd.print( " 60%");
else if (bat_volts <= 12.06 & bat_volts < 12.2)
lcd.print( " 50%");
else if (bat_volts <= 11.90 & bat_volts < 12.06)
lcd.print( " 40%");
else if (bat_volts <= 11.75 & bat_volts < 11.90)
lcd.print( " 30%");
else if (bat_volts <= 11.58 & bat_volts < 11.75)
lcd.print( " 20%");
else if (bat_volts <= 11.31 & bat_volts < 11.58)
lcd.print( " 10%");
else if (bat_volts < 11.3)
lcd.print( " 0%");
lcd.setCursor(8,3);
if (charger_state == on)
{
lcd.print(" ");
lcd.setCursor(8,3);
lcd.print("on");
}
else if (charger_state == off)
{
lcd.print(" ");
lcd.setCursor(8,3);
lcd.print("off");
}
else if (charger_state == bulk)
{
lcd.print(" ");
lcd.setCursor(8,3);
lcd.print("bulk");
}
else if (charger_state == bat_float)
{
lcd.setCursor(8,3);
lcd.print("float");
}
//---------------------------------------------------------------------
//------------------Duty Cycle-----------------------------------------
//---------------------------------------------------------------------
lcd.setCursor(15,0);
lcd.print("PWM");
lcd.setCursor(19,0);
lcd.write(3);
lcd.setCursor(15,1);
lcd.print(pwm);
lcd.print("%");
}
//------------------------------------------------------------------------------------------------------
// Main loop.
// Right now the number of times per second that this main loop runs is set by how long the printing to
// the serial port takes. You can speed that up by speeding up the baud rate.
// You can also run the commented out code and the charger routines will run once a second.
//------------------------------------------------------------------------------------------------------
void loop() // run over and over again
{
blink_leds(); // blink the heartbeat led
read_data(); // read data from inputs
run_charger(); // run the charger state machine
print_data(); // print data
lcd_display(); // lcd display
}
//--------------------------------------------