#define __AVR_ATtiny85__ true // required definition for TinyWireS
#define __AVR_ATtinyX5__ true // required definition for ADC reference .. note reference to 2.56v did not work without it
#include<avr/io.h> // for pin definitions
#include<EEPROM.h> // required for EEPROM
//#define SCL PB2
//#define SDA PB0
//#define slave_i2c true
#ifndef slave_i2c
#include "TinyWireM.h"
#include "LiquidCrystal_I2C.h"
#else
#include "TinyWireS.h" // calling the i2c slave library
#endif
#define Debugit true
#define LCD_i2c true
#include "TinyDebug.h"
//#include "arduino.h" // required for ADC
#ifndef slave_i2c
#ifdef LCD_i2c
LiquidCrystal_I2C lcd (0x27,16,2);
#endif
#endif
static float PB1_Analog = 0.0; // global parameter to preserve the PID functions output
static float PB3_Analog = 0.0; //
union { // union structure for timer to ease the send and receive over i2c
uint32_t val32; //
byte bval[4]; //
} precond_time_out, charge_time_out, topoff_time_out; //
static float EOT, EOC; // global parameters for battery current setting .. these are functions of COC
volatile byte chargeStatus = 0; // initialize the battery state machine
union { // union struction for the variables to be sent by i2c
float fval;
byte bval[4];
} batteryVoltReading,batteryCurrentReading, VOC, COC ;
#define STATUS_PIN_1 PB1 // define the discharge command pin
#define STATUS_PIN_2 PB5 // define the charge command pin
#define ADC_PIN0 A3 // define the battery volts ADC pin
#define ADC_PIN1 A2 // define the battery current ADC pin
enum charge_state{ initial = 0, // self explainatory
precond =1,
charging =2,
timertopoff =3,
maintenance =4,
badbattery =5,
nobattery=6};
// C++ function from the net for setting up timers
//char str[16];
class TimerMs {
public:
// (период, мс), (0 не запущен / 1 запущен), (режим: 0 период / 1 таймер)
TimerMs(uint32_t prd = 1000, bool state = 0, bool mode = 0) {
setTime(prd);
if (state) start();
_mode = mode;
}
void setTimerMode() { // установить в режим таймера (остановится после срабатывания)
_mode = 1;
}
void setPeriodMode() { // установить в режим периода (перезапустится после срабатывания)
_mode = 0;
}
void setTime(uint32_t prd) { // установить время
_prd = (prd == 0) ? 1 : prd;
}
void attach(void (*handler)()) { // подключить коллбэк
_handler = *handler;
}
void detach() {
_handler = NULL;
}
void start() { // запустить/перезапустить таймер
_state = true;
_tmr = uptime();
}
void restart() {
start();
}
void resume() { // продолжить после остановки
_state = true;
_tmr = uptime() - _buf;
}
void stop() { // остановить/приостановить таймер
_state = false;
_buf = uptime() - _tmr;
}
void force() { // принудительно переполнить таймер
_tmr = uptime() - _prd;
}
// в режиме периода однократно вернёт true при каждом периоде
// в режиме таймера будет возвращать true при срабатывании
bool tick() {
if (_state) _buf = uptime() - _tmr;
if (_state && _buf >= _prd) {
if (!_mode) _tmr += _prd * (_buf / _prd);
else stop();
if (*_handler) _handler();
_ready = 1;
return true;
}
return false;
}
bool ready() { // однократно вернёт true при срабатывании (флаг)
if (_ready) {
_ready = 0;
return true;
}
return false;
}
bool elapsed() { // всегда возвращает true при срабатывании
return (uptime() - _tmr >= _prd);
}
bool active() { // работает ли таймер (start/resume)
return _state;
}
bool status() { // elapsed+active: работает ли таймер + не сработал ли он
return _state && !elapsed();
}
// остаток времени
uint32_t timeLeft() { // остаток времени в мс
return max(long(_prd - _buf), 0L);
}
uint8_t timeLeft8() { // остаток времени в 0-255
return max(255 - _buf * 255l / _prd, 0ul);
}
uint16_t timeLeft16() { // остаток времени в 0-65535
return max(65535 - _buf * 65535l / _prd, 0ul);
}
uint32_t uptime() { // на случай использования в других фреймворках
return millis();
}
private:
uint32_t _tmr = 0, _prd = 1000, _buf = 0;
bool _state = 0, _mode = 0, _ready = 0;
void (*_handler)() = NULL;
};
static TimerMs PreCondTimer(precond_time_out.val32, 1, 1); // define instances of timers
static TimerMs ChargingTimer(charge_time_out.val32, 1, 1); //
static TimerMs TopOffTimer(topoff_time_out.val32, 1, 1); //
static TimerMs ADCTimer(5000, 1, 1); //
#ifdef LCD_i2c
static TimerMs lcdTimer(5000, 1, 1);
#endif
/*void enters_sleep() // sleep function not required at this stage
{
MCUCR&=~(1<<SM0); // enabling sleep mode and idle sleep mode
MCUCR&=~(1<<SM1); // enabling sleep mode and idle sleep mode
MCUCR|= (1<<SE); //Enabling sleep enable bit
#ifdef Debugit
Debug.println("sleeping");
#endif
__asm__ __volatile__ ( "sleep" "\n\t" :: ); //Sleep instruction to put controller to sleep
//controller stops executing instruction after entering sleep mode
}*/
/*
memory map
address var size
0 i2caddress 1 byte
1 deviceName 6 bytes
7 Version 3 bytes
10 VOC 4 bytes
14 COC 4 bytes
18 preCondTOut 4 bytes
22 ChargingTOut 4 bytes
26 TopOffTOut 4 bytes
*/
char deviceName[]="btry01"; // some initail parameters
char deviceVersion[]={1,0,0}; //
byte i2caddress; //
int I2C_SLAVE_ADDRESS =0; //
void setup(){
#ifdef Debugit
Debug.begin();
Debug.println(F("Hello Tiny!"));
#endif
analogReference(INTERNAL2V56 ); // initernal voltage reference for ADC works fine but need at least 3 V on VCC
pinMode(ADC_PIN0, INPUT); // setting up the pin modes
pinMode(ADC_PIN1, INPUT); //
pinMode(STATUS_PIN_1, OUTPUT); //
pinMode(STATUS_PIN_2, OUTPUT); //
I2C_SLAVE_ADDRESS = EEPROM.read(0); // read the eeprom and decide if the controller is configured on the bus or not
if((I2C_SLAVE_ADDRESS==0)|(I2C_SLAVE_ADDRESS==1)){ // start for the first time, address 0 and 1 not allowed as it is to be used by master
I2C_SLAVE_ADDRESS =10;
strcpy(deviceName,"btry01");
strcpy(deviceVersion, "100");
VOC.fval=4.3;
COC.fval =2.2;
precond_time_out.val32 =0x0fffffff;
charge_time_out.val32 =0x0fffffff;
topoff_time_out.val32 =0x0fffffff;
}else if (I2C_SLAVE_ADDRESS>127){ // insaint .. means the eeprom is corrupt or not configured on the bus
I2C_SLAVE_ADDRESS =36;
strcpy(deviceName,"btry02");
strcpy(deviceVersion, "200");
VOC.fval=4.3;
COC.fval =2.0;
precond_time_out.val32 =0x0ffffffe;
charge_time_out.val32 =0x0ffffffe;
topoff_time_out.val32 =0x0ffffffe;
}else{ // possibly configured on the bus .. proceed in reading the remaining of the eeprom
//I2C_SLAVE_ADDRESS = 0x12;
for(int k=0;k<6;k++){
deviceName[k]=EEPROM.read(k+1);
delay(75);
}
for(int k=0;k<3;k++){
deviceVersion[k]=EEPROM.read(k+7);
delay(75);
}
for(int k=0;k<4;k++){
VOC.bval[k]=EEPROM.read(k+10);
delay(75);
}
if (!((VOC.fval>0)&(VOC.fval<15))){
VOC.fval=4.3;
}
for(int k=0;k<4;k++){
COC.bval[k]=EEPROM.read(k+14);
delay(75);
}
if (!((COC.fval>0)&(COC.fval<10.0))){
COC.fval=2.2;
}
for(int k=0;k<4;k++){
precond_time_out.bval[k]=EEPROM.read(k+18);
delay(75);
}
if (!((precond_time_out.val32>0)&(precond_time_out.val32<0x0fffffff))){
precond_time_out.val32=0x0fffffff;
}
for(int k=0;k<4;k++){
charge_time_out.bval[k]=EEPROM.read(k+22);
delay(75);
}
if (!((charge_time_out.val32>0)&(charge_time_out.val32<0x0fffffff))){
charge_time_out.val32=0x0fffffff;
}
for(int k=0;k<4;k++){
topoff_time_out.bval[k]=EEPROM.read(k+26);
delay(75);
}
if (!((topoff_time_out.val32>0)&(topoff_time_out.val32<0x0fffffff))){
topoff_time_out.val32=0x0fffffff;
}
}
#ifdef slave_i2c
TinyWireS.begin(I2C_SLAVE_ADDRESS); // initialize the i2c
TinyWireS.onRequest(requestEvent); // set the interrupt handler of read to the requestEvent
TinyWireS.onReceive(recieveEvent); // set the interrupt handler of write to the receiveEvent
#endif
#ifndef slave_i2c
#ifdef LCD_i2c
TinyWireM.begin();
lcd. init ();
lcd. backlight ();
lcd. print("Battery Manager");
lcd.setCursor(1,1);
lcd. print ("Welcome "); // Print a message to the LCD.
delay(100);
Debug.println("LCD attached");
//lcd.clear(); // display it
//lcd.print("C: ");
lcdTimer.setPeriodMode(); // initialize the ADC timer
lcdTimer.setTime(500); // tick is 100 millis
lcdTimer.start(); // start
#endif
#endif
ADCTimer.setPeriodMode(); // initialize the ADC timer
ADCTimer.setTime(100); // tick is 100 millis
ADCTimer.start(); // start
// enters_sleep(); // disabled as not needed at the current time .. will be required for ultra low power consumption
}
int order=0; // parametr to organize the read of volt and current with gap of 100 millis between each
float rawvolts,rawamps = 0 ; // raw volts and amps initialization as global
void loop(){
#ifdef slave_i2c
TinyWireS_stop_check(); // to clear the i2c interrupt
#endif
if (ADCTimer.tick()){ // timer schedule on ADC added to allow conversion. and permit CPU to response on i2c requests
EOC = 0.1* COC.fval;
EOT = 0.025 * COC.fval;
if (order == 0){
for (int r=0;r<5;r++){ // multiple reading and average... loop structure available.. i removed the average as the reading is accurate
rawvolts = analogRead(ADC_PIN0);
}
order++;
}else if (order==1){
for ( int r=0;r<5;r++) // multiple reading and average... loop structure available.. i removed the average as the reading is accurate
{
rawamps = analogRead(ADC_PIN1);
}
order=0;
}
batteryVoltReading.fval = ((rawvolts*5.12)-(rawamps*2.56))/1023; // after trial the battery voltage is lower than the charging voltage since current on bypass is accomulated
// by subtract the voltage read on the bypass the result is exact to battery voltage
// consider correction .. as voltage divider shall allow high voltage charger. the divider shall be passed as parameter in future
batteryCurrentReading.fval = rawamps*0.005; // the current value depends on the bypass shunt resistor.. consider correction here.. and allow replace the resistor
} // for future shall make this equation parametrical
chargeStatus = chargeState(chargeStatus,batteryVoltReading.fval ,batteryCurrentReading.fval); // main state machine for battery charging called here. shall be at loop speed.
//enters_sleep();
#ifdef LCD_i2c
if (lcdTimer.tick()){
lcd.clear();
lcd.setCursor(0, 0);
lcd.print ("V:");
lcd.setCursor(2, 0);
lcd.print(batteryVoltReading.fval);
lcd.setCursor(6, 0);
lcd.print("/");
lcd.setCursor(7, 0);
lcd.print(VOC.fval);
lcd.setCursor(12, 0);
switch (chargeStatus){
case 0: lcd.print("int"); break;
case 1: lcd.print("pre"); break;
case 2: lcd.print("chrg"); break;
case 3: lcd.print("full"); break;
case 4: lcd.print("main"); break;
case 5: lcd.print("bad"); break;
case 6: lcd.print("no"); break;
}
lcd.setCursor(0, 1);
lcd.print("A:");
lcd.setCursor(2, 1);
lcd.print(batteryCurrentReading.fval);
lcd.setCursor(6, 1);
lcd.print("/");
lcd.setCursor(7, 1);
lcd.print(COC.fval);
lcd.setCursor(12, 1);
switch (chargeStatus){
case 5: lcd.print("b:bad"); break;
case 6: lcd.print("b:no"); break;
default: lcd.print("b:ok"); break;
}
}
#endif
}
byte chargeState(byte charge_state, float batteryVolt, float batteryCurrent) {
switch (charge_state)
{
case initial:
if (batteryVolt >= 4.5) // these values are specific for Li ion batteries
{
charge_state = nobattery; // select the proper state
/*#ifdef Debugit
Debug.println("from initial to no battery");
#endif*/
digitalWrite(STATUS_PIN_2,LOW); // make sure not to charge or discharge accedently ..
digitalWrite(STATUS_PIN_1,LOW); // shall turn off both commands
}else if(batteryVolt < 3)
{
PreCondTimer.setPeriodMode(); // initiate the precodition timer
PreCondTimer.setTime(precond_time_out.val32); // set the timer value note the value is in milli seconds .. often the precondition take 2 hours or more.
PreCondTimer.start(); // starts the timer counting
charge_state = precond; // select the proper state
/*#ifdef Debugit
Debug.println("from initial to Preconditioning");
#endif*/
digitalWrite(STATUS_PIN_2,LOW); // make sure not to charge or discharge accedently ..
digitalWrite(STATUS_PIN_1,LOW); // shall turn off both commands
}else
{
charge_state = maintenance; // select the proper state
/*#ifdef Debugit
Debug.println("from initial to Maintenance");
#endif*/
digitalWrite(STATUS_PIN_2,LOW); // make sure not to charge or discharge accedently ..
digitalWrite(STATUS_PIN_1,LOW); // shall turn off both commands
}
return charge_state;
break;
case precond:
if (batteryVolt > 3.0) // if voltage reaches 3V then the battery is allowed to charge in charging mode and enables current to araise
{
ChargingTimer.setPeriodMode(); // initiate the charging timer
ChargingTimer.setTime(charge_time_out.val32); // set time from the eeprom .. note charging take about 3-4 hours and value is in milli seconds
ChargingTimer.start(); //
charge_state = charging; //
/*#ifdef Debugit
Debug.println("from Preconditioning to Charging");
#endif*/
digitalWrite(STATUS_PIN_2,LOW); //
digitalWrite(STATUS_PIN_1,LOW); //
}else if (PreCondTimer.tick()) // check if precondition time is out.. if out with out reaching 3volts the battery consider bad..
{
charge_state = badbattery ; // select the proper next state
/*#ifdef Debugit
Debug.println("from Preconditioning to bad Battery");
#endif*/
digitalWrite(STATUS_PIN_2,LOW); //
digitalWrite(STATUS_PIN_1,LOW); //
}else
{
charge_state = precond; // stay in precoditioning
/*#ifdef Debugit
Debug.println("stay in preconditioning");
Debug.println(EOC);
#endif*/
PB3_Analog=PID1(batteryCurrent,EOC,0.1,0,0,0,100); // PID loop to control charging voltage based on consumed current
/*#ifdef Debugit
Debug.println(PB3_Analog);
#endif*/
digitalWrite(STATUS_PIN_2,pwmDuty(PB3_Analog)); // convert the PID anaolg out put to pulse width modulation.. for future may be removed from the function and placed in the main loop
digitalWrite(STATUS_PIN_1,LOW); // make sure charging and discharging not occuring the the same time
}
return charge_state;
break;
case charging:
if ((batteryVolt > 4.3) and (batteryCurrent < EOC)) // condition to exit the charging state.. once battery volts is more than 4.3 and its current drops below the specific capacity charge.
{ // move to fully charged mode or simply .. TOP OFF
TopOffTimer.setPeriodMode(); // initiate the timer for fully charged
TopOffTimer.setTime(topoff_time_out.val32); // set period in milli second
TopOffTimer.start(); // start timer
charge_state = timertopoff; // move to next proper state
/*#ifdef Debugit
Debug.println("from Charging to TopOff");
#endif*/
digitalWrite(STATUS_PIN_2,LOW); // turn off charging output
digitalWrite(STATUS_PIN_1,LOW); // turn off discharging output
}else if (batteryVolt < 3.0)
{
PreCondTimer.setPeriodMode(); // initiate the precodition timer
PreCondTimer.setTime(precond_time_out.val32); // set the timer value note the value is in milli seconds .. often the precondition take 2 hours or more.
PreCondTimer.start(); // starts the timer counting
charge_state = precond; // select the proper state
/*#ifdef Debugit
Debug.println("from initial to Preconditioning");
#endif*/
digitalWrite(STATUS_PIN_2,LOW); // make sure not to charge or discharge accedently ..
digitalWrite(STATUS_PIN_1,LOW); // shall turn off both commands
}else
{
if (ChargingTimer.tick()) // if charging time out
{
charge_state = badbattery ; // the battery consider bad.. just to mention battery could timer out is the charger voltage is low and current is high.. always check schematic and measure both
/*#ifdef Debugit
Debug.println("from Charging to bad battery");
#endif*/
digitalWrite(STATUS_PIN_2,LOW); // turn off charging output
digitalWrite(STATUS_PIN_1,LOW); // turn off discharing output
}else
{
charge_state = charging ; // stay in charging mode
/*#ifdef Debugit
Debug.println("Stay in Charging ");
#endif*/
PB3_Analog=PID1(batteryVolt,VOC.fval,200,0.01,0.00001,0.0,100.0); // PID controller to control charging voltage vs charging setpoint
digitalWrite(STATUS_PIN_2,pwmDuty(PB3_Analog)); // convert PID percentage output to pulse width modulation
digitalWrite(STATUS_PIN_1,LOW); // turn off unnecessary output
}
}
return charge_state;
break;
case timertopoff:
if (batteryVolt > 4.5) // while battery being fully charged
{
charge_state = nobattery;
/*#ifdef Debugit
Debug.println("from TopOff to bad battery");
#endif*/
digitalWrite(STATUS_PIN_2,LOW);
digitalWrite(STATUS_PIN_1,LOW);
}else
{
if (batteryCurrent < EOT) // if battery current droped below end of charge current.. its a function of fully charged battery current parameter
{
charge_state = maintenance; // battery current required to be maintained by charging
/*#ifdef Debugit
Debug.println("from TopOff to maintenance");
#endif*/
digitalWrite(STATUS_PIN_2,LOW);
digitalWrite(STATUS_PIN_1,LOW);
}else{
if (TopOffTimer.tick()) // if top off timer ..times out
{
charge_state = maintenance ;
/*#ifdef Debugit
Debug.println(TopOffTimer.elapsed());
Debug.println("from TopOff to maintenance time out");
#endif*/
digitalWrite(STATUS_PIN_2,LOW);
digitalWrite(STATUS_PIN_1,LOW);
}else
{
charge_state = timertopoff;
/*#ifdef Debugit
Debug.println("stay in TopOff");
#endif*/
digitalWrite(STATUS_PIN_2,LOW);
digitalWrite(STATUS_PIN_1,LOW);
}
}
return charge_state;
break;
case maintenance: // in maintenance
if (batteryVolt < 4.0) // check if battery self discharged
{
ChargingTimer.setPeriodMode(); // charge again
ChargingTimer.setTime(charge_time_out.val32);
ChargingTimer.start();
charge_state = charging;
/*#ifdef Debugit
Debug.println("from maintenance to Charging");
#endif*/
digitalWrite(STATUS_PIN_2,LOW);
digitalWrite(STATUS_PIN_1,LOW);
}else
{
if (batteryVolt > 4.5) // if battery voltage reaches the charger voltage means no battery available
{
charge_state = nobattery;
/*#ifdef Debugit
Debug.println("from maintenance to no battery");
#endif*/
digitalWrite(STATUS_PIN_2,LOW);
digitalWrite(STATUS_PIN_1,LOW);
}else{
if (batteryVolt > 4.3) // if over charged
{
charge_state = maintenance;
/*#ifdef Debugit
Debug.println("stay in maintenance");
#endif*/
PB1_Analog= PID1(batteryVolt,VOC.fval,200,0.01,0.0001,-100,100.0); // PID loop to control discharge to maintain the voltage reading and the battery specific voltage setting
digitalWrite(STATUS_PIN_1,pwmDuty(abs(PB1_Analog))); // convert the PID output pulse width modulation
digitalWrite(STATUS_PIN_2,LOW); // disable the charging output.
}else{
ChargingTimer.setPeriodMode(); // this state added for not to stuck in the maintenance state
ChargingTimer.setTime(charge_time_out.val32); // as maintenance state is trasitional to determind what to do and has no timer
ChargingTimer.start(); // after chacking all conditions and found nothing .. we send it back to charging
charge_state = charging; // the charging state then charge or topoff
/*#ifdef Debugit
Debug.println("from maint to TopOff");
#endif*/
digitalWrite(STATUS_PIN_2,LOW);
digitalWrite(STATUS_PIN_1,LOW);
}
}
}
return charge_state;
break;
case badbattery:
/*#ifdef Debugit
Debug.println("bad battery"); // if bad battery nothig is moving.. fix .. replace or disconnect and connect back
#endif*/
digitalWrite(STATUS_PIN_2,LOW); // here these is a risk to stuck in this state and require human introvention
digitalWrite(STATUS_PIN_1,LOW);
return charge_state;
break;
case nobattery: // no battery state
if (batteryVolt < 4.5) // checks if battery hawked up again
{
PreCondTimer.start(); // start from first state
charge_state = precond;
/*#ifdef Debugit
Debug.println("from no battery to preconditioning");
#endif*/
digitalWrite(STATUS_PIN_2,LOW);
digitalWrite(STATUS_PIN_1,LOW);
}
return charge_state;
break;
default: // default state is initiallizaiton
charge_state = initial;
digitalWrite(STATUS_PIN_2,LOW);
digitalWrite(STATUS_PIN_1,LOW);
return charge_state;
break;
}
/*#ifdef Debugit
Debug.println(charge_state);
#endif*/
return charge_state;
break;
}
}
#ifdef slave_i2c
volatile uint8_t i2c_rx_buff[16]; // setup the i2c buffers
volatile uint8_t i2c_tx_buff[] = //
{
0, // i2c address // the communication bytes map
0, // i2c register
0, // first
0, // second
0, // third
0, // fourth
0, // fifth
0, // Sixth
0 // seventh
};
volatile byte pos,reg_position, tx_reg_position, request_register = 0; // global variables needed in i2c functions
const byte reg_size = sizeof(i2c_rx_buff); //
const byte tx_reg_size = sizeof(i2c_tx_buff); //
int receiveCount; //
void requestEvent() // the request event function executed after the receive event function
{ // processing the read requests
if(reg_position==0) // register position is 0 only in read requests .. this is to make sure not to conflect between read and write
{
switch (request_register) // check what data to read
{
case 0:// reg address equals i2c slave device address // reads the own i2c address
{
i2c_tx_buff[0] = I2C_SLAVE_ADDRESS; // filling buffer
tx_reg_position = 1; // filling the buffer size
for (int i = 0;i<tx_reg_position;i++){ // loop to send the data .. note data is sent byte by byte
TinyWireS.send (i2c_tx_buff[i]);
}
}
break;
case 1:// reg address equals i2c slave device name // reads the i2c device name .. 6 bytes
{
for(int i=0;i<strlen(deviceName);i++){ // filling buffer
i2c_tx_buff[i]=deviceName[i];
}
tx_reg_position = 6; // filling buffer length
for (int i = 0;i<tx_reg_position;i++){ // sending data byte by byte
TinyWireS.send (i2c_tx_buff[i]);
}
}
break;
case 2:// reg address equals version do nothing version is flashed once deviceVersion
{
for(int i=0;i<strlen(deviceVersion);i++){ // device version
i2c_tx_buff[i]=deviceVersion[i];
}
tx_reg_position = strlen(deviceVersion);
for (int i = 0;i<tx_reg_position;i++){ // sending data byte by byte
TinyWireS.send (i2c_tx_buff[i]);
}
}
break;
case 3:// reg address equals battery status do nothing // battery status
{
byte batteryStatus =0;
if (chargeStatus == nobattery) { // filling data depend on battery state
batteryStatus = 0; // 0.. for no battery
}else if (chargeStatus == badbattery) // 1.. for bad battery
{
batteryStatus = 1;
}else
{
batteryStatus = 2; // 2.. for good battery
}
i2c_tx_buff[0] = batteryStatus;
tx_reg_position = 1;
for (int i = 0;i<tx_reg_position;i++){
TinyWireS.send (i2c_tx_buff[i]);
}
}
break;
case 4:// reg address equals charging status do nothing // charging state machine status
{
i2c_tx_buff[0] = chargeStatus;
tx_reg_position = 1;
for (int i = 0;i<tx_reg_position;i++){
TinyWireS.send (i2c_tx_buff[i]);
}
}
break;
case 5:// reg address equals charge VOC which is battery VOC setting // volt of charge setting float type takes 4 bytes
{
for(int i=0;i<4;i++){
i2c_tx_buff[i]=VOC.bval[i];
}
tx_reg_position = 4;
for (int i = 0;i<tx_reg_position;i++){
TinyWireS.send (i2c_tx_buff[i]);
}
}
break;
case 6:// reg address equals charge COC which is battery COC setting // current of charge setting .. float type takes 4 bytes
{
for(int i=0;i<4;i++){
i2c_tx_buff[i]=COC.bval[i];
}
tx_reg_position = 4;
for (int i = 0;i<tx_reg_position;i++){
TinyWireS.send (i2c_tx_buff[i]);
}
}
break;
case 7:// reg address equals precodintion time out precond_time_out in milli seconds unsiged 32 takes 4 bytes
{
for(int i=0;i<4;i++){
i2c_tx_buff[i]=precond_time_out.bval[i];
}
tx_reg_position = 4;
for (int i = 0;i<tx_reg_position;i++){
TinyWireS.send (i2c_tx_buff[i]);
}
}
break;
case 8:// reg address equals charge time out charge_time_out in milli seconds unsiged 32 takes 4 bytes
{
for(int i=0;i<4;i++){
i2c_tx_buff[i]=charge_time_out.bval[i];
}
tx_reg_position = 4;
for (int i = 0;i<tx_reg_position;i++){
TinyWireS.send (i2c_tx_buff[i]);
}
}
break;
case 9:// reg address equals topoff time out topoff_time_out in milli seconds unsiged 32 takes 4 bytes
{
for(int i=0;i<4;i++){
i2c_tx_buff[i]=topoff_time_out.bval[i];
}
tx_reg_position = 4;
for (int i = 0;i<tx_reg_position;i++){
TinyWireS.send (i2c_tx_buff[i]);
}
}
break;
case 10: // actual battery volts batteryVoltReading float type takes 4 bytes
{
for(int i=0;i<4;i++){
i2c_tx_buff[i]=batteryVoltReading.bval[i];
}
tx_reg_position = 4;
for (int i = 0;i<tx_reg_position;i++){
TinyWireS.send (i2c_tx_buff[i]);
}
}
break;
case 11: // actual battery Current batteryCurrentReading float type takes 4 bytes
{
for(int i=0;i<4;i++){
i2c_tx_buff[i]=batteryCurrentReading.bval[i];
}
tx_reg_position = 4;
for (int i = 0;i<tx_reg_position;i++){
TinyWireS.send (i2c_tx_buff[i]);
}
}
break;
default:
break;
}
reg_position = 0;
// receiveCount = 0;
}
}
void recieveEvent(int howMany) // receive event triggered by interrupt once receiving any data in i2c .. not the how many in tiny85 not working but works on arduino
{
receiveCount = TinyWireS.available(); // number of byte available on i2c to receive
int p = receiveCount; // initialize the receving counter
if (p < 1) // less than 1 means to check if device is alive and usually it is used to check the addresses on the bus
{
// Sanity-check
return; // exit
}
if (p > sizeof(i2c_rx_buff)) // recived data bigger than the buffer size .. will not be able to read it.
{
// Also insane number
return; // exit
}
request_register = TinyWireS.receive(); // receive the first byte .. i used the first byte to be the requested register.. not like modbus or bacnet
// Serial.print("request_register..");
// Serial.println(request_register);
p--; // reduce the counter by one
if (!p) // if the count become zero this means read request
{
// This write was only to set the buffer for next read
return; // exit to allow read interrupt calling request Event function
}
while(p--) // if reached this point means we have qualified write command to process
{
i2c_rx_buff[reg_position] = TinyWireS.receive(); // filling the read buffer
reg_position++; // increment the receive counter
}
if(reg_position>0){ // double check the number of received bytes
// Serial.println("Processing Update request");
switch (request_register){ // processing the write command based on the first received byte
//digitalWrite(STATUS_PIN_1,~STATUS_PIN_1);
case 0:// reg address equals i2c slave device address
{
I2C_SLAVE_ADDRESS = i2c_rx_buff[0]; // the receieved is update to i2c slave address
// write i2caddress to eeprom
EEPROM.update(0,I2C_SLAVE_ADDRESS); // save the recieved address to eeprom
}
break;
case 1:// reg address equals i2c slave device name
{
for (int i=0;i<6;i++)
{
deviceName[i]=i2c_rx_buff[i];
}
// write deviceName to eeprom
//cli();
for(int k=0;k<6;k++){
EEPROM.update(1+k,deviceName[k]);
//delay(45);
}
// optional send confirmation
// reboot
}
break;
case 2:// reg address equals version do nothing version is flashed once
break;
case 3:// reg address equals battery status do nothing
break;
case 4:// reg address equals charging status do nothing
break;
case 5:// reg address equals charge VOC which is battery VOC setting
{
for (int i=0;i<4;i++)
{
VOC.bval[i]=i2c_rx_buff[i];
}
// write VOC to eeprom
for(int k=0;k<4;k++){
EEPROM.update(10+k,VOC.bval[k]);
}
// reboot
}
break;
case 6:// reg address equals charge COC which is battery COC setting
{
for (int i=0;i<4;i++)
{
COC.bval[i]=i2c_rx_buff[i];
}
// write COC to eeprom
for(int k=0;k<4;k++){
EEPROM.update(14+k,COC.bval[k]);
}
// optional send confirmation
// reboot
}
break;
case 7:// reg address equals precodintion time out
{
for (int i=0;i<4;i++)
{
precond_time_out.bval[i]=i2c_rx_buff[i];
}
// write precond_time_out to eeprom
for(int k=0;k<4;k++){
EEPROM.update(18+k,precond_time_out.bval[k]);
}
// optional send confirmation
// reboot
}
break;
case 8:// reg address equals charge time out
{
for (int i=0;i<4;i++)
{
charge_time_out.bval[i]=i2c_rx_buff[i];
}
// write charge_time_out to eeprom
for(int k=0;k<4;k++){
EEPROM.update(22+k,charge_time_out.bval[k]);
}
// optional send confirmation
// reboot
}
break;
case 9:// reg address equals topoff time out
{
for (int i=0;i<4;i++)
{
topoff_time_out.bval[i]=i2c_rx_buff[i];
}
// write topoff_time_out to eeprom
for(int k=0;k<4;k++){
EEPROM.update(26+k,topoff_time_out.bval[k]);
}
// optional send confirmation
// reboot
}
break;
default:
break;
}
reg_position = 0;
}
}
#endif
int lastOutput = 0; // initialize global parameters for PID function
float err= 0; //
double lastMillis = 0; //
double _integral = 0; //
double _dt = 0; //
double _pre_error = 0; //
float PID1(float PV, float _SP, float kp, float ki, float kd,float _min, float _max ) // fisrt PID instance function
{
_dt = millis() - lastMillis;
// Calculate error
float err = _SP - PV;
// Proportional term
float Pout = kp * err;
// Integral term
_integral += err * _dt;
double Iout = ki * _integral;
// Derivative term
double derivative = (err - _pre_error) / _dt;
double Dout = kd * derivative;
// Calculate total output
double output = Pout + Iout + Dout;
// Restrict to max/min
if( output > _max )
output = _max;
else if( output < _min )
output = _min;
// Save error to previous error
_pre_error = err;
lastMillis = millis();
lastOutput = output;
return lastOutput;
}
bool lstOutput = false; // initialize the global parameters for the pwm function
int dcount= 0; //
bool pwmDuty(int duty) // pwm convert the pid output from percentage to train of pulses
{
/*#ifdef Debugit
Debug.println(duty);
#endif*/
if (duty == 0 ){
lstOutput = false;
dcount = 0;
/*#ifdef Debugit
Debug.println("duty 0");
#endif*/
}else if (duty==100){
lstOutput = true;
dcount = 0;
/*#ifdef Debugit
Debug.println("duty 100");
#endif*/
}else if ((dcount > 100 - duty) & (lstOutput == false))
{
lstOutput = true;
dcount = 0;
/*#ifdef Debugit
Debug.println("on");
#endif*/
} else if ((dcount > duty) & (lstOutput == true)){
lstOutput = false;
dcount = 0;
/*#ifdef Debugit
Debug.println("off");
#endif*/
} else{
/*#ifdef Debugit
Debug.println("count up");
#endif*/
dcount++;
}
return lstOutput;
}