Since someone always says it...
/*
Code Version 1.0
WMH Racing Battery Wizard
Written by Andrew Sarratore
Date: 10/28/2023
Code Version 1.1
Add getCurrentAverage()
add getFVoltageAverage()
add getCalculations()
add Program()
Changes to the "Run Program" Menu
Date:12/11/2023
Code Version 1.2
add getPVoltageAverage
add more readings to the dishcharge display
Code Version 1.3
Date:12/27/2023
add self calibrate VCC
add mAH reading, untested
add PID_v1.h library, still need to write PID code for the PWM value and replace current feedback loop
Code Version 1.4
Date:12/30/2023
Add IR struct - Untested
Add getIR function - still need to write the menu and page function
Code Version 1.4.2
Date:1/4/2024
Calibrate initial QOV based on VCC during setup
Fixed averages to be floats instead of int...
Adding IR Reading menus
Code Version 1.5
PID added, Kp=0.25, Ki=10, Kd=0
Added page 6 for displaying voltage data
Date:1/15/24
Code Version 1.6
Changed where the code calls the calculations and true VCC functions from. This made other parts of the loop faster and vcc more accurate
Added in a multiplier to voltage under load. (Icurrent/1000)*4. I can simplify to Icurrent/250.
This fixes voltage under load readings at various discharge rates. May change for each unit
Date:1/16/24
Code Version 1.7
Adding 1S support
Date:1/17/24
Code Version 1.8
Adding Struct for Time/Voltage variable
Filling the page for the Time/Voltage variable vTime
Voltage values every 15 seconds - look into graphing this, would be cool.
*/
#include "SPI.h"
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"
#include "XPT2046_Touchscreen.h"
#include "Math.h"
#include "PID_v1.h"
#include "movingAvg.h"
// Define SPI pins for both display and touch
#define TFT_CS 10
#define TFT_DC 9
#define TFT_MOSI 11
#define TFT_CLK 13
#define TFT_RST 8
#define TFT_MISO 12
#define TS_CS 7
#define ROTATION 1
#define Isens A0
#define VFsens A1
#define VPsens A2
#define PWM 3
#define K (1.0 / 30)
char currentPage;
char numberCells;
// Use hardware SPI (on Uno, #13, #12, #11) and the above for CS/DC/RST
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);
XPT2046_Touchscreen ts(TS_CS);
// calibration values
float xCalM = -0.09, yCalM = -0.06; // gradients
float xCalC = 329.36, yCalC = 248.13; // y axis crossing points
int8_t blockWidth = 20; // block size
int8_t blockHeight = 20;
int16_t blockX = 0, blockY = 0; // block position (pixels)
class ScreenPoint {
public:
int16_t x;
int16_t y;
// default constructor
ScreenPoint() {
}
ScreenPoint(int16_t xIn, int16_t yIn) {
x = xIn;
y = yIn;
}
};
class Button {
public:
int x;
int y;
int width;
int height;
char *text;
Button() {
}
void initButtonG(int xPos, int yPos, int butWidth, int butHeight, char *butText) {
x = xPos;
y = yPos;
width = butWidth;
height = butHeight;
text = butText;
renderG();
}
void initButtonR(int xPos, int yPos, int butWidth, int butHeight, char *butText) {
x = xPos;
y = yPos;
width = butWidth;
height = butHeight;
text = butText;
renderR();
}
void initButtonB(int xPos, int yPos, int butWidth, int butHeight, char *butText) {
x = xPos;
y = yPos;
width = butWidth;
height = butHeight;
text = butText;
renderB();
}
void initButtonY(int xPos, int yPos, int butWidth, int butHeight, char *butText) {
x = xPos;
y = yPos;
width = butWidth;
height = butHeight;
text = butText;
renderY();
}
void renderG() {
tft.fillRect(x, y, width, height, ILI9341_GREEN);
tft.setCursor(x + 5, y + 5);
tft.setTextSize(2);
tft.setTextColor(ILI9341_WHITE);
tft.print(text);
}
void renderR() {
tft.fillRect(x, y, width, height, ILI9341_RED);
tft.setCursor(x + 5, y + 5);
tft.setTextSize(2);
tft.setTextColor(ILI9341_WHITE);
tft.print(text);
}
void renderB() {
tft.fillRect(x, y, width, height, ILI9341_BLUE);
tft.setCursor(x + 5, y + 5);
tft.setTextSize(2);
tft.setTextColor(ILI9341_WHITE);
tft.print(text);
}
void renderY() {
tft.fillRect(x, y, width, height, ILI9341_YELLOW);
tft.setCursor(x + 5, y + 5);
tft.setTextSize(2);
tft.setTextColor(ILI9341_WHITE);
tft.print(text);
}
bool isClicked(ScreenPoint sp) {
if ((sp.x >= x) && (sp.x <= (x + width)) && (sp.y >= y) && (sp.y <= (y + height))) {
return true;
} else {
return false;
}
}
};
unsigned const int numChan = 5;
unsigned int chan;
struct ChannelData {
float startVoltageP;
float startVoltageF;
float endVoltageP;
float endVoltageF;
float voltageDropP;
float voltageDropF;
float internalResistanceP;
float internalResistanceF;
};
ChannelData Channels[numChan];
unsigned const int numChanT = 40;
unsigned int chanT;
struct voltageTime {
float vTime;
};
voltageTime ChannelsT[numChanT];
//Touch Screen
ScreenPoint getScreenCoords(int16_t x, int16_t y) {
int16_t xCoord = round((x * xCalM) + xCalC);
int16_t yCoord = round((y * yCalM) + yCalC);
if (xCoord < 0) xCoord = 0;
if (xCoord >= tft.width()) xCoord = tft.width() - 1;
if (yCoord < 0) yCoord = 0;
if (yCoord >= tft.height()) yCoord = tft.height() - 1;
return (ScreenPoint(xCoord, yCoord));
}
// I can probably make an array of the following button variables...
Button btnIP; // Current Selection +
Button btnIM; // Current Selection -
Button btnVP; // Cutoff V +
Button btnVM; // Cutoff V -
Button btnMI; // Main - Discharge Rate
Button btnMV; // Main - Voltage Cutoff
Button btnMM; // Main Menu on other pages
Button btnCT; // Main Calibrate Touch
Button btnRP; // Main Menu - Run Program
Button btnPN; // Run/Running
Button btnMN; // Discharge - Main Menu
Button btnRN; // STOP
Button btnIR; // Main - IR Reading
Button btnRC; // IR Reading - Check IR
Button btnDD; // Discharge - dischargeData
Button btnRPD;
double IV = 30; // Initial rate of discharge in ampres. User changable in code
float VV = 350.00; // Cutoff voltage*100 so ++ works to change the value
unsigned long StartTime; // Start time to measure time of discharge
unsigned long CurrentTime; // Will give how long the discharge is running
unsigned long mahTime; // used for mAH
unsigned long prevmahTime; // used for mAH
unsigned long intervalTime; //used for vTime data
unsigned long previntervalTime; //used for vTime data
unsigned long runTime; // runTime of the discharge function
//unsigned long lastFrame = millis();
//PID
double Input;
double Output;
//Specify the links and initial tuning parameters
double Kp = .01, Ki = 15, Kd = 0;
PID myPID(&Input, &Output, &IV, Kp, Ki, Kd, DIRECT);
// Current Variables
float VCC = 0; // 4.967 measure the value(Measure value not needed with the VCC calibration code)
float VCCC = 0; // Used for Accurate Voltage
float QOV = 0; // Calibration happens in setup
float sens = 0.04; //sensitivity of the current sensor
float iavg; // analogRead(Isens)/IN
float Icurrent = 0; // Current Reading in Amps
float Icorrected = 0; //iavg-IOFF
float voltageI = 0; // iavg translated to voltage
float IOFF = 0.6; // Offset to the raw read to zero current
int IN = 1000; // Sample number to average current
float mAH; // mAH used
float rTmAH; // Runtime mAH used, highest value
// Battery Voltage Variables (may be able to make a struct or class here as well)
int ValueR1F = 5094; //Measured value
int ValueR2F = 5104; //Measured value
int ValueR1P = 5106; //Measured value
int ValueR2P = 5082; //Measured value
int VFN = 1000; // Sample number for average of analogRead(VFsens) Full voltage
int VPN = 1000; // Sample number for average of analogRead(VPsens) Partial voltage
float vfavg; // Average of the analogRead(VFsens)/VFN -Full Voltage
float vpavg; // Average of the analogRead(VPsens)/VPN -Partial Voltage
float Fvoltage = 0; // Calculation of FV seen by MCU
float Pvoltage = 0; // Calculation of PV seen by MCU
float voltageFull = 0; // Calculation of Full Voltage
float voltagePartial = 0; // Calculation of Partial Voltage
float cell1 = 0; // Voltage of Cell 1
float cell2 = 0; // Voltage of Cell 2
float internalResistanceAVGP = 0;
float internalResistanceAVGF = 0;
boolean isrunning = false;
boolean irRunning = false;
int zz = 0; // variable used for analogWrite(PWM,zz)
//===================================================================SETUP=========================================================================================
void setup() {
Serial.begin(115200);
// avoid chip select contention
pinMode(TS_CS, OUTPUT);
digitalWrite(TS_CS, HIGH);
pinMode(TFT_CS, OUTPUT);
pinMode(Isens, INPUT);
pinMode(VFsens, INPUT);
pinMode(VPsens, INPUT);
pinMode(PWM, OUTPUT);
digitalWrite(TFT_CS, HIGH);
tft.begin();
tft.setRotation(ROTATION);
tft.fillScreen(ILI9341_BLACK);
ts.begin();
ts.setRotation(ROTATION);
//calibrateTouchScreen(); Leaving here for first run of the dispaly to get coordinates of touch screen
getAccurateVoltage();
getCurrentAverage();
getFVoltageAverage();
getPVoltageAverage();
getCalculations();
calibrateQOV();
//initialize the variables we're linked to
Input = Icurrent;
//turn the PID on
myPID.SetMode(AUTOMATIC);
currentPage = '0'; // Indicates that we are at Home Screen
Home();
}//==================================================================END SETUP=====================================================================
//======================================================================LOOP======================================================================
void loop() {
ScreenPoint sp;
// limit frame rate
//while((millis() - lastFrame) < 20); //(caused issues with millis function to get mAH, may need to revisit as this helped some flickering)
//lastFrame = millis();
// Home Screen
if (currentPage == '0') {
if (ts.touched()) {
TS_Point p = ts.getPoint();
sp = getScreenCoords(p.x, p.y);
if (btnMV.isClicked(sp)) {
currentPage = '1'; // Go to Discharge Current
tft.fillScreen(ILI9341_BLACK);
discharge();
} else if (btnMI.isClicked(sp)) {
currentPage = '2'; // Go to Cutoff Voltage
tft.fillScreen(ILI9341_BLACK);
cutoff();
}
else if (btnCT.isClicked(sp)) {
currentPage = '3'; // Go to Calibrate Screen
tft.fillScreen(ILI9341_BLACK);
calibrateTouchScreen();
}
else if (btnRP.isClicked(sp)) {
currentPage = '4'; // Go to Run Program
getCalculations();
tft.fillScreen(ILI9341_BLACK);
program();
}
else if (btnIR.isClicked(sp)) {
currentPage = '5'; // Go to irReading
tft.fillScreen(ILI9341_BLACK);
irReading();
}
}
}
// Discharge Current
if (currentPage == '1') {
tft.setCursor(100, 150);
tft.print(IV, 0);
tft.setCursor(190, 150);
tft.print("A");
if (ts.touched()) {
TS_Point p = ts.getPoint();
sp = getScreenCoords(p.x, p.y);
if (btnIP.isClicked(sp)) {
if (IV < 45) {
IV++;
}
tft.fillRect(100, 150, 125, 50, ILI9341_BLACK);
delay(100);
} else if (btnIM.isClicked(sp)) {
IV--;
tft.fillRect(100, 150, 125, 50, ILI9341_BLACK);
delay(100);
} else if (btnMM.isClicked(sp)) {
currentPage = '0';
Home();
}
}
} // End Page 1
// Cutoff Voltage
if (currentPage == '2') {
tft.setCursor(100, 150);
tft.print(VV / 100);
tft.setCursor(210, 150);
tft.print("V");
if (ts.touched()) {
TS_Point p = ts.getPoint();
sp = getScreenCoords(p.x, p.y);
if (btnIP.isClicked(sp)) {
VV++;
tft.fillRect(100, 150, 100, 50, ILI9341_BLACK);
delay(100);
} else if (btnIM.isClicked(sp)) {
if (VV > 280) {
VV--;
}
tft.fillRect(100, 150, 100, 50, ILI9341_BLACK);
delay(100);
} else if (btnMM.isClicked(sp)) {
currentPage = '0';
Home();
}
}
} // End Page 2
// Run Discharge (Surely I can move some of this to the page function, but when moved it caused the screen to refresh every loop)
if (currentPage == '4') {
getAccurateVoltage();
getCalculations();
getVTime();
SerialData();
tft.setCursor(0, 50);
tft.setTextSize(2);
tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
tft.print("Battery:");
tft.setCursor(0, 75);
tft.print("Cutoff ");
tft.print(VV / 100);
tft.print("V");
tft.setTextSize(2);
tft.setCursor(0, 100);
tft.print("Actual ");
tft.print(voltageFull, 2);
tft.setCursor(145, 100);
tft.print("V");
tft.setCursor(160, 50);
tft.print("Current:");
tft.setCursor(160, 75);
tft.print("Rate ");
tft.print(IV, 0);
tft.print("A");
tft.setCursor(160, 100);
tft.print("Actual ");
tft.print(Icurrent, 1);
tft.setCursor(305, 100);
tft.print("A");
tft.setTextSize(1);
tft.setCursor(160, 120);
tft.print("Time");
tft.setCursor(160, 130);
tft.fillRect(160, 130, 50, 10, ILI9341_BLACK);
tft.print(runTime);
tft.setCursor(240, 120);
tft.print("mAH");
tft.setCursor(240, 130);
tft.print(rTmAH, 0);
tft.setCursor(0, 150);
tft.print("VCC");
tft.setCursor(0, 160);
tft.print(VCC, 3);
if (voltageFull > 5) {
tft.setTextSize(1);
tft.setCursor(0, 120);
tft.print("Cell 1 ");
tft.setCursor(45, 120);
tft.print(cell1, 3);
tft.setCursor(80, 120);
tft.print("V");
tft.setCursor(0, 130);
tft.print("Cell 2 ");
tft.setCursor(45, 130);
tft.print(cell2, 3);
tft.setCursor(80, 130);
tft.print("V");
} //
else if (voltageFull < 5) {
tft.setTextSize(1);
tft.setCursor(0, 120);
tft.print("Cell 1 ");
tft.setCursor(45, 120);
tft.print(cell1, 3);
tft.setCursor(80, 120);
tft.print("V");
tft.setCursor(0, 130);
} //end if
if (isrunning == true) {
btnPN.initButtonR(0, 15, 140, 25, "DISCHARGE");
RunDischarge();
} else {
tft.setTextColor(ILI9341_WHITE, ILI9341_GREEN);
btnPN.initButtonG(0, 15, 140, 25, " RUN");
}
if (ts.touched()) {
TS_Point p = ts.getPoint();
sp = getScreenCoords(p.x, p.y);
if (btnMN.isClicked(sp)) {
currentPage = '0';
Home();
} else if (btnRN.isClicked(sp)) {
zz = 0;
analogWrite(PWM, zz);
isrunning = false;
} else if (btnPN.isClicked(sp)) {
tft.fillRect(160, 130, 320, 10, ILI9341_BLACK);
rTmAH = 0;
runTime = 0;
StartTime = millis();
prevmahTime = millis();
previntervalTime = millis();
isrunning = true;
} else if (btnDD.isClicked(sp)) {
tft.fillScreen(ILI9341_BLACK);
currentPage = '6';
dischargeData();
}
} //end if
} // End Page 4
// IR Reading
if (currentPage == '5') {
tft.setCursor(0, 60);
tft.print("Cell 1");
tft.setCursor(0, 80);
tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
if (numberCells == '2') {
tft.print(internalResistanceAVGF - internalResistanceAVGP);
} else if (numberCells == '1') {
tft.print(internalResistanceAVGF);
}
tft.setCursor(80, 80);
tft.print("m");
tft.drawChar(90, 80, 233, ILI9341_WHITE, 2, 2);
if (numberCells == '2') {
tft.setCursor(0, 100);
tft.print("Cell 2");
tft.setCursor(0, 120);
tft.print(internalResistanceAVGP);
tft.setCursor(80, 120);
tft.print("m");
tft.drawChar(90, 120, 233, ILI9341_WHITE, 2, 2);
}
if (irRunning == true) {
btnRC.initButtonR(90, 15, 140, 25, "Reading IR");
tft.setCursor(0, 80);
tft.fillRect(0, 40, 100, 100, ILI9341_BLACK);
getIR();
} else {
tft.setTextColor(ILI9341_WHITE, ILI9341_GREEN);
btnRC.initButtonG(90, 15, 140, 25, " Check IR ");
}
if (ts.touched()) {
TS_Point p = ts.getPoint();
sp = getScreenCoords(p.x, p.y);
if (btnMM.isClicked(sp)) {
currentPage = '0';
Home();
}
else if (btnRC.isClicked(sp)) {
irRunning = true;
}
}
} // End Page 5
if (currentPage == '6') {
getCalculations();
getVTime();
tft.setTextSize(1);
tft.setTextColor(ILI9341_ORANGE);
tft.setCursor(25, 45);
tft.print(voltageFull);
if (ts.touched()) {
TS_Point p = ts.getPoint();
sp = getScreenCoords(p.x, p.y);
if (btnMM.isClicked(sp)) {
currentPage = '0';
Home();
} else if (btnRPD.isClicked(sp)) {
currentPage = '4'; // Go to Run Program
getCalculations();
tft.fillScreen(ILI9341_BLACK);
program();
}
}
} // End Page 6
delay(0);
}
//===========================================END LOOP=============================================
// Home Screen CurrentPage=0
void Home() {
tft.fillScreen(ILI9341_BLACK);
tft.setCursor(50, 0);
tft.setTextColor(0x07FF);
tft.setTextSize(4);
tft.print("MAIN MENU");
tft.setTextColor(0x07FF);
tft.setTextSize(3);
tft.setCursor(0, 40);
tft.print("1.");
tft.setCursor(0, 80);
tft.print("2.");
tft.setCursor(0, 120);
tft.print("3.");
tft.setCursor(0, 160);
tft.print("4.");
tft.setCursor(0, 200);
tft.print("5.");
btnMV.initButtonG(50, 40, 200, 25, "Discharge Rate");
btnMI.initButtonG(50, 80, 200, 25, "Voltage Cutoff");
btnCT.initButtonG(50, 120, 200, 25, "Calibrate Screen");
btnRP.initButtonG(50, 160, 200, 25, "Run Discharge");
btnIR.initButtonG(50, 200, 200, 25, "IR Reading");
// add button for getIR function page, adjust button heights to fit, or make 2 colums and adjust width...
}
// Discharge Current Selection CurrentPage=1
void discharge() {
btnIM.initButtonR(80, 75, 80, 60, "-");
btnIP.initButtonG(160, 75, 80, 60, "+");
btnMM.initButtonB(90, 200, 140, 25, " Main Menu");
tft.setCursor(50, 0);
tft.setTextSize(4);
tft.setTextColor(ILI9341_YELLOW);
tft.print("DISCHARGE");
tft.setCursor(70, 40);
tft.print("CURRENT");
tft.setCursor(100, 150);
tft.print(IV, 0);
tft.setCursor(190, 150);
tft.print("A");
} // End discharge
// Cutoff Voltage Selection CurrentPage=2
void cutoff() {
btnIM.initButtonR(80, 75, 80, 60, "-");
btnIP.initButtonG(160, 75, 80, 60, "+");
btnMM.initButtonB(90, 200, 140, 25, " Main Menu");
tft.setCursor(80, 0);
tft.setTextSize(4);
tft.setTextColor(ILI9341_YELLOW);
tft.print("VOLTAGE");
tft.setCursor(95, 40);
tft.print("CUTOFF");
} // End Cutoff
// Run Program CurrentPage=4
void program() {
btnPN.initButtonG(0, 15, 140, 25, " RUN");
btnMN.initButtonB(90, 200, 140, 25, " Main Menu");
btnRN.initButtonR(160, 15, 140, 25, " STOP ");
btnDD.initButtonY(90, 150, 140, 25, " DATA ");
} // End Program
// IR Reading CurrentPage=5
void irReading() {
btnRC.initButtonG(90, 15, 140, 25, " Check IR ");
btnMM.initButtonB(90, 200, 140, 25, " Main Menu");
} // End IR Reading
void dischargeData() {
tft.setTextSize(1);
tft.setTextColor(ILI9341_BLUE);
tft.setCursor(0, 45);
tft.print("000:");
tft.setCursor(55, 45);
tft.print("015:");
btnRPD.initButtonG(90, 15, 140, 25, " Discharge");
btnMM.initButtonB(90, 200, 140, 25, " Main Menu");
} // End dischargeData
void calibrateTouchScreen() {
TS_Point p;
int16_t x1, y1, x2, y2;
tft.fillScreen(ILI9341_BLACK);
// wait for no touch
while (ts.touched())
;
tft.drawFastHLine(10, 20, 20, ILI9341_RED);
tft.drawFastVLine(20, 10, 20, ILI9341_RED);
while (!ts.touched())
;
delay(50);
p = ts.getPoint();
x1 = p.x;
y1 = p.y;
tft.drawFastHLine(10, 20, 20, ILI9341_BLACK);
tft.drawFastVLine(20, 10, 20, ILI9341_BLACK);
delay(500);
while (ts.touched())
;
tft.drawFastHLine(tft.width() - 30, tft.height() - 20, 20, ILI9341_RED);
tft.drawFastVLine(tft.width() - 20, tft.height() - 30, 20, ILI9341_RED);
while (!ts.touched())
;
delay(50);
p = ts.getPoint();
x2 = p.x;
y2 = p.y;
tft.drawFastHLine(tft.width() - 30, tft.height() - 20, 20, ILI9341_BLACK);
tft.drawFastVLine(tft.width() - 20, tft.height() - 30, 20, ILI9341_BLACK);
int16_t xDist = tft.width() - 40;
int16_t yDist = tft.height() - 40;
// translate in form pos = m x val + c
// x
xCalM = (float)xDist / (float)(x2 - x1);
xCalC = 20.0 - ((float)x1 * xCalM);
// y
yCalM = (float)yDist / (float)(y2 - y1);
yCalC = 20.0 - ((float)y1 * yCalM);
currentPage = '0';
Home();
/* // Serial print the actual coordinates from the touch calibrate, enter into the global variables, for first run of the screen
Serial.print("x1 = ");Serial.print(x1);
Serial.print(", y1 = ");Serial.print(y1);
Serial.print("x2 = ");Serial.print(x2);
Serial.print(", y2 = ");Serial.println(y2);
Serial.print("xCalM = ");Serial.print(xCalM);
Serial.print(", xCalC = ");Serial.print(xCalC);
Serial.print("yCalM = ");Serial.print(yCalM);
Serial.print(", yCalC = ");Serial.println(yCalC);
*/
} // END Calibrate
// Current Average
void getCurrentAverage() {
int ii;
double rawIRead;
for (int ii = 0; ii < IN; ii++) {
//analogRead(Isens);
rawIRead += analogRead(Isens);
}
iavg = rawIRead / IN;
Icorrected = (iavg - IOFF);
} // end getCurrent
// Full voltage average (2s voltage if a 2s battery or 1s voltage for a 1s battery)
void getFVoltageAverage() {
int jj;
float rawVFRead;
for (int jj = 0; jj < VFN; jj++) {
//analogRead(VFsens);
rawVFRead += analogRead(VFsens);
}
vfavg = rawVFRead / VFN;
} // end getFVoltage
// Partial Voltage average (only applies for 2s, this is cell 2 voltage)
void getPVoltageAverage() {
int kk;
float rawVPRead;
for (int kk = 0; kk < VPN; kk++) {
// analogRead(VPsens);
rawVPRead += analogRead(VPsens) + 1;
}
vpavg = rawVPRead / VPN;
} // end Partial Voltage
// Runs the main discharge function
void RunDischarge() {
if (isrunning == true) {
if (numberCells == '2') {
if (cell1 > VV / 100 && cell2 > VV / 100) {
Input = Icurrent;
myPID.Compute();
analogWrite(PWM, Output);
}
else if (cell1 < VV / 100 || cell2 < VV / 100) {
zz = 0;
analogWrite(PWM, zz);
isrunning = false;
}
}
else if (numberCells == '1') {
if (cell1 > VV / 100) {
Input = Icurrent;
myPID.Compute();
analogWrite(PWM, Output);
}
else if (cell1 < VV / 100) {
zz = 0;
analogWrite(PWM, zz);
isrunning = false;
}
}
}
} // end runDischarge
void getCalculations() {
getCurrentAverage();
voltageI = Icorrected * (VCC / 1023);
Icurrent = abs((voltageI - QOV) / sens);
getPVoltageAverage();
getFVoltageAverage();
Pvoltage = vpavg * (VCC / 1023);
Fvoltage = vfavg * (VCC / 1023);
if (isrunning == true || irRunning == true) {
voltagePartial = ((Pvoltage * (ValueR1P + ValueR2P)) / ValueR2P) + (Icurrent / 250);
voltageFull = ((Fvoltage * (ValueR1F + ValueR2F)) / ValueR2F) + (Icurrent / 250);
if (numberCells == '2') {
cell1 = voltageFull - voltagePartial;
cell2 = voltagePartial;
} else if (numberCells == '1') {
cell1 = voltageFull;
}
} else {
voltagePartial = ((Pvoltage * (ValueR1P + ValueR2P)) / ValueR2P);
voltageFull = ((Fvoltage * (ValueR1F + ValueR2F)) / ValueR2F);
if (numberCells == '2') {
cell1 = voltageFull - voltagePartial;
cell2 = voltagePartial;
} else if (numberCells == '1') {
cell1 = voltageFull;
}
}
if (voltageFull >= 5) {
numberCells = '2';
} else if (voltageFull < 5) {
numberCells = '1';
}
VCC = VCCC / 1000;
if (isrunning == true) {
CurrentTime = (millis() - StartTime) / 1000;
mahTime = (millis() - prevmahTime) / 1000;
}
if (CurrentTime > runTime) {
runTime = CurrentTime;
} else if (isrunning == false) {
CurrentTime = 0;
}
if (mahTime - prevmahTime >= 1) {
mAH += (Icurrent / 3.6);
prevmahTime = mahTime;
}
if (mAH > rTmAH) {
rTmAH = mAH;
}
} // end Calculations
// calibrates the VCC using the bandgap ref. This works and doesn't work. Probably be better to add an external vref to the design
void getAccurateVoltage() {
int vv;
float rawgetVoltage = 0;
for (vv = 0; vv < 100; vv++) {
getVoltage();
rawgetVoltage += getVoltage();
}
VCCC = rawgetVoltage / 100;
}
// Read the voltage of the battery the Arduino is currently running on (in millivolts)
float getVoltage() {
const long InternalReferenceVoltage = 1080; // Adjust this value to your boards specific internal BG voltage x1000
ADMUX = (0 << REFS1) | (1 << REFS0) | (0 << ADLAR) | (1 << MUX3) | (1 << MUX2) | (1 << MUX1) | (0 << MUX0);
ADCSRA |= _BV(ADSC); // Start a conversion
while (((ADCSRA & (1 << ADSC)) != 0))
; // Wait for it to complete
float results = (((InternalReferenceVoltage * 1024) / ADC) + 5); // Scale the value; calculates for straight line value
return results;
} // end VCC calibration
//Internal Resistance Measurement
void getIR() {
if (isrunning == false) {
irRunning = true;
for (int chan = 0; chan < (numChan - 1); chan++) {
tft.fillRect(0, 120, 130, 40, ILI9341_BLACK);
getCalculations();
SerialData();
Channels[chan].startVoltageF = voltageFull - (Icurrent / 250); // record unloaded voltage of battery
Channels[chan].startVoltageP = voltagePartial - (Icurrent / 250); // record unloaded voltage of battery
zz = 75; // Will be appx 20A with 2s, may split this into if statements based on current battery voltage
analogWrite(PWM, zz); //Turn Mosfets on
delay(250); //delay to stabalize - may need to fine tune this delay, but the shorter the better
getCalculations();
SerialData();
Channels[chan].endVoltageF = voltageFull; // record voltage of battery under load
Channels[chan].endVoltageP = voltagePartial; // record voltage of battery under load
zz = 0; //
analogWrite(PWM, zz); //Turn off the mosfets
Channels[chan].voltageDropF = Channels[chan].startVoltageF - Channels[chan].endVoltageF; // Voltage drop from the load
Channels[chan].voltageDropP = Channels[chan].startVoltageP - Channels[chan].endVoltageP; // Voltage drop from the load
Channels[chan].internalResistanceF = (Channels[chan].voltageDropF / Icurrent) * 1000; //Ohms Law V=IR, R=V/I, Readings in mΩ
Channels[chan].internalResistanceP = (Channels[chan].voltageDropP / Icurrent) * 1000; //Ohms Law V=IR, R=V/I, Readings in mΩ
delay(500); // Allow for stablization between readings - may need to fine tune this delay, but start on the high side
}
for (int chan = 0; chan < (numChan - 1); chan++) {
internalResistanceAVGF = +Channels[chan].internalResistanceF; // run the readings numChan times and average the IR
internalResistanceAVGP = +Channels[chan].internalResistanceP; // run the readings numChan times and average the IR
}
} //End if
irRunning = false;
} //end getIR
void getVTime() {
intervalTime = millis() - previntervalTime / 1000;
if (intervalTime >= 15) {
ChannelsT[chanT].vTime = voltageFull;
previntervalTime = intervalTime;
chanT++;
}
}
void calibrateQOV() {
analogRead(Isens);
QOV = (analogRead(Isens) * VCC) / 1023;
}
void SerialData() {
Serial.println("----------------------------------------");
Serial.println();
Serial.print(VCC);
Serial.print("V VCC ");
Serial.print(QOV);
Serial.print(" QOV ");
Serial.println();
Serial.print(analogRead(Isens));
Serial.print(" RAW Read ");
Serial.print(Icorrected);
Serial.print(" Corrected ");
Serial.print(voltageI);
Serial.print("V ");
Serial.print(Icurrent);
Serial.print(" Amps ");
Serial.print(iavg);
Serial.print(" AVG ");
Serial.print(Output);
Serial.println();
Serial.print(voltageFull);
Serial.print(" V Full ");
Serial.print(vfavg);
Serial.print(" ACT ");
Serial.print(analogRead(VFsens));
Serial.print(" ");
Serial.println();
Serial.print(voltagePartial);
Serial.print(" ");
Serial.print(" V Partial ");
Serial.print(vpavg);
Serial.print(" ACT ");
Serial.print(analogRead(VPsens));
Serial.println();
Serial.print("Cell 1 ");
Serial.print(cell1, 3);
Serial.print("V Cell 2 ");
Serial.print(cell2, 3);
Serial.print("V");
Serial.println();
Serial.println(ChannelsT[chanT].vTime);
}