#include <Wire.
h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27,16,2);
#define PV_VOLT_PIN A0
#define PV_CURR_PIN A1
#define BAT_VOLT_PIN A2
#define BAT_CURR_PIN A3
#define PWM_PIN 10
#define AVG_SAMPLES 16
float pvCurrentOffset = 2.5;
float batCurrentOffset = 2.5;
float Vout = 14.6;
float CC_LIMIT = 10.0;
float D = 0.6;
float step = 0.003;
float Dmin = 0.25;
float Dmax_lim = 0.95;
int state = 0;
int counter = 0;
float V[3];
float I[3];
float Iph = 1;
float Rseries = 0;
float A = 1;
float B = 1;
float Vmax = 18;
float Pmax = 1;
float Dmax;
unsigned long last_reset = 0;
unsigned long reset_period = 1000;
unsigned long lcd_timer = 0;
int lcd_page = 0;
float readVoltage(int pin)
{
long sum = 0;
for(int i=0;i<AVG_SAMPLES;i++)
sum += analogRead(pin);
float adc = sum/(float)AVG_SAMPLES;
float V = adc * 5.0 / 1023.0;
return V * 5.0;
}
float readCurrent(int pin, float offset)
{
long sum = 0;
for(int i=0;i<AVG_SAMPLES;i++)
sum += analogRead(pin);
float adc = sum/(float)AVG_SAMPLES;
float V = adc * 5.0 / 1023.0;
float current = (V - offset) / 0.1;
if(abs(current) < 0.05) current = 0;
return current;
}
float calibrateSensor(int pin)
{
long sum = 0;
for(int i=0;i<200;i++)
{
sum += analogRead(pin);
delay(5);
}
float adc = sum / 200.0;
float V = adc * 5.0 / 1023.0;
return V;
}
void setPWM(float duty)
{
duty = constrain(duty,0,0.95);
OCR1B = duty * ICR1;
}
void setup()
{
pinMode(PWM_PIN,OUTPUT);
[Link]();
[Link]();
[Link](0,0);
[Link]("Calibrating...");
pvCurrentOffset = calibrateSensor(PV_CURR_PIN);
batCurrentOffset = calibrateSensor(BAT_CURR_PIN);
[Link]();
[Link]("Calibration OK");
delay(1000);
TCCR1A = 0;
TCCR1B = 0;
TCCR1A |= (1<<COM1B1);
TCCR1A |= (1<<WGM11);
TCCR1B |= (1<<WGM12);
TCCR1B |= (1<<WGM13);
TCCR1B |= (1<<CS10);
ICR1 = 800;
Dmax = Vout / Vmax;
setPWM(D);
}
void loop()
{
float Vpv = readVoltage(PV_VOLT_PIN);
float Ipv = readCurrent(PV_CURR_PIN, pvCurrentOffset);
float Vbat = readVoltage(BAT_VOLT_PIN);
float Ibat = readCurrent(BAT_CURR_PIN, batCurrentOffset);
float Ppv = Vpv * Ipv;
String chargeStage;
if(Vbat < 14.4)
chargeStage = "BULK";
else if(Vbat <= 14.6)
chargeStage = "ABSORB";
else if(Ibat < 1.0)
chargeStage = "FLOAT";
else
chargeStage = "ABSORB";
if(state==0)
{
D = 0.8;
if(counter==1)
Iph = max(Ipv,0.001);
counter++;
if(counter>=2)
{
state = 1;
counter = 0;
}
}
else if(state>=1 && state<=3)
{
float Dsample[3] = {0.9,0.85,0.8};
D = Dsample[state-1];
if(counter==1)
{
V[state-1] = Vpv;
I[state-1] = Ipv;
}
counter++;
if(counter>=2)
{
state++;
counter = 0;
}
}
else if(state==4)
{
float dI1 = max(Iph-I[0],0.001);
float dI2 = max(Iph-I[1],0.001);
float K1 = log(dI1);
float K2 = log(dI2);
float V21 = V[1]-V[0];
float I21 = I[1]-I[0];
float K21 = K2-K1;
float dI3 = max(Iph-I[2],0.001);
float K3 = log(dI3);
float V31 = V[2]-V[0];
float I31 = I[2]-I[0];
float K31 = K3-K1;
Rseries = (K31*V21 - K21*V31)/(K21*I31 - K31*I21);
B = K21/(V21 + I21*Rseries);
A = exp(K1 - B*(V[0] + I[0]*Rseries));
float Ptemp = 0;
for(float I_scan = 0.85*Iph; I_scan < 0.95*Iph; I_scan += 0.01)
{
float V_est = (1/B)*log((Iph-I_scan)/A) - Rseries*I_scan;
float P_est = V_est * I_scan;
if(P_est > Ptemp)
{
Ptemp = P_est;
Vmax = V_est;
}
}
Pmax = Ptemp;
Dmax = Vout / Vmax;
D = Dmax;
state = 5;
last_reset = millis();
}
else
{
float P = Vpv * Ipv;
if(P > Pmax)
{
Pmax = P;
Vmax = Vpv;
Dmax = Vout / Vmax;
D = Dmax - step;
}
else
{
step = -step;
D = Dmax - step;
}
}
if(Vbat >= 14.6)
D -= 0.01;
if(Ibat >= CC_LIMIT)
D -= 0.01;
D = constrain(D,Dmin,Dmax_lim);
setPWM(D);
if(millis()-last_reset > reset_period)
{
state = 0;
counter = 0;
}
if(millis() - lcd_timer > 2000)
{
lcd_timer = millis();
lcd_page++;
if(lcd_page > 3) lcd_page = 0;
[Link]();
}
if(lcd_page == 0)
{
[Link](0,0);
[Link]("PV V:");
[Link](Vpv,1);
[Link]("V");
[Link](0,1);
[Link]("PV I:");
[Link](Ipv,2);
[Link]("A");
}
else if(lcd_page == 1)
{
[Link](0,0);
[Link]("PV Power");
[Link](0,1);
[Link](Ppv,1);
[Link]("W");
}
else if(lcd_page == 2)
{
[Link](0,0);
[Link]("Bat V:");
[Link](Vbat,1);
[Link]("V");
[Link](0,1);
[Link]("Bat I:");
[Link](Ibat,2);
[Link]("A");
}
else if(lcd_page == 3)
{
[Link](0,0);
[Link]("Charging:");
[Link](0,1);
[Link](chargeStage);
}
delay(10);
}