mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-03 09:49:32 +02:00
Merge branch 'main' into feature/mqtt_library_replacement
This commit is contained in:
commit
0444b4b185
23 changed files with 660 additions and 97 deletions
|
@ -39,6 +39,11 @@ Here's how to connect the high voltage lines
|
||||||
For more examples showing wiring, see each battery types own Wiki page. For instance the [Nissan LEAF page](https://github.com/dalathegreat/Battery-Emulator/wiki/Battery:-Nissan-LEAF---e%E2%80%90NV200)
|
For more examples showing wiring, see each battery types own Wiki page. For instance the [Nissan LEAF page](https://github.com/dalathegreat/Battery-Emulator/wiki/Battery:-Nissan-LEAF---e%E2%80%90NV200)
|
||||||
|
|
||||||
## How to compile the software 💻
|
## How to compile the software 💻
|
||||||
|
|
||||||
|
Start by watching this [quickstart guide](https://www.youtube.com/watch?v=hcl2GdHc0Y0)
|
||||||
|
|
||||||
|
[](https://www.youtube.com/watch?v=hcl2GdHc0Y0)
|
||||||
|
|
||||||
1. Download the Arduino IDE: https://www.arduino.cc/en/software
|
1. Download the Arduino IDE: https://www.arduino.cc/en/software
|
||||||
2. Open the Arduino IDE.
|
2. Open the Arduino IDE.
|
||||||
3. Click `File` menu -> `Preferences` -> `Additional Development` -> `Additional Board Manager URLs` -> Enter the URL in the input box: `https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json` and click OK.
|
3. Click `File` menu -> `Preferences` -> `Additional Development` -> `Additional Board Manager URLs` -> Enter the URL in the input box: `https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json` and click OK.
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
#include "src/communication/contactorcontrol/comm_contactorcontrol.h"
|
#include "src/communication/contactorcontrol/comm_contactorcontrol.h"
|
||||||
#include "src/communication/equipmentstopbutton/comm_equipmentstopbutton.h"
|
#include "src/communication/equipmentstopbutton/comm_equipmentstopbutton.h"
|
||||||
#include "src/communication/nvm/comm_nvm.h"
|
#include "src/communication/nvm/comm_nvm.h"
|
||||||
|
#include "src/communication/precharge_control/precharge_control.h"
|
||||||
#include "src/communication/rs485/comm_rs485.h"
|
#include "src/communication/rs485/comm_rs485.h"
|
||||||
#include "src/communication/seriallink/comm_seriallink.h"
|
#include "src/communication/seriallink/comm_seriallink.h"
|
||||||
#include "src/datalayer/datalayer.h"
|
#include "src/datalayer/datalayer.h"
|
||||||
|
@ -120,6 +121,10 @@ void setup() {
|
||||||
|
|
||||||
init_contactors();
|
init_contactors();
|
||||||
|
|
||||||
|
#ifdef PRECHARGE_CONTROL
|
||||||
|
init_precharge_control();
|
||||||
|
#endif // PRECHARGE_CONTROL
|
||||||
|
|
||||||
init_rs485();
|
init_rs485();
|
||||||
|
|
||||||
init_serialDataLink();
|
init_serialDataLink();
|
||||||
|
@ -247,6 +252,9 @@ void core_loop(void* task_time_us) {
|
||||||
previousMillis10ms = millis();
|
previousMillis10ms = millis();
|
||||||
led_exe();
|
led_exe();
|
||||||
handle_contactors(); // Take care of startup precharge/contactor closing
|
handle_contactors(); // Take care of startup precharge/contactor closing
|
||||||
|
#ifdef PRECHARGE_CONTROL
|
||||||
|
handle_precharge_control();
|
||||||
|
#endif // PRECHARGE_CONTROL
|
||||||
}
|
}
|
||||||
END_TIME_MEASUREMENT_MAX(time_10ms, datalayer.system.status.time_10ms_us);
|
END_TIME_MEASUREMENT_MAX(time_10ms, datalayer.system.status.time_10ms_us);
|
||||||
|
|
||||||
|
@ -298,6 +306,9 @@ void core_loop(void* task_time_us) {
|
||||||
if (check_pause_2s.elapsed()) {
|
if (check_pause_2s.elapsed()) {
|
||||||
emulator_pause_state_transmit_can_battery();
|
emulator_pause_state_transmit_can_battery();
|
||||||
}
|
}
|
||||||
|
#ifdef DEBUG_LOG
|
||||||
|
logging.log_bms_status(datalayer.battery.status.real_bms_status, 1);
|
||||||
|
#endif
|
||||||
|
|
||||||
vTaskDelayUntil(&xLastWakeTime, xFrequency);
|
vTaskDelayUntil(&xLastWakeTime, xFrequency);
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,6 +76,9 @@
|
||||||
/* Shunt/Contactor settings */
|
/* Shunt/Contactor settings */
|
||||||
//#define BMW_SBOX // SBOX relay control & battery current/voltage measurement
|
//#define BMW_SBOX // SBOX relay control & battery current/voltage measurement
|
||||||
|
|
||||||
|
/* Automatic Precharge settings. If you have a battery that expects an external voltage applied before opening contactors (within the battery), configure this section */
|
||||||
|
//#define PRECHARGE_CONTROL //Enable this line to control a modified HIA4V1 (see wiki) by PWM on the PRECHARGE_PIN.
|
||||||
|
|
||||||
/* Other options */
|
/* Other options */
|
||||||
//#define LOG_TO_SD //Enable this line to log diagnostic data to SD card
|
//#define LOG_TO_SD //Enable this line to log diagnostic data to SD card
|
||||||
//#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_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)
|
||||||
|
|
|
@ -373,20 +373,15 @@ void update_values_battery2() { //This function maps all the values fetched via
|
||||||
|
|
||||||
datalayer.battery2.status.current_dA = battery2_current;
|
datalayer.battery2.status.current_dA = battery2_current;
|
||||||
|
|
||||||
datalayer.battery2.status.remaining_capacity_Wh = (battery2_energy_content_maximum_kWh * 1000); // Convert kWh to Wh
|
datalayer.battery2.info.total_capacity_Wh = (battery2_energy_content_maximum_kWh * 1000); // Convert kWh to Wh
|
||||||
|
|
||||||
|
datalayer.battery2.status.remaining_capacity_Wh = battery2_predicted_energy_charge_condition;
|
||||||
|
|
||||||
datalayer.battery2.status.soh_pptt = battery2_soh * 100;
|
datalayer.battery2.status.soh_pptt = battery2_soh * 100;
|
||||||
|
|
||||||
if (battery2_BEV_available_power_longterm_discharge > 65000) {
|
datalayer.battery2.status.max_discharge_power_W = battery2_BEV_available_power_longterm_discharge;
|
||||||
datalayer.battery2.status.max_discharge_power_W = 65000;
|
|
||||||
} else {
|
datalayer.battery2.status.max_charge_power_W = battery2_BEV_available_power_longterm_charge;
|
||||||
datalayer.battery2.status.max_discharge_power_W = battery2_BEV_available_power_longterm_discharge;
|
|
||||||
}
|
|
||||||
if (battery2_BEV_available_power_longterm_charge > 65000) {
|
|
||||||
datalayer.battery2.status.max_charge_power_W = 65000;
|
|
||||||
} else {
|
|
||||||
datalayer.battery2.status.max_charge_power_W = battery2_BEV_available_power_longterm_charge;
|
|
||||||
}
|
|
||||||
|
|
||||||
datalayer.battery2.status.temperature_min_dC = battery2_temperature_min * 10; // Add a decimal
|
datalayer.battery2.status.temperature_min_dC = battery2_temperature_min * 10; // Add a decimal
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
#include "../include.h"
|
#include "../include.h"
|
||||||
#ifdef MEB_BATTERY
|
#ifdef MEB_BATTERY
|
||||||
#include <algorithm> // For std::min and std::max
|
#include <algorithm> // For std::min and std::max
|
||||||
|
#include "../communication/can/comm_can.h"
|
||||||
|
#include "../communication/can/obd.h"
|
||||||
#include "../datalayer/datalayer.h"
|
#include "../datalayer/datalayer.h"
|
||||||
#include "../datalayer/datalayer_extended.h" //For "More battery info" webpage
|
#include "../datalayer/datalayer_extended.h" //For "More battery info" webpage
|
||||||
#include "../devboard/utils/events.h"
|
#include "../devboard/utils/events.h"
|
||||||
|
@ -77,7 +79,7 @@ static bool BMS_fault_performance = false; //Error: Battery performance is limi
|
||||||
static uint16_t BMS_current = 16300;
|
static uint16_t BMS_current = 16300;
|
||||||
static bool BMS_fault_emergency_shutdown_crash =
|
static bool BMS_fault_emergency_shutdown_crash =
|
||||||
false; //Error: Safety-critical error (crash detection) Battery contactors are already opened / will be opened immediately Signal is read directly by the EMS and initiates an AKS of the PWR and an active discharge of the DC link
|
false; //Error: Safety-critical error (crash detection) Battery contactors are already opened / will be opened immediately Signal is read directly by the EMS and initiates an AKS of the PWR and an active discharge of the DC link
|
||||||
static uint32_t BMS_voltage_intermediate = 0;
|
static uint32_t BMS_voltage_intermediate = 2000;
|
||||||
static uint32_t BMS_voltage = 1480;
|
static uint32_t BMS_voltage = 1480;
|
||||||
static uint8_t BMS_status_voltage_free =
|
static uint8_t BMS_status_voltage_free =
|
||||||
0; //0=Init, 1=BMS intermediate circuit voltage-free (U_Zwkr < 20V), 2=BMS intermediate circuit not voltage-free (U_Zwkr >/= 25V, hysteresis), 3=Error
|
0; //0=Init, 1=BMS intermediate circuit voltage-free (U_Zwkr < 20V), 2=BMS intermediate circuit not voltage-free (U_Zwkr >/= 25V, hysteresis), 3=Error
|
||||||
|
@ -360,6 +362,23 @@ CAN_frame MEB_14C = {
|
||||||
.ID = 0x14C, //CRC needed, static content otherwise
|
.ID = 0x14C, //CRC needed, static content otherwise
|
||||||
.data = {0x38, 0x0A, 0xFF, 0x01, 0x01, 0xFF, 0x01, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE,
|
.data = {0x38, 0x0A, 0xFF, 0x01, 0x01, 0xFF, 0x01, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE,
|
||||||
0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x25, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE}};
|
0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x25, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE}};
|
||||||
|
|
||||||
|
uint32_t can_msg_received = 0;
|
||||||
|
#define RX_0x17F0007B 0x0001
|
||||||
|
#define RX_0x12DD54D0 0x0002
|
||||||
|
#define RX_0x12DD54D1 0x0004
|
||||||
|
#define RX_0x12DD54D2 0x0008
|
||||||
|
#define RX_0x1A555550 0x0010
|
||||||
|
#define RX_0x1A555551 0x0020
|
||||||
|
#define RX_0x1A5555B2 0x0040
|
||||||
|
#define RX_0x16A954A6 0x0080
|
||||||
|
#define RX_0x1A5555B0 0x0100
|
||||||
|
#define RX_0x1A5555B1 0x0200
|
||||||
|
#define RX_0x5A2 0x0400
|
||||||
|
#define RX_0x5CA 0x0800
|
||||||
|
#define RX_0x0CF 0x1000
|
||||||
|
#define RX_DEFAULT 0xE000
|
||||||
|
|
||||||
/** Calculate the CRC checksum for VAG CAN Messages
|
/** Calculate the CRC checksum for VAG CAN Messages
|
||||||
*
|
*
|
||||||
* The method used is described in Chapter "7.2.1.2 8-bit 0x2F polynomial CRC Calculation".
|
* The method used is described in Chapter "7.2.1.2 8-bit 0x2F polynomial CRC Calculation".
|
||||||
|
@ -491,6 +510,7 @@ uint8_t vw_crc_calc(uint8_t* inputBytes, uint8_t length, uint16_t address) {
|
||||||
magicByte = MB16A954A6[counter];
|
magicByte = MB16A954A6[counter];
|
||||||
break;
|
break;
|
||||||
default: // this won't lead to correct CRC checksums
|
default: // this won't lead to correct CRC checksums
|
||||||
|
logging.println("Checksum request uknown");
|
||||||
magicByte = 0x00;
|
magicByte = 0x00;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -525,7 +545,7 @@ void update_values_battery() { //This function maps all the values fetched via
|
||||||
|
|
||||||
datalayer.battery.status.voltage_dV = BMS_voltage * 2.5; // *0.25*10
|
datalayer.battery.status.voltage_dV = BMS_voltage * 2.5; // *0.25*10
|
||||||
|
|
||||||
datalayer.battery.status.current_dA = (BMS_current / 10) - 1630;
|
datalayer.battery.status.current_dA = (BMS_current - 16300); // 0.1 * 10
|
||||||
|
|
||||||
datalayer.battery.info.total_capacity_Wh =
|
datalayer.battery.info.total_capacity_Wh =
|
||||||
((float)datalayer.battery.info.number_of_cells) * 3.6458 * ((float)BMS_capacity_ah) * 0.2;
|
((float)datalayer.battery.info.number_of_cells) * 3.6458 * ((float)BMS_capacity_ah) * 0.2;
|
||||||
|
@ -620,10 +640,15 @@ void update_values_battery() { //This function maps all the values fetched via
|
||||||
|
|
||||||
void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
|
void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
|
||||||
last_can_msg_timestamp = millis();
|
last_can_msg_timestamp = millis();
|
||||||
if (first_can_msg == 0)
|
if (first_can_msg == 0) {
|
||||||
|
#ifdef DEBUG_LOG
|
||||||
|
logging.printf("MEB: First CAN msg received\n");
|
||||||
|
#endif
|
||||||
first_can_msg = last_can_msg_timestamp;
|
first_can_msg = last_can_msg_timestamp;
|
||||||
|
}
|
||||||
switch (rx_frame.ID) {
|
switch (rx_frame.ID) {
|
||||||
case 0x17F0007B: // BMS 500ms
|
case 0x17F0007B: // BMS 500ms
|
||||||
|
can_msg_received |= RX_0x17F0007B;
|
||||||
component_protection_active = (rx_frame.data.u8[0] & 0x01);
|
component_protection_active = (rx_frame.data.u8[0] & 0x01);
|
||||||
shutdown_active = ((rx_frame.data.u8[0] & 0x02) >> 1);
|
shutdown_active = ((rx_frame.data.u8[0] & 0x02) >> 1);
|
||||||
transportation_mode_active = ((rx_frame.data.u8[0] & 0x02) >> 1);
|
transportation_mode_active = ((rx_frame.data.u8[0] & 0x02) >> 1);
|
||||||
|
@ -643,7 +668,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
|
||||||
instrumentation_cluster_request = ((rx_frame.data.u8[1] & 0x40) >> 6); //True/false
|
instrumentation_cluster_request = ((rx_frame.data.u8[1] & 0x40) >> 6); //True/false
|
||||||
break;
|
break;
|
||||||
case 0x12DD54D0: // BMS Limits 100ms
|
case 0x12DD54D0: // BMS Limits 100ms
|
||||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
can_msg_received |= RX_0x12DD54D0;
|
||||||
max_discharge_power_watt =
|
max_discharge_power_watt =
|
||||||
((rx_frame.data.u8[6] & 0x07) << 10) | (rx_frame.data.u8[5] << 2) | (rx_frame.data.u8[4] & 0xC0) >> 6; //*100
|
((rx_frame.data.u8[6] & 0x07) << 10) | (rx_frame.data.u8[5] << 2) | (rx_frame.data.u8[4] & 0xC0) >> 6; //*100
|
||||||
max_discharge_current_amp =
|
max_discharge_current_amp =
|
||||||
|
@ -652,7 +677,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
|
||||||
max_charge_current_amp = ((rx_frame.data.u8[4] & 0x3F) << 7) | (rx_frame.data.u8[3] >> 1); //*0.2
|
max_charge_current_amp = ((rx_frame.data.u8[4] & 0x3F) << 7) | (rx_frame.data.u8[3] >> 1); //*0.2
|
||||||
break;
|
break;
|
||||||
case 0x12DD54D1: // BMS 100ms
|
case 0x12DD54D1: // BMS 100ms
|
||||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
can_msg_received |= RX_0x12DD54D1;
|
||||||
if (rx_frame.data.u8[6] != 0xFE || rx_frame.data.u8[7] != 0xFF) { // Init state, values below invalid
|
if (rx_frame.data.u8[6] != 0xFE || rx_frame.data.u8[7] != 0xFF) { // Init state, values below invalid
|
||||||
battery_SOC = ((rx_frame.data.u8[3] & 0x0F) << 7) | (rx_frame.data.u8[2] >> 1); //*0.05
|
battery_SOC = ((rx_frame.data.u8[3] & 0x0F) << 7) | (rx_frame.data.u8[2] >> 1); //*0.05
|
||||||
usable_energy_amount_Wh = (rx_frame.data.u8[7] << 8) | rx_frame.data.u8[6]; //*5
|
usable_energy_amount_Wh = (rx_frame.data.u8[7] << 8) | rx_frame.data.u8[6]; //*5
|
||||||
|
@ -663,7 +688,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
|
||||||
warning_support = (rx_frame.data.u8[1] & 0x70) >> 4;
|
warning_support = (rx_frame.data.u8[1] & 0x70) >> 4;
|
||||||
break;
|
break;
|
||||||
case 0x12DD54D2: // BMS 100ms
|
case 0x12DD54D2: // BMS 100ms
|
||||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
can_msg_received |= RX_0x12DD54D2;
|
||||||
battery_heating_active = (rx_frame.data.u8[4] & 0x40) >> 6;
|
battery_heating_active = (rx_frame.data.u8[4] & 0x40) >> 6;
|
||||||
heating_request = (rx_frame.data.u8[5] & 0xE0) >> 5;
|
heating_request = (rx_frame.data.u8[5] & 0xE0) >> 5;
|
||||||
cooling_request = (rx_frame.data.u8[5] & 0x1C) >> 2;
|
cooling_request = (rx_frame.data.u8[5] & 0x1C) >> 2;
|
||||||
|
@ -671,6 +696,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
|
||||||
power_battery_heating_req_watt = rx_frame.data.u8[7];
|
power_battery_heating_req_watt = rx_frame.data.u8[7];
|
||||||
break;
|
break;
|
||||||
case 0x1A555550: // BMS 500ms
|
case 0x1A555550: // BMS 500ms
|
||||||
|
can_msg_received |= RX_0x1A555550;
|
||||||
balancing_active = (rx_frame.data.u8[1] & 0xC0) >> 6;
|
balancing_active = (rx_frame.data.u8[1] & 0xC0) >> 6;
|
||||||
charging_active = (rx_frame.data.u8[2] & 0x01);
|
charging_active = (rx_frame.data.u8[2] & 0x01);
|
||||||
max_energy_Wh = ((rx_frame.data.u8[6] & 0x1F) << 8) | rx_frame.data.u8[5]; //*40
|
max_energy_Wh = ((rx_frame.data.u8[6] & 0x1F) << 8) | rx_frame.data.u8[5]; //*40
|
||||||
|
@ -679,6 +705,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
|
||||||
isolation_resistance_kOhm = (((rx_frame.data.u8[3] & 0x1F) << 7) | rx_frame.data.u8[2] >> 1); //*5
|
isolation_resistance_kOhm = (((rx_frame.data.u8[3] & 0x1F) << 7) | rx_frame.data.u8[2] >> 1); //*5
|
||||||
break;
|
break;
|
||||||
case 0x1A555551: // BMS 500ms
|
case 0x1A555551: // BMS 500ms
|
||||||
|
can_msg_received |= RX_0x1A555551;
|
||||||
battery_heating_installed = (rx_frame.data.u8[1] & 0x20) >> 5;
|
battery_heating_installed = (rx_frame.data.u8[1] & 0x20) >> 5;
|
||||||
error_NT_circuit = (rx_frame.data.u8[1] & 0x40) >> 6;
|
error_NT_circuit = (rx_frame.data.u8[1] & 0x40) >> 6;
|
||||||
pump_1_control = rx_frame.data.u8[2] & 0x0F; //*10, percent
|
pump_1_control = rx_frame.data.u8[2] & 0x0F; //*10, percent
|
||||||
|
@ -691,6 +718,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
|
||||||
return_temperature_C = rx_frame.data.u8[7]; //*0,5 -40
|
return_temperature_C = rx_frame.data.u8[7]; //*0,5 -40
|
||||||
break;
|
break;
|
||||||
case 0x1A5555B2: // BMS
|
case 0x1A5555B2: // BMS
|
||||||
|
can_msg_received |= RX_0x1A5555B2;
|
||||||
performance_index_discharge_peak_temperature_percentage =
|
performance_index_discharge_peak_temperature_percentage =
|
||||||
(((rx_frame.data.u8[3] & 0x07) << 6) | rx_frame.data.u8[2] >> 2); //*0.2
|
(((rx_frame.data.u8[3] & 0x07) << 6) | rx_frame.data.u8[2] >> 2); //*0.2
|
||||||
performance_index_charge_peak_temperature_percentage =
|
performance_index_charge_peak_temperature_percentage =
|
||||||
|
@ -698,15 +726,18 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
|
||||||
temperature_status_discharge = (rx_frame.data.u8[1] & 0x70) >> 4;
|
temperature_status_discharge = (rx_frame.data.u8[1] & 0x70) >> 4;
|
||||||
temperature_status_charge = (((rx_frame.data.u8[2] & 0x03) << 1) | rx_frame.data.u8[1] >> 7);
|
temperature_status_charge = (((rx_frame.data.u8[2] & 0x03) << 1) | rx_frame.data.u8[1] >> 7);
|
||||||
break;
|
break;
|
||||||
case 0x16A954A6: // BMS
|
case 0x16A954A6: // BMS
|
||||||
|
can_msg_received |= RX_0x16A954A6;
|
||||||
BMS_16A954A6_CRC = rx_frame.data.u8[0]; // Can be used to check CAN signal integrity later on
|
BMS_16A954A6_CRC = rx_frame.data.u8[0]; // Can be used to check CAN signal integrity later on
|
||||||
BMS_16A954A6_counter = (rx_frame.data.u8[1] & 0x0F); // Can be used to check CAN signal integrity later on
|
BMS_16A954A6_counter = (rx_frame.data.u8[1] & 0x0F); // Can be used to check CAN signal integrity later on
|
||||||
isolation_fault = (rx_frame.data.u8[2] & 0xE0) >> 5;
|
isolation_fault = (rx_frame.data.u8[2] & 0xE0) >> 5;
|
||||||
isolation_status = (rx_frame.data.u8[2] & 0x1E) >> 1;
|
isolation_status = (rx_frame.data.u8[2] & 0x1E) >> 1;
|
||||||
actual_temperature_highest_C = rx_frame.data.u8[3]; //*0,5 -40
|
if (isolation_fault != 0) {
|
||||||
actual_temperature_lowest_C = rx_frame.data.u8[4]; //*0,5 -40
|
actual_temperature_highest_C = rx_frame.data.u8[3]; //*0,5 -40
|
||||||
actual_cellvoltage_highest_mV = (((rx_frame.data.u8[6] & 0x0F) << 8) | rx_frame.data.u8[5]);
|
actual_temperature_lowest_C = rx_frame.data.u8[4]; //*0,5 -40
|
||||||
actual_cellvoltage_lowest_mV = ((rx_frame.data.u8[7] << 4) | rx_frame.data.u8[6] >> 4);
|
actual_cellvoltage_highest_mV = (((rx_frame.data.u8[6] & 0x0F) << 8) | rx_frame.data.u8[5]);
|
||||||
|
actual_cellvoltage_lowest_mV = ((rx_frame.data.u8[7] << 4) | rx_frame.data.u8[6] >> 4);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 0x16A954F8: // BMS
|
case 0x16A954F8: // BMS
|
||||||
predicted_power_dyn_standard_watt = ((rx_frame.data.u8[6] << 1) | rx_frame.data.u8[5] >> 7); //*50
|
predicted_power_dyn_standard_watt = ((rx_frame.data.u8[6] << 1) | rx_frame.data.u8[5] >> 7); //*50
|
||||||
|
@ -897,12 +928,14 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
|
||||||
//hybrid_01_response_fd_data (Whole frame)
|
//hybrid_01_response_fd_data (Whole frame)
|
||||||
break;
|
break;
|
||||||
case 0x1A5555B0: // BMS 1000ms cyclic
|
case 0x1A5555B0: // BMS 1000ms cyclic
|
||||||
|
can_msg_received |= RX_0x1A5555B0;
|
||||||
duration_discharge_power_watt = ((rx_frame.data.u8[6] & 0x0F) << 8) | rx_frame.data.u8[5];
|
duration_discharge_power_watt = ((rx_frame.data.u8[6] & 0x0F) << 8) | rx_frame.data.u8[5];
|
||||||
duration_charge_power_watt = (rx_frame.data.u8[7] << 4) | rx_frame.data.u8[6] >> 4;
|
duration_charge_power_watt = (rx_frame.data.u8[7] << 4) | rx_frame.data.u8[6] >> 4;
|
||||||
maximum_voltage = ((rx_frame.data.u8[3] & 0x3F) << 4) | rx_frame.data.u8[2] >> 4;
|
maximum_voltage = ((rx_frame.data.u8[3] & 0x3F) << 4) | rx_frame.data.u8[2] >> 4;
|
||||||
minimum_voltage = (rx_frame.data.u8[4] << 2) | rx_frame.data.u8[3] >> 6;
|
minimum_voltage = (rx_frame.data.u8[4] << 2) | rx_frame.data.u8[3] >> 6;
|
||||||
break;
|
break;
|
||||||
case 0x1A5555B1: // BMS 1000ms cyclic
|
case 0x1A5555B1: // BMS 1000ms cyclic
|
||||||
|
can_msg_received |= RX_0x1A5555B1;
|
||||||
// All realtime_ have same enumeration, 0 = no fault, 1 = error level 1, 2 error level 2, 3 error level 3
|
// All realtime_ have same enumeration, 0 = no fault, 1 = error level 1, 2 error level 2, 3 error level 3
|
||||||
realtime_overcurrent_monitor = ((rx_frame.data.u8[3] & 0x01) << 2) | rx_frame.data.u8[2] >> 6;
|
realtime_overcurrent_monitor = ((rx_frame.data.u8[3] & 0x01) << 2) | rx_frame.data.u8[2] >> 6;
|
||||||
realtime_CAN_communication_fault = (rx_frame.data.u8[3] & 0x0E) >> 1;
|
realtime_CAN_communication_fault = (rx_frame.data.u8[3] & 0x0E) >> 1;
|
||||||
|
@ -937,7 +970,8 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
|
||||||
max_fastcharging_current_amp = ((rx_frame.data.u8[4] & 0x01) << 8) | rx_frame.data.u8[3];
|
max_fastcharging_current_amp = ((rx_frame.data.u8[4] & 0x01) << 8) | rx_frame.data.u8[3];
|
||||||
DC_voltage_chargeport = (rx_frame.data.u8[7] << 4) | (rx_frame.data.u8[6] >> 4);
|
DC_voltage_chargeport = (rx_frame.data.u8[7] << 4) | (rx_frame.data.u8[6] >> 4);
|
||||||
break;
|
break;
|
||||||
case 0x5A2: // BMS 500ms normal, 100ms fast
|
case 0x5A2: // BMS 500ms normal, 100ms fast
|
||||||
|
can_msg_received |= RX_0x5A2;
|
||||||
BMS_5A2_CRC = rx_frame.data.u8[0]; // Can be used to check CAN signal integrity later on
|
BMS_5A2_CRC = rx_frame.data.u8[0]; // Can be used to check CAN signal integrity later on
|
||||||
BMS_5A2_counter = (rx_frame.data.u8[1] & 0x0F); // Can be used to check CAN signal integrity later on
|
BMS_5A2_counter = (rx_frame.data.u8[1] & 0x0F); // Can be used to check CAN signal integrity later on
|
||||||
service_disconnect_switch_missing = (rx_frame.data.u8[1] & 0x20) >> 5;
|
service_disconnect_switch_missing = (rx_frame.data.u8[1] & 0x20) >> 5;
|
||||||
|
@ -952,7 +986,8 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
|
||||||
BMS_capacity_ah = ((rx_frame.data.u8[4] & 0x03) << 9) | (rx_frame.data.u8[3] << 1) | (rx_frame.data.u8[2] >> 7);
|
BMS_capacity_ah = ((rx_frame.data.u8[4] & 0x03) << 9) | (rx_frame.data.u8[3] << 1) | (rx_frame.data.u8[2] >> 7);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 0x5CA: // BMS 500ms
|
case 0x5CA: // BMS 500ms
|
||||||
|
can_msg_received |= RX_0x5CA;
|
||||||
BMS_5CA_CRC = rx_frame.data.u8[0]; // Can be used to check CAN signal integrity later on
|
BMS_5CA_CRC = rx_frame.data.u8[0]; // Can be used to check CAN signal integrity later on
|
||||||
BMS_5CA_counter = (rx_frame.data.u8[1] & 0x0F); // Can be used to check CAN signal integrity later on
|
BMS_5CA_counter = (rx_frame.data.u8[1] & 0x0F); // Can be used to check CAN signal integrity later on
|
||||||
balancing_request = (rx_frame.data.u8[5] & 0x08) >> 3; //True/False
|
balancing_request = (rx_frame.data.u8[5] & 0x08) >> 3; //True/False
|
||||||
|
@ -966,19 +1001,51 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
|
||||||
battery_Wh_max =
|
battery_Wh_max =
|
||||||
((rx_frame.data.u8[5] & 0x07) << 8) | rx_frame.data.u8[4]; //*50 ! Not usable, seems to always contain 0x7F0
|
((rx_frame.data.u8[5] & 0x07) << 8) | rx_frame.data.u8[4]; //*50 ! Not usable, seems to always contain 0x7F0
|
||||||
break;
|
break;
|
||||||
case 0x0CF: //BMS 10ms
|
case 0x0CF: //BMS 10ms
|
||||||
|
can_msg_received |= RX_0x0CF;
|
||||||
BMS_0CF_CRC = rx_frame.data.u8[0]; // Can be used to check CAN signal integrity later on
|
BMS_0CF_CRC = rx_frame.data.u8[0]; // Can be used to check CAN signal integrity later on
|
||||||
BMS_0CF_counter = (rx_frame.data.u8[1] & 0x0F); // Can be used to check CAN signal integrity later on
|
BMS_0CF_counter = (rx_frame.data.u8[1] & 0x0F); // Can be used to check CAN signal integrity later on
|
||||||
BMS_welded_contactors_status = (rx_frame.data.u8[1] & 0x60) >> 5;
|
BMS_welded_contactors_status = (rx_frame.data.u8[1] & 0x60) >> 5;
|
||||||
BMS_ext_limits_active = (rx_frame.data.u8[1] & 0x80) >> 7;
|
BMS_ext_limits_active = (rx_frame.data.u8[1] & 0x80) >> 7;
|
||||||
BMS_mode = (rx_frame.data.u8[2] & 0x07);
|
BMS_mode = (rx_frame.data.u8[2] & 0x07);
|
||||||
switch (BMS_mode) {
|
switch (BMS_mode) {
|
||||||
case 1:
|
case 1: // HV_ACTIVE
|
||||||
case 3:
|
case 3: // EXTERN CHARGING
|
||||||
case 4:
|
case 4: // AC_CHARGING
|
||||||
|
case 6: // DC_CHARGING
|
||||||
|
#ifdef DEBUG_LOG
|
||||||
|
if (!datalayer.system.status.battery_allows_contactor_closing)
|
||||||
|
logging.printf("MEB Contactors closed\n");
|
||||||
|
#endif
|
||||||
|
if (datalayer.battery.status.real_bms_status != BMS_FAULT)
|
||||||
|
datalayer.battery.status.real_bms_status = BMS_ACTIVE;
|
||||||
datalayer.system.status.battery_allows_contactor_closing = true;
|
datalayer.system.status.battery_allows_contactor_closing = true;
|
||||||
break;
|
break;
|
||||||
|
case 5: // Error
|
||||||
|
#ifdef DEBUG_LOG
|
||||||
|
if (datalayer.system.status.battery_allows_contactor_closing)
|
||||||
|
logging.printf("MEB Contactors opened\n");
|
||||||
|
#endif
|
||||||
|
datalayer.battery.status.real_bms_status = BMS_FAULT;
|
||||||
|
datalayer.system.status.battery_allows_contactor_closing = false;
|
||||||
|
break;
|
||||||
|
case 7: // Init
|
||||||
|
#ifdef DEBUG_LOG
|
||||||
|
if (datalayer.system.status.battery_allows_contactor_closing)
|
||||||
|
logging.printf("MEB Contactors opened\n");
|
||||||
|
#endif
|
||||||
|
if (datalayer.battery.status.real_bms_status != BMS_FAULT)
|
||||||
|
datalayer.battery.status.real_bms_status = BMS_STANDBY;
|
||||||
|
datalayer.system.status.battery_allows_contactor_closing = false;
|
||||||
|
break;
|
||||||
|
case 2: // BALANCING
|
||||||
default:
|
default:
|
||||||
|
#ifdef DEBUG_LOG
|
||||||
|
if (datalayer.system.status.battery_allows_contactor_closing)
|
||||||
|
logging.printf("MEB Contactors opened\n");
|
||||||
|
#endif
|
||||||
|
if (datalayer.battery.status.real_bms_status != BMS_FAULT)
|
||||||
|
datalayer.battery.status.real_bms_status = BMS_STANDBY;
|
||||||
datalayer.system.status.battery_allows_contactor_closing = false;
|
datalayer.system.status.battery_allows_contactor_closing = false;
|
||||||
}
|
}
|
||||||
BMS_HVIL_status = (rx_frame.data.u8[2] & 0x18) >> 3;
|
BMS_HVIL_status = (rx_frame.data.u8[2] & 0x18) >> 3;
|
||||||
|
@ -1448,21 +1515,43 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
case 0x18DAF105:
|
||||||
|
handle_obd_frame(rx_frame);
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
#ifdef DEBUG_LOG
|
||||||
|
logging.printf("Unknown CAN frame received:\n");
|
||||||
|
dump_can_frame(rx_frame, MSG_RX);
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
|
if (can_msg_received == 0xFFFF && nof_cells_determined) {
|
||||||
|
if (datalayer.battery.status.real_bms_status == BMS_DISCONNECTED)
|
||||||
|
datalayer.battery.status.real_bms_status = BMS_STANDBY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void transmit_can_battery() {
|
void transmit_can_battery() {
|
||||||
unsigned long currentMillis = millis();
|
unsigned long currentMillis = millis();
|
||||||
// Send 10ms CAN Message
|
// Send 10ms CAN Message
|
||||||
if (currentMillis > last_can_msg_timestamp + 500) {
|
if (datalayer.system.settings.equipment_stop_active || currentMillis > last_can_msg_timestamp + 500) {
|
||||||
|
#ifdef DEBUG_LOG
|
||||||
|
if (first_can_msg)
|
||||||
|
logging.printf("MEB: No CAN msg received for 500ms\n");
|
||||||
|
#endif
|
||||||
|
can_msg_received = RX_DEFAULT;
|
||||||
first_can_msg = 0;
|
first_can_msg = 0;
|
||||||
|
if (datalayer.battery.status.real_bms_status != BMS_FAULT) {
|
||||||
|
datalayer.battery.status.real_bms_status = BMS_DISCONNECTED;
|
||||||
|
datalayer.system.status.battery_allows_contactor_closing = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentMillis - previousMillis10ms >= INTERVAL_10_MS) {
|
if (currentMillis - previousMillis10ms >= INTERVAL_10_MS) {
|
||||||
// Check if sending of CAN messages has been delayed too much.
|
// Check if sending of CAN messages has been delayed too much.
|
||||||
if ((currentMillis - previousMillis10ms >= INTERVAL_10_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
|
if ((currentMillis - previousMillis10ms >= INTERVAL_10_MS_DELAYED) && (currentMillis > BOOTUP_TIME) &&
|
||||||
|
previousMillis10ms > 0) {
|
||||||
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis10ms));
|
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis10ms));
|
||||||
} else {
|
} else {
|
||||||
clear_event(EVENT_CAN_OVERRUN);
|
clear_event(EVENT_CAN_OVERRUN);
|
||||||
|
@ -1525,18 +1614,32 @@ void transmit_can_battery() {
|
||||||
previousMillis100ms = currentMillis;
|
previousMillis100ms = currentMillis;
|
||||||
|
|
||||||
//HV request and DC/DC control lies in 0x503
|
//HV request and DC/DC control lies in 0x503
|
||||||
MEB_503.data.u8[3] = 0x00;
|
|
||||||
if (datalayer.battery.status.bms_status != FAULT && first_can_msg > 0 && currentMillis > first_can_msg + 2000) {
|
if (datalayer.battery.status.real_bms_status != BMS_FAULT &&
|
||||||
MEB_503.data.u8[1] = 0xB0;
|
(datalayer.battery.status.real_bms_status == BMS_STANDBY ||
|
||||||
MEB_503.data.u8[3] = BMS_TARGET_HV_ON; //BMS_TARGET_AC_CHARGING; //TODO, should we try AC_2 or DC charging?
|
datalayer.battery.status.real_bms_status == BMS_ACTIVE) &&
|
||||||
|
(labs(((int32_t)datalayer.battery.status.voltage_dV) -
|
||||||
|
((int32_t)datalayer_extended.meb.BMS_voltage_intermediate_dV)) < 200)) {
|
||||||
|
#ifdef DEBUG_LOG
|
||||||
|
if (MEB_503.data.u8[3] == BMS_TARGET_HV_OFF) {
|
||||||
|
logging.printf("MEB Requesting HV\n");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
MEB_503.data.u8[1] =
|
||||||
|
0x30 |
|
||||||
|
(datalayer.battery.status.real_bms_status == BMS_ACTIVE ? 0x00 : 0x80); // Disable precharing if ACTIVE
|
||||||
|
MEB_503.data.u8[3] = BMS_TARGET_HV_ON; //TODO, should we try AC_2 or DC charging?
|
||||||
MEB_503.data.u8[5] = 0x82; // Bordnetz Active
|
MEB_503.data.u8[5] = 0x82; // Bordnetz Active
|
||||||
MEB_503.data.u8[6] = 0xE0; // Request emergency shutdown HV system == 0, false
|
MEB_503.data.u8[6] = 0xE0; // Request emergency shutdown HV system == 0, false
|
||||||
} else if (first_can_msg > 0 && currentMillis > first_can_msg + 2000) { //FAULT STATE, open contactors
|
} else if (first_can_msg > 0 && currentMillis > first_can_msg + 2000 && BMS_mode != 0 &&
|
||||||
|
BMS_mode != 7) { //FAULT STATE, open contactors
|
||||||
MEB_503.data.u8[1] = 0x90;
|
MEB_503.data.u8[1] = 0x90;
|
||||||
MEB_503.data.u8[3] = BMS_TARGET_HV_OFF;
|
MEB_503.data.u8[3] = BMS_TARGET_HV_OFF;
|
||||||
MEB_503.data.u8[5] = 0x80; // Bordnetz Inactive
|
MEB_503.data.u8[5] = 0x80; // Bordnetz Inactive
|
||||||
MEB_503.data.u8[6] =
|
MEB_503.data.u8[6] =
|
||||||
0xE3; // Request emergency shutdown HV system == init (3) (not sure if we dare activate this, this is done with 0xE1)
|
0xE3; // Request emergency shutdown HV system == init (3) (not sure if we dare activate this, this is done with 0xE1)
|
||||||
|
} else {
|
||||||
|
MEB_503.data.u8[3] = 0;
|
||||||
}
|
}
|
||||||
MEB_503.data.u8[1] = ((MEB_503.data.u8[1] & 0xF0) | counter_100ms);
|
MEB_503.data.u8[1] = ((MEB_503.data.u8[1] & 0xF0) | counter_100ms);
|
||||||
MEB_503.data.u8[0] = vw_crc_calc(MEB_503.data.u8, MEB_503.DLC, MEB_503.ID);
|
MEB_503.data.u8[0] = vw_crc_calc(MEB_503.data.u8, MEB_503.DLC, MEB_503.ID);
|
||||||
|
@ -1549,7 +1652,7 @@ void transmit_can_battery() {
|
||||||
MEB_272.data.u8[5] = DC_FASTCHARGE_NO_START_REQUEST; //DC_FASTCHARGE_VEHICLE; //DC charging
|
MEB_272.data.u8[5] = DC_FASTCHARGE_NO_START_REQUEST; //DC_FASTCHARGE_VEHICLE; //DC charging
|
||||||
|
|
||||||
//Klemmen status
|
//Klemmen status
|
||||||
MEB_3C0.data.u8[2] = 0x00; //0x02; //bit to signal that KL_15 is ON // Always 0 in start4.log
|
MEB_3C0.data.u8[2] = 0x02; //bit to signal that KL_15 is ON // Always 0 in start4.log
|
||||||
MEB_3C0.data.u8[1] = ((MEB_3C0.data.u8[1] & 0xF0) | counter_100ms);
|
MEB_3C0.data.u8[1] = ((MEB_3C0.data.u8[1] & 0xF0) | counter_100ms);
|
||||||
MEB_3C0.data.u8[0] = vw_crc_calc(MEB_3C0.data.u8, MEB_3C0.DLC, MEB_3C0.ID);
|
MEB_3C0.data.u8[0] = vw_crc_calc(MEB_3C0.data.u8, MEB_3C0.DLC, MEB_3C0.ID);
|
||||||
|
|
||||||
|
@ -2075,7 +2178,7 @@ void transmit_can_battery() {
|
||||||
poll_pid = PID_SOC;
|
poll_pid = PID_SOC;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (first_can_msg > 0 && currentMillis > first_can_msg + 2000) {
|
if (first_can_msg > 0 && currentMillis > first_can_msg + 1000) {
|
||||||
transmit_can_frame(&MEB_POLLING_FRAME, can_config.battery);
|
transmit_can_frame(&MEB_POLLING_FRAME, can_config.battery);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2115,6 +2218,8 @@ void transmit_can_battery() {
|
||||||
transmit_can_frame(&MEB_5F5, can_config.battery); // Loading profile
|
transmit_can_frame(&MEB_5F5, can_config.battery); // Loading profile
|
||||||
transmit_can_frame(&MEB_585, can_config.battery); // Systeminfo
|
transmit_can_frame(&MEB_585, can_config.battery); // Systeminfo
|
||||||
transmit_can_frame(&MEB_1A5555A6, can_config.battery); // Temperature QBit
|
transmit_can_frame(&MEB_1A5555A6, can_config.battery); // Temperature QBit
|
||||||
|
|
||||||
|
transmit_obd_can_frame(0x18DA05F1, can_config.battery);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -515,7 +515,10 @@ void handle_incoming_can_frame_battery2(CAN_frame rx_frame) {
|
||||||
battery2_Current2 |= 0xf800;
|
battery2_Current2 |= 0xf800;
|
||||||
} //BatteryCurrentSignal , 2s comp, 1lSB = 0.5A/bit
|
} //BatteryCurrentSignal , 2s comp, 1lSB = 0.5A/bit
|
||||||
|
|
||||||
battery2_Total_Voltage2 = ((rx_frame.data.u8[2] << 2) | (rx_frame.data.u8[3] & 0xc0) >> 6); //0.5V/bit
|
battery2_TEMP = ((rx_frame.data.u8[2] << 2) | (rx_frame.data.u8[3] & 0xc0) >> 6); //0.5V/bit
|
||||||
|
if (battery2_TEMP != 0x3ff) { //3FF is unavailable value. Can happen directly on reboot.
|
||||||
|
battery2_Total_Voltage2 = battery2_TEMP;
|
||||||
|
}
|
||||||
|
|
||||||
//Collect various data from the BMS
|
//Collect various data from the BMS
|
||||||
battery2_Relay_Cut_Request = ((rx_frame.data.u8[1] & 0x18) >> 3);
|
battery2_Relay_Cut_Request = ((rx_frame.data.u8[1] & 0x18) >> 3);
|
||||||
|
@ -754,7 +757,10 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
|
||||||
battery_Current2 |= 0xf800;
|
battery_Current2 |= 0xf800;
|
||||||
} //BatteryCurrentSignal , 2s comp, 1lSB = 0.5A/bit
|
} //BatteryCurrentSignal , 2s comp, 1lSB = 0.5A/bit
|
||||||
|
|
||||||
battery_Total_Voltage2 = ((rx_frame.data.u8[2] << 2) | (rx_frame.data.u8[3] & 0xc0) >> 6); //0.5V/bit
|
battery_TEMP = ((rx_frame.data.u8[2] << 2) | (rx_frame.data.u8[3] & 0xc0) >> 6); //0.5V/bit
|
||||||
|
if (battery_TEMP != 0x3ff) { //3FF is unavailable value. Can happen directly on reboot.
|
||||||
|
battery_Total_Voltage2 = battery_TEMP;
|
||||||
|
}
|
||||||
|
|
||||||
//Collect various data from the BMS
|
//Collect various data from the BMS
|
||||||
battery_Relay_Cut_Request = ((rx_frame.data.u8[1] & 0x18) >> 3);
|
battery_Relay_Cut_Request = ((rx_frame.data.u8[1] & 0x18) >> 3);
|
||||||
|
|
|
@ -454,6 +454,11 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
|
||||||
case 0x2E:
|
case 0x2E:
|
||||||
if (requested_poll == GROUP1_CELLVOLTAGES_1_POLL) {
|
if (requested_poll == GROUP1_CELLVOLTAGES_1_POLL) {
|
||||||
cellvoltages[47] = (highbyte_cell_next_frame << 8) | rx_frame.data.u8[1];
|
cellvoltages[47] = (highbyte_cell_next_frame << 8) | rx_frame.data.u8[1];
|
||||||
|
if (cellvoltages[47] < 100) { //This cell measurement is inbetween pack halves. If low, fuse blown
|
||||||
|
set_event(EVENT_BATTERY_FUSE, cellvoltages[47]);
|
||||||
|
} else {
|
||||||
|
clear_event(EVENT_BATTERY_FUSE);
|
||||||
|
}
|
||||||
cellvoltages[48] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
|
cellvoltages[48] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
|
||||||
cellvoltages[49] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
cellvoltages[49] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
cellvoltages[50] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
|
cellvoltages[50] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
|
||||||
|
|
|
@ -282,38 +282,7 @@ void print_can_frame(CAN_frame frame, frameDirection msgDir) {
|
||||||
#endif // DEBUG_CAN_DATA
|
#endif // DEBUG_CAN_DATA
|
||||||
|
|
||||||
if (datalayer.system.info.can_logging_active) { // If user clicked on CAN Logging page in webserver, start recording
|
if (datalayer.system.info.can_logging_active) { // If user clicked on CAN Logging page in webserver, start recording
|
||||||
char* message_string = datalayer.system.info.logged_can_messages;
|
dump_can_frame(frame, msgDir);
|
||||||
int offset = datalayer.system.info.logged_can_messages_offset; // Keeps track of the current position in the buffer
|
|
||||||
size_t message_string_size = sizeof(datalayer.system.info.logged_can_messages);
|
|
||||||
|
|
||||||
if (offset + 128 > sizeof(datalayer.system.info.logged_can_messages)) {
|
|
||||||
// Not enough space, reset and start from the beginning
|
|
||||||
offset = 0;
|
|
||||||
}
|
|
||||||
unsigned long currentTime = millis();
|
|
||||||
// Add timestamp
|
|
||||||
offset += snprintf(message_string + offset, message_string_size - offset, "(%lu.%03lu) ", currentTime / 1000,
|
|
||||||
currentTime % 1000);
|
|
||||||
|
|
||||||
// Add direction. The 0 and 1 after RX and TX ensures that SavvyCAN puts TX and RX in a different bus.
|
|
||||||
offset +=
|
|
||||||
snprintf(message_string + offset, message_string_size - offset, "%s ", (msgDir == MSG_RX) ? "RX0" : "TX1");
|
|
||||||
|
|
||||||
// Add ID and DLC
|
|
||||||
offset += snprintf(message_string + offset, message_string_size - offset, "%X [%u] ", frame.ID, frame.DLC);
|
|
||||||
|
|
||||||
// Add data bytes
|
|
||||||
for (uint8_t i = 0; i < frame.DLC; i++) {
|
|
||||||
if (i < frame.DLC - 1) {
|
|
||||||
offset += snprintf(message_string + offset, message_string_size - offset, "%02X ", frame.data.u8[i]);
|
|
||||||
} else {
|
|
||||||
offset += snprintf(message_string + offset, message_string_size - offset, "%02X", frame.data.u8[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Add linebreak
|
|
||||||
offset += snprintf(message_string + offset, message_string_size - offset, "\n");
|
|
||||||
|
|
||||||
datalayer.system.info.logged_can_messages_offset = offset; // Update offset in buffer
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -351,3 +320,36 @@ void map_can_frame_to_variable(CAN_frame* rx_frame, int interface) {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
void dump_can_frame(CAN_frame& frame, frameDirection msgDir) {
|
||||||
|
char* message_string = datalayer.system.info.logged_can_messages;
|
||||||
|
int offset = datalayer.system.info.logged_can_messages_offset; // Keeps track of the current position in the buffer
|
||||||
|
size_t message_string_size = sizeof(datalayer.system.info.logged_can_messages);
|
||||||
|
|
||||||
|
if (offset + 128 > sizeof(datalayer.system.info.logged_can_messages)) {
|
||||||
|
// Not enough space, reset and start from the beginning
|
||||||
|
offset = 0;
|
||||||
|
}
|
||||||
|
unsigned long currentTime = millis();
|
||||||
|
// Add timestamp
|
||||||
|
offset += snprintf(message_string + offset, message_string_size - offset, "(%lu.%03lu) ", currentTime / 1000,
|
||||||
|
currentTime % 1000);
|
||||||
|
|
||||||
|
// Add direction. The 0 and 1 after RX and TX ensures that SavvyCAN puts TX and RX in a different bus.
|
||||||
|
offset += snprintf(message_string + offset, message_string_size - offset, "%s ", (msgDir == MSG_RX) ? "RX0" : "TX1");
|
||||||
|
|
||||||
|
// Add ID and DLC
|
||||||
|
offset += snprintf(message_string + offset, message_string_size - offset, "%X [%u] ", frame.ID, frame.DLC);
|
||||||
|
|
||||||
|
// Add data bytes
|
||||||
|
for (uint8_t i = 0; i < frame.DLC; i++) {
|
||||||
|
if (i < frame.DLC - 1) {
|
||||||
|
offset += snprintf(message_string + offset, message_string_size - offset, "%02X ", frame.data.u8[i]);
|
||||||
|
} else {
|
||||||
|
offset += snprintf(message_string + offset, message_string_size - offset, "%02X", frame.data.u8[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add linebreak
|
||||||
|
offset += snprintf(message_string + offset, message_string_size - offset, "\n");
|
||||||
|
|
||||||
|
datalayer.system.info.logged_can_messages_offset = offset; // Update offset in buffer
|
||||||
|
}
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
#include "../../lib/pierremolinaro-ACAN2517FD/ACAN2517FD.h"
|
#include "../../lib/pierremolinaro-ACAN2517FD/ACAN2517FD.h"
|
||||||
#endif //CANFD_ADDON
|
#endif //CANFD_ADDON
|
||||||
|
|
||||||
|
void dump_can_frame(CAN_frame& frame, frameDirection msgDir);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Initialization function for CAN.
|
* @brief Initialization function for CAN.
|
||||||
*
|
*
|
||||||
|
|
148
Software/src/communication/can/obd.cpp
Normal file
148
Software/src/communication/can/obd.cpp
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
#include "obd.h"
|
||||||
|
#include "comm_can.h"
|
||||||
|
|
||||||
|
void show_dtc(uint8_t byte0, uint8_t byte1);
|
||||||
|
|
||||||
|
void show_dtc(uint8_t byte0, uint8_t byte1) {
|
||||||
|
char letter;
|
||||||
|
switch (byte0 >> 6) {
|
||||||
|
case 0:
|
||||||
|
letter = 'P';
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
letter = 'C';
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
letter = 'B';
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
letter = 'U';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
logging.printf("%c%d\n", letter, ((byte0 & 0x3F) << 8) | byte1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void handle_obd_frame(CAN_frame& rx_frame) {
|
||||||
|
#ifdef DEBUG_LOG
|
||||||
|
if (rx_frame.data.u8[1] == 0x7F) {
|
||||||
|
const char* error_str = "?";
|
||||||
|
switch (rx_frame.data.u8[3]) { // See https://automotive.wiki/index.php/ISO_14229
|
||||||
|
case 0x10:
|
||||||
|
error_str = "generalReject";
|
||||||
|
break;
|
||||||
|
case 0x11:
|
||||||
|
error_str = "serviceNotSupported";
|
||||||
|
break;
|
||||||
|
case 0x12:
|
||||||
|
error_str = "subFunctionNotSupported";
|
||||||
|
break;
|
||||||
|
case 0x13:
|
||||||
|
error_str = "incorrectMessageLengthOrInvalidFormat";
|
||||||
|
break;
|
||||||
|
case 0x14:
|
||||||
|
error_str = "responseTooLong";
|
||||||
|
break;
|
||||||
|
case 0x21:
|
||||||
|
error_str = "busyRepeatReques";
|
||||||
|
break;
|
||||||
|
case 0x22:
|
||||||
|
error_str = "conditionsNotCorrect";
|
||||||
|
break;
|
||||||
|
case 0x24:
|
||||||
|
error_str = "requestSequenceError";
|
||||||
|
break;
|
||||||
|
case 0x31:
|
||||||
|
error_str = "requestOutOfRange";
|
||||||
|
break;
|
||||||
|
case 0x33:
|
||||||
|
error_str = "securityAccessDenied";
|
||||||
|
break;
|
||||||
|
case 0x35:
|
||||||
|
error_str = "invalidKey";
|
||||||
|
break;
|
||||||
|
case 0x36:
|
||||||
|
error_str = "exceedNumberOfAttempts";
|
||||||
|
break;
|
||||||
|
case 0x37:
|
||||||
|
error_str = "requiredTimeDelayNotExpired";
|
||||||
|
break;
|
||||||
|
case 0x70:
|
||||||
|
error_str = "uploadDownloadNotAccepted";
|
||||||
|
break;
|
||||||
|
case 0x71:
|
||||||
|
error_str = "transferDataSuspended";
|
||||||
|
break;
|
||||||
|
case 0x72:
|
||||||
|
error_str = "generalProgrammingFailure";
|
||||||
|
break;
|
||||||
|
case 0x73:
|
||||||
|
error_str = "wrongBlockSequenceCounter";
|
||||||
|
break;
|
||||||
|
case 0x78:
|
||||||
|
error_str = "requestCorrectlyReceived-ResponsePending";
|
||||||
|
break;
|
||||||
|
case 0x7E:
|
||||||
|
error_str = "subFunctionNotSupportedInActiveSession";
|
||||||
|
break;
|
||||||
|
case 0x7F:
|
||||||
|
error_str = "serviceNotSupportedInActiveSession";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
logging.printf("ODB reply Request for service 0x%02X: %s\n", rx_frame.data.u8[2], error_str);
|
||||||
|
} else {
|
||||||
|
switch (rx_frame.data.u8[1] & 0x3F) {
|
||||||
|
case 3:
|
||||||
|
logging.printf("ODB reply service 03: Show stored DTCs, %d present:\n", rx_frame.data.u8[2]);
|
||||||
|
for (int i = 0; i < rx_frame.data.u8[2]; i++)
|
||||||
|
show_dtc(rx_frame.data.u8[3 + 2 * i], rx_frame.data.u8[4 + 2 * i]);
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
logging.printf("ODB reply service 07: Show pending DTCs, %d present:\n", rx_frame.data.u8[2]);
|
||||||
|
for (int i = 0; i < rx_frame.data.u8[2]; i++)
|
||||||
|
show_dtc(rx_frame.data.u8[3 + 2 * i], rx_frame.data.u8[4 + 2 * i]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
logging.printf("ODBx reply frame received:\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dump_can_frame(rx_frame, MSG_RX);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void transmit_obd_can_frame(unsigned int address, int interface) {
|
||||||
|
static CAN_frame OBD_frame;
|
||||||
|
OBD_frame.ID = address;
|
||||||
|
OBD_frame.ext_ID = address > 0x7FF;
|
||||||
|
OBD_frame.DLC = 8;
|
||||||
|
OBD_frame.data.u8[0] = 0x01;
|
||||||
|
OBD_frame.data.u8[1] = 0x03;
|
||||||
|
OBD_frame.data.u8[2] = 0xAA;
|
||||||
|
OBD_frame.data.u8[3] = 0xAA;
|
||||||
|
OBD_frame.data.u8[4] = 0xAA;
|
||||||
|
OBD_frame.data.u8[5] = 0xAA;
|
||||||
|
OBD_frame.data.u8[6] = 0xAA;
|
||||||
|
OBD_frame.data.u8[7] = 0xAA;
|
||||||
|
static int cnt = 0;
|
||||||
|
switch (cnt) {
|
||||||
|
case 2:
|
||||||
|
transmit_can_frame(&OBD_frame, interface); // DTC TP-ISO
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
OBD_frame.data.u8[1] = 0x07;
|
||||||
|
transmit_can_frame(&OBD_frame, interface); // DTC TP-ISO
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
OBD_frame.data.u8[1] = 0x0A;
|
||||||
|
transmit_can_frame(&OBD_frame, interface); // DTC TP-ISO
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
OBD_frame.data.u8[0] = 0x02;
|
||||||
|
OBD_frame.data.u8[1] = 0x01;
|
||||||
|
OBD_frame.data.u8[2] = 0x1C;
|
||||||
|
transmit_can_frame(&OBD_frame, interface); // DTC TP-ISO
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
cnt++;
|
||||||
|
if (cnt == 3600)
|
||||||
|
cnt = 0;
|
||||||
|
}
|
11
Software/src/communication/can/obd.h
Normal file
11
Software/src/communication/can/obd.h
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
#ifndef _OBD_H_
|
||||||
|
#define _OBD_H_
|
||||||
|
|
||||||
|
#include "../../include.h"
|
||||||
|
#include "../../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||||
|
|
||||||
|
void handle_obd_frame(CAN_frame& rx_frame);
|
||||||
|
|
||||||
|
void transmit_obd_can_frame(unsigned int address, int interface);
|
||||||
|
|
||||||
|
#endif // _OBD_H_
|
|
@ -0,0 +1,156 @@
|
||||||
|
#include "precharge_control.h"
|
||||||
|
#include "../../datalayer/datalayer.h"
|
||||||
|
#include "../../datalayer/datalayer_extended.h"
|
||||||
|
#include "../../include.h"
|
||||||
|
|
||||||
|
// Parameters
|
||||||
|
|
||||||
|
#ifdef PRECHARGE_CONTROL
|
||||||
|
enum State { PRECHARGE_IDLE, START_PRECHARGE, PRECHARGE, PRECHARGE_OFF, COMPLETED };
|
||||||
|
State prechargeStatus = PRECHARGE_IDLE;
|
||||||
|
|
||||||
|
#define MAX_PRECHARGE_TIME_MS 15000 // Maximum time precharge may be enabled
|
||||||
|
|
||||||
|
#define Precharge_default_PWM_Freq 11000
|
||||||
|
#define Precharge_min_PWM_Freq 5000
|
||||||
|
#define Precharge_max_PWM_Freq 34000
|
||||||
|
#define PWM_Res 8
|
||||||
|
#define PWM_OFF_DUTY 0
|
||||||
|
|
||||||
|
#define PWM_Precharge_Channel 0
|
||||||
|
unsigned long prechargeStartTime = 0;
|
||||||
|
static uint32_t freq = Precharge_default_PWM_Freq;
|
||||||
|
uint16_t delta_freq = 1;
|
||||||
|
static int32_t prev_external_voltage = 20000;
|
||||||
|
|
||||||
|
// Initialization functions
|
||||||
|
|
||||||
|
void init_precharge_control() {
|
||||||
|
// Setup PWM Channel Frequency and Resolution
|
||||||
|
#ifdef DEBUG_LOG
|
||||||
|
logging.printf("Precharge control initialised\n");
|
||||||
|
#endif
|
||||||
|
pinMode(PRECHARGE_PIN, OUTPUT);
|
||||||
|
digitalWrite(PRECHARGE_PIN, LOW);
|
||||||
|
digitalWrite(POSITIVE_CONTACTOR_PIN, LOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main functions
|
||||||
|
void handle_precharge_control() {
|
||||||
|
unsigned long currentTime = millis();
|
||||||
|
#ifdef MEB_BATTERY
|
||||||
|
int32_t target_voltage = datalayer.battery.status.voltage_dV;
|
||||||
|
int32_t external_voltage = datalayer_extended.meb.BMS_voltage_intermediate_dV;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Handle actual state machine. This first turns on Negative, then Precharge, then Positive, and finally turns OFF precharge
|
||||||
|
switch (prechargeStatus) {
|
||||||
|
case PRECHARGE_IDLE:
|
||||||
|
|
||||||
|
if (datalayer.battery.status.bms_status != FAULT && datalayer.battery.status.real_bms_status == BMS_STANDBY &&
|
||||||
|
datalayer.system.status.inverter_allows_contactor_closing &&
|
||||||
|
!datalayer.system.settings.equipment_stop_active) {
|
||||||
|
prechargeStatus = START_PRECHARGE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case START_PRECHARGE:
|
||||||
|
freq = Precharge_default_PWM_Freq;
|
||||||
|
ledcAttachChannel(PRECHARGE_PIN, freq, PWM_Res, PWM_Precharge_Channel);
|
||||||
|
ledcWriteTone(PRECHARGE_PIN, freq); // Set frequency and set dutycycle to 50%
|
||||||
|
prechargeStartTime = currentTime;
|
||||||
|
prechargeStatus = PRECHARGE;
|
||||||
|
#ifdef DEBUG_LOG
|
||||||
|
logging.printf("Precharge: Starting sequence\n");
|
||||||
|
#endif
|
||||||
|
digitalWrite(POSITIVE_CONTACTOR_PIN, HIGH);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PRECHARGE:
|
||||||
|
// Check if external voltage measurement changed, for instance with the MEB batteries, the external voltage is only updated every 100ms.
|
||||||
|
if (prev_external_voltage != external_voltage) {
|
||||||
|
prev_external_voltage = external_voltage;
|
||||||
|
|
||||||
|
if (labs(target_voltage - external_voltage) > 150) {
|
||||||
|
delta_freq = 2000;
|
||||||
|
} else if (labs(target_voltage - external_voltage) > 80) {
|
||||||
|
delta_freq = labs(target_voltage - external_voltage) * 6;
|
||||||
|
} else {
|
||||||
|
delta_freq = labs(target_voltage - external_voltage) * 3;
|
||||||
|
}
|
||||||
|
if (target_voltage > external_voltage) {
|
||||||
|
freq += delta_freq;
|
||||||
|
} else {
|
||||||
|
freq -= delta_freq;
|
||||||
|
}
|
||||||
|
if (freq > Precharge_max_PWM_Freq)
|
||||||
|
freq = Precharge_max_PWM_Freq;
|
||||||
|
if (freq < Precharge_min_PWM_Freq)
|
||||||
|
freq = Precharge_min_PWM_Freq;
|
||||||
|
#ifdef DEBUG_LOG
|
||||||
|
logging.printf("Precharge: Target: %d V Extern: %d V Frequency: %u\n", target_voltage / 10,
|
||||||
|
external_voltage / 10, freq);
|
||||||
|
#endif
|
||||||
|
ledcWriteTone(PRECHARGE_PIN, freq);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((datalayer.battery.status.real_bms_status != BMS_STANDBY &&
|
||||||
|
datalayer.battery.status.real_bms_status != BMS_ACTIVE) ||
|
||||||
|
datalayer.battery.status.bms_status != ACTIVE || datalayer.system.settings.equipment_stop_active) {
|
||||||
|
pinMode(PRECHARGE_PIN, OUTPUT);
|
||||||
|
digitalWrite(PRECHARGE_PIN, LOW);
|
||||||
|
digitalWrite(POSITIVE_CONTACTOR_PIN, LOW);
|
||||||
|
prechargeStatus = PRECHARGE_IDLE;
|
||||||
|
#ifdef DEBUG_LOG
|
||||||
|
logging.printf("Precharge: Disabling Precharge bms not standby/active or equipment stop\n");
|
||||||
|
#endif
|
||||||
|
} else if (currentTime - prechargeStartTime >= MAX_PRECHARGE_TIME_MS) {
|
||||||
|
pinMode(PRECHARGE_PIN, OUTPUT);
|
||||||
|
digitalWrite(PRECHARGE_PIN, LOW);
|
||||||
|
digitalWrite(POSITIVE_CONTACTOR_PIN, LOW);
|
||||||
|
prechargeStatus = PRECHARGE_OFF;
|
||||||
|
#ifdef DEBUG_LOG
|
||||||
|
logging.printf("Precharge: Disabled (timeout reached) -> PRECHARGE_OFF\n");
|
||||||
|
#endif
|
||||||
|
set_event(EVENT_AUTOMATIC_PRECHARGE_FAILURE, 0);
|
||||||
|
|
||||||
|
// Add event
|
||||||
|
} else if (datalayer.system.status.battery_allows_contactor_closing) {
|
||||||
|
pinMode(PRECHARGE_PIN, OUTPUT);
|
||||||
|
digitalWrite(PRECHARGE_PIN, LOW);
|
||||||
|
digitalWrite(POSITIVE_CONTACTOR_PIN, LOW);
|
||||||
|
prechargeStatus = COMPLETED;
|
||||||
|
#ifdef DEBUG_LOG
|
||||||
|
logging.printf("Precharge: Disabled (contacts closed) -> COMPLETED\n");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case COMPLETED:
|
||||||
|
if (datalayer.system.settings.equipment_stop_active || datalayer.battery.status.bms_status != ACTIVE) {
|
||||||
|
prechargeStatus = PRECHARGE_IDLE;
|
||||||
|
#ifdef DEBUG_LOG
|
||||||
|
logging.printf("Precharge: equipment stop activated -> IDLE\n");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PRECHARGE_OFF:
|
||||||
|
if (!datalayer.system.status.battery_allows_contactor_closing ||
|
||||||
|
!datalayer.system.status.inverter_allows_contactor_closing ||
|
||||||
|
datalayer.system.settings.equipment_stop_active || datalayer.battery.status.bms_status != FAULT) {
|
||||||
|
prechargeStatus = PRECHARGE_IDLE;
|
||||||
|
pinMode(PRECHARGE_PIN, OUTPUT);
|
||||||
|
digitalWrite(PRECHARGE_PIN, LOW);
|
||||||
|
#ifdef DEBUG_LOG
|
||||||
|
logging.printf("Precharge: equipment stop activated -> IDLE\n");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif // PRECHARGE_CONTROL
|
|
@ -0,0 +1,35 @@
|
||||||
|
#ifndef _PRECHARGE_CONTROL_H_
|
||||||
|
#define _PRECHARGE_CONTROL_H_
|
||||||
|
|
||||||
|
#include "../../include.h"
|
||||||
|
|
||||||
|
#include "../../devboard/utils/events.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Contactor initialization
|
||||||
|
*
|
||||||
|
* @param[in] void
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
void init_precharge_control();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Handle contactors
|
||||||
|
*
|
||||||
|
* @param[in] void
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
void handle_precharge_control();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Handle contactors of battery 2
|
||||||
|
*
|
||||||
|
* @param[in] void
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
void handle_contactors_battery2();
|
||||||
|
|
||||||
|
#endif // _PRECHARGE_CONTROL_H_
|
|
@ -92,8 +92,11 @@ typedef struct {
|
||||||
uint8_t CAN_battery_still_alive = CAN_STILL_ALIVE;
|
uint8_t CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
|
|
||||||
/** Other */
|
/** Other */
|
||||||
/** The current BMS status */
|
/** The current system status, which for now still has the name bms_status */
|
||||||
bms_status_enum bms_status = ACTIVE;
|
bms_status_enum bms_status = ACTIVE;
|
||||||
|
|
||||||
|
/** The current battery status, which for now has the name real_bms_status */
|
||||||
|
real_bms_status_enum real_bms_status = BMS_DISCONNECTED;
|
||||||
} DATALAYER_BATTERY_STATUS_TYPE;
|
} DATALAYER_BATTERY_STATUS_TYPE;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|
|
@ -91,15 +91,17 @@ void update_machineryprotection() {
|
||||||
|
|
||||||
// Battery is empty. Do not allow further discharge.
|
// Battery is empty. Do not allow further discharge.
|
||||||
// Normally the BMS will send 0W allowed, but this acts as an additional layer of safety
|
// Normally the BMS will send 0W allowed, but this acts as an additional layer of safety
|
||||||
if (datalayer.battery.status.reported_soc == 0) { //Scaled SOC% value is 0.00%
|
if (datalayer.battery.status.bms_status == ACTIVE) {
|
||||||
if (!battery_empty_event_fired) {
|
if (datalayer.battery.status.reported_soc == 0) { //Scaled SOC% value is 0.00%
|
||||||
set_event(EVENT_BATTERY_EMPTY, 0);
|
if (!battery_empty_event_fired) {
|
||||||
battery_empty_event_fired = true;
|
set_event(EVENT_BATTERY_EMPTY, 0);
|
||||||
|
battery_empty_event_fired = true;
|
||||||
|
}
|
||||||
|
datalayer.battery.status.max_discharge_power_W = 0;
|
||||||
|
} else {
|
||||||
|
clear_event(EVENT_BATTERY_EMPTY);
|
||||||
|
battery_empty_event_fired = false;
|
||||||
}
|
}
|
||||||
datalayer.battery.status.max_discharge_power_W = 0;
|
|
||||||
} else {
|
|
||||||
clear_event(EVENT_BATTERY_EMPTY);
|
|
||||||
battery_empty_event_fired = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Battery is extremely degraded, not fit for secondlifestorage!
|
// Battery is extremely degraded, not fit for secondlifestorage!
|
||||||
|
@ -341,10 +343,16 @@ void emulator_pause_state_transmit_can_battery() {
|
||||||
allowed_to_send_CAN = (!emulator_pause_CAN_send_ON || emulator_pause_status == NORMAL);
|
allowed_to_send_CAN = (!emulator_pause_CAN_send_ON || emulator_pause_status == NORMAL);
|
||||||
|
|
||||||
if (previous_allowed_to_send_CAN && !allowed_to_send_CAN) {
|
if (previous_allowed_to_send_CAN && !allowed_to_send_CAN) {
|
||||||
|
#ifdef DEBUG_LOG
|
||||||
|
logging.printf("Safety: Pausing CAN sending");
|
||||||
|
#endif
|
||||||
//completely force stop the CAN communication
|
//completely force stop the CAN communication
|
||||||
ESP32Can.CANStop();
|
ESP32Can.CANStop();
|
||||||
} else if (!previous_allowed_to_send_CAN && allowed_to_send_CAN) {
|
} else if (!previous_allowed_to_send_CAN && allowed_to_send_CAN) {
|
||||||
//resume CAN communication
|
//resume CAN communication
|
||||||
|
#ifdef DEBUG_LOG
|
||||||
|
logging.printf("Safety: Resuming CAN sending");
|
||||||
|
#endif
|
||||||
ESP32Can.CANInit();
|
ESP32Can.CANInit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -153,6 +153,7 @@ void init_events(void) {
|
||||||
events.entries[EVENT_BALANCING_END].level = EVENT_LEVEL_INFO;
|
events.entries[EVENT_BALANCING_END].level = EVENT_LEVEL_INFO;
|
||||||
events.entries[EVENT_BATTERY_EMPTY].level = EVENT_LEVEL_INFO;
|
events.entries[EVENT_BATTERY_EMPTY].level = EVENT_LEVEL_INFO;
|
||||||
events.entries[EVENT_BATTERY_FULL].level = EVENT_LEVEL_INFO;
|
events.entries[EVENT_BATTERY_FULL].level = EVENT_LEVEL_INFO;
|
||||||
|
events.entries[EVENT_BATTERY_FUSE].level = EVENT_LEVEL_WARNING;
|
||||||
events.entries[EVENT_BATTERY_FROZEN].level = EVENT_LEVEL_INFO;
|
events.entries[EVENT_BATTERY_FROZEN].level = EVENT_LEVEL_INFO;
|
||||||
events.entries[EVENT_BATTERY_CAUTION].level = EVENT_LEVEL_INFO;
|
events.entries[EVENT_BATTERY_CAUTION].level = EVENT_LEVEL_INFO;
|
||||||
events.entries[EVENT_BATTERY_CHG_STOP_REQ].level = EVENT_LEVEL_ERROR;
|
events.entries[EVENT_BATTERY_CHG_STOP_REQ].level = EVENT_LEVEL_ERROR;
|
||||||
|
@ -168,6 +169,7 @@ void init_events(void) {
|
||||||
events.entries[EVENT_SOH_LOW].level = EVENT_LEVEL_ERROR;
|
events.entries[EVENT_SOH_LOW].level = EVENT_LEVEL_ERROR;
|
||||||
events.entries[EVENT_HVIL_FAILURE].level = EVENT_LEVEL_ERROR;
|
events.entries[EVENT_HVIL_FAILURE].level = EVENT_LEVEL_ERROR;
|
||||||
events.entries[EVENT_PRECHARGE_FAILURE].level = EVENT_LEVEL_INFO;
|
events.entries[EVENT_PRECHARGE_FAILURE].level = EVENT_LEVEL_INFO;
|
||||||
|
events.entries[EVENT_AUTOMATIC_PRECHARGE_FAILURE].level = EVENT_LEVEL_ERROR;
|
||||||
events.entries[EVENT_INTERNAL_OPEN_FAULT].level = EVENT_LEVEL_ERROR;
|
events.entries[EVENT_INTERNAL_OPEN_FAULT].level = EVENT_LEVEL_ERROR;
|
||||||
events.entries[EVENT_INVERTER_OPEN_CONTACTOR].level = EVENT_LEVEL_INFO;
|
events.entries[EVENT_INVERTER_OPEN_CONTACTOR].level = EVENT_LEVEL_INFO;
|
||||||
events.entries[EVENT_INTERFACE_MISSING].level = EVENT_LEVEL_INFO;
|
events.entries[EVENT_INTERFACE_MISSING].level = EVENT_LEVEL_INFO;
|
||||||
|
@ -304,6 +306,8 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
|
||||||
return "Battery is completely discharged";
|
return "Battery is completely discharged";
|
||||||
case EVENT_BATTERY_FULL:
|
case EVENT_BATTERY_FULL:
|
||||||
return "Battery is fully charged";
|
return "Battery is fully charged";
|
||||||
|
case EVENT_BATTERY_FUSE:
|
||||||
|
return "Battery internal fuse blown. Inspect battery";
|
||||||
case EVENT_BATTERY_FROZEN:
|
case EVENT_BATTERY_FROZEN:
|
||||||
return "Battery is too cold to operate optimally. Consider warming it up!";
|
return "Battery is too cold to operate optimally. Consider warming it up!";
|
||||||
case EVENT_BATTERY_CAUTION:
|
case EVENT_BATTERY_CAUTION:
|
||||||
|
@ -340,6 +344,8 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
|
||||||
"Battery will be disabled!";
|
"Battery will be disabled!";
|
||||||
case EVENT_PRECHARGE_FAILURE:
|
case EVENT_PRECHARGE_FAILURE:
|
||||||
return "Battery failed to precharge. Check that capacitor is seated on high voltage output.";
|
return "Battery failed to precharge. Check that capacitor is seated on high voltage output.";
|
||||||
|
case EVENT_AUTOMATIC_PRECHARGE_FAILURE:
|
||||||
|
return "Automatic precharge failed to reach target voltae.";
|
||||||
case EVENT_INTERNAL_OPEN_FAULT:
|
case EVENT_INTERNAL_OPEN_FAULT:
|
||||||
return "High voltage cable removed while battery running. Opening contactors!";
|
return "High voltage cable removed while battery running. Opening contactors!";
|
||||||
case EVENT_INVERTER_OPEN_CONTACTOR:
|
case EVENT_INVERTER_OPEN_CONTACTOR:
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
// #define INCLUDE_EVENTS_TEST // Enable to run an event test loop, see events_test_on_target.cpp
|
// #define INCLUDE_EVENTS_TEST // Enable to run an event test loop, see events_test_on_target.cpp
|
||||||
|
|
||||||
#define EE_MAGIC_HEADER_VALUE 0x0020 // 0x0000 to 0xFFFF
|
#define EE_MAGIC_HEADER_VALUE 0x0021 // 0x0000 to 0xFFFF
|
||||||
|
|
||||||
#define GENERATE_ENUM(ENUM) ENUM,
|
#define GENERATE_ENUM(ENUM) ENUM,
|
||||||
#define GENERATE_STRING(STRING) #STRING,
|
#define GENERATE_STRING(STRING) #STRING,
|
||||||
|
@ -49,6 +49,7 @@
|
||||||
XX(EVENT_BALANCING_END) \
|
XX(EVENT_BALANCING_END) \
|
||||||
XX(EVENT_BATTERY_EMPTY) \
|
XX(EVENT_BATTERY_EMPTY) \
|
||||||
XX(EVENT_BATTERY_FULL) \
|
XX(EVENT_BATTERY_FULL) \
|
||||||
|
XX(EVENT_BATTERY_FUSE) \
|
||||||
XX(EVENT_BATTERY_FROZEN) \
|
XX(EVENT_BATTERY_FROZEN) \
|
||||||
XX(EVENT_BATTERY_CAUTION) \
|
XX(EVENT_BATTERY_CAUTION) \
|
||||||
XX(EVENT_BATTERY_CHG_STOP_REQ) \
|
XX(EVENT_BATTERY_CHG_STOP_REQ) \
|
||||||
|
@ -112,6 +113,7 @@
|
||||||
XX(EVENT_MQTT_CONNECT) \
|
XX(EVENT_MQTT_CONNECT) \
|
||||||
XX(EVENT_MQTT_DISCONNECT) \
|
XX(EVENT_MQTT_DISCONNECT) \
|
||||||
XX(EVENT_EQUIPMENT_STOP) \
|
XX(EVENT_EQUIPMENT_STOP) \
|
||||||
|
XX(EVENT_AUTOMATIC_PRECHARGE_FAILURE) \
|
||||||
XX(EVENT_NOF_EVENTS)
|
XX(EVENT_NOF_EVENTS)
|
||||||
|
|
||||||
typedef enum { EVENTS_ENUM_TYPE(GENERATE_ENUM) } EVENTS_ENUM_TYPE;
|
typedef enum { EVENTS_ENUM_TYPE(GENERATE_ENUM) } EVENTS_ENUM_TYPE;
|
||||||
|
|
|
@ -134,3 +134,31 @@ void Logging::printf(const char* fmt, ...) {
|
||||||
previous_message_was_newline = message_buffer[size - 1] == '\n';
|
previous_message_was_newline = message_buffer[size - 1] == '\n';
|
||||||
#endif // DEBUG_LOG
|
#endif // DEBUG_LOG
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Logging::log_bms_status(real_bms_status_enum bms_status, int battery_id) {
|
||||||
|
static real_bms_status_enum previous_state = BMS_FAULT;
|
||||||
|
const char* id = "";
|
||||||
|
if (battery_id == 2) {
|
||||||
|
id = "2";
|
||||||
|
}
|
||||||
|
if (previous_state != bms_status) {
|
||||||
|
switch (bms_status) {
|
||||||
|
case BMS_ACTIVE:
|
||||||
|
logging.printf("Battery%s BMS state changed to: OK\n", id);
|
||||||
|
break;
|
||||||
|
case BMS_DISCONNECTED:
|
||||||
|
logging.printf("Battery%s BMS state changed to: DISCONNECTED\n");
|
||||||
|
break;
|
||||||
|
case BMS_FAULT:
|
||||||
|
logging.printf("Battery%s BMS state changed to: FAULT\n");
|
||||||
|
break;
|
||||||
|
case BMS_STANDBY:
|
||||||
|
logging.printf("Battery%s BMS state changed to: STANDBY\n");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
logging.printf("Battery%s BMS state changed to: ??\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
previous_state = bms_status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include "Print.h"
|
#include "Print.h"
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
class Logging : public Print {
|
class Logging : public Print {
|
||||||
void add_timestamp(size_t size);
|
void add_timestamp(size_t size);
|
||||||
|
@ -11,6 +12,7 @@ class Logging : public Print {
|
||||||
virtual size_t write(const uint8_t* buffer, size_t size);
|
virtual size_t write(const uint8_t* buffer, size_t size);
|
||||||
virtual size_t write(uint8_t) { return 0; }
|
virtual size_t write(uint8_t) { return 0; }
|
||||||
void printf(const char* fmt, ...);
|
void printf(const char* fmt, ...);
|
||||||
|
void log_bms_status(real_bms_status_enum bms_status, int battery_id);
|
||||||
Logging() {}
|
Logging() {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
enum bms_status_enum { STANDBY = 0, INACTIVE = 1, DARKSTART = 2, ACTIVE = 3, FAULT = 4, UPDATING = 5 };
|
enum bms_status_enum { STANDBY = 0, INACTIVE = 1, DARKSTART = 2, ACTIVE = 3, FAULT = 4, UPDATING = 5 };
|
||||||
|
enum real_bms_status_enum { BMS_DISCONNECTED = 0, BMS_STANDBY = 1, BMS_ACTIVE = 2, BMS_FAULT = 3 };
|
||||||
enum battery_chemistry_enum { NCA, NMC, LFP };
|
enum battery_chemistry_enum { NCA, NMC, LFP };
|
||||||
enum led_color { GREEN, YELLOW, RED, BLUE };
|
enum led_color { GREEN, YELLOW, RED, BLUE };
|
||||||
|
|
||||||
|
|
|
@ -981,21 +981,24 @@ String advanced_battery_processor(const String& var) {
|
||||||
content +=
|
content +=
|
||||||
datalayer_extended.meb.battery_heating ? "<h4>Battery heating: Active!</h4>" : "<h4>Battery heating: Off</h4>";
|
datalayer_extended.meb.battery_heating ? "<h4>Battery heating: Active!</h4>" : "<h4>Battery heating: Off</h4>";
|
||||||
const char* rt_enum[] = {"No", "Error level 1", "Error level 2", "Error level 3"};
|
const char* rt_enum[] = {"No", "Error level 1", "Error level 2", "Error level 3"};
|
||||||
content += "<h4>Overcurrent: " + String(rt_enum[datalayer_extended.meb.rt_overcurrent]) + "</h4>";
|
content += "<h4>Overcurrent: " + String(rt_enum[datalayer_extended.meb.rt_overcurrent & 0x03]) + "</h4>";
|
||||||
content += "<h4>CAN fault: " + String(rt_enum[datalayer_extended.meb.rt_CAN_fault]) + "</h4>";
|
content += "<h4>CAN fault: " + String(rt_enum[datalayer_extended.meb.rt_CAN_fault & 0x03]) + "</h4>";
|
||||||
content += "<h4>Overcharged: " + String(rt_enum[datalayer_extended.meb.rt_overcharge]) + "</h4>";
|
content += "<h4>Overcharged: " + String(rt_enum[datalayer_extended.meb.rt_overcharge & 0x03]) + "</h4>";
|
||||||
content += "<h4>SOC too high: " + String(rt_enum[datalayer_extended.meb.rt_SOC_high]) + "</h4>";
|
content += "<h4>SOC too high: " + String(rt_enum[datalayer_extended.meb.rt_SOC_high & 0x03]) + "</h4>";
|
||||||
content += "<h4>SOC too low: " + String(rt_enum[datalayer_extended.meb.rt_SOC_low]) + "</h4>";
|
content += "<h4>SOC too low: " + String(rt_enum[datalayer_extended.meb.rt_SOC_low & 0x03]) + "</h4>";
|
||||||
content += "<h4>SOC jumping: " + String(rt_enum[datalayer_extended.meb.rt_SOC_jumping]) + "</h4>";
|
content += "<h4>SOC jumping: " + String(rt_enum[datalayer_extended.meb.rt_SOC_jumping & 0x03]) + "</h4>";
|
||||||
content += "<h4>Temp difference: " + String(rt_enum[datalayer_extended.meb.rt_temp_difference]) + "</h4>";
|
content += "<h4>Temp difference: " + String(rt_enum[datalayer_extended.meb.rt_temp_difference & 0x03]) + "</h4>";
|
||||||
content += "<h4>Cell overtemp: " + String(rt_enum[datalayer_extended.meb.rt_cell_overtemp]) + "</h4>";
|
content += "<h4>Cell overtemp: " + String(rt_enum[datalayer_extended.meb.rt_cell_overtemp & 0x03]) + "</h4>";
|
||||||
content += "<h4>Cell undertemp: " + String(rt_enum[datalayer_extended.meb.rt_cell_undertemp]) + "</h4>";
|
content += "<h4>Cell undertemp: " + String(rt_enum[datalayer_extended.meb.rt_cell_undertemp & 0x03]) + "</h4>";
|
||||||
content += "<h4>Battery overvoltage: " + String(rt_enum[datalayer_extended.meb.rt_battery_overvolt]) + "</h4>";
|
content +=
|
||||||
content += "<h4>Battery undervoltage: " + String(rt_enum[datalayer_extended.meb.rt_battery_undervol]) + "</h4>";
|
"<h4>Battery overvoltage: " + String(rt_enum[datalayer_extended.meb.rt_battery_overvolt & 0x03]) + "</h4>";
|
||||||
content += "<h4>Cell overvoltage: " + String(rt_enum[datalayer_extended.meb.rt_cell_overvolt]) + "</h4>";
|
content +=
|
||||||
content += "<h4>Cell undervoltage: " + String(rt_enum[datalayer_extended.meb.rt_cell_undervol]) + "</h4>";
|
"<h4>Battery undervoltage: " + String(rt_enum[datalayer_extended.meb.rt_battery_undervol & 0x03]) + "</h4>";
|
||||||
content += "<h4>Cell imbalance: " + String(rt_enum[datalayer_extended.meb.rt_cell_imbalance]) + "</h4>";
|
content += "<h4>Cell overvoltage: " + String(rt_enum[datalayer_extended.meb.rt_cell_overvolt & 0x03]) + "</h4>";
|
||||||
content += "<h4>Battery unathorized: " + String(rt_enum[datalayer_extended.meb.rt_battery_unathorized]) + "</h4>";
|
content += "<h4>Cell undervoltage: " + String(rt_enum[datalayer_extended.meb.rt_cell_undervol & 0x03]) + "</h4>";
|
||||||
|
content += "<h4>Cell imbalance: " + String(rt_enum[datalayer_extended.meb.rt_cell_imbalance & 0x03]) + "</h4>";
|
||||||
|
content +=
|
||||||
|
"<h4>Battery unathorized: " + String(rt_enum[datalayer_extended.meb.rt_battery_unathorized & 0x03]) + "</h4>";
|
||||||
#endif //MEB_BATTERY
|
#endif //MEB_BATTERY
|
||||||
|
|
||||||
#ifdef RENAULT_ZOE_GEN2_BATTERY
|
#ifdef RENAULT_ZOE_GEN2_BATTERY
|
||||||
|
|
|
@ -922,6 +922,28 @@ String processor(const String& var) {
|
||||||
}
|
}
|
||||||
content += "</h4>";
|
content += "</h4>";
|
||||||
|
|
||||||
|
#ifdef MEB_BATTERY
|
||||||
|
content += "<h4>Battery BMS status: ";
|
||||||
|
switch (datalayer.battery.status.real_bms_status) {
|
||||||
|
case BMS_ACTIVE:
|
||||||
|
content += String("OK");
|
||||||
|
break;
|
||||||
|
case BMS_FAULT:
|
||||||
|
content += String("FAULT");
|
||||||
|
break;
|
||||||
|
case BMS_DISCONNECTED:
|
||||||
|
content += String("DISCONNECTED");
|
||||||
|
break;
|
||||||
|
case BMS_STANDBY:
|
||||||
|
content += String("STANDBY");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
content += String("??");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
content += "</h4>";
|
||||||
|
#endif
|
||||||
|
|
||||||
if (datalayer.battery.status.current_dA == 0) {
|
if (datalayer.battery.status.current_dA == 0) {
|
||||||
content += "<h4>Battery idle</h4>";
|
content += "<h4>Battery idle</h4>";
|
||||||
} else if (datalayer.battery.status.current_dA < 0) {
|
} else if (datalayer.battery.status.current_dA < 0) {
|
||||||
|
|
|
@ -196,12 +196,16 @@ void update_values_can_inverter() { //This function maps all the values fetched
|
||||||
|
|
||||||
// Status=Bit 0,1,2= 0:Sleep, 1:Charge, 2:Discharge 3:Idle. Bit3 ForceChargeReq. Bit4 Balance charge Request
|
// Status=Bit 0,1,2= 0:Sleep, 1:Charge, 2:Discharge 3:Idle. Bit3 ForceChargeReq. Bit4 Balance charge Request
|
||||||
if (datalayer.battery.status.bms_status == FAULT) {
|
if (datalayer.battery.status.bms_status == FAULT) {
|
||||||
|
PYLON_4250.data.u8[0] = (0x00); // Sleep
|
||||||
PYLON_4251.data.u8[0] = (0x00); // Sleep
|
PYLON_4251.data.u8[0] = (0x00); // Sleep
|
||||||
} else if (datalayer.battery.status.current_dA < 0) {
|
} else if (datalayer.battery.status.current_dA < 0) {
|
||||||
|
PYLON_4250.data.u8[0] = (0x01); // Charge
|
||||||
PYLON_4251.data.u8[0] = (0x01); // Charge
|
PYLON_4251.data.u8[0] = (0x01); // Charge
|
||||||
} else if (datalayer.battery.status.current_dA > 0) {
|
} else if (datalayer.battery.status.current_dA > 0) {
|
||||||
|
PYLON_4250.data.u8[0] = (0x02); // Discharge
|
||||||
PYLON_4251.data.u8[0] = (0x02); // Discharge
|
PYLON_4251.data.u8[0] = (0x02); // Discharge
|
||||||
} else if (datalayer.battery.status.current_dA == 0) {
|
} else if (datalayer.battery.status.current_dA == 0) {
|
||||||
|
PYLON_4250.data.u8[0] = (0x03); // Idle
|
||||||
PYLON_4251.data.u8[0] = (0x03); // Idle
|
PYLON_4251.data.u8[0] = (0x03); // Idle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue