mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-05 10:49:42 +02:00
BMW S-BOX Shunt/Contactor support
This commit is contained in:
parent
a467575a57
commit
da8c7198c2
9 changed files with 230 additions and 1 deletions
|
@ -1,3 +1,4 @@
|
|||
|
||||
/* Do not change any code below this line unless you are sure what you are doing */
|
||||
/* Only change battery specific settings in "USER_SETTINGS.h" */
|
||||
|
||||
|
@ -198,6 +199,9 @@ void setup() {
|
|||
setup_battery();
|
||||
#ifdef EQUIPMENT_STOP_BUTTON
|
||||
init_equipment_stop_button();
|
||||
#endif
|
||||
#ifdef CAN_SHUNT_SELECTED
|
||||
setup_can_shunt();
|
||||
#endif
|
||||
// BOOT button at runtime is used as an input for various things
|
||||
pinMode(0, INPUT_PULLUP);
|
||||
|
@ -730,6 +734,10 @@ void send_can() {
|
|||
#ifdef CHARGER_SELECTED
|
||||
send_can_charger();
|
||||
#endif // CHARGER_SELECTED
|
||||
|
||||
#ifdef CAN_SHUNT_SELECTED
|
||||
send_can_shunt();
|
||||
#endif // CAN_SHUNT_SELECTED
|
||||
}
|
||||
|
||||
#ifdef DUAL_CAN
|
||||
|
@ -1008,6 +1016,7 @@ void update_values_inverter() {
|
|||
#ifdef MODBUS_INVERTER_SELECTED
|
||||
update_modbus_registers_inverter();
|
||||
#endif
|
||||
|
||||
#ifdef RS485_INVERTER_SELECTED
|
||||
update_RS485_registers_inverter();
|
||||
#endif
|
||||
|
@ -1221,6 +1230,11 @@ void receive_can(CAN_frame* rx_frame, int interface) {
|
|||
if (interface == can_config.charger) {
|
||||
#ifdef CHARGER_SELECTED
|
||||
receive_can_charger(*rx_frame);
|
||||
#endif
|
||||
}
|
||||
if (interface == can_config.shunt) {
|
||||
#ifdef CAN_SHUNT_SELECTED
|
||||
receive_can_shunt(*rx_frame);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,8 @@ volatile CAN_Configuration can_config = {
|
|||
.battery = CAN_NATIVE, // Which CAN is your battery connected to?
|
||||
.inverter = CAN_NATIVE, // Which CAN is your inverter connected to? (No need to configure incase you use RS485)
|
||||
.battery_double = CAN_ADDON_MCP2515, // (OPTIONAL) Which CAN is your second battery connected to?
|
||||
.charger = CAN_NATIVE // (OPTIONAL) Which CAN is your charger connected to?
|
||||
.charger = CAN_NATIVE, // (OPTIONAL) Which CAN is your charger connected to?
|
||||
.shunt = CAN_NATIVE // (OPTIONAL) Which CAN is your charger connected to?
|
||||
};
|
||||
|
||||
#ifdef WIFI
|
||||
|
|
|
@ -63,6 +63,9 @@
|
|||
//#define PWM_CONTACTOR_CONTROL //Enable this line to use PWM for CONTACTOR_CONTROL, which lowers power consumption and heat generation. CONTACTOR_CONTROL must be enabled.
|
||||
//#define NC_CONTACTORS //Enable this line to control normally closed contactors. CONTACTOR_CONTROL must be enabled for this option. Extremely rare setting!
|
||||
|
||||
/* Shunt/Contactor settings */
|
||||
//#define BMW_SBOX // SBOX relay control & battery current/voltage measurement
|
||||
|
||||
/* Other options */
|
||||
//#define DEBUG_VIA_USB //Enable this line to have the USB port output serial diagnostic data while program runs (WARNING, raises CPU load, do not use for production)
|
||||
//#define DEBUG_CAN_DATA //Enable this line to print incoming/outgoing CAN & CAN-FD messages to USB serial (WARNING, raises CPU load, do not use for production)
|
||||
|
@ -137,6 +140,7 @@ typedef struct {
|
|||
CAN_Interface inverter;
|
||||
CAN_Interface battery_double;
|
||||
CAN_Interface charger;
|
||||
CAN_Interface shunt;
|
||||
} CAN_Configuration;
|
||||
extern volatile CAN_Configuration can_config;
|
||||
extern volatile uint8_t AccessPointEnabled;
|
||||
|
|
|
@ -2,6 +2,13 @@
|
|||
#define BATTERIES_H
|
||||
#include "../../USER_SETTINGS.h"
|
||||
|
||||
#ifdef BMW_SBOX
|
||||
#include "BMW-SBOX.h"
|
||||
void receive_can_shunt(CAN_frame rx_frame);
|
||||
void send_can_shunt();
|
||||
void setup_can_shunt();
|
||||
#endif
|
||||
|
||||
#ifdef BMW_I3_BATTERY
|
||||
#include "BMW-I3-BATTERY.h"
|
||||
#endif
|
||||
|
|
175
Software/src/battery/BMW-SBOX.cpp
Normal file
175
Software/src/battery/BMW-SBOX.cpp
Normal file
|
@ -0,0 +1,175 @@
|
|||
#include "../include.h"
|
||||
#ifdef BMW_SBOX
|
||||
#include "../datalayer/datalayer.h"
|
||||
#include "BMW-SBOX.h"
|
||||
|
||||
|
||||
#define MAX_ALLOWED_FAULT_TICKS 1000
|
||||
/* NOTE: modify the precharge time constant below to account for the resistance and capacitance of the target system.
|
||||
* t=3RC at minimum, t=5RC ideally
|
||||
*/
|
||||
|
||||
#define PRECHARGE_TIME_MS 160 // Time before negative contactor engages and precharging starts
|
||||
#define NEGATIVE_CONTACTOR_TIME_MS 1000 // Precharge time before precharge resistor is bypassed by positive contactor
|
||||
#define POSITIVE_CONTACTOR_TIME_MS 2000 // Precharge relay lead time after positive contactor has been engaged
|
||||
|
||||
enum State { DISCONNECTED, PRECHARGE, NEGATIVE, POSITIVE, PRECHARGE_OFF, COMPLETED, SHUTDOWN_REQUESTED };
|
||||
State contactorStatus = DISCONNECTED;
|
||||
|
||||
unsigned long prechargeStartTime = 0;
|
||||
unsigned long negativeStartTime = 0;
|
||||
unsigned long positiveStartTime = 0;
|
||||
unsigned long timeSpentInFaultedMode = 0;
|
||||
unsigned long LastMsgTime = 0; // will store last time a 20ms CAN Message was send
|
||||
|
||||
uint8_t CAN100_cnt=0;
|
||||
|
||||
CAN_frame SBOX_100 = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 4,
|
||||
.ID = 0x100,
|
||||
.data = {0x55, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00}}; // Byte 0: relay control, Byte 1: counter 0-E, Byte 4: CRC
|
||||
|
||||
CAN_frame SBOX_300 = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 4,
|
||||
.ID = 0x300,
|
||||
.data = {0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00}}; // Static frame
|
||||
|
||||
|
||||
uint8_t reverse_bits(uint8_t byte) {
|
||||
uint8_t reversed = 0;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
reversed = (reversed << 1) | (byte & 1);
|
||||
byte >>= 1;
|
||||
}
|
||||
return reversed;
|
||||
}
|
||||
|
||||
uint8_t calculateCRC(CAN_frame CAN) {
|
||||
uint8_t crc = 0;
|
||||
for (size_t i = 0; i < CAN.DLC; i++) {
|
||||
uint8_t reversed_byte = reverse_bits(CAN.data.u8[i]);
|
||||
crc ^= reversed_byte;
|
||||
for (int j = 0; j < 8; j++) {
|
||||
if (crc & 0x80) {
|
||||
crc = (crc << 1) ^ 0x31;
|
||||
} else {
|
||||
crc <<= 1;
|
||||
}
|
||||
crc &= 0xFF;
|
||||
}
|
||||
}
|
||||
crc=reverse_bits(crc);
|
||||
return crc;
|
||||
}
|
||||
|
||||
void receive_can_shunt(CAN_frame rx_frame) {
|
||||
if(rx_frame.ID ==0x200)
|
||||
{
|
||||
datalayer.shunt.measured_amperage_mA=((rx_frame.data.u8[2]<<24)| (rx_frame.data.u8[1]<<16)| (rx_frame.data.u8[0]<<8))/256;
|
||||
}
|
||||
else if(rx_frame.ID ==0x210) //Battery voltage
|
||||
{
|
||||
datalayer.shunt.measured_voltage_mV==((rx_frame.data.u8[2]<<24)| (rx_frame.data.u8[1]<<16)| (rx_frame.data.u8[0]<<8))/256;
|
||||
}
|
||||
else if(rx_frame.ID ==0x220) //SBOX output voltage
|
||||
{
|
||||
datalayer.shunt.measured_outvoltage_mV==((rx_frame.data.u8[2]<<24)| (rx_frame.data.u8[1]<<16)| (rx_frame.data.u8[0]<<8))/256;
|
||||
}
|
||||
}
|
||||
|
||||
void send_can_shunt() {
|
||||
// First check if we have any active errors, incase we do, turn off the battery
|
||||
if (datalayer.battery.status.bms_status == FAULT) {
|
||||
timeSpentInFaultedMode++;
|
||||
} else {
|
||||
timeSpentInFaultedMode = 0;
|
||||
}
|
||||
|
||||
//handle contactor control SHUTDOWN_REQUESTED
|
||||
if (timeSpentInFaultedMode > MAX_ALLOWED_FAULT_TICKS) {
|
||||
contactorStatus = SHUTDOWN_REQUESTED;
|
||||
SBOX_100.data.u8[0]=0x55; // All open
|
||||
}
|
||||
|
||||
if (contactorStatus == SHUTDOWN_REQUESTED) {
|
||||
datalayer.shunt.contactors_engaged = false;
|
||||
return; // A fault scenario latches the contactor control. It is not possible to recover without a powercycle (and investigation why fault occured)
|
||||
}
|
||||
|
||||
// After that, check if we are OK to start turning on the battery
|
||||
if (contactorStatus == DISCONNECTED) {
|
||||
datalayer.shunt.contactors_engaged = false;
|
||||
SBOX_100.data.u8[0]=0x55; // All open
|
||||
if (datalayer.system.status.battery_allows_contactor_closing &&
|
||||
datalayer.system.status.inverter_allows_contactor_closing && !datalayer.system.settings.equipment_stop_active) {
|
||||
contactorStatus = PRECHARGE;
|
||||
}
|
||||
}
|
||||
|
||||
// In case the inverter requests contactors to open, set the state accordingly
|
||||
if (contactorStatus == COMPLETED) {
|
||||
//Incase inverter (or estop) requests contactors to open, make state machine jump to Disconnected state (recoverable)
|
||||
if (!datalayer.system.status.inverter_allows_contactor_closing || datalayer.system.settings.equipment_stop_active) {
|
||||
contactorStatus = DISCONNECTED;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned long currentTime = millis();
|
||||
// Handle actual state machine. This first turns on Precharge, then Negative, then Positive, and finally turns OFF precharge
|
||||
switch (contactorStatus) {
|
||||
case PRECHARGE:
|
||||
SBOX_100.data.u8[0]=0x86; // Precharge relay only
|
||||
prechargeStartTime = currentTime;
|
||||
contactorStatus = NEGATIVE;
|
||||
break;
|
||||
|
||||
case NEGATIVE:
|
||||
if (currentTime - prechargeStartTime >= PRECHARGE_TIME_MS) {
|
||||
SBOX_100.data.u8[0]=0xA6; // Precharge + Negative
|
||||
negativeStartTime = currentTime;
|
||||
contactorStatus = POSITIVE;
|
||||
}
|
||||
break;
|
||||
|
||||
case POSITIVE:
|
||||
if (currentTime - negativeStartTime >= NEGATIVE_CONTACTOR_TIME_MS) {
|
||||
SBOX_100.data.u8[0]=0xAA; // Precharge + Negative + Positive
|
||||
positiveStartTime = currentTime;
|
||||
contactorStatus = PRECHARGE_OFF;
|
||||
}
|
||||
break;
|
||||
|
||||
case PRECHARGE_OFF:
|
||||
if (currentTime - positiveStartTime >= POSITIVE_CONTACTOR_TIME_MS) {
|
||||
SBOX_100.data.u8[0]=0x6A; // Negative + Positive
|
||||
contactorStatus = COMPLETED;
|
||||
datalayer.shunt.contactors_engaged = true;
|
||||
}
|
||||
break;
|
||||
case COMPLETED:
|
||||
SBOX_100.data.u8[0]=0x6A; // Negative + Positive
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Send 20ms CAN Message
|
||||
if (currentTime - LastMsgTime >= INTERVAL_20_MS) {
|
||||
LastMsgTime = currentTime;
|
||||
CAN100_cnt++;
|
||||
if (CAN100_cnt>0x0E) {
|
||||
CAN100_cnt=0;
|
||||
}
|
||||
SBOX_100.data.u8[1]=CAN100_cnt<<4|0x01;
|
||||
SBOX_100.data.u8[3]=0x00;
|
||||
SBOX_100.data.u8[3]=calculateCRC(SBOX_100);
|
||||
transmit_can(&SBOX_100, can_config.shunt);
|
||||
transmit_can(&SBOX_300, can_config.shunt);
|
||||
}
|
||||
}
|
||||
|
||||
void setup_can_shunt() {
|
||||
datalayer.system.info.shunt_protocol[63] = 'BMW SBOX\0';
|
||||
}
|
||||
#endif
|
7
Software/src/battery/BMW-SBOX.h
Normal file
7
Software/src/battery/BMW-SBOX.h
Normal file
|
@ -0,0 +1,7 @@
|
|||
#ifndef BMW_SBOX_CONTROL_H
|
||||
#define BMW_SBOX_CONTROL_H
|
||||
#include "../include.h"
|
||||
#define CAN_SHUNT_SELECTED
|
||||
void transmit_can(CAN_frame* tx_frame, int interface);
|
||||
|
||||
#endif
|
|
@ -124,6 +124,14 @@ typedef struct {
|
|||
uint16_t measured_voltage_dV = 0;
|
||||
/** measured amperage in deciAmperes. 300 = 30.0 A */
|
||||
uint16_t measured_amperage_dA = 0;
|
||||
/** measured battery voltage in mV (S-BOX) **/
|
||||
uint32_t measured_voltage_mV = 0;
|
||||
/** measured output voltage in mV (eg. S-BOX) **/
|
||||
uint32_t measured_outvoltage_mV = 0;
|
||||
/** measured amperage in mA (eg. S-BOX) **/
|
||||
uint32_t measured_amperage_mA = 0;
|
||||
/** True if the contactor controlled by battery-emulator is closed */
|
||||
bool contactors_engaged = false;
|
||||
} DATALAYER_SHUNT_TYPE;
|
||||
|
||||
typedef struct {
|
||||
|
@ -131,6 +139,8 @@ typedef struct {
|
|||
char battery_protocol[64] = {0};
|
||||
/** array with type of inverter used, for displaying on webserver */
|
||||
char inverter_protocol[64] = {0};
|
||||
/** array with type of battery used, for displaying on webserver */
|
||||
char shunt_protocol[64] = {0};
|
||||
/** array with incoming CAN messages, for displaying on webserver */
|
||||
char logged_can_messages[15000] = {0};
|
||||
size_t logged_can_messages_offset = 0;
|
||||
|
|
|
@ -41,6 +41,11 @@ String settings_processor(const String& var) {
|
|||
content += "<h4 style='color: white;'>Inverter interface: RS485<span id='Inverter'></span></h4>";
|
||||
#endif
|
||||
|
||||
#ifdef CAN_SHUNT_SELECTED
|
||||
content += "<h4 style='color: white;'>Shunt Interface: <span id='BMW S-BOX'>" +
|
||||
String(getCANInterfaceName(can_config.shunt)) + "</span></h4>";
|
||||
#endif //CAN_SHUNT_SELECTED
|
||||
|
||||
// Close the block
|
||||
content += "</div>";
|
||||
|
||||
|
|
|
@ -552,6 +552,12 @@ String processor(const String& var) {
|
|||
}
|
||||
content += "</h4>";
|
||||
|
||||
#ifdef CAN_SHUNT_SELECTED
|
||||
content += "<h4 style='color: white;'>Shunt protocol: ";
|
||||
content += datalayer.system.info.shunt_protocol;
|
||||
content += "</h4>";
|
||||
#endif
|
||||
|
||||
#if defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER
|
||||
content += "<h4 style='color: white;'>Charger protocol: ";
|
||||
#ifdef CHEVYVOLT_CHARGER
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue