mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-04 10:19:29 +02:00
Sbox
This commit is contained in:
parent
6ad5c41b04
commit
bcc69647fe
4 changed files with 137 additions and 93 deletions
|
@ -3,24 +3,22 @@
|
||||||
#include "../datalayer/datalayer.h"
|
#include "../datalayer/datalayer.h"
|
||||||
#include "BMW-SBOX.h"
|
#include "BMW-SBOX.h"
|
||||||
|
|
||||||
|
|
||||||
#define MAX_ALLOWED_FAULT_TICKS 1000
|
#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
|
enum SboxState { DISCONNECTED, PRECHARGE, NEGATIVE, POSITIVE, PRECHARGE_OFF, COMPLETED, SHUTDOWN_REQUESTED };
|
||||||
#define NEGATIVE_CONTACTOR_TIME_MS 1000 // Precharge time before precharge resistor is bypassed by positive contactor
|
SboxState contactorStatus = DISCONNECTED;
|
||||||
#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 prechargeStartTime = 0;
|
||||||
unsigned long negativeStartTime = 0;
|
unsigned long negativeStartTime = 0;
|
||||||
unsigned long positiveStartTime = 0;
|
unsigned long positiveStartTime = 0;
|
||||||
unsigned long timeSpentInFaultedMode = 0;
|
unsigned long timeSpentInFaultedMode = 0;
|
||||||
unsigned long LastMsgTime = 0; // will store last time a 20ms CAN Message was send
|
unsigned long LastMsgTime = 0; // will store last time a 20ms CAN Message was send
|
||||||
|
unsigned long LastAvgTime = 0; // Last current storage time
|
||||||
|
|
||||||
|
uint32_t avg_mA_array[10];
|
||||||
|
uint32_t avg_sum;
|
||||||
|
|
||||||
|
uint8_t k; //avg array pointer
|
||||||
|
|
||||||
uint8_t CAN100_cnt=0;
|
uint8_t CAN100_cnt=0;
|
||||||
|
|
||||||
|
@ -46,6 +44,8 @@ uint8_t reverse_bits(uint8_t byte) {
|
||||||
return reversed;
|
return reversed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** CRC8, both inverted, poly 0x31 **/
|
||||||
uint8_t calculateCRC(CAN_frame CAN) {
|
uint8_t calculateCRC(CAN_frame CAN) {
|
||||||
uint8_t crc = 0;
|
uint8_t crc = 0;
|
||||||
for (size_t i = 0; i < CAN.DLC; i++) {
|
for (size_t i = 0; i < CAN.DLC; i++) {
|
||||||
|
@ -65,100 +65,131 @@ uint8_t calculateCRC(CAN_frame CAN) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void receive_can_shunt(CAN_frame rx_frame) {
|
void receive_can_shunt(CAN_frame rx_frame) {
|
||||||
|
unsigned long currentTime = millis();
|
||||||
if(rx_frame.ID ==0x200)
|
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;
|
datalayer.shunt.measured_amperage_mA=((rx_frame.data.u8[2]<<24)| (rx_frame.data.u8[1]<<16)| (rx_frame.data.u8[0]<<8))/256;
|
||||||
|
|
||||||
|
/** Calculate 1S avg current **/
|
||||||
|
if(LastAvgTime+100<currentTime)
|
||||||
|
{
|
||||||
|
LastAvgTime=currentTime;
|
||||||
|
if(k>9) {
|
||||||
|
k=0;
|
||||||
|
}
|
||||||
|
avg_mA_array[k]=datalayer.shunt.measured_amperage_mA;
|
||||||
|
k++;
|
||||||
|
avg_sum=0;
|
||||||
|
for (uint8_t i = 0; i < 10; i++)
|
||||||
|
{
|
||||||
|
avg_sum=avg_sum+avg_mA_array[i];
|
||||||
|
}
|
||||||
|
datalayer.shunt.measured_avg1S_amperage_mA=avg_sum/10;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if(rx_frame.ID ==0x210) //Battery voltage
|
else if(rx_frame.ID ==0x210) //SBOX input (battery side) 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;
|
datalayer.shunt.measured_voltage_mV=((rx_frame.data.u8[2]<<16)| (rx_frame.data.u8[1]<<8)| (rx_frame.data.u8[0]));
|
||||||
}
|
}
|
||||||
else if(rx_frame.ID ==0x220) //SBOX output voltage
|
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;
|
datalayer.shunt.measured_outvoltage_mV=((rx_frame.data.u8[2]<<16)| (rx_frame.data.u8[1]<<8)| (rx_frame.data.u8[0]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void send_can_shunt() {
|
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 && ( datalayer.shunt.measured_voltage_mV => MINIMUM_INPUT_VOLTAGE*1000)) {
|
|
||||||
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();
|
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;
|
|
||||||
datalayer.shunt.precharging = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case POSITIVE:
|
|
||||||
if (currentTime - negativeStartTime >= NEGATIVE_CONTACTOR_TIME_MS && (datalayer.shunt.measured_voltage_mV * MAX_PRECHARGE_RESISTOR_VOLTAGE_PERCENT < datalalyer.shunt.measured_outvoltage_mV)) {
|
|
||||||
SBOX_100.data.u8[0]=0xAA; // Precharge + Negative + Positive
|
|
||||||
positiveStartTime = currentTime;
|
|
||||||
contactorStatus = PRECHARGE_OFF;
|
|
||||||
datalayer.shunt.precharging = false;
|
|
||||||
}
|
|
||||||
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
|
// Send 20ms CAN Message
|
||||||
if (currentTime - LastMsgTime >= INTERVAL_20_MS) {
|
if (currentTime - LastMsgTime >= INTERVAL_20_MS) {
|
||||||
LastMsgTime = currentTime;
|
LastMsgTime = currentTime;
|
||||||
|
// 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 contactors
|
||||||
|
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 && ( datalayer.shunt.measured_voltage_mV > MINIMUM_INPUT_VOLTAGE*1000)) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
#ifdef DEBUG_VIA_USB
|
||||||
|
Serial.println("S-BOX Precharge relay engaged");
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NEGATIVE:
|
||||||
|
if (currentTime - prechargeStartTime >= CONTACTOR_CONTROL_T1) {
|
||||||
|
SBOX_100.data.u8[0]=0xA6; // Precharge + Negative
|
||||||
|
negativeStartTime = currentTime;
|
||||||
|
contactorStatus = POSITIVE;
|
||||||
|
datalayer.shunt.precharging = true;
|
||||||
|
#ifdef DEBUG_VIA_USB
|
||||||
|
Serial.println("S-BOX Negative relay engaged");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case POSITIVE:
|
||||||
|
if (currentTime - negativeStartTime >= CONTACTOR_CONTROL_T2 && (datalayer.shunt.measured_voltage_mV * MAX_PRECHARGE_RESISTOR_VOLTAGE_PERCENT < datalayer.shunt.measured_outvoltage_mV)) {
|
||||||
|
SBOX_100.data.u8[0]=0xAA; // Precharge + Negative + Positive
|
||||||
|
positiveStartTime = currentTime;
|
||||||
|
contactorStatus = PRECHARGE_OFF;
|
||||||
|
datalayer.shunt.precharging = false;
|
||||||
|
#ifdef DEBUG_VIA_USB
|
||||||
|
Serial.println("S-BOX Positive relay engaged");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PRECHARGE_OFF:
|
||||||
|
if (currentTime - positiveStartTime >= CONTACTOR_CONTROL_T3) {
|
||||||
|
SBOX_100.data.u8[0]=0x6A; // Negative + Positive
|
||||||
|
contactorStatus = COMPLETED;
|
||||||
|
#ifdef DEBUG_VIA_USB
|
||||||
|
Serial.println("S-BOX Precharge relay released");
|
||||||
|
#endif
|
||||||
|
datalayer.shunt.contactors_engaged = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case COMPLETED:
|
||||||
|
SBOX_100.data.u8[0]=0x6A; // Negative + Positive
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
CAN100_cnt++;
|
CAN100_cnt++;
|
||||||
if (CAN100_cnt>0x0E) {
|
if (CAN100_cnt>0x0E) {
|
||||||
CAN100_cnt=0;
|
CAN100_cnt=0;
|
||||||
|
@ -172,6 +203,7 @@ void send_can_shunt() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void setup_can_shunt() {
|
void setup_can_shunt() {
|
||||||
datalayer.system.info.shunt_protocol[63] = 'BMW SBOX\0';
|
strncpy(datalayer.system.info.shunt_protocol, "BMW SBOX", 63);
|
||||||
|
datalayer.system.info.shunt_protocol[63] = '\0';
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -7,8 +7,16 @@ void transmit_can(CAN_frame* tx_frame, int interface);
|
||||||
/** Minimum input voltage required to enable relay control **/
|
/** Minimum input voltage required to enable relay control **/
|
||||||
#define MINIMUM_INPUT_VOLTAGE 250
|
#define MINIMUM_INPUT_VOLTAGE 250
|
||||||
|
|
||||||
/** Maximum allowable percentage of input voltage across the precharge resistor to close the positive relay **/
|
/** Maximum allowable percentage of input voltage across the precharge resistor to engage the positive relay. **/
|
||||||
#define MAX_PRECHARGE_RESISTOR_VOLTAGE_PERCENT 96
|
/** SAFETY FEATURE: If precharge resistor is faulty, positive contactor will not be engaged **/
|
||||||
|
#define MAX_PRECHARGE_RESISTOR_VOLTAGE_PERCENT 0.99
|
||||||
|
|
||||||
|
/* NOTE: modify the T2 time constant below to account for the resistance and capacitance of the target system.
|
||||||
|
* t=3RC at minimum, t=5RC ideally
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define CONTACTOR_CONTROL_T1 5000 // Time before negative contactor engages and precharging starts
|
||||||
|
#define CONTACTOR_CONTROL_T2 5000 // Precharge time before precharge resistor is bypassed by positive contactor
|
||||||
|
#define CONTACTOR_CONTROL_T3 2000 // Precharge relay lead time after positive contactor has been engaged
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -129,7 +129,11 @@ typedef struct {
|
||||||
/** measured output voltage in mV (eg. S-BOX) **/
|
/** measured output voltage in mV (eg. S-BOX) **/
|
||||||
uint32_t measured_outvoltage_mV = 0;
|
uint32_t measured_outvoltage_mV = 0;
|
||||||
/** measured amperage in mA (eg. S-BOX) **/
|
/** measured amperage in mA (eg. S-BOX) **/
|
||||||
uint32_t measured_amperage_mA = 0;
|
int32_t measured_amperage_mA = 0;
|
||||||
|
/** Sum of current readings during measuring period **/
|
||||||
|
int32_t measured_avg1S_amperage_mA = 0;
|
||||||
|
/** Number of samples **/
|
||||||
|
uint16_t measured_sum_amperage_count = 0;
|
||||||
/** True if contactors are precharging state */
|
/** True if contactors are precharging state */
|
||||||
bool precharging = false;
|
bool precharging = false;
|
||||||
/** True if the contactor controlled by battery-emulator is closed */
|
/** True if the contactor controlled by battery-emulator is closed */
|
||||||
|
|
|
@ -42,7 +42,7 @@ String settings_processor(const String& var) {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef CAN_SHUNT_SELECTED
|
#ifdef CAN_SHUNT_SELECTED
|
||||||
content += "<h4 style='color: white;'>Shunt Interface: <span id='BMW S-BOX'>" +
|
content += "<h4 style='color: white;'>Shunt Interface: <span id='Shunt'>" +
|
||||||
String(getCANInterfaceName(can_config.shunt)) + "</span></h4>";
|
String(getCANInterfaceName(can_config.shunt)) + "</span></h4>";
|
||||||
#endif //CAN_SHUNT_SELECTED
|
#endif //CAN_SHUNT_SELECTED
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue