This is my full code used in a Arduino Pro Mini to control my 3 FP. Alot of the code is for controling the LCD.
Keep in mind, I'm not a SW guy so use eye protection when reading through...
Code: Select all
// Include libraries
#include <mcp_can.h>
#include <SPI.h>
#include <Wire.h>
enum State_enum
{
START, SELECT, PLOT, INIT, MAIN, DONE, STOP, END
};
// Stated declaration
State_enum state;
// Constant declaration
// newData MASK
const uint8_t statusFPMaster = B00000001;
const uint8_t statusFPSlave1 = B00000010;
const uint8_t statusFPSlave2 = B00000100;
const uint8_t statusBMSMain = B00001000;
const uint8_t statusBMSHiLo = B00010000;
const uint8_t statusBMStemp = B00100000;
const uint8_t statusFPall = B00000111;
const uint8_t statusAllCAN = B00111111;
// stopReason MASK
const uint8_t stopDone = B00000001;
const uint8_t stopButton = B00000010;
const uint8_t stopLowVoltage = B00000100;
const uint8_t stopTemperature = B00001000;
const uint8_t stopBMS = B00010000;
const uint8_t stopWatchdog = B00100000;
// chargerState MASK
const uint8_t chargerStateCurrentLimiting = B00000001;
const uint8_t chargerStateCurrentRegulating = B00000010;
const uint8_t chargerStateEndVoltage = B00000100;
// Timing constants
const uint32_t timePeriod100ms = 100; // Delay 100ms
const uint32_t timePeriod1s = 1000; // Delay, 1s
const uint32_t timePeriod3s = 3000; // Delay 3s
const uint32_t timePeriod5s = 5000; // Delay, 5s
const uint32_t timePeriod30s = 30000; // Delay 30s
// Configuration
const uint16_t startVoltage = 4370; // Set start voltage to 43.70 V (divide by 100)
const uint16_t initialLimitVoltage = 4900; // Set switch voltage to 49.00 V (divide by 100), must be lower than end voltage
const uint16_t endVoltage = 5040; // Set end voltage to 50.40 V (divide by 100)
const uint16_t overVoltage = 5110; // Set the overVoltage protection limit at 51.10 V (divide by 100)
const uint16_t maxCurrent1x10AAC = 135; // set charge current to 13.5 A (divide by 10) for max 10AAC at 230V
const uint16_t maxCurrent1x16AAC = 216; // set charge current to 21.6 A (divide by 10) for max 16AAC at 230V
const uint16_t maxCurrent3x16AAC = 600; // set charge current to 60.0 A (divide by 10), no limit, for 3-phase 16A
const uint16_t maxCurrent = 700; // Set slave maximum current to 70.0 A (divide by 10)
const uint16_t endCurrent = 10; // Set endCurrent to 1.0 A (divide by 10)
const uint16_t lowestCellVoltageLimit = 2500; // Set the lowest voltage acceptable to allow charging
const int16_t maxAllowedTemperature = 70; // Set max allowed BMS card temperature
const int8_t temperatureCorrection[4] = {1, 5, 0, 1};
// Display commands
const uint8_t address = 0x3C;
const uint8_t commands = 0x00;
const uint8_t onecommand = 0x80;
const uint8_t data = 0x40;
const uint8_t onedata = 0xC0;
// Variable declaration
// For FlatPacks
uint8_t mainBMS[8];
uint8_t highLowBMS[8];
uint8_t tempBMS[8];
uint16_t measuredMasterCurrent;
uint8_t newData = 0;
uint8_t stopReason = 0;
uint8_t chargerState = 0;
// Set pin number
const uint8_t SPI_CS_PIN0 = 10; // CAN0, BMS
const uint8_t SPI_CS_PIN1 = 15; // CAN1, FlatPack Master (Current limiting)
const uint8_t SPI_CS_PIN2 = 14; // CAN2, FlatPack Slave 1
const uint8_t SPI_CS_PIN3 = 16; // CAN3, FlatPack Slave 2
const uint8_t threePhasePIN = 17; // Input PIN for 3-phase detection
const uint8_t contactorPIN = 9; // Contactor control and +12V supply to BMS
const uint8_t buttonPIN = 8; // Button to select charge current
// Set CS pin for CAN-buses
MCP_CAN CAN0(SPI_CS_PIN0);
MCP_CAN CAN1(SPI_CS_PIN1);
MCP_CAN CAN2(SPI_CS_PIN2);
MCP_CAN CAN3(SPI_CS_PIN3);
// Function declaration
// For FlatPack
void chargerMain();
void voltageControl();
uint16_t chargeCurrentCalc();
void checkLowestCell();
int16_t checkTemperature();
void updateDisplay();
void plotChargerState();
void stopCharging();
void BMSStopCharging();
void readCAN();
uint16_t readMasterCAN();
uint16_t readSlave1CAN();
uint16_t readSlave2CAN();
void sendCAN();
void sendFlatPackCAN();
void sendMasterCAN();
void sendSlave1CAN();
void sendSlave2CAN();
// HMI
void clearDisplay();
void initDisplay();
void plotChar();
void plotText();
void plotValue();
void moveMarker();
uint16_t selectCurrent();
void buttonStopCharging();
// OLED display
// Character set for text - stored in program memory
const uint8_t CharMap[96][6] PROGMEM = {
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // 32: "Space"
{ 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00 }, // 33: !
{ 0x00, 0x07, 0x00, 0x07, 0x00, 0x00 }, // 34: "
{ 0x14, 0x7F, 0x14, 0x7F, 0x14, 0x00 }, // 35: #
{ 0x24, 0x2A, 0x7F, 0x2A, 0x12, 0x00 }, // 36: $
{ 0x23, 0x13, 0x08, 0x64, 0x62, 0x00 }, // 37: %
{ 0x36, 0x49, 0x56, 0x20, 0x50, 0x00 }, // 38: &
{ 0x00, 0x08, 0x07, 0x03, 0x00, 0x00 }, // 39: '
{ 0x00, 0x1C, 0x22, 0x41, 0x00, 0x00 }, // 40: (
{ 0x00, 0x41, 0x22, 0x1C, 0x00, 0x00 }, // 41: )
{ 0x2A, 0x1C, 0x7F, 0x1C, 0x2A, 0x00 }, // 42: *
{ 0x08, 0x08, 0x3E, 0x08, 0x08, 0x00 }, // 43: +
{ 0x00, 0x80, 0x70, 0x30, 0x00, 0x00 }, // 44: ,
{ 0x08, 0x08, 0x08, 0x08, 0x08, 0x00 }, // 45: -
{ 0x00, 0x00, 0x60, 0x60, 0x00, 0x00 }, // 46: .
{ 0x20, 0x10, 0x08, 0x04, 0x02, 0x00 }, // 47: /
{ 0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00 }, // 48: 0
{ 0x00, 0x42, 0x7F, 0x40, 0x00, 0x00 }, // 49: 1
{ 0x72, 0x49, 0x49, 0x49, 0x46, 0x00 }, // 50: 2
{ 0x21, 0x41, 0x49, 0x4D, 0x33, 0x00 }, // 51: 3
{ 0x18, 0x14, 0x12, 0x7F, 0x10, 0x00 }, // 52: 4
{ 0x27, 0x45, 0x45, 0x45, 0x39, 0x00 }, // 53: 5
{ 0x3C, 0x4A, 0x49, 0x49, 0x31, 0x00 }, // 54: 6
{ 0x41, 0x21, 0x11, 0x09, 0x07, 0x00 }, // 55: 7
{ 0x36, 0x49, 0x49, 0x49, 0x36, 0x00 }, // 56: 8
{ 0x46, 0x49, 0x49, 0x29, 0x1E, 0x00 }, // 57: 9
{ 0x00, 0x00, 0x14, 0x00, 0x00, 0x00 }, // 58: :
{ 0x00, 0x40, 0x34, 0x00, 0x00, 0x00 }, // 59: ;
{ 0x00, 0x08, 0x14, 0x22, 0x41, 0x00 }, // 60: <
{ 0x14, 0x14, 0x14, 0x14, 0x14, 0x00 }, // 61: =
{ 0x00, 0x41, 0x22, 0x14, 0x08, 0x00 }, // 62: >
{ 0x02, 0x01, 0x59, 0x09, 0x06, 0x00 }, // 63: ?
{ 0x3E, 0x41, 0x5D, 0x59, 0x4E, 0x00 }, // 64: @
{ 0x7C, 0x12, 0x11, 0x12, 0x7C, 0x00 }, // 65: A
{ 0x7F, 0x49, 0x49, 0x49, 0x36, 0x00 }, // 66: B
{ 0x3E, 0x41, 0x41, 0x41, 0x22, 0x00 }, // 67: C
{ 0x7F, 0x41, 0x41, 0x41, 0x3E, 0x00 }, // 68: D
{ 0x7F, 0x49, 0x49, 0x49, 0x41, 0x00 }, // 69: E
{ 0x7F, 0x09, 0x09, 0x09, 0x01, 0x00 }, // 70: F
{ 0x3E, 0x41, 0x41, 0x51, 0x73, 0x00 }, // 71: G
{ 0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00 }, // 72: H
{ 0x00, 0x41, 0x7F, 0x41, 0x00, 0x00 }, // 73: I
{ 0x20, 0x40, 0x41, 0x3F, 0x01, 0x00 }, // 74: J
{ 0x7F, 0x08, 0x14, 0x22, 0x41, 0x00 }, // 75: K
{ 0x7F, 0x40, 0x40, 0x40, 0x40, 0x00 }, // 76: L
{ 0x7F, 0x02, 0x1C, 0x02, 0x7F, 0x00 }, // 77: M
{ 0x7F, 0x04, 0x08, 0x10, 0x7F, 0x00 }, // 78: N
{ 0x3E, 0x41, 0x41, 0x41, 0x3E, 0x00 }, // 79: O
{ 0x7F, 0x09, 0x09, 0x09, 0x06, 0x00 }, // 80: P
{ 0x3E, 0x41, 0x51, 0x21, 0x5E, 0x00 }, // 81: Q
{ 0x7F, 0x09, 0x19, 0x29, 0x46, 0x00 }, // 82: R
{ 0x26, 0x49, 0x49, 0x49, 0x32, 0x00 }, // 83: S
{ 0x03, 0x01, 0x7F, 0x01, 0x03, 0x00 }, // 84: T
{ 0x3F, 0x40, 0x40, 0x40, 0x3F, 0x00 }, // 85: U
{ 0x1F, 0x20, 0x40, 0x20, 0x1F, 0x00 }, // 86: V
{ 0x3F, 0x40, 0x38, 0x40, 0x3F, 0x00 }, // 87: W
{ 0x63, 0x14, 0x08, 0x14, 0x63, 0x00 }, // 88: X
{ 0x03, 0x04, 0x78, 0x04, 0x03, 0x00 }, // 89: Y
{ 0x61, 0x59, 0x49, 0x4D, 0x43, 0x00 }, // 90: Z
{ 0x00, 0x7F, 0x41, 0x41, 0x41, 0x00 }, // 91: [
{ 0x02, 0x04, 0x08, 0x10, 0x20, 0x00 }, // 92: backslash
{ 0x00, 0x41, 0x41, 0x41, 0x7F, 0x00 }, // 93: ]
{ 0x04, 0x02, 0x01, 0x02, 0x04, 0x00 }, // 94: ^
{ 0x40, 0x40, 0x40, 0x40, 0x40, 0x00 }, // 95: _
{ 0x00, 0x03, 0x07, 0x08, 0x00, 0x00 }, // 96: `
{ 0x20, 0x54, 0x54, 0x78, 0x40, 0x00 }, // 97: a
{ 0x7F, 0x28, 0x44, 0x44, 0x38, 0x00 }, // 98: b
{ 0x38, 0x44, 0x44, 0x44, 0x28, 0x00 }, // 99: c
{ 0x38, 0x44, 0x44, 0x28, 0x7F, 0x00 }, // 100: d
{ 0x38, 0x54, 0x54, 0x54, 0x18, 0x00 }, // 101: e
{ 0x00, 0x08, 0x7E, 0x09, 0x02, 0x00 }, // 102: f
{ 0x18, 0xA4, 0xA4, 0x9C, 0x78, 0x00 }, // 103: g
{ 0x7F, 0x08, 0x04, 0x04, 0x78, 0x00 }, // 104: h
{ 0x00, 0x44, 0x7D, 0x40, 0x00, 0x00 }, // 105: i
{ 0x20, 0x40, 0x40, 0x3D, 0x00, 0x00 }, // 106: j
{ 0x7F, 0x10, 0x28, 0x44, 0x00, 0x00 }, // 107: k
{ 0x00, 0x41, 0x7F, 0x40, 0x00, 0x00 }, // 108: l
{ 0x7C, 0x04, 0x78, 0x04, 0x78, 0x00 }, // 109: m
{ 0x7C, 0x08, 0x04, 0x04, 0x78, 0x00 }, // 110: n
{ 0x38, 0x44, 0x44, 0x44, 0x38, 0x00 }, // 111: o
{ 0xFC, 0x18, 0x24, 0x24, 0x18, 0x00 }, // 112: p
{ 0x18, 0x24, 0x24, 0x18, 0xFC, 0x00 }, // 113: q
{ 0x7C, 0x08, 0x04, 0x04, 0x08, 0x00 }, // 114: r
{ 0x48, 0x54, 0x54, 0x54, 0x24, 0x00 }, // 115: s
{ 0x04, 0x04, 0x3F, 0x44, 0x24, 0x00 }, // 116: t
{ 0x3C, 0x40, 0x40, 0x20, 0x7C, 0x00 }, // 117: u
{ 0x1C, 0x20, 0x40, 0x20, 0x1C, 0x00 }, // 118: v
{ 0x3C, 0x40, 0x30, 0x40, 0x3C, 0x00 }, // 119: w
{ 0x44, 0x28, 0x10, 0x28, 0x44, 0x00 }, // 120: x
{ 0x4C, 0x90, 0x90, 0x90, 0x7C, 0x00 }, // 121: y
{ 0x44, 0x64, 0x54, 0x4C, 0x44, 0x00 }, // 122: z
{ 0x00, 0x08, 0x36, 0x41, 0x00, 0x00 }, // 123: {
{ 0x00, 0x00, 0x77, 0x00, 0x00, 0x00 }, // 124: |
{ 0x00, 0x41, 0x36, 0x08, 0x00, 0x00 }, // 125: }
{ 0x00, 0x06, 0x09, 0x06, 0x00, 0x00 }, // 126: degree symbol = '~'
{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00 } // 127: DEL
};
/********
* SETUP *
********/
void setup()
{
pinMode(buttonPIN, INPUT_PULLUP);
pinMode(contactorPIN, OUTPUT);
digitalWrite(contactorPIN, LOW);
Wire.begin();
initDisplay();
clearDisplay();
//Serial.begin(115200);
while (CAN_OK != CAN0.begin(MCP_STDEXT, CAN_250KBPS, MCP_8MHZ)) // Init car CAN bus : baudrate = 250k
{ delay(100); }
CAN0.init_Mask(0,0,0x07FF0000); // Init first mask
CAN0.init_Filt(0,0,0x01800000); // Init first filter
CAN0.init_Filt(1,0,0x01810000); // Init second filter
CAN0.init_Mask(1,0,0x07FF0000); // Init second mask
CAN0.init_Filt(2,0,0x01800000); // Init third filter
CAN0.init_Filt(3,0,0x01810000); // Init fouth filter
CAN0.init_Filt(4,0,0x01820000); // Init fifth filter
CAN0.init_Filt(5,0,0x01820000); // Init sixth filter
CAN0.setMode(MCP_NORMAL); // Set operation mode to normal so the MCP2515 sends acks to received data.
while (CAN_OK != CAN1.begin(MCP_STDEXT, CAN_125KBPS, MCP_8MHZ)) // Init FlatPack Master CAN bus : baudrate = 125k
{ delay(100); }
// These are the status messages (05014004 is not current-limiting, 05014008 is current limiting 05014010 = busy with walkin, 0501400C in input voltage low)
CAN1.init_Mask(0,1,0x1FFFFFFF); // Init first mask
CAN1.init_Filt(0,1,0x05014004); // Init first filter
CAN1.init_Filt(1,1,0x05014008); // Init second filter
CAN1.init_Mask(1,1,0x1FFFFFFF); // Init second mask
CAN1.init_Filt(2,1,0x05014004); // Init third filter
CAN1.init_Filt(3,1,0x05014008); // Init fouth filter
CAN1.init_Filt(4,1,0x05014004); // Init fifth filter
CAN1.init_Filt(5,1,0x05014004); // Init sixth filter
CAN1.setMode(MCP_NORMAL); // Set operation mode to normal so the MCP2515 sends acks to received data.
while (CAN_OK != CAN2.begin(MCP_STDEXT, CAN_125KBPS, MCP_8MHZ)) // Init FlatPack Slave 1 CAN bus : baudrate = 125k
{ delay(100); }
// These are the status messages (05014004 is not current-limiting, 05014008 is current limiting 05014010 = busy with walkin, 0501400C in input voltage low)
CAN2.init_Mask(0,1,0x1FFFFFFF); // Init first mask
CAN2.init_Filt(0,1,0x05014004); // Init first filter
CAN2.init_Filt(1,1,0x05014004); // Init second filter
CAN2.init_Mask(1,1,0x1FFFFFFF); // Init second mask
CAN2.init_Filt(2,1,0x05014004); // Init third filter
CAN2.init_Filt(3,1,0x05014004); // Init fouth filter
CAN2.init_Filt(4,1,0x05014004); // Init fifth filter
CAN2.init_Filt(5,1,0x05014004); // Init sixth filter
CAN2.setMode(MCP_NORMAL); // Set operation mode to normal so the MCP2515 sends acks to received data.
while (CAN_OK != CAN3.begin(MCP_STDEXT, CAN_125KBPS, MCP_8MHZ)) // Init FlatPack Slave 2 CAN bus : baudrate = 125k
{ delay(100); }
// These are the status messages (05014004 is not current-limiting, 05014008 is current limiting 05014010 = busy with walkin, 0501400C in input voltage low)
CAN3.init_Mask(0,1,0x1FFFFFFF); // Init first mask
CAN3.init_Filt(0,1,0x05014004); // Init first filter
CAN3.init_Filt(1,1,0x05014004); // Init second filter
CAN3.init_Mask(1,1,0x1FFFFFFF); // Init second mask
CAN3.init_Filt(2,1,0x05014004); // Init third filter
CAN3.init_Filt(3,1,0x05014004); // Init fouth filter
CAN3.init_Filt(4,1,0x05014004); // Init fifth filter
CAN3.init_Filt(5,1,0x05014004); // Init sixth filter
CAN3.setMode(MCP_NORMAL); // Set operation mode to normal so the MCP2515 sends acks to received data.
state = START;
}
/*******
* LOOP *
*******/
void loop()
{
readCAN();
uint16_t measuredMasterVoltage = readMasterCAN();
uint16_t measuredSlave1Voltage = readSlave1CAN();
uint16_t measuredSlave2Voltage = readSlave2CAN();
chargerMain(measuredMasterVoltage, measuredSlave1Voltage, measuredSlave2Voltage);
}
/************************************************************************
* Sets the output both master and slave. Checks if charging is complete *
************************************************************************/
void chargerMain(uint16_t measuredMasterVoltage, uint16_t measuredSlave1Voltage, uint16_t measuredSlave2Voltage)
{
static uint16_t startCapacity;
static uint16_t selectedMaxCurrent = 0;
uint16_t endCapacity;
switch (state)
{
case START:
plotText("U:000.0V T:00", 0, 0);
plotChar(126, 13, 0); // Degree symbol
plotChar('C', 14, 0);
plotText("Is:00.0A I:00.0A", 0, 1);
plotText("Cs:00.0Ah C:00.0Ah", 0, 2);
plotText("Status:", 0, 3);
plotChar('*', 0, 4);
plotText("IAC=1x10A", 2, 4);
plotText("IAC=1x16A", 2, 5);
plotText("IAC=3x16A", 2, 6);
state = SELECT;
break;
case SELECT:
static uint32_t lastMillisTimer = millis(); // Reset timer
selectedMaxCurrent = selectCurrent();
if(selectedMaxCurrent != 0)
{
state = PLOT;
}
if((millis() - lastMillisTimer) > timePeriod30s)
{
selectedMaxCurrent = maxCurrent1x10AAC;
state = PLOT;
}
break;
case PLOT:
plotText(" ", 0, 4);
plotText(" ", 0, 5);
plotText(" ", 0, 6);
plotValue(selectedMaxCurrent, 10.0, 1, 3, 1);
state = INIT;
break;
case INIT:
digitalWrite(contactorPIN, HIGH); // Turn on contactor and +12V supply to BMS
sendFlatPackCAN(startVoltage, startVoltage, selectedMaxCurrent); // Send data
if((newData & statusAllCAN) == statusAllCAN) // All FlatPacks and BMS are responding
{
startCapacity = (mainBMS[4] << 8) + mainBMS[5]; // Battery pack capacity
plotValue(startCapacity, 10.0, 1, 3, 2);
state = MAIN;
}
break;
case MAIN:
if((newData & statusAllCAN) == statusAllCAN) // All FlatPacks and BMS are responding
{
static uint32_t lastMillisDo = millis();
if(millis() - lastMillisDo >= timePeriod1s)
{
lastMillisDo = millis(); // Reset timer
voltageControl(selectedMaxCurrent, measuredMasterVoltage);
checkLowestCell();
int16_t maxTemperature = checkTemperature();
updateDisplay(maxTemperature);
}
if(((measuredMasterVoltage + measuredSlave1Voltage + measuredSlave2Voltage) >= (endVoltage * 3)) && (measuredMasterCurrent <= endCurrent)) // Check if charging is complete
{
stopReason = stopDone;
state = STOP;
}
}
buttonStopCharging();
BMSStopCharging();
break;
case STOP:
sendFlatPackCAN(startVoltage, startVoltage, endCurrent); // Send data to stop FlatPack power output
delay(2000);
endCapacity = (mainBMS[4] << 8) + mainBMS[5]; // Battery pack capacity
if(stopReason & stopDone) // Check if battery capacity needs reset
{
while(!(mainBMS[6] & B00000001)) // Do until battery capacity is reset
{
sendCAN(); // Reset BMS battery capacity
delay(100);
readCAN(); // Readback to check reset
}
}
stopCharging(startCapacity, endCapacity);
state = END;
break;
case END:
break;
default:
// Error
break;
}
}
/******************************************
* Sets the FP output voltages and current *
******************************************/
void voltageControl(uint16_t selectedMaxCurrent, uint16_t measuredMasterVoltage)
{
static uint16_t setMasterVoltage = startVoltage;
static uint16_t setSlaveVoltage = startVoltage;
static uint16_t limitVoltage = initialLimitVoltage;
((setMasterVoltage + 10) < endVoltage) ? setMasterVoltage += 10 : setMasterVoltage = endVoltage; // Increases setMasterVoltage with 100mV
if(measuredMasterVoltage >= limitVoltage)
{
((setSlaveVoltage + 10) < endVoltage) ? setSlaveVoltage += 10 : setSlaveVoltage = endVoltage; // Increases setSlaveVoltage with 100mV
}
if(setSlaveVoltage > limitVoltage)
{
((limitVoltage + 10) < endVoltage) ? limitVoltage += 10 : limitVoltage = endVoltage; // Increases limitVoltage with 100mV
}
uint16_t chargeCurrent = chargeCurrentCalc(selectedMaxCurrent);
sendFlatPackCAN(setMasterVoltage, setSlaveVoltage, chargeCurrent); // Send new data
newData &= ~statusAllCAN; // Reset FlatPack and BMS bits to check if new data is received
if(limitVoltage == endVoltage) // Check and set charger state constant voltage
{
chargerState |= chargerStateEndVoltage;
}
}
/************************************************************************************
* Calculates the max allowed charging current based on highest battery cell voltage *
************************************************************************************/
uint16_t chargeCurrentCalc(uint16_t selectedMaxCurrent)
{
uint16_t chargeCurrent;
uint16_t highestCellVoltage = (highLowBMS[1] << 8) + highLowBMS[2]; // Highest cell voltage
const uint16_t highVoltageLowerLimit = 3590; // Set highest cell lower voltage for start of current limiting, in mV
const uint16_t highVoltageUpperLimit = 3640; // Set highest cell upper voltage for max current limiting, in mV
if(highestCellVoltage < highVoltageLowerLimit)
{
chargeCurrent = selectedMaxCurrent;
}
else if((highestCellVoltage >= highVoltageLowerLimit) && (highestCellVoltage <= highVoltageUpperLimit))
{
//chargeCurrent = selectedMaxCurrent * (highVoltageUpperLimit - highestCellVoltage) / (highVoltageUpperLimit - highVoltageLowerLimit);
chargeCurrent = (11L * highestCellVoltage * highestCellVoltage - 80118L * highestCellVoltage + 145883920L) / 49;
if (chargeCurrent > selectedMaxCurrent)
{
chargeCurrent = selectedMaxCurrent;
}
}
else
{
chargeCurrent = 0;
}
(chargeCurrent < selectedMaxCurrent) ? chargerState |= chargerStateCurrentRegulating : chargerState &= ~chargerStateCurrentRegulating; // Check and set charger state current regulation
return chargeCurrent;
}
/**********************************************************************
* Checks that lowest battery cell is above limit, else stops charging *
**********************************************************************/
void checkLowestCell()
{
uint16_t lowestCellVoltage = (highLowBMS[4] << 8) + highLowBMS[5]; // Lowest cell voltage
if(lowestCellVoltage < lowestCellVoltageLimit)
{
state = STOP; // Abort charging
stopReason = stopLowVoltage;
}
}
/*****************************************************************************************************************
* Finds max temperature after board temperatures have been corrected, checks if below limit, else stops charging *
*****************************************************************************************************************/
int16_t checkTemperature()
{
int16_t maxTemperature = 0;
int16_t tempModule;
for(int i=0; i<4; i++)
{
tempModule = (tempBMS[i*2] << 8) + tempBMS[(i*2)+1]; // BMS module card temperature in degree C
tempModule = tempModule - temperatureCorrection[i]; // Correction so all boards show the same temperature for the same ambient temperature
if(maxTemperature < tempModule)
{
maxTemperature = tempModule;
}
}
if(maxTemperature > maxAllowedTemperature)
{
state = STOP; // Abort charging
stopReason = stopTemperature;
}
return maxTemperature;
}
/***********************************
* Updates display with latest data *
***********************************/
void updateDisplay(int16_t maxTemperature)
{
uint16_t capacity = (mainBMS[4] << 8) + mainBMS[5]; // Battery pack remaining capacity
uint16_t totalBatteryVoltage = (mainBMS[2] << 8) + mainBMS[3]; // Battery pack total voltage
plotValue(totalBatteryVoltage, 10.0, 1, 2, 0);
plotValue(maxTemperature, 1, 0, 11, 0);
plotValue(measuredMasterCurrent, 10.0, 1, 11, 1);
plotValue(capacity, 10.0, 1, 12, 2);
plotChargerState();
}
/**********************************
* Blinks charger state on display *
**********************************/
void plotChargerState()
{
static uint8_t flash = 0;
if(flash == 0)
{
if(!chargerState)
{
plotText("RV", 7, 3);
}
else if((chargerState & chargerStateCurrentLimiting) && !(chargerState & chargerStateCurrentRegulating))
{
plotText("CC", 7, 3);
}
else if((chargerState & chargerStateCurrentLimiting) && (chargerState & chargerStateCurrentRegulating))
{
plotText("CR", 7, 3);
}
else if(!(chargerState & chargerStateCurrentLimiting) && (chargerState & chargerStateEndVoltage))
{
plotText("CV", 7, 3);
}
else
{
plotText("EE", 7, 3);
}
flash = 1;
}
else
{
plotText(" ", 7, 3);
flash = 0;
}
}
/**********************************************
* Stops charging and prints reason on display *
**********************************************/
void stopCharging(uint16_t startCapacity, uint16_t endCapacity)
{
digitalWrite(contactorPIN, LOW); // Turn off contactor and +12V supply to BMS
if(stopReason & stopDone)
{
plotText("Done! ", 0, 5);
}
else if (stopReason & stopButton)
{
plotText("Stop! ", 0, 5);
}
else if (stopReason & stopLowVoltage)
{
plotText("LowV! ", 0, 5);
}
else if (stopReason & stopTemperature)
{
plotText("Temp! ", 0, 5);
}
else if (stopReason & stopBMS)
{
plotText("BMS! ", 0, 5);
}
else if (stopReason & stopWatchdog)
{
plotText("Wdog! ", 0, 5);
}
else
{
plotText("Err! ", 0, 5);
}
plotValue((endCapacity - startCapacity), 10.0, 1, 6, 5);
plotText("Ah", 10, 5);
}
/***************************************************
* Stops charging if BMS sends charging not allowed *
***************************************************/
void BMSStopCharging()
{
if(!(mainBMS[6] & B00001000))
{
state = STOP;
stopReason = stopBMS;
}
}
/*********************************************************************************************
* Checks BMS CAN-bus buffer and if new data is available stores it in appropriate data array *
*********************************************************************************************/
void readCAN()
{
uint32_t rxId;
uint8_t len;
uint8_t rxBuf[8];
if(CAN_MSGAVAIL == CAN0.checkReceive()) // Check if data is coming
{
CAN0.readMsgBuf(&rxId, &len, rxBuf); // read data, rxId: message ID, ext: flag extended ID, len: data length, rxBuf: data buf
rxId &= 0x7FF; // Remove bits outside of the 11-bits ID
if(rxId == 0x180) // Main data from BMS
{
newData |= statusBMSMain;
for(int i=0; i<8; i++)
{
mainBMS[i] = rxBuf[i];
}
}
if(rxId == 0x181) // Highest and lowest cell data from BMS
{
newData |= statusBMSHiLo;
for(int i=0; i<8; i++)
{
highLowBMS[i] = rxBuf[i];
}
}
if(rxId == 0x182) // Temperature data from BMS
{
newData |= statusBMStemp;
for(int i=0; i<8; i++)
{
tempBMS[i] = rxBuf[i];
}
}
}
}
/*****************************************************************************************************************************
* Checks FlatPack Master CAN-bus buffer and if new data is available stores it in appropriate variable. Also resets watchdog *
*****************************************************************************************************************************/
uint16_t readMasterCAN()
{
static uint32_t lastMillisWatchDog = millis();
uint32_t rxId;
uint8_t len;
uint8_t rxBuf[8];
uint16_t measuredMasterVoltage;
if(CAN_MSGAVAIL == CAN1.checkReceive()) // Check if data is coming
{
CAN1.readMsgBuf(&rxId, &len, rxBuf); // Read data, rxId: message ID, ext: flag extended ID, len: data length, rxBuf: data buf
rxId &= 0x1FFFFFFF; // Remove bits outside of the 29-bits ID
lastMillisWatchDog = millis(); // Reset WatchDog
newData |= statusFPMaster;
measuredMasterVoltage = (rxBuf[4] << 8) + rxBuf[3]; // The voltage measured at the output in centiVolt
measuredMasterCurrent = (rxBuf[2] << 8) + rxBuf[1]; // The current measured at the output in deciAmpere
(rxId == 0x05014008) ? chargerState |= chargerStateCurrentLimiting : chargerState &= ~chargerStateCurrentLimiting; // Check and set charger state current limiting
}
if((newData & statusFPMaster) && (millis() - lastMillisWatchDog >= timePeriod5s) && (state != END))
{
lastMillisWatchDog = millis(); // Reset WatchDog
newData &= ~statusFPMaster;
state = STOP; // Abort charging
stopReason = stopWatchdog;
//state = START;
}
return measuredMasterVoltage;
}
/********************************************************************************************************
* Checks FlatPack Slave 1 CAN-bus buffer and if new data is available stores it in appropriate variable *
********************************************************************************************************/
uint16_t readSlave1CAN()
{
uint32_t rxId;
uint8_t len;
uint8_t rxBuf[8];
uint16_t measuredSlave1Voltage;
if(CAN_MSGAVAIL == CAN2.checkReceive()) // check if data is coming
{
CAN2.readMsgBuf(&rxId, &len, rxBuf); // read data, rxId: message ID, ext: flag extended ID, len: data length, rxBuf: data buf
newData |= statusFPSlave1;
measuredSlave1Voltage = (rxBuf[4] << 8) + rxBuf[3]; // The voltage measured at the output in centiVolt
}
return measuredSlave1Voltage;
}
/********************************************************************************************************
* Checks FlatPack Slave 2 CAN-bus buffer and if new data is available stores it in appropriate variable *
********************************************************************************************************/
uint16_t readSlave2CAN()
{
uint32_t rxId;
uint8_t len;
uint8_t rxBuf[8];
uint16_t measuredSlave2Voltage;
if(CAN_MSGAVAIL == CAN3.checkReceive()) // check if data is coming
{
CAN3.readMsgBuf(&rxId, &len, rxBuf); // read data, rxId: message ID, ext: flag extended ID, len: data length, rxBuf: data buf
newData |= statusFPSlave2;
measuredSlave2Voltage = (rxBuf[4] << 8) + rxBuf[3]; // The voltage measured at the output in centiVolt
}
return measuredSlave2Voltage;
}
/******************************************
* Sends reset capacity to BMS via CAN-bus *
******************************************/
void sendCAN()
{
uint8_t reset[1] = {'Z'};
CAN0.sendMsgBuf(0x170, 0, 1, reset);
}
/***********************************************************
* Sends setVoltage and setCurrent to FlatPacks via CAN-bus *
***********************************************************/
void sendFlatPackCAN(uint16_t setMasterVoltage, uint16_t setSlaveVoltage, uint16_t setCurrent)
{
sendMasterCAN(setMasterVoltage, setCurrent);
sendSlave1CAN(setSlaveVoltage);
sendSlave2CAN(setSlaveVoltage);
}
/*****************************************************************
* Sends setVoltage and setCurrent to FlatPack Master via CAN-bus *
*****************************************************************/
void sendMasterCAN(uint16_t setVoltage, uint16_t setCurrent)
{
uint8_t login[8] = {0x14, 0x43, 0x71, 0x03, 0x97, 0x57, 0x00, 0x00}; //this is the serial number of the flatpack followed by two 00 bytes
CAN1.sendMsgBuf(0x05004804, 1, 8, login); //send message to log in
uint8_t config[8] = {lowByte(setCurrent), highByte(setCurrent), lowByte(setVoltage), highByte(setVoltage), lowByte(setVoltage), highByte(setVoltage), lowByte(overVoltage), highByte(overVoltage)}; // set rectifiers maxCurrent, outputVoltage and OVP
CAN1.sendMsgBuf(0x05FF4004, 1, 8, config); // short walk-in 4004, for long walk-in set 4005
//Serial.print(setVoltage);
//Serial.print(" ");
//Serial.println(setCurrent);
}
/***************************************************
* Sends setVoltage to FlatPack Slave 1 via CAN-bus *
***************************************************/
void sendSlave1CAN(uint16_t setVoltage)
{
uint8_t login[8] = {0x15, 0x10, 0x72, 0x00, 0x77, 0x78, 0x00, 0x00}; //this is the serial number of the flatpack followed by two 00 bytes
CAN2.sendMsgBuf(0x05004804, 1, 8, login); //send message to log in
uint8_t config[8] = {lowByte(maxCurrent), highByte(maxCurrent), lowByte(setVoltage), highByte(setVoltage), lowByte(setVoltage), highByte(setVoltage), lowByte(overVoltage), highByte(overVoltage)}; // set rectifiers maxCurrent, outputVoltage and OVP
CAN2.sendMsgBuf(0x05FF4004, 1, 8, config); // short walk-in 4004, for long walk-in set 4005
//Serial.println(setVoltage);
}
/***************************************************
* Sends setVoltage to FlatPack Slave 2 via CAN-bus *
***************************************************/
void sendSlave2CAN(uint16_t setVoltage)
{
uint8_t login[8] = {0x14, 0x22, 0x71, 0x14, 0x90, 0x69, 0x00, 0x00}; //this is the serial number of the flatpack followed by two 00 bytes
CAN3.sendMsgBuf(0x05004804, 1, 8, login); //send message to log in
uint8_t config[8] = {lowByte(maxCurrent), highByte(maxCurrent), lowByte(setVoltage), highByte(setVoltage), lowByte(setVoltage), highByte(setVoltage), lowByte(overVoltage), highByte(overVoltage)}; // set rectifiers maxCurrent, outputVoltage and OVP
CAN3.sendMsgBuf(0x05FF4004, 1, 8, config); // short walk-in 4004, for long walk-in set 4005
//Serial.println(setVoltage);
}
/***************
* Initiate LCD *
***************/
void initDisplay()
{
Wire.beginTransmission(address);
Wire.write(commands);
Wire.write(0xA0); // No flip
Wire.write(0xAF); // Display on
Wire.write(0x40); // Display start at bottom line
Wire.write(0x81); // Set contrast
Wire.write(0x00);
Wire.endTransmission();
}
/****************
* Clear the LCD *
****************/
void clearDisplay()
{
for (int p = 0 ; p < 8; p++)
{
Wire.beginTransmission(address);
Wire.write(commands);
Wire.write(0xB0 + p); // Page
Wire.write(0x02); // Column low nibble
Wire.write(0x10); // Column high nibble
Wire.endTransmission();
for (int q = 0 ; q < 5; q++)
{
Wire.beginTransmission(address);
Wire.write(data);
for (int i = 0 ; i < 26; i++)
{
Wire.write(0x00);
}
Wire.endTransmission();
}
}
}
/*****************************************************************
* Plot an ASCII character with bottom left corner at x and row y *
*****************************************************************/
void plotChar (uint8_t c, uint8_t x, uint8_t y)
{
if(x > 20 || y > 7) return;
x = (x * 6) + 1;
Wire.beginTransmission(address);
Wire.write(commands);
Wire.write(0xB0 + y); // Page
Wire.write(0x00 + ((x+2) & 0x0F)); // Column low nibble
Wire.write(0x10 + ((x+2)>>4)); // Column high nibble
Wire.endTransmission();
Wire.beginTransmission(address);
Wire.write(data);
for (int col=0; col<6; col++)
{
uint8_t bits = pgm_read_byte(&CharMap[c-32][col]);
Wire.write(bits);
}
Wire.endTransmission();
}
/****************************************************
* Plot text starting with bottom left corner at x,y *
****************************************************/
void plotText(String textToPlot, uint8_t x, uint8_t y)
{
int8_t lengthOfString = textToPlot.length();
int8_t characters[lengthOfString + 1];
textToPlot.toCharArray(characters, (lengthOfString + 1));
for (int i=0; i<lengthOfString; i++)
{
plotChar(characters[i], x, y);
x++;
}
}
/*******************************************************
* Plot a value starting with bottom left corner at x,y *
*******************************************************/
void plotValue(uint16_t valueToPlot, float division, uint8_t decimal, uint8_t x, uint8_t y)
{
String textToPlot = String(valueToPlot / division, decimal);
plotText(textToPlot, x, y);
}
/**************************************************************
* Reads button and moves marker or selects max charge current *
**************************************************************/
uint16_t selectCurrent()
{
static uint32_t lastMillisButton = millis();
static uint8_t buttonPressed = 0;
static uint8_t row = 4;
uint16_t selectedMaxCurrent = 0;
if(digitalRead(buttonPIN) == HIGH) // Button not pressed
{
lastMillisButton = millis(); // reset timer
buttonPressed &= B110;
}
else if((millis() - lastMillisButton) > timePeriod100ms) // Button pressed for 100ms
{
buttonPressed |= B011;
}
if((buttonPressed == B011) && (millis() - lastMillisButton > timePeriod3s)) // Button pressed for 3s -> select current and stop further marker movment
{
buttonPressed |= B100;
plotChar('-', 1, row);
if(row == 4) selectedMaxCurrent = maxCurrent1x10AAC;
else if (row == 5) selectedMaxCurrent = maxCurrent1x16AAC;
else if (row == 6) selectedMaxCurrent = maxCurrent3x16AAC;
}
if(buttonPressed == B010) // Button released, before timeout, after having been depressed -> move marker
{
plotChar(' ', 0, row);
row = (row>=6) ? 4 : row + 1;
plotChar('*', 0, row);
buttonPressed &= B101;
}
return selectedMaxCurrent;
}
/******************************************
* Stops charging if button is held for 3s *
******************************************/
void buttonStopCharging()
{
static uint32_t lastMillisButton = millis();
if(digitalRead(buttonPIN) == HIGH) // Button not pressed
{
lastMillisButton = millis(); // reset timer
}
else if((millis() - lastMillisButton) > timePeriod3s) // Button pressed for 3s
{
state = STOP;
stopReason = stopButton;
}
}