Merge branch 'main' into feature/stellantis-ecmp-values

This commit is contained in:
Daniel Öster 2025-05-23 11:11:35 +03:00
commit 2f9a7b05e7
147 changed files with 11079 additions and 10116 deletions

View file

@ -1,5 +1,5 @@
# This is the name of the workflow, visible on GitHub UI. # This is the name of the workflow, visible on GitHub UI.
name: Compile All Batteries name: 🔋 Compile All Batteries
# Here we tell GitHub when to run the workflow. # Here we tell GitHub when to run the workflow.
on: on:

View file

@ -1,5 +1,5 @@
# This is the name of the workflow, visible on GitHub UI. # This is the name of the workflow, visible on GitHub UI.
name: Compile All Combinations name: 🔌🔋💫 Compile All Combinations
# Here we tell GitHub when to run the workflow. # Here we tell GitHub when to run the workflow.
on: on:

View file

@ -0,0 +1,93 @@
# This is the name of the workflow, visible on GitHub UI.
name: 🔋🔋 Compile All Double Batteries
# Here we tell GitHub when to run the workflow.
on:
# The workflow is run when a commit is pushed or for a
# Pull Request.
- push
- pull_request
# This is the list of jobs that will be run concurrently.
jobs:
# This pre-job is run to skip workflows in case a workflow is already run, i.e. because the workflow is triggered by both push and pull_request
skip-duplicate-actions:
runs-on: ubuntu-latest
# Map a step output to a job output
outputs:
should_skip: ${{ steps.skip_check.outputs.should_skip }}
steps:
- id: skip_check
uses: fkirc/skip-duplicate-actions@v5
with:
# All of these options are optional, so you can remove them if you are happy with the defaults
concurrent_skipping: 'never'
skip_after_successful_duplicate: 'true'
do_not_skip: '["pull_request", "workflow_dispatch", "schedule"]'
# Since we use a build matrix, the actual number of jobs
# started depends on how many configurations the matrix
# will produce.
build-batteries:
needs: skip-duplicate-actions
if: needs.skip-duplicate-actions.outputs.should_skip != 'true'
# Here we tell GitHub that the jobs must be determined
# dynamically depending on a matrix configuration.
strategy:
# The matrix will produce one job for each combination of parameters.
matrix:
# This is the development board hardware for which the code will be compiled.
# FBQN stands for "fully qualified board name", and is used by Arduino to define the hardware to compile for.
fqbn:
- esp32:esp32:esp32
# further ESP32 chips
#- esp32:esp32:esp32c3
#- esp32:esp32:esp32c2
#- esp32:esp32:esp32c6
#- esp32:esp32:esp32h2
#- esp32:esp32:esp32s3
# These are the batteries for which the code will be compiled.
battery:
- BMW_I3_BATTERY
- BYD_ATTO_3_BATTERY
- KIA_HYUNDAI_64_BATTERY
- PYLON_BATTERY
- SANTA_FE_PHEV_BATTERY
- TEST_FAKE_BATTERY
# These are the emulated inverter communication protocols for which the code will be compiled.
inverter:
- BYD_CAN
# These are the supported hardware platforms for which the code will be compiled.
hardware:
- HW_LILYGO
# This is the platform GitHub will use to run our workflow.
runs-on: ubuntu-latest
# This is the list of steps this job will run.
steps:
# First we clone the repo using the `checkout` action.
- name: Checkout
uses: actions/checkout@v4
# Copy USER_SECRETS.TEMPLATE.h to USER_SECRETS.h
- name: Copy USER_SECRETS.TEMPLATE.h to USER_SECRETS.h
run: cp ./Software/USER_SECRETS.TEMPLATE.h ./Software/USER_SECRETS.h
# We use the `arduino/setup-arduino-cli` action to install and
# configure the Arduino CLI on the system.
- name: Setup Arduino CLI
uses: arduino/setup-arduino-cli@v2
# We then install the platform.
- name: Install platform
run: |
arduino-cli core update-index
arduino-cli core install esp32:esp32
# Finally, we compile the sketch, using the FQBN that was set
# in the build matrix, and using build flags to define the
# battery and inverter set in the build matrix.
- name: Compile Sketch
run: arduino-cli compile --fqbn ${{ matrix.fqbn }} --build-property "build.extra_flags=-Wall -Wextra -Wpedantic -Werror -DESP32 -DDOUBLE_BATTERY -D${{ matrix.battery}} -D${{ matrix.inverter}} -D${{ matrix.hardware}}" ./Software

View file

@ -1,5 +1,5 @@
# This is the name of the workflow, visible on GitHub UI. # This is the name of the workflow, visible on GitHub UI.
name: Compile All Hardware name: 🤖 Compile All Hardware
# Here we tell GitHub when to run the workflow. # Here we tell GitHub when to run the workflow.
on: on:

View file

@ -1,5 +1,5 @@
# This is the name of the workflow, visible on GitHub UI. # This is the name of the workflow, visible on GitHub UI.
name: Compile All Inverters name: 🔌 Compile All Inverters
# Here we tell GitHub when to run the workflow. # Here we tell GitHub when to run the workflow.
on: on:

View file

@ -1,6 +1,6 @@
# This GithHub Action runs the pre-commit defined in the .pre-commit-config.yaml file # This GithHub Action runs the pre-commit defined in the .pre-commit-config.yaml file
name: Run pre-commit name: 📝 Run pre-commit
on: on:
- push - push

View file

@ -1,4 +1,4 @@
name: Run Unit Tests name: ⚙️ Run Unit Tests
on: [push, pull_request] on: [push, pull_request]

View file

@ -49,11 +49,7 @@ Start by watching this [quickstart guide](https://www.youtube.com/watch?v=hcl2Gd
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.
4. Click `Tools` menu -> `Board: "...."` -> `Boards Manager...`, install the `esp32` package by `Espressif Systems` (not `Arduino ESP32 Boards`), then press `Close`. 4. Click `Tools` menu -> `Board: "...."` -> `Boards Manager...`, install the `esp32` package by `Espressif Systems` (not `Arduino ESP32 Boards`), then press `Close`.
**NOTE: The ESP32 version depends on which release of Battery-Emulator you are running!** **NOTE: The ESP32 version depends on which release of Battery-Emulator you are running! See the [Release Notes](https://github.com/dalathegreat/Battery-Emulator/releases) for more info on which version to use with the current version (Suggested ESP32 version X.Y.Z)**
- ⚠️ Make sure to use a 2.x.x version if you are on a release **older** than 6.0.0 (For instance ESP32 v2.0.11 when using Battery-Emulator v5.4.0)
- ⚠️ Make sure to use a 3.0.x version if you are on a release **newer** than 6.0.0 (For instance ESP32 v3.0.0 when using Battery-Emulator v6.0.0)
- ⚠️ Make sure to use a 3.1.x version if you are on a release **newer** than 8.0.0 (For instance ESP32 v3.1.0 when using Battery-Emulator v8.0.0)
![bild](https://github.com/dalathegreat/Battery-Emulator/assets/26695010/6a2414b1-f2ca-4746-8e8d-9afd78bd9252) ![bild](https://github.com/dalathegreat/Battery-Emulator/assets/26695010/6a2414b1-f2ca-4746-8e8d-9afd78bd9252)

View file

@ -101,9 +101,8 @@ void setup() {
init_precharge_control(); init_precharge_control();
#endif // PRECHARGE_CONTROL #endif // PRECHARGE_CONTROL
#if defined(CAN_INVERTER_SELECTED) || defined(MODBUS_INVERTER_SELECTED) || defined(RS485_INVERTER_SELECTED) setup_charger();
setup_inverter(); setup_inverter();
#endif
setup_battery(); setup_battery();
init_rs485(); init_rs485();
@ -242,7 +241,7 @@ void core_loop(void*) {
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 #ifdef PRECHARGE_CONTROL
handle_precharge_control(); handle_precharge_control(currentMillis);
#endif // PRECHARGE_CONTROL #endif // PRECHARGE_CONTROL
#ifdef FUNCTION_TIME_MEASUREMENT #ifdef FUNCTION_TIME_MEASUREMENT
END_TIME_MEASUREMENT_MAX(time_10ms, datalayer.system.status.time_10ms_us); END_TIME_MEASUREMENT_MAX(time_10ms, datalayer.system.status.time_10ms_us);
@ -274,7 +273,7 @@ void core_loop(void*) {
transmit_can(currentMillis); // Send CAN messages to all components transmit_can(currentMillis); // Send CAN messages to all components
#ifdef RS485_BATTERY_SELECTED #ifdef RS485_BATTERY_SELECTED
transmit_rs485(); transmit_rs485(currentMillis);
#endif // RS485_BATTERY_SELECTED #endif // RS485_BATTERY_SELECTED
#ifdef FUNCTION_TIME_MEASUREMENT #ifdef FUNCTION_TIME_MEASUREMENT
END_TIME_MEASUREMENT_MAX(cantx, datalayer.system.status.time_cantx_us); END_TIME_MEASUREMENT_MAX(cantx, datalayer.system.status.time_cantx_us);
@ -332,9 +331,9 @@ void check_interconnect_available() {
clear_event(EVENT_VOLTAGE_DIFFERENCE); clear_event(EVENT_VOLTAGE_DIFFERENCE);
if (datalayer.battery.status.bms_status == FAULT) { if (datalayer.battery.status.bms_status == FAULT) {
// If main battery is in fault state, disengage the second battery // If main battery is in fault state, disengage the second battery
datalayer.system.status.battery2_allows_contactor_closing = false; datalayer.system.status.battery2_allowed_contactor_closing = false;
} else { // If main battery is OK, allow second battery to join } else { // If main battery is OK, allow second battery to join
datalayer.system.status.battery2_allows_contactor_closing = true; datalayer.system.status.battery2_allowed_contactor_closing = true;
} }
} else { //Voltage between the two packs is too large } else { //Voltage between the two packs is too large
set_event(EVENT_VOLTAGE_DIFFERENCE, (uint8_t)(voltage_diff / 10)); set_event(EVENT_VOLTAGE_DIFFERENCE, (uint8_t)(voltage_diff / 10));
@ -510,15 +509,9 @@ void update_calculated_values() {
} }
void update_values_inverter() { void update_values_inverter() {
#ifdef CAN_INVERTER_SELECTED if (inverter) {
update_values_can_inverter(); inverter->update_values();
#endif // CAN_INVERTER_SELECTED }
#ifdef MODBUS_INVERTER_SELECTED
update_modbus_registers_inverter();
#endif // CAN_INVERTER_SELECTED
#ifdef RS485_INVERTER_SELECTED
update_RS485_registers_inverter();
#endif // CAN_INVERTER_SELECTED
} }
void check_reset_reason() { void check_reset_reason() {

View file

@ -17,6 +17,7 @@
//#define FOXESS_BATTERY //#define FOXESS_BATTERY
//#define CELLPOWER_BMS //#define CELLPOWER_BMS
//#define CHADEMO_BATTERY //NOTE: inherently enables CONTACTOR_CONTROL below //#define CHADEMO_BATTERY //NOTE: inherently enables CONTACTOR_CONTROL below
//#define GEELY_GEOMETRY_C_BATTERY
//#define CMFA_EV_BATTERY //#define CMFA_EV_BATTERY
//#define IMIEV_CZERO_ION_BATTERY //#define IMIEV_CZERO_ION_BATTERY
//#define JAGUAR_IPACE_BATTERY //#define JAGUAR_IPACE_BATTERY
@ -92,7 +93,8 @@
//#define NISSANLEAF_CHARGER //Enable this line to control a Nissan LEAF PDM connected to battery - for example, when generator charging //#define NISSANLEAF_CHARGER //Enable this line to control a Nissan LEAF PDM connected to battery - for example, when generator charging
/* Automatic Precharge settings (Optional) If you have a battery that expects an external voltage applied before opening contactors (within the battery), configure this section */ /* Automatic Precharge settings (Optional) 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. //#define PRECHARGE_CONTROL //Enable this line to control a modified HIA4V1 via PWM on the HIA4V1_PIN (see Wiki and HAL for pin definition)
//#define INVERTER_DISCONNECT_CONTACTOR_IS_NORMALLY_OPEN //Enable this line if you use a normally open contactor instead of normally closed
/* Other options */ /* Other options */
//#define EQUIPMENT_STOP_BUTTON // Enable this to allow an equipment stop button connected to the Battery-Emulator to disengage the battery //#define EQUIPMENT_STOP_BUTTON // Enable this to allow an equipment stop button connected to the Battery-Emulator to disengage the battery
@ -179,6 +181,7 @@ typedef struct {
CAN_Interface charger; CAN_Interface charger;
CAN_Interface shunt; CAN_Interface shunt;
} CAN_Configuration; } CAN_Configuration;
extern const char* getCANInterfaceName(CAN_Interface interface);
extern volatile CAN_Configuration can_config; extern volatile CAN_Configuration can_config;
extern volatile uint8_t AccessPointEnabled; extern volatile uint8_t AccessPointEnabled;
extern const uint8_t wifi_channel; extern const uint8_t wifi_channel;

View file

@ -1,5 +1,9 @@
#include "../include.h" #include "../include.h"
#include "../datalayer/datalayer_extended.h"
#include "CanBattery.h"
#include "RS485Battery.h"
// These functions adapt the old C-style global functions battery-API to the // These functions adapt the old C-style global functions battery-API to the
// object-oriented battery API. // object-oriented battery API.
@ -7,7 +11,11 @@
// to support battery class selection at compile-time // to support battery class selection at compile-time
#ifdef SELECTED_BATTERY_CLASS #ifdef SELECTED_BATTERY_CLASS
static CanBattery* battery = nullptr; static Battery* battery = nullptr;
#ifdef DOUBLE_BATTERY
static Battery* battery2 = nullptr;
#endif
void setup_battery() { void setup_battery() {
// Instantiate the battery only once just in case this function gets called multiple times. // Instantiate the battery only once just in case this function gets called multiple times.
@ -15,18 +23,64 @@ void setup_battery() {
battery = new SELECTED_BATTERY_CLASS(); battery = new SELECTED_BATTERY_CLASS();
} }
battery->setup(); battery->setup();
#ifdef DOUBLE_BATTERY
if (battery2 == nullptr) {
#if defined(BMW_I3_BATTERY)
battery2 =
new SELECTED_BATTERY_CLASS(&datalayer.battery2, &datalayer.system.status.battery2_allowed_contactor_closing,
can_config.battery_double, WUP_PIN2);
#elif defined(KIA_HYUNDAI_64_BATTERY)
battery2 = new SELECTED_BATTERY_CLASS(&datalayer.battery2, &datalayer_extended.KiaHyundai64_2,
&datalayer.system.status.battery2_allowed_contactor_closing,
can_config.battery_double);
#elif defined(SANTA_FE_PHEV_BATTERY) || defined(TEST_FAKE_BATTERY)
battery2 = new SELECTED_BATTERY_CLASS(&datalayer.battery2, can_config.battery_double);
#else
battery2 = new SELECTED_BATTERY_CLASS(&datalayer.battery2, nullptr, can_config.battery_double);
#endif
}
battery2->setup();
#endif
} }
void update_values_battery() { void update_values_battery() {
battery->update_values(); battery->update_values();
} }
// transmit_can_battery is called once and we need to
// call both batteries.
void transmit_can_battery(unsigned long currentMillis) { void transmit_can_battery(unsigned long currentMillis) {
battery->transmit_can(currentMillis); ((CanBattery*)battery)->transmit_can(currentMillis);
#ifdef DOUBLE_BATTERY
((CanBattery*)battery2)->transmit_can(currentMillis);
#endif
} }
void handle_incoming_can_frame_battery(CAN_frame rx_frame) { void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
battery->handle_incoming_can_frame(rx_frame); ((CanBattery*)battery)->handle_incoming_can_frame(rx_frame);
}
#ifdef DOUBLE_BATTERY
void update_values_battery2() {
battery2->update_values();
}
void handle_incoming_can_frame_battery2(CAN_frame rx_frame) {
((CanBattery*)battery2)->handle_incoming_can_frame(rx_frame);
}
#endif
#ifdef RS485_BATTERY_SELECTED
void transmit_rs485() {
((RS485Battery*)battery)->transmit_rs485();
}
void receive_RS485() {
((RS485Battery*)battery)->receive_RS485();
} }
#endif #endif
#endif

View file

@ -46,6 +46,10 @@ void setup_can_shunt();
#include "FOXESS-BATTERY.h" #include "FOXESS-BATTERY.h"
#endif #endif
#ifdef GEELY_GEOMETRY_C_BATTERY
#include "GEELY-GEOMETRY-C-BATTERY.h"
#endif
#ifdef ORION_BMS #ifdef ORION_BMS
#include "ORION-BMS.h" #include "ORION-BMS.h"
#endif #endif
@ -151,7 +155,7 @@ void setup_battery(void);
void update_values_battery(); void update_values_battery();
#ifdef RS485_BATTERY_SELECTED #ifdef RS485_BATTERY_SELECTED
void transmit_rs485(); void transmit_rs485(unsigned long currentMillis);
void receive_RS485(); void receive_RS485();
#else #else
void handle_incoming_can_frame_battery(CAN_frame rx_frame); void handle_incoming_can_frame_battery(CAN_frame rx_frame);

File diff suppressed because it is too large Load diff

View file

@ -1,25 +1,341 @@
#ifndef BMW_I3_BATTERY_H #ifndef BMW_I3_BATTERY_H
#define BMW_I3_BATTERY_H #define BMW_I3_BATTERY_H
#include <Arduino.h>
#include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h"
#include "../include.h" #include "../include.h"
#include "CanBattery.h"
#define BATTERY_SELECTED #define BATTERY_SELECTED
#define SELECTED_BATTERY_CLASS BmwI3Battery
#define MAX_CELL_VOLTAGE_60AH 4110 // Battery is put into emergency stop if one cell goes over this value class BmwI3Battery : public CanBattery {
#define MIN_CELL_VOLTAGE_60AH 2700 // Battery is put into emergency stop if one cell goes below this value public:
#define MAX_CELL_VOLTAGE_94AH 4140 // Battery is put into emergency stop if one cell goes over this value // Use this constructor for the second battery.
#define MIN_CELL_VOLTAGE_94AH 2700 // Battery is put into emergency stop if one cell goes below this value BmwI3Battery(DATALAYER_BATTERY_TYPE* datalayer_ptr, bool* contactor_closing_allowed_ptr, int targetCan, int wakeup) {
#define MAX_CELL_VOLTAGE_120AH 4190 // Battery is put into emergency stop if one cell goes over this value datalayer_battery = datalayer_ptr;
#define MIN_CELL_VOLTAGE_120AH 2790 // Battery is put into emergency stop if one cell goes below this value contactor_closing_allowed = contactor_closing_allowed_ptr;
#define MAX_CELL_DEVIATION_MV 250 // LED turns yellow on the board if mv delta exceeds this value allows_contactor_closing = nullptr;
#define MAX_PACK_VOLTAGE_60AH 3950 // Charge stops if pack voltage exceeds this value can_interface = targetCan;
#define MIN_PACK_VOLTAGE_60AH 2590 // Discharge stops if pack voltage exceeds this value wakeup_pin = wakeup;
#define MAX_PACK_VOLTAGE_94AH 3980 // Charge stops if pack voltage exceeds this value *allows_contactor_closing = true;
#define MIN_PACK_VOLTAGE_94AH 2590 // Discharge stops if pack voltage exceeds this value
#define MAX_PACK_VOLTAGE_120AH 4030 // Charge stops if pack voltage exceeds this value //Init voltage to 0 to allow contactor check to operate without fear of default values colliding
#define MIN_PACK_VOLTAGE_120AH 2680 // Discharge stops if pack voltage exceeds this value battery_volts = 0;
#define NUMBER_OF_CELLS 96 }
void setup_battery(void);
void transmit_can_frame(CAN_frame* tx_frame, int interface); // Use the default constructor to create the first or single battery.
BmwI3Battery() {
datalayer_battery = &datalayer.battery;
allows_contactor_closing = &datalayer.system.status.battery_allows_contactor_closing;
contactor_closing_allowed = nullptr;
can_interface = can_config.battery;
wakeup_pin = WUP_PIN1;
}
virtual void setup(void);
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
virtual void update_values();
virtual void transmit_can(unsigned long currentMillis);
private:
const int MAX_CELL_VOLTAGE_60AH = 4110; // Battery is put into emergency stop if one cell goes over this value
const int MIN_CELL_VOLTAGE_60AH = 2700; // Battery is put into emergency stop if one cell goes below this value
const int MAX_CELL_VOLTAGE_94AH = 4140; // Battery is put into emergency stop if one cell goes over this value
const int MIN_CELL_VOLTAGE_94AH = 2700; // Battery is put into emergency stop if one cell goes below this value
const int MAX_CELL_VOLTAGE_120AH = 4190; // Battery is put into emergency stop if one cell goes over this value
const int MIN_CELL_VOLTAGE_120AH = 2790; // Battery is put into emergency stop if one cell goes below this value
const int MAX_CELL_DEVIATION_MV = 250; // LED turns yellow on the board if mv delta exceeds this value
const int MAX_PACK_VOLTAGE_60AH = 3950; // Charge stops if pack voltage exceeds this value
const int MIN_PACK_VOLTAGE_60AH = 2590; // Discharge stops if pack voltage exceeds this value
const int MAX_PACK_VOLTAGE_94AH = 3980; // Charge stops if pack voltage exceeds this value
const int MIN_PACK_VOLTAGE_94AH = 2590; // Discharge stops if pack voltage exceeds this value
const int MAX_PACK_VOLTAGE_120AH = 4030; // Charge stops if pack voltage exceeds this value
const int MIN_PACK_VOLTAGE_120AH = 2680; // Discharge stops if pack voltage exceeds this value
const int NUMBER_OF_CELLS = 96;
DATALAYER_BATTERY_TYPE* datalayer_battery;
// If not null, this battery decides when the contactor can be closed and writes the value here.
bool* allows_contactor_closing;
// If not null, this battery listens to this boolean to determine whether contactor closing is allowed
bool* contactor_closing_allowed;
int wakeup_pin;
int can_interface;
unsigned long previousMillis20 = 0; // will store last time a 20ms CAN Message was send
unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was send
unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was send
unsigned long previousMillis640 = 0; // will store last time a 600ms CAN Message was send
unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was send
unsigned long previousMillis5000 = 0; // will store last time a 5000ms CAN Message was send
unsigned long previousMillis10000 = 0; // will store last time a 10000ms CAN Message was send
#define ALIVE_MAX_VALUE 14 // BMW CAN messages contain alive counter, goes from 0...14
enum BatterySize { BATTERY_60AH, BATTERY_94AH, BATTERY_120AH };
BatterySize detectedBattery = BATTERY_60AH;
enum CmdState { SOH, CELL_VOLTAGE_MINMAX, SOC, CELL_VOLTAGE_CELLNO, CELL_VOLTAGE_CELLNO_LAST };
CmdState cmdState = SOC;
/* CAN messages from PT-CAN2 not needed to operate the battery
0AA 105 13D 0BB 0AD 0A5 150 100 1A1 10E 153 197 429 1AA 12F 59A 2E3 2BE 211 2b3 3FD 2E8 2B7 108 29D 29C 29B 2C0 330
3E9 32F 19E 326 55E 515 509 50A 51A 2F5 3A4 432 3C9
*/
CAN_frame BMW_10B = {.FD = false,
.ext_ID = false,
.DLC = 3,
.ID = 0x10B,
.data = {0xCD, 0x00, 0xFC}}; // Contactor closing command
CAN_frame BMW_12F = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x12F,
.data = {0xE6, 0x24, 0x86, 0x1A, 0xF1, 0x31, 0x30, 0x00}}; //0x12F Wakeup VCU
CAN_frame BMW_13E = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x13E,
.data = {0xFF, 0x31, 0xFA, 0xFA, 0xFA, 0xFA, 0x0C, 0x00}};
CAN_frame BMW_192 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x192,
.data = {0xFF, 0xFF, 0xA3, 0x8F, 0x93, 0xFF, 0xFF, 0xFF}};
CAN_frame BMW_19B = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x19B,
.data = {0x20, 0x40, 0x40, 0x55, 0xFD, 0xFF, 0xFF, 0xFF}};
CAN_frame BMW_1D0 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x1D0,
.data = {0x4D, 0xF0, 0xAE, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF}};
CAN_frame BMW_2CA = {.FD = false, .ext_ID = false, .DLC = 2, .ID = 0x2CA, .data = {0x57, 0x57}};
CAN_frame BMW_2E2 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x2E2,
.data = {0x4F, 0xDB, 0x7F, 0xB9, 0x07, 0x51, 0xff, 0x00}};
CAN_frame BMW_30B = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x30B,
.data = {0xe1, 0xf0, 0xff, 0xff, 0xf1, 0xff, 0xff, 0xff}};
CAN_frame BMW_328 = {.FD = false,
.ext_ID = false,
.DLC = 6,
.ID = 0x328,
.data = {0xB0, 0xE4, 0x87, 0x0E, 0x30, 0x22}};
CAN_frame BMW_37B = {.FD = false,
.ext_ID = false,
.DLC = 6,
.ID = 0x37B,
.data = {0x40, 0x00, 0x00, 0xFF, 0xFF, 0x00}};
CAN_frame BMW_380 = {.FD = false,
.ext_ID = false,
.DLC = 7,
.ID = 0x380,
.data = {0x56, 0x5A, 0x37, 0x39, 0x34, 0x34, 0x34}};
CAN_frame BMW_3A0 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x3A0,
.data = {0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC}};
CAN_frame BMW_3A7 = {.FD = false,
.ext_ID = false,
.DLC = 7,
.ID = 0x3A7,
.data = {0x05, 0xF5, 0x0A, 0x00, 0x4F, 0x11, 0xF0}};
CAN_frame BMW_3C5 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x3C5,
.data = {0x30, 0x05, 0x47, 0x70, 0x2c, 0xce, 0xc3, 0x34}};
CAN_frame BMW_3CA = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x3CA,
.data = {0x87, 0x80, 0x30, 0x0C, 0x0C, 0x81, 0xFF, 0xFF}};
CAN_frame BMW_3D0 = {.FD = false, .ext_ID = false, .DLC = 2, .ID = 0x3D0, .data = {0xFD, 0xFF}};
CAN_frame BMW_3E4 = {.FD = false,
.ext_ID = false,
.DLC = 6,
.ID = 0x3E4,
.data = {0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF}};
CAN_frame BMW_3E5 = {.FD = false, .ext_ID = false, .DLC = 3, .ID = 0x3E5, .data = {0xFC, 0xFF, 0xFF}};
CAN_frame BMW_3E8 = {.FD = false, .ext_ID = false, .DLC = 2, .ID = 0x3E8, .data = {0xF0, 0xFF}}; //1000ms OBD reset
CAN_frame BMW_3EC = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x3EC,
.data = {0xF5, 0x10, 0x00, 0x00, 0x80, 0x25, 0x0F, 0xFC}};
CAN_frame BMW_3F9 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x3F9,
.data = {0xA7, 0x2A, 0x00, 0xE2, 0xA6, 0x30, 0xC3, 0xFF}};
CAN_frame BMW_3FB = {.FD = false,
.ext_ID = false,
.DLC = 6,
.ID = 0x3FB,
.data = {0xFF, 0xFF, 0xFF, 0xFF, 0x5F, 0x00}};
CAN_frame BMW_3FC = {.FD = false, .ext_ID = false, .DLC = 3, .ID = 0x3FC, .data = {0xC0, 0xF9, 0x0F}};
CAN_frame BMW_418 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x418,
.data = {0xFF, 0x7C, 0xFF, 0x00, 0xC0, 0x3F, 0xFF, 0xFF}};
CAN_frame BMW_41D = {.FD = false, .ext_ID = false, .DLC = 4, .ID = 0x41D, .data = {0xFF, 0xF7, 0x7F, 0xFF}};
CAN_frame BMW_433 = {.FD = false,
.ext_ID = false,
.DLC = 4,
.ID = 0x433,
.data = {0xFF, 0x00, 0x0F, 0xFF}}; // HV specification
CAN_frame BMW_512 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x512,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12}}; // 0x512 Network management
CAN_frame BMW_592_0 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x592,
.data = {0x86, 0x10, 0x07, 0x21, 0x6e, 0x35, 0x5e, 0x86}};
CAN_frame BMW_592_1 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x592,
.data = {0x86, 0x21, 0xb4, 0xdd, 0x00, 0x00, 0x00, 0x00}};
CAN_frame BMW_5F8 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x5F8,
.data = {0x64, 0x01, 0x00, 0x0B, 0x92, 0x03, 0x00, 0x05}};
CAN_frame BMW_6F1_CELL = {.FD = false,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F1,
.data = {0x07, 0x03, 0x22, 0xDD, 0xBF}};
CAN_frame BMW_6F1_SOH = {.FD = false, .ext_ID = false, .DLC = 5, .ID = 0x6F1, .data = {0x07, 0x03, 0x22, 0x63, 0x35}};
CAN_frame BMW_6F1_SOC = {.FD = false, .ext_ID = false, .DLC = 5, .ID = 0x6F1, .data = {0x07, 0x03, 0x22, 0xDD, 0xBC}};
CAN_frame BMW_6F1_CELL_VOLTAGE_AVG = {.FD = false,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F1,
.data = {0x07, 0x03, 0x22, 0xDF, 0xA0}};
CAN_frame BMW_6F1_CONTINUE = {.FD = false, .ext_ID = false, .DLC = 4, .ID = 0x6F1, .data = {0x07, 0x30, 0x00, 0x02}};
CAN_frame BMW_6F4_CELL_VOLTAGE_CELLNO = {.FD = false,
.ext_ID = false,
.DLC = 7,
.ID = 0x6F4,
.data = {0x07, 0x05, 0x31, 0x01, 0xAD, 0x6E, 0x01}};
CAN_frame BMW_6F4_CELL_CONTINUE = {.FD = false,
.ext_ID = false,
.DLC = 6,
.ID = 0x6F4,
.data = {0x07, 0x04, 0x31, 0x03, 0xAD, 0x6E}};
//The above CAN messages need to be sent towards the battery to keep it alive
uint8_t startup_counter_contactor = 0;
uint8_t alive_counter_20ms = 0;
uint8_t alive_counter_100ms = 0;
uint8_t alive_counter_200ms = 0;
uint8_t alive_counter_500ms = 0;
uint8_t alive_counter_1000ms = 0;
uint8_t alive_counter_5000ms = 0;
uint8_t BMW_1D0_counter = 0;
uint8_t BMW_13E_counter = 0;
uint8_t BMW_380_counter = 0;
uint32_t BMW_328_seconds = 243785948; // Initialized to make the battery think vehicle was made 7.7years ago
uint16_t BMW_328_days =
9244; //Time since 1.1.2000. Hacky implementation to make it think current date is 23rd April 2025
uint32_t BMS_328_seconds_to_day = 0; //Counter to keep track of days uptime
bool battery_awake = false;
bool battery_info_available = false;
bool skipCRCCheck = false;
bool CRCCheckPassedPreviously = false;
uint16_t cellvoltage_temp_mV = 0;
uint32_t battery_serial_number = 0;
uint32_t battery_available_power_shortterm_charge = 0;
uint32_t battery_available_power_shortterm_discharge = 0;
uint32_t battery_available_power_longterm_charge = 0;
uint32_t battery_available_power_longterm_discharge = 0;
uint32_t battery_BEV_available_power_shortterm_charge = 0;
uint32_t battery_BEV_available_power_shortterm_discharge = 0;
uint32_t battery_BEV_available_power_longterm_charge = 0;
uint32_t battery_BEV_available_power_longterm_discharge = 0;
uint16_t battery_energy_content_maximum_Wh = 0;
uint16_t battery_display_SOC = 0;
uint16_t battery_volts = 0;
uint16_t battery_HVBatt_SOC = 0;
uint16_t battery_DC_link_voltage = 0;
uint16_t battery_max_charge_voltage = 0;
uint16_t battery_min_discharge_voltage = 0;
uint16_t battery_predicted_energy_charge_condition = 0;
uint16_t battery_predicted_energy_charging_target = 0;
uint16_t battery_actual_value_power_heating = 0; //0 - 4094 W
uint16_t battery_prediction_voltage_shortterm_charge = 0;
uint16_t battery_prediction_voltage_shortterm_discharge = 0;
uint16_t battery_prediction_voltage_longterm_charge = 0;
uint16_t battery_prediction_voltage_longterm_discharge = 0;
uint16_t battery_prediction_duration_charging_minutes = 0;
uint16_t battery_target_voltage_in_CV_mode = 0;
uint16_t battery_soc = 0;
uint16_t battery_soc_hvmax = 0;
uint16_t battery_soc_hvmin = 0;
uint16_t battery_capacity_cah = 0;
int16_t battery_temperature_HV = 0;
int16_t battery_temperature_heat_exchanger = 0;
int16_t battery_temperature_max = 0;
int16_t battery_temperature_min = 0;
int16_t battery_max_charge_amperage = 0;
int16_t battery_max_discharge_amperage = 0;
int16_t battery_current = 0;
uint8_t battery_status_error_isolation_external_Bordnetz = 0;
uint8_t battery_status_error_isolation_internal_Bordnetz = 0;
uint8_t battery_request_cooling = 0;
uint8_t battery_status_valve_cooling = 0;
uint8_t battery_status_error_locking = 0;
uint8_t battery_status_precharge_locked = 0;
uint8_t battery_status_disconnecting_switch = 0;
uint8_t battery_status_emergency_mode = 0;
uint8_t battery_request_service = 0;
uint8_t battery_error_emergency_mode = 0;
uint8_t battery_status_error_disconnecting_switch = 0;
uint8_t battery_status_warning_isolation = 0;
uint8_t battery_status_cold_shutoff_valve = 0;
uint8_t battery_request_open_contactors = 0;
uint8_t battery_request_open_contactors_instantly = 0;
uint8_t battery_request_open_contactors_fast = 0;
uint8_t battery_charging_condition_delta = 0;
uint8_t battery_status_service_disconnection_plug = 0;
uint8_t battery_status_measurement_isolation = 0;
uint8_t battery_request_abort_charging = 0;
uint8_t battery_prediction_time_end_of_charging_minutes = 0;
uint8_t battery_request_operating_mode = 0;
uint8_t battery_request_charging_condition_minimum = 0;
uint8_t battery_request_charging_condition_maximum = 0;
uint8_t battery_status_cooling_HV = 0; //1 works, 2 does not start
uint8_t battery_status_diagnostics_HV = 0; // 0 all OK, 1 HV protection function error, 2 diag not yet expired
uint8_t battery_status_diagnosis_powertrain_maximum_multiplexer = 0;
uint8_t battery_status_diagnosis_powertrain_immediate_multiplexer = 0;
uint8_t battery_ID2 = 0;
uint8_t battery_soh = 99;
uint8_t message_data[50];
uint8_t next_data = 0;
uint8_t current_cell_polled = 0;
};
#endif #endif

View file

@ -1,502 +1,13 @@
#include "../include.h" #include "../include.h"
#ifdef BMW_IX_BATTERY #ifdef BMW_IX_BATTERY
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h" #include "../datalayer/datalayer_extended.h"
#include "../devboard/utils/events.h" #include "../devboard/utils/events.h"
#include "BMW-IX-BATTERY.h" #include "BMW-IX-BATTERY.h"
/* Do not change code below unless you are sure what you are doing */
static unsigned long previousMillis20 = 0; // will store last time a 20ms CAN Message was send
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
static unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was send
static unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was send
static unsigned long previousMillis640 = 0; // will store last time a 600ms CAN Message was send
static unsigned long previousMillis10000 = 0; // will store last time a 10000ms CAN Message was send
#define ALIVE_MAX_VALUE 14 // BMW CAN messages contain alive counter, goes from 0...14
enum CmdState { SOH, CELL_VOLTAGE_MINMAX, SOC, CELL_VOLTAGE_CELLNO, CELL_VOLTAGE_CELLNO_LAST };
static CmdState cmdState = SOC;
/*
SME output:
0x12B8D087 5000ms - Extended ID
0x1D2 DLC8 1000ms
0x20B DLC8 1000ms
0x2E2 DLC16 1000ms
0x31F DLC16 100ms - 2 downward counters?
0x3EA DLC8
0x453 DLC20 200ms
0x486 DLC48 1000ms
0x49C DLC8 1000ms
0x4A1 DLC8 1000ms
0x4BB DLC64 200ms - seems multplexed on [0]
0x4D0 DLC64 1000ms - some slow/flickering values - possible change during fault
0x507 DLC8
0x587 DLC8 - appears at startup 0x78 0x07 0x00 0x00 0xFF 0xFF 0xFF 0xFF , 0x01 0x03 0x80 0xFF 0xFF 0xFF 0xFF 0xFF, 0x78 0x07 0x00 0x00 0xFF 0xFF 0xFF 0xFF, 0x06 0x00 0x00 0xFF 0xFF 0xFF 0xFF 0xFF, 0x01 0x03 0x82 0xFF 0xFF 0xFF 0xFF 0xFF, 0x01 0x03 0x80 0xFF 0xFF 0xFF 0xFF 0xFF
0x607 - UDS response
0x7AB DLC64 - seen at startup
0x8F DLC48 10ms - appears to have analog readings like volt/temp/current
0xD0D087 DLC4
BDC output:
0x276 DLC8 - vehicle condition
0x2F1 DLC8 1000ms - during run: 0xFF, 0xFF, 0xFF, 0xFF, 0x9B, 0x00, 0xF3, 0xFF - at startup 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xF3, 0xFF. Suspect byte [4] is a counter
0x439 DLC4 1000ms - STATIC: byte0, byte1. byte3 changes, byte4 = 0xF2 OR 0xF3
0x4EB DLC8 - RSU condition
0x510 DLC8 100ms - FIXME:(update as this content is not seen in car logs:) STATIC 40 10 40 00 6F DF 19 00 during run - Startup sends this once: 0x40 0x10 0x02 0x00 0x00 0x00 0x00 0x00
0x6D DLC8 1000ms - counters? counter on byte3
0xC0 DLC2 200ms - needs counter
SME asks for:
0x125 (CCU)
0x16E (CCU)
0x188 (CCU)
0x1A1 (DSC) - vehicle speed (not seen in car logs)
0x1EA (KOMBI)
0x1FC (FIXME:(add transmitter node.))
0x21D (FIXME:(add transmitter node.))
0x276 (BDC)
0x2ED (FIXME:(add transmitter node.))
0x340 (CCU)
0x380 (FIXME:(add transmitter node.))
0x442 (FIXME:(add transmitter node.))
0x4EB (BDC)
0x4F8 (CCU)
0x91 (EME1)
0xAA (EME2) - all wheel drive only
0x?? Suspect there is a drive mode flag somewhere - balancing might only be active in some modes
TODO:
- Request batt serial number on F1 8C (already parsing RX)
*/
//Vehicle CAN START
CAN_frame BMWiX_125 = {
.FD = true,
.ext_ID = false,
.DLC = 20,
.ID = 0x125,
//.data = {TODO:, TODO:, TODO:, TODO:, 0xFE, 0x7F, 0xFE, 0x7F, TODO:, TODO:, TODO:, TODO:, TODO:, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xFF, 0xFF}
}; // CCU output
/* SME output
CAN_frame BMWiX_12B8D087 = {.FD = true,
.ext_ID = true,
.DLC = 2,
.ID = 0x12B8D087,
.data = {0xFC, 0xFF}}; // 5000ms SME output - Static values
*/
CAN_frame BMWiX_16E = {
.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x16E,
// .data = {TODO:, TODO:, TODO: 0xC8 or 0xC9, 0xFF, TODO:, 0xC9, TODO:, TODO:, }
}; // CCU output
CAN_frame BMWiX_188 = {.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x188,
.data = {0x00, 0x00, 0x00, 0x00, 0x3C, 0xFF, 0xFF, 0xFF}}; // CCU output - values while driving
CAN_frame BMWiX_1EA = {
.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x1EA,
//.data = {TODO:km_least_significant, TODO:, TODO:, TODO:, TODO:km_most_significant, 0xFF, TODO:, TODO:}
}; // KOMBI output - kilometerstand
CAN_frame BMWiX_1FC = {
.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x1FC,
.data = {0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0xC0,
0x00}}; // FIXME:(add transmitter node) output - heat management engine control - static values
CAN_frame BMWiX_21D = {
.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x21D,
// .data = {TODO:, TODO:, TODO:, 0xFF, 0xFF, 0xFF, 0xFF, TODO:}
}; // FIXME:(add transmitter node) output - request heating and air conditioning system 1
CAN_frame BMWiX_276 = {
.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x276,
.data = {0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC}}; // 5000ms BDC output - vehicle condition
CAN_frame BMWiX_2ED = {
.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x2ED,
.data = {
0x75,
0x75}}; // FIXME:(add transmitter node) output - ambient temperature (values seen in logs vary between 0x72 and 0x79)
CAN_frame BMWiX_2F1 = {
.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x2F1,
.data = {0xFF, 0xFF, 0xD0, 0x39, 0x94, 0x00, 0xF3, 0xFF}}; // 1000ms BDC output - Static values - varies at startup
CAN_frame BMWiX_340 = {
.FD = true,
.ext_ID = false,
.DLC = 12,
.ID = 0x340,
// .data = {TODO:, TODO:, TODO:, 0xFF, TODO:, TODO:, 0x00, 0x00, TODO:, TODO:, TODO:, 0xFF, 0xFF, }
}; // CCU output
CAN_frame BMWiX_380 = {
.FD = true,
.ext_ID = false,
.DLC = 7,
.ID = 0x380,
// .data = {FIXME:(VIN_char1), FIXME:(VIN_char2), FIXME:(VIN_char3), FIXME:(VIN_char4), FIXME:(VIN_char5), FIXME:(VIN_char6), FIXME:(VIN_char7)}
}; // FIXME:(add transmitter node) output - VIN: ASCII2HEX
/* Not requested by SME
CAN_frame BMWiX_439 = {.FD = true,
.ext_ID = false,
.DLC = 4,
.ID = 0x439,
.data = {0xFF, 0x3F, 0xFF, 0xF3}}; // 1000ms BDC output
*/
CAN_frame BMWiX_442 = {
.FD = true,
.ext_ID = false,
.DLC = 6,
.ID = 0x442,
// .data = {TODO: relative time byte 0, TODO: relative time byte1, 0xA9, 0x00, 0xE0, 0x23}
}; // FIXME:(add transmitter node) output - relative time BN2020
/* SME output
CAN_frame
BMWiX_486 =
{
.FD = true,
.ext_ID = false,
.DLC = 48,
.ID = 0x486,
.data =
{
0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE,
0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF,
0xFE, 0xFF, 0xFF, 0x7F, 0x33, 0xFD, 0xFD, 0xFD, 0xFD, 0xC0, 0x41, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}}; // 1000ms SME output - Suspected keep alive Static CONFIRM NEEDED
*/
/* SME output
CAN_frame BMWiX_49C = {.FD = true,
.ext_ID = false,
.DLC = 4,
.ID = 0x49C,
.data = {0xD2, 0xF2, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF}}; // 1000ms SME output - Suspected keep alive Static CONFIRM NEEDED
*/
CAN_frame BMWiX_4EB = {
.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x4EB,
// .data = {TODO:, TODO:, TODO: 0xE0 or 0xE5 (while driving), 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
}; // BDC output - RSU condition
CAN_frame BMWiX_4F8 = {
.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x4F8,
// .data = {0xFF, 0xFD, 0xFF, 0xFF, 0xFF, TODO:, TODO:, 0xC8, 0x00, 0x00, 0xF0, 0x40, 0xFE, 0xFF, 0xFD, 0xFF, TODO:, TODO:, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
}; // CCU output
CAN_frame BMWiX_510 = {
.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x510,
.data = {0x40, 0x10, 0x00, 0x00, 0x00, 0x80, 0x00,
0x00}}; // 100ms BDC output - Values change in car logs, these bytes are the most common
CAN_frame BMWiX_6D = {
.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x6D,
.data = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
0xFF}}; // 1000ms BDC output - [0] static [1,2][3,4] counter x2. 3,4 is 9 higher than 1,2 is needed? [5-7] static
CAN_frame BMWiX_C0 = {
.FD = true,
.ext_ID = false,
.DLC = 2,
.ID = 0xC0,
.data = {
0xF0,
0x00}}; // BDC output - Keep Alive 2 BDC>SME 200ms First byte cycles F0 > FE second byte 00 static - MINIMUM ID TO KEEP SME AWAKE
//Vehicle CAN END
//Request Data CAN START
CAN_frame BMWiX_6F4 = {
.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0xC7}}; // Generic UDS Request data from SME. byte 4 selects requested value
CAN_frame BMWiX_6F4_REQUEST_SLEEPMODE = {
.FD = true,
.ext_ID = false,
.DLC = 4,
.ID = 0x6F4,
.data = {0x07, 0x02, 0x11, 0x04}}; // UDS Request Request BMS/SME goes to Sleep Mode
CAN_frame BMWiX_6F4_REQUEST_HARD_RESET = {.FD = true,
.ext_ID = false,
.DLC = 4,
.ID = 0x6F4,
.data = {0x07, 0x02, 0x11, 0x01}}; // UDS Request Hard reset of BMS/SME
CAN_frame BMWiX_6F4_REQUEST_CELL_TEMP = {.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xDD, 0xC0}}; // UDS Request Cell Temperatures
CAN_frame BMWiX_6F4_REQUEST_SOC = {.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0xCE}}; // Min/Avg/Max SOC%
CAN_frame BMWiX_6F4_REQUEST_CAPACITY = {
.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0xC7}}; //Current and max capacity kWh. Stored in kWh as 0.01 scale with -50 bias
CAN_frame BMWiX_6F4_REQUEST_MINMAXCELLV = {
.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x53}}; //Min and max cell voltage 10V = Qualifier Invalid
CAN_frame BMWiX_6F4_REQUEST_MAINVOLTAGE_POSTCONTACTOR = {
.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x4A}}; //Main Battery Voltage (After Contactor)
CAN_frame BMWiX_6F4_REQUEST_MAINVOLTAGE_PRECONTACTOR = {
.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x4D}}; //Main Battery Voltage (Pre Contactor)
CAN_frame BMWiX_6F4_REQUEST_BATTERYCURRENT = {
.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x61}}; //Current amps 32bit signed MSB. dA . negative is discharge
CAN_frame BMWiX_6F4_REQUEST_CELL_VOLTAGE = {
.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x54}}; //MultiFrameIndividual Cell Voltages
CAN_frame BMWiX_6F4_REQUEST_T30VOLTAGE = {
.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0xA7}}; //Terminal 30 Voltage (12V SME supply)
CAN_frame BMWiX_6F4_REQUEST_EOL_ISO = {.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xA8, 0x60}}; //Request EOL Reading including ISO
CAN_frame BMWiX_6F4_REQUEST_SOH = {.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x45}}; //SOH Max Min Mean Request
CAN_frame BMWiX_6F4_REQUEST_DATASUMMARY = {
.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {
0x07, 0x03, 0x22, 0xE5,
0x45}}; //MultiFrame Summary Request, includes SOC/SOH/MinMax/MaxCapac/RemainCapac/max v and t at last charge. slow refreshrate
CAN_frame BMWiX_6F4_REQUEST_PYRO = {.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xAC, 0x93}}; //Pyro Status
CAN_frame BMWiX_6F4_REQUEST_UPTIME = {.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE4, 0xC0}}; // Uptime and Vehicle Time Status
CAN_frame BMWiX_6F4_REQUEST_HVIL = {.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x69}}; // Request HVIL State
CAN_frame BMWiX_6F4_REQUEST_BALANCINGSTATUS = {.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE4, 0xCA}}; // Request Balancing Data
CAN_frame BMWiX_6F4_REQUEST_MAX_CHARGE_DISCHARGE_AMPS = {
.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x62}}; // Request allowable charge discharge amps
CAN_frame BMWiX_6F4_REQUEST_VOLTAGE_QUALIFIER_CHECK = {
.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x4B}}; // Request HV Voltage Qualifier
CAN_frame BMWiX_6F4_REQUEST_CONTACTORS_CLOSE = {
.FD = true,
.ext_ID = false,
.DLC = 6,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x51, 0x01}}; // Request Contactors Close - Unconfirmed
CAN_frame BMWiX_6F4_REQUEST_CONTACTORS_OPEN = {
.FD = true,
.ext_ID = false,
.DLC = 6,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x51, 0x01}}; // Request Contactors Open - Unconfirmed
CAN_frame BMWiX_6F4_REQUEST_BALANCING_START = {
.FD = true,
.ext_ID = false,
.DLC = 6,
.ID = 0x6F4,
.data = {0xF4, 0x04, 0x71, 0x01, 0xAE, 0x77}}; // Request Balancing command?
CAN_frame BMWiX_6F4_REQUEST_BALANCING_START2 = {
.FD = true,
.ext_ID = false,
.DLC = 6,
.ID = 0x6F4,
.data = {0xF4, 0x04, 0x31, 0x01, 0xAE, 0x77}}; // Request Balancing command?
CAN_frame BMWiX_6F4_REQUEST_PACK_VOLTAGE_LIMITS = {
.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x4C}}; // Request pack voltage limits
CAN_frame BMWiX_6F4_CONTINUE_DATA = {.FD = true,
.ext_ID = false,
.DLC = 4,
.ID = 0x6F4,
.data = {0x07, 0x30, 0x00, 0x02}};
//Action Requests:
CAN_frame BMWiX_6F4_CELL_SOC = {.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x9A}};
CAN_frame BMWiX_6F4_CELL_TEMP = {.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0xCA}};
//Request Data CAN End
static bool battery_awake = false;
//Setup UDS values to poll for
CAN_frame* UDS_REQUESTS100MS[] = {&BMWiX_6F4_REQUEST_CELL_TEMP,
&BMWiX_6F4_REQUEST_SOC,
&BMWiX_6F4_REQUEST_CAPACITY,
&BMWiX_6F4_REQUEST_MINMAXCELLV,
&BMWiX_6F4_REQUEST_MAINVOLTAGE_POSTCONTACTOR,
&BMWiX_6F4_REQUEST_MAINVOLTAGE_PRECONTACTOR,
&BMWiX_6F4_REQUEST_BATTERYCURRENT,
&BMWiX_6F4_REQUEST_CELL_VOLTAGE,
&BMWiX_6F4_REQUEST_T30VOLTAGE,
&BMWiX_6F4_REQUEST_SOH,
&BMWiX_6F4_REQUEST_UPTIME,
&BMWiX_6F4_REQUEST_PYRO,
&BMWiX_6F4_REQUEST_EOL_ISO,
&BMWiX_6F4_REQUEST_HVIL,
&BMWiX_6F4_REQUEST_MAX_CHARGE_DISCHARGE_AMPS,
&BMWiX_6F4_REQUEST_BALANCINGSTATUS,
&BMWiX_6F4_REQUEST_PACK_VOLTAGE_LIMITS};
int numUDSreqs = sizeof(UDS_REQUESTS100MS) / sizeof(UDS_REQUESTS100MS[0]); // Number of elements in the array
//iX Intermediate vars
static bool battery_info_available = false;
static uint32_t battery_serial_number = 0;
static int32_t battery_current = 0;
static int16_t battery_voltage = 370; //Startup with valid values - needs fixing in future
static int16_t terminal30_12v_voltage = 0;
static int16_t battery_voltage_after_contactor = 0;
static int16_t min_soc_state = 5000;
static int16_t avg_soc_state = 5000;
static int16_t max_soc_state = 5000;
static int16_t min_soh_state = 9900; // Uses E5 45, also available in 78 73
static int16_t avg_soh_state = 9900; // Uses E5 45, also available in 78 73
static int16_t max_soh_state = 9900; // Uses E5 45, also available in 78 73
static uint16_t max_design_voltage = 0;
static uint16_t min_design_voltage = 0;
static int32_t remaining_capacity = 0;
static int32_t max_capacity = 0;
static int16_t min_battery_temperature = 0;
static int16_t avg_battery_temperature = 0;
static int16_t max_battery_temperature = 0;
static int16_t main_contactor_temperature = 0;
static int16_t min_cell_voltage = 3700; //Startup with valid values - needs fixing in future
static int16_t max_cell_voltage = 3700; //Startup with valid values - needs fixing in future
static unsigned long min_cell_voltage_lastchanged = 0;
static unsigned long max_cell_voltage_lastchanged = 0;
static unsigned min_cell_voltage_lastreceived = 0;
static unsigned max_cell_voltage_lastreceived = 0;
static uint32_t sme_uptime = 0; //Uses E4 C0
static int16_t allowable_charge_amps = 0; //E5 62
static int16_t allowable_discharge_amps = 0; //E5 62
static int32_t iso_safety_positive = 0; //Uses A8 60
static int32_t iso_safety_negative = 0; //Uses A8 60
static int32_t iso_safety_parallel = 0; //Uses A8 60
static int16_t count_full_charges = 0; //TODO 42
static int16_t count_charges = 0; //TODO 42
static int16_t hvil_status = 0;
static int16_t voltage_qualifier_status = 0; //0 = Valid, 1 = Invalid
static int16_t balancing_status = 0; //4 = not active
static uint8_t contactors_closed = 0; //TODO E5 BF or E5 51
static uint8_t contactor_status_precharge = 0; //TODO E5 BF
static uint8_t contactor_status_negative = 0; //TODO E5 BF
static uint8_t contactor_status_positive = 0; //TODO E5 BF
static uint8_t pyro_status_pss1 = 0; //Using AC 93
static uint8_t pyro_status_pss4 = 0; //Using AC 93
static uint8_t pyro_status_pss6 = 0; //Using AC 93
static uint8_t uds_req_id_counter = 0;
static uint8_t detected_number_of_cells = 108;
const unsigned long STALE_PERIOD =
STALE_PERIOD_CONFIG; // Time in milliseconds to check for staleness (e.g., 5000 ms = 5 seconds)
//End iX Intermediate vars
static uint8_t current_cell_polled = 0;
// Function to check if a value has gone stale over a specified time period // Function to check if a value has gone stale over a specified time period
bool isStale(int16_t currentValue, uint16_t& lastValue, unsigned long& lastChangeTime) { bool BmwIXBattery::isStale(int16_t currentValue, uint16_t& lastValue, unsigned long& lastChangeTime) {
unsigned long currentTime = millis(); unsigned long currentTime = millis();
// Check if the value has changed // Check if the value has changed
@ -511,7 +22,7 @@ bool isStale(int16_t currentValue, uint16_t& lastValue, unsigned long& lastChang
return (currentTime - lastChangeTime >= STALE_PERIOD); return (currentTime - lastChangeTime >= STALE_PERIOD);
} }
static uint8_t increment_uds_req_id_counter(uint8_t index) { uint8_t BmwIXBattery::increment_uds_req_id_counter(uint8_t index) {
index++; index++;
if (index >= numUDSreqs) { if (index >= numUDSreqs) {
index = 0; index = 0;
@ -519,7 +30,7 @@ static uint8_t increment_uds_req_id_counter(uint8_t index) {
return index; return index;
} }
static uint8_t increment_alive_counter(uint8_t counter) { uint8_t BmwIXBattery::increment_alive_counter(uint8_t counter) {
counter++; counter++;
if (counter > ALIVE_MAX_VALUE) { if (counter > ALIVE_MAX_VALUE) {
counter = 0; counter = 0;
@ -536,7 +47,7 @@ static byte increment_C0_counter(byte counter) {
return counter; return counter;
} }
void update_values_battery() { //This function maps all the values fetched via CAN to the battery datalayer void BmwIXBattery::update_values() { //This function maps all the values fetched via CAN to the battery datalayer
datalayer.battery.status.real_soc = avg_soc_state; datalayer.battery.status.real_soc = avg_soc_state;
@ -629,13 +140,53 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV; datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
} }
} }
void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
void BmwIXBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
battery_awake = true; battery_awake = true;
switch (rx_frame.ID) { switch (rx_frame.ID) {
case 0x112: case 0x12B8D087:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x1D2:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x20B:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x2E2:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x31F:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x3EA:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x453:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x486:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x49C:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x4A1:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x4BB:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x4D0:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x507:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x587:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break; break;
case 0x607: //SME responds to UDS requests on 0x607 case 0x607: //SME responds to UDS requests on 0x607
if (rx_frame.DLC > 6 && rx_frame.data.u8[0] == 0xF4 && rx_frame.data.u8[1] == 0x10 && if (rx_frame.DLC > 6 && rx_frame.data.u8[0] == 0xF4 && rx_frame.data.u8[1] == 0x10 &&
rx_frame.data.u8[2] == 0xE3 && rx_frame.data.u8[3] == 0x62 && rx_frame.data.u8[4] == 0xE5) { rx_frame.data.u8[2] == 0xE3 && rx_frame.data.u8[3] == 0x62 && rx_frame.data.u8[4] == 0xE5) {
//First of multi frame data - Parse the first frame //First of multi frame data - Parse the first frame
@ -785,7 +336,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
(rx_frame.data.u8[8] << 8 | rx_frame.data.u8[9]) == 10000) { //Qualifier Invalid Mode - Request Reboot (rx_frame.data.u8[8] << 8 | rx_frame.data.u8[9]) == 10000) { //Qualifier Invalid Mode - Request Reboot
#ifdef DEBUG_LOG #ifdef DEBUG_LOG
logging.println("Cell MinMax Qualifier Invalid - Requesting BMS Reset"); logging.println("Cell MinMax Qualifier Invalid - Requesting BMS Reset");
#endif #endif // DEBUG_LOG
//set_event(EVENT_BATTERY_VALUE_UNAVAILABLE, (millis())); //Eventually need new Info level event type //set_event(EVENT_BATTERY_VALUE_UNAVAILABLE, (millis())); //Eventually need new Info level event type
transmit_can_frame(&BMWiX_6F4_REQUEST_HARD_RESET, can_config.battery); transmit_can_frame(&BMWiX_6F4_REQUEST_HARD_RESET, can_config.battery);
} else { //Only ingest values if they are not the 10V Error state } else { //Only ingest values if they are not the 10V Error state
@ -832,47 +383,103 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
battery_serial_number = strtoul(numberString, NULL, 10); battery_serial_number = strtoul(numberString, NULL, 10);
} }
break; break;
case 0x7AB:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x8F:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0xD0D087:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
default: default:
break; break;
} }
} }
void transmit_can_battery(unsigned long currentMillis) { void BmwIXBattery::transmit_can(unsigned long currentMillis) {
// We can always send CAN as the iX BMS will wake up on vehicle comms
if (currentMillis - previousMillis10 >= INTERVAL_10_MS) {
previousMillis10 = currentMillis;
ContactorCloseRequest.present = contactorCloseReq;
// Detect edge
if (ContactorCloseRequest.previous == false && ContactorCloseRequest.present == true) {
// Rising edge detected
#ifdef DEBUG_LOG
logging.println("Rising edge detected. Resetting 10ms counter.");
#endif // DEBUG_LOG
counter_10ms = 0; // reset counter
} else if (ContactorCloseRequest.previous == true && ContactorCloseRequest.present == false) {
// Dropping edge detected
#ifdef DEBUG_LOG
logging.println("Dropping edge detected. Resetting 10ms counter.");
#endif // DEBUG_LOG
counter_10ms = 0; // reset counter
}
ContactorCloseRequest.previous = ContactorCloseRequest.present;
HandleBmwIxCloseContactorsRequest(counter_10ms);
HandleBmwIxOpenContactorsRequest(counter_10ms);
counter_10ms++;
//if (battery_awake) { //We can always send CAN as the iX BMS will wake up on vehicle comms // prevent counter overflow: 2^16-1 = 65535
if (counter_10ms == 65535) {
counter_10ms = 1; // set to 1, to differentiate the counter being set to 0 by the functions above
}
}
// Send 100ms CAN Message // Send 100ms CAN Message
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) { if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
previousMillis100 = currentMillis; previousMillis100 = currentMillis;
HandleIncomingInverterRequest();
//Loop through and send a different UDS request each cycle //Loop through and send a different UDS request once the contactors are closed
if (contactorCloseReq == true &&
ContactorState.closed ==
true) { // Do not send unless the contactors are requested to be closed and are closed, as sending these does not allow the contactors to close
uds_req_id_counter = increment_uds_req_id_counter(uds_req_id_counter); uds_req_id_counter = increment_uds_req_id_counter(uds_req_id_counter);
transmit_can_frame(UDS_REQUESTS100MS[uds_req_id_counter], can_config.battery); transmit_can_frame(UDS_REQUESTS100MS[uds_req_id_counter],
can_config.battery); // FIXME: sending these does not allow the contactors to close
} else { // FIXME: hotfix: If contactors are not requested to be closed, ensure the battery is reported as alive, even if no CAN messages are received
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
}
// Keep contactors closed if needed
BmwIxKeepContactorsClosed(counter_100ms);
counter_100ms++;
if (counter_100ms == 140) {
counter_100ms = 0; // reset counter every 14 seconds
}
//Send SME Keep alive values 100ms //Send SME Keep alive values 100ms
transmit_can_frame(&BMWiX_510, can_config.battery); //transmit_can_frame(&BMWiX_510, can_config.battery);
} }
// Send 200ms CAN Message // Send 200ms CAN Message
if (currentMillis - previousMillis200 >= INTERVAL_200_MS) { if (currentMillis - previousMillis200 >= INTERVAL_200_MS) {
previousMillis200 = currentMillis; previousMillis200 = currentMillis;
//Send SME Keep alive values 200ms //Send SME Keep alive values 200ms
BMWiX_C0.data.u8[0] = increment_C0_counter(BMWiX_C0.data.u8[0]); //Keep Alive 1 //BMWiX_C0.data.u8[0] = increment_C0_counter(BMWiX_C0.data.u8[0]); //Keep Alive 1
transmit_can_frame(&BMWiX_C0, can_config.battery); //transmit_can_frame(&BMWiX_C0, can_config.battery);
}
// Send 1000ms CAN Message
if (currentMillis - previousMillis1000 >= INTERVAL_1_S) {
previousMillis1000 = currentMillis;
HandleIncomingUserRequest();
} }
// Send 10000ms CAN Message // Send 10000ms CAN Message
if (currentMillis - previousMillis10000 >= INTERVAL_10_S) { if (currentMillis - previousMillis10000 >= INTERVAL_10_S) {
previousMillis10000 = currentMillis; previousMillis10000 = currentMillis;
transmit_can_frame(&BMWiX_6F4_REQUEST_BALANCING_START2, can_config.battery); //transmit_can_frame(&BMWiX_6F4_REQUEST_BALANCING_START2, can_config.battery);
transmit_can_frame(&BMWiX_6F4_REQUEST_BALANCING_START, can_config.battery); //transmit_can_frame(&BMWiX_6F4_REQUEST_BALANCING_START, can_config.battery);
} }
} }
void setup_battery(void) { // Performs one time setup at startup void BmwIXBattery::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "BMW iX and i4-7 platform", 63); strncpy(datalayer.system.info.battery_protocol, "BMW iX and i4-7 platform", 63);
datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.info.battery_protocol[63] = '\0';
//Reset Battery at bootup //Reset Battery at bootup
transmit_can_frame(&BMWiX_6F4_REQUEST_HARD_RESET, can_config.battery); //transmit_can_frame(&BMWiX_6F4_REQUEST_HARD_RESET, can_config.battery);
//Before we have started up and detected which battery is in use, use 108S values //Before we have started up and detected which battery is in use, use 108S values
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
@ -883,4 +490,211 @@ void setup_battery(void) { // Performs one time setup at startup
datalayer.system.status.battery_allows_contactor_closing = true; datalayer.system.status.battery_allows_contactor_closing = true;
} }
#endif void BmwIXBattery::HandleIncomingUserRequest(void) {
// Debug user request to open or close the contactors
#ifdef DEBUG_LOG
logging.print("User request: contactor close: ");
logging.print(datalayer_extended.bmwix.UserRequestContactorClose);
logging.print(" User request: contactor open: ");
logging.println(datalayer_extended.bmwix.UserRequestContactorOpen);
#endif // DEBUG_LOG
if ((datalayer_extended.bmwix.UserRequestContactorClose == false) &&
(datalayer_extended.bmwix.UserRequestContactorOpen == false)) {
// do nothing
} else if ((datalayer_extended.bmwix.UserRequestContactorClose == true) &&
(datalayer_extended.bmwix.UserRequestContactorOpen == false)) {
BmwIxCloseContactors();
// set user request to false
datalayer_extended.bmwix.UserRequestContactorClose = false;
} else if ((datalayer_extended.bmwix.UserRequestContactorClose == false) &&
(datalayer_extended.bmwix.UserRequestContactorOpen == true)) {
BmwIxOpenContactors();
// set user request to false
datalayer_extended.bmwix.UserRequestContactorOpen = false;
} else if ((datalayer_extended.bmwix.UserRequestContactorClose == true) &&
(datalayer_extended.bmwix.UserRequestContactorOpen == true)) {
// these flasgs should not be true at the same time, therefore open contactors, as that is the safest state
BmwIxOpenContactors();
// set user request to false
datalayer_extended.bmwix.UserRequestContactorClose = false;
datalayer_extended.bmwix.UserRequestContactorOpen = false;
// print error, as both these flags shall not be true at the same time
#ifdef DEBUG_LOG
logging.println(
"Error: user requested contactors to close and open at the same time. Contactors have been opened.");
#endif // DEBUG_LOG
}
}
void BmwIXBattery::HandleIncomingInverterRequest(void) {
InverterContactorCloseRequest.present = datalayer.system.status.inverter_allows_contactor_closing;
// Detect edge
if (InverterContactorCloseRequest.previous == false && InverterContactorCloseRequest.present == true) {
// Rising edge detected
#ifdef DEBUG_LOG
logging.println("Inverter requests to close contactors");
#endif // DEBUG_LOG
BmwIxCloseContactors();
} else if (InverterContactorCloseRequest.previous == true && InverterContactorCloseRequest.present == false) {
// Falling edge detected
#ifdef DEBUG_LOG
logging.println("Inverter requests to open contactors");
#endif // DEBUG_LOG
BmwIxOpenContactors();
} // else: do nothing
// Update state
InverterContactorCloseRequest.previous = InverterContactorCloseRequest.present;
}
void BmwIXBattery::BmwIxCloseContactors(void) {
#ifdef DEBUG_LOG
logging.println("Closing contactors");
#endif // DEBUG_LOG
contactorCloseReq = true;
}
void BmwIXBattery::BmwIxOpenContactors(void) {
#ifdef DEBUG_LOG
logging.println("Opening contactors");
#endif // DEBUG_LOG
contactorCloseReq = false;
counter_100ms = 0; // reset counter, such that keep contactors closed message sequence starts from the beginning
}
void BmwIXBattery::HandleBmwIxCloseContactorsRequest(uint16_t counter_10ms) {
if (contactorCloseReq == true) { // Only when contactor close request is set to true
if (ContactorState.closed == false &&
ContactorState.open ==
true) { // Only when the following commands have not been completed yet, because it shall not be run when commands have already been run, AND only when contactor open commands have finished
// Initially 0x510[2] needs to be 0x02, and 0x510[5] needs to be 0x00
BMWiX_510.data = {0x40, 0x10,
0x02, // 0x02 at contactor closing, afterwards 0x04 and 0x10, 0x00 to open contactors
0x00, 0x00,
0x00, // 0x00 at start of contactor closing, changing to 0x80, afterwards 0x80
0x01, // 0x01 at contactor closing
0x00}; // Explicit declaration, to prevent modification by other functions
BMWiX_16E.data = {
0x00, // Almost any possible number in 0x00 and 0xFF
0xA0, // Almost any possible number in 0xA0 and 0xAF
0xC9, 0xFF, 0x60,
0xC9, 0x3A, 0xF7}; // Explicit declaration of default values, to prevent modification by other functions
if (counter_10ms == 0) {
// @0 ms
transmit_can_frame(&BMWiX_510, can_config.battery);
#ifdef DEBUG_LOG
logging.println("Transmitted 0x510 - 1/6");
#endif // DEBUG_LOG
} else if (counter_10ms == 5) {
// @50 ms
transmit_can_frame(&BMWiX_276, can_config.battery);
#ifdef DEBUG_LOG
logging.println("Transmitted 0x276 - 2/6");
#endif // DEBUG_LOG
} else if (counter_10ms == 10) {
// @100 ms
BMWiX_510.data.u8[2] = 0x04; // TODO: check if needed
transmit_can_frame(&BMWiX_510, can_config.battery);
#ifdef DEBUG_LOG
logging.println("Transmitted 0x510 - 3/6");
#endif // DEBUG_LOG
} else if (counter_10ms == 20) {
// @200 ms
BMWiX_510.data.u8[2] = 0x10; // TODO: check if needed
BMWiX_510.data.u8[5] = 0x80; // needed to close contactors
transmit_can_frame(&BMWiX_510, can_config.battery);
#ifdef DEBUG_LOG
logging.println("Transmitted 0x510 - 4/6");
#endif // DEBUG_LOG
} else if (counter_10ms == 30) {
// @300 ms
BMWiX_16E.data.u8[0] = 0x6A;
BMWiX_16E.data.u8[1] = 0xAD;
transmit_can_frame(&BMWiX_16E, can_config.battery);
#ifdef DEBUG_LOG
logging.println("Transmitted 0x16E - 5/6");
#endif // DEBUG_LOG
} else if (counter_10ms == 50) {
// @500 ms
BMWiX_16E.data.u8[0] = 0x03;
BMWiX_16E.data.u8[1] = 0xA9;
transmit_can_frame(&BMWiX_16E, can_config.battery);
#ifdef DEBUG_LOG
logging.println("Transmitted 0x16E - 6/6");
#endif // DEBUG_LOG
ContactorState.closed = true;
ContactorState.open = false;
}
}
}
}
void BmwIXBattery::BmwIxKeepContactorsClosed(uint8_t counter_100ms) {
if ((ContactorState.closed == true) && (ContactorState.open == false)) {
BMWiX_510.data = {0x40, 0x10,
0x04, // 0x02 at contactor closing, afterwards 0x04 and 0x10, 0x00 to open contactors
0x00, 0x00,
0x80, // 0x00 at start of contactor closing, changing to 0x80, afterwards 0x80
0x01, // 0x01 at contactor closing
0x00}; // Explicit declaration, to prevent modification by other functions
BMWiX_16E.data = {0x00, // Almost any possible number in 0x00 and 0xFF
0xA0, // Almost any possible number in 0xA0 and 0xAF
0xC9, 0xFF, 0x60,
0xC9, 0x3A, 0xF7}; // Explicit declaration, to prevent modification by other functions
if (counter_100ms == 0) {
#ifdef DEBUG_LOG
logging.println("Sending keep contactors closed messages started");
#endif // DEBUG_LOG
// @0 ms
transmit_can_frame(&BMWiX_510, can_config.battery);
} else if (counter_100ms == 7) {
// @ 730 ms
BMWiX_16E.data.u8[0] = 0x8C;
BMWiX_16E.data.u8[1] = 0xA0;
transmit_can_frame(&BMWiX_16E, can_config.battery);
} else if (counter_100ms == 24) {
// @2380 ms
transmit_can_frame(&BMWiX_510, can_config.battery);
} else if (counter_100ms == 29) {
// @ 2900 ms
BMWiX_16E.data.u8[0] = 0x02;
BMWiX_16E.data.u8[1] = 0xA7;
transmit_can_frame(&BMWiX_16E, can_config.battery);
#ifdef DEBUG_LOG
logging.println("Sending keep contactors closed messages finished");
#endif // DEBUG_LOG
} else if (counter_100ms == 140) {
// @14000 ms
// reset counter (outside of this function)
}
}
}
void BmwIXBattery::HandleBmwIxOpenContactorsRequest(uint16_t counter_10ms) {
if (contactorCloseReq == false) { // if contactors are not requested to be closed, they are requested to be opened
if (ContactorState.open == false) { // only if contactors are not open yet
// message content to quickly open contactors
if (counter_10ms == 0) {
// @0 ms (0.00) RX0 510 [8] 40 10 00 00 00 80 00 00
BMWiX_510.data = {0x40, 0x10, 0x00, 0x00,
0x00, 0x80, 0x00, 0x00}; // Explicit declaration, to prevent modification by other functions
transmit_can_frame(&BMWiX_510, can_config.battery);
// set back to default values
BMWiX_510.data = {0x40, 0x10, 0x04, 0x00, 0x00, 0x80, 0x01, 0x00}; // default values
} else if (counter_10ms == 6) {
// @60 ms (0.06) RX0 16E [8] E6 A4 C8 FF 60 C9 33 F0
BMWiX_16E.data = {0xE6, 0xA4, 0xC8, 0xFF,
0x60, 0xC9, 0x33, 0xF0}; // Explicit declaration, to prevent modification by other functions
transmit_can_frame(&BMWiX_16E, can_config.battery);
// set back to default values
BMWiX_16E.data = {0x00, 0xA0, 0xC9, 0xFF, 0x60, 0xC9, 0x3A, 0xF7}; // default values
ContactorState.closed = false;
ContactorState.open = true;
}
}
}
}
#endif // BMW_IX_BATTERY

View file

@ -2,21 +2,624 @@
#define BMW_IX_BATTERY_H #define BMW_IX_BATTERY_H
#include <Arduino.h> #include <Arduino.h>
#include "../include.h" #include "../include.h"
#include "CanBattery.h"
#define BATTERY_SELECTED #define BATTERY_SELECTED
#define SELECTED_BATTERY_CLASS BmwIXBattery
#define MAX_PACK_VOLTAGE_DV 4650 //4650 = 465.0V class BmwIXBattery : public CanBattery {
#define MIN_PACK_VOLTAGE_DV 3000 public:
#define MAX_CELL_DEVIATION_MV 250 virtual void setup(void);
#define MAX_CELL_VOLTAGE_MV 4300 //Battery is put into emergency stop if one cell goes over this value virtual void handle_incoming_can_frame(CAN_frame rx_frame);
#define MIN_CELL_VOLTAGE_MV 2800 //Battery is put into emergency stop if one cell goes below this value virtual void update_values();
#define MAX_DISCHARGE_POWER_ALLOWED_W 10000 virtual void transmit_can(unsigned long currentMillis);
#define MAX_CHARGE_POWER_ALLOWED_W 10000
#define MAX_CHARGE_POWER_WHEN_TOPBALANCING_W 500 private:
#define RAMPDOWN_SOC 9000 // (90.00) SOC% to start ramping down from max charge power towards 0 at 100.00% static const int MAX_PACK_VOLTAGE_DV = 4650; //4650 = 465.0V
#define STALE_PERIOD_CONFIG \ static const int MIN_PACK_VOLTAGE_DV = 3000;
static const int MAX_CELL_DEVIATION_MV = 250;
static const int MAX_CELL_VOLTAGE_MV = 4300; //Battery is put into emergency stop if one cell goes over this value
static const int MIN_CELL_VOLTAGE_MV = 2800; //Battery is put into emergency stop if one cell goes below this value
static const int MAX_DISCHARGE_POWER_ALLOWED_W = 10000;
static const int MAX_CHARGE_POWER_ALLOWED_W = 10000;
static const int MAX_CHARGE_POWER_WHEN_TOPBALANCING_W = 500;
static const int RAMPDOWN_SOC =
9000; // (90.00) SOC% to start ramping down from max charge power towards 0 at 100.00%
static const int STALE_PERIOD_CONFIG =
900000; //Number of milliseconds before critical values are classed as stale/stuck 900000 = 900 seconds 900000; //Number of milliseconds before critical values are classed as stale/stuck 900000 = 900 seconds
void setup_battery(void);
void transmit_can_frame(CAN_frame* tx_frame, int interface); unsigned long previousMillis10 = 0; // will store last time a 20ms CAN Message was send
unsigned long previousMillis20 = 0; // will store last time a 20ms CAN Message was send
unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was send
unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was send
unsigned long previousMillis640 = 0; // will store last time a 600ms CAN Message was send
unsigned long previousMillis1000 = 0; // will store last time a 600ms CAN Message was send
unsigned long previousMillis10000 = 0; // will store last time a 10000ms CAN Message was send
static const int ALIVE_MAX_VALUE = 14; // BMW CAN messages contain alive counter, goes from 0...14
enum CmdState { SOH, CELL_VOLTAGE_MINMAX, SOC, CELL_VOLTAGE_CELLNO, CELL_VOLTAGE_CELLNO_LAST };
CmdState cmdState = SOC;
bool battery_awake = false;
bool contactorCloseReq = false;
struct ContactorCloseRequestStruct {
bool previous;
bool present;
} ContactorCloseRequest = {false, false};
struct ContactorStateStruct {
bool closed;
bool open;
};
ContactorStateStruct ContactorState = {false, true};
struct InverterContactorCloseRequestStruct {
bool previous;
bool present;
};
InverterContactorCloseRequestStruct InverterContactorCloseRequest = {false, false};
/*
SME output:
0x12B8D087 5000ms - Extended ID
0x1D2 DLC8 1000ms
0x20B DLC8 1000ms
0x2E2 DLC16 1000ms
0x31F DLC16 100ms - 2 downward counters?
0x3EA DLC8
0x453 DLC20 200ms
0x486 DLC48 1000ms
0x49C DLC8 1000ms
0x4A1 DLC8 1000ms
0x4BB DLC64 200ms - seems multplexed on [0]
0x4D0 DLC64 1000ms - some slow/flickering values - possible change during fault
0x507 DLC8
0x587 DLC8 - appears at startup 0x78 0x07 0x00 0x00 0xFF 0xFF 0xFF 0xFF , 0x01 0x03 0x80 0xFF 0xFF 0xFF 0xFF 0xFF, 0x78 0x07 0x00 0x00 0xFF 0xFF 0xFF 0xFF, 0x06 0x00 0x00 0xFF 0xFF 0xFF 0xFF 0xFF, 0x01 0x03 0x82 0xFF 0xFF 0xFF 0xFF 0xFF, 0x01 0x03 0x80 0xFF 0xFF 0xFF 0xFF 0xFF
0x607 - UDS response
0x7AB DLC64 - seen at startup
0x8F DLC48 10ms - appears to have analog readings like volt/temp/current
0xD0D087 DLC4
BDC output:
0x276 DLC8 - vehicle condition
0x2F1 DLC8 1000ms - during run: 0xFF, 0xFF, 0xFF, 0xFF, 0x9B, 0x00, 0xF3, 0xFF - at startup 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xF3, 0xFF. Suspect byte [4] is a counter
0x439 DLC4 1000ms - STATIC: byte0, byte1. byte3 changes, byte4 = 0xF2 OR 0xF3
0x4EB DLC8 - RSU condition
0x510 DLC8 100ms - FIXME:(update as this content is not seen in car logs:) 40 10 40 00 6F DF 19 00 during run - Startup sends this once: 0x40 0x10 0x02 0x00 0x00 0x00 0x00 0x00
0x6D DLC8 1000ms - counters? counter on byte3
0xC0 DLC2 200ms - needs counter
SME asks for:
0x125 (CCU)
0x16E (CCU)
0x188 (CCU)
0x1A1 (DSC) - vehicle speed (not seen in car logs)
0x1EA (KOMBI)
0x1FC (FIXME:(add transmitter node.))
0x21D (FIXME:(add transmitter node.))
0x276 (BDC)
0x2ED (FIXME:(add transmitter node.))
0x340 (CCU)
0x380 (FIXME:(add transmitter node.))
0x442 (FIXME:(add transmitter node.))
0x4EB (BDC)
0x4F8 (CCU)
0x91 (EME1)
0xAA (EME2) - all wheel drive only
0x?? Suspect there is a drive mode flag somewhere - balancing might only be active in some modes
TODO:
- Request batt serial number on F1 8C (already parsing RX)
*/
//Vehicle CAN START
CAN_frame BMWiX_125 = {
.FD = true,
.ext_ID = false,
.DLC = 20,
.ID = 0x125,
//.data = {TODO:, TODO:, TODO:, TODO:, 0xFE, 0x7F, 0xFE, 0x7F, TODO:, TODO:, TODO:, TODO:, TODO:, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xFF, 0xFF}
}; // CCU output
/* SME output
CAN_frame BMWiX_12B8D087 = {.FD = true,
.ext_ID = true,
.DLC = 2,
.ID = 0x12B8D087,
.data = {0xFC, 0xFF}}; // 5000ms SME output - values
*/
CAN_frame BMWiX_16E = {.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x16E,
.data = {0x00, // Almost any possible number in 0x00 and 0xFF
0xA0, // Almost any possible number in 0xA0 and 0xAF
0xC9, 0xFF,
0x60, // FIXME: find out what this value represents
0xC9,
0x3A, // 0x3A to close contactors, 0x33 to open contactors
0xF7}}; // 0xF7 to close contactors, 0xF0 to open contactors // CCU output.
CAN_frame BMWiX_188 = {
.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x188,
.data = {0x00, 0x00, 0x00, 0x00, 0x3C, 0xFF, 0xFF, 0xFF}}; // CCU output - values while driving
CAN_frame BMWiX_1EA = {
.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x1EA,
//.data = {TODO:km_least_significant, TODO:, TODO:, TODO:, TODO:km_most_significant, 0xFF, TODO:, TODO:}
}; // KOMBI output - kilometerstand
CAN_frame BMWiX_1FC = {
.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x1FC,
.data = {0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0xC0,
0x00}}; // FIXME:(add transmitter node) output - heat management engine control - values
CAN_frame BMWiX_21D = {
.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x21D,
// .data = {TODO:, TODO:, TODO:, 0xFF, 0xFF, 0xFF, 0xFF, TODO:}
}; // FIXME:(add transmitter node) output - request heating and air conditioning system 1
CAN_frame BMWiX_276 = {.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x276,
.data = {0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF,
0xFD}}; // BDC output - vehicle condition. Used for contactor closing
CAN_frame BMWiX_2ED = {
.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x2ED,
.data = {
0x75,
0x75}}; // FIXME:(add transmitter node) output - ambient temperature (values seen in logs vary between 0x72 and 0x79)
CAN_frame BMWiX_2F1 = {
.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x2F1,
.data = {0xFF, 0xFF, 0xD0, 0x39, 0x94, 0x00, 0xF3, 0xFF}}; // 1000ms BDC output - values - varies at startup
CAN_frame BMWiX_340 = {
.FD = true,
.ext_ID = false,
.DLC = 12,
.ID = 0x340,
// .data = {TODO:, TODO:, TODO:, 0xFF, TODO:, TODO:, 0x00, 0x00, TODO:, TODO:, TODO:, 0xFF, 0xFF, }
}; // CCU output
CAN_frame BMWiX_380 = {
.FD = true,
.ext_ID = false,
.DLC = 7,
.ID = 0x380,
// .data = {FIXME:(VIN_char1), FIXME:(VIN_char2), FIXME:(VIN_char3), FIXME:(VIN_char4), FIXME:(VIN_char5), FIXME:(VIN_char6), FIXME:(VIN_char7)}
}; // FIXME:(add transmitter node) output - VIN: ASCII2HEX
/* Not requested by SME
CAN_frame BMWiX_439 = {.FD = true,
.ext_ID = false,
.DLC = 4,
.ID = 0x439,
.data = {0xFF, 0x3F, 0xFF, 0xF3}}; // 1000ms BDC output
*/
CAN_frame BMWiX_442 = {
.FD = true,
.ext_ID = false,
.DLC = 6,
.ID = 0x442,
// .data = {TODO: relative time byte 0, TODO: relative time byte1, 0xA9, 0x00, 0xE0, 0x23}
}; // FIXME:(add transmitter node) output - relative time BN2020
/* SME output
CAN_frame
BMWiX_486 =
{
.FD = true,
.ext_ID = false,
.DLC = 48,
.ID = 0x486,
.data =
{
0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE,
0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF,
0xFE, 0xFF, 0xFF, 0x7F, 0x33, 0xFD, 0xFD, 0xFD, 0xFD, 0xC0, 0x41, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}}; // 1000ms SME output - Suspected keep alive CONFIRM NEEDED
*/
/* SME output
CAN_frame BMWiX_49C = {.FD = true,
.ext_ID = false,
.DLC = 4,
.ID = 0x49C,
.data = {0xD2, 0xF2, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF}}; // 1000ms SME output - Suspected keep alive CONFIRM NEEDED
*/
CAN_frame BMWiX_4EB = {
.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x4EB,
// .data = {TODO:, TODO:, TODO: 0xE0 or 0xE5 (while driving), 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
}; // BDC output - RSU condition
CAN_frame BMWiX_4F8 = {
.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x4F8,
// .data = {0xFF, 0xFD, 0xFF, 0xFF, 0xFF, TODO:, TODO:, 0xC8, 0x00, 0x00, 0xF0, 0x40, 0xFE, 0xFF, 0xFD, 0xFF, TODO:, TODO:, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
}; // CCU output
CAN_frame BMWiX_510 = {
.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x510,
.data = {
0x40, 0x10,
0x04, // 0x02 at contactor closing, afterwards 0x04 and 0x10, 0x00 to open contactors
0x00, 0x00,
0x80, // 0x00 at start of contactor closing, changing to 0x80, afterwards 0x80
0x01,
0x00}}; // 100ms BDC output - Values change in car logs, these bytes are the most common. Used for contactor closing
CAN_frame BMWiX_6D = {
.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x6D,
.data = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
0xFF}}; // 1000ms BDC output - [0] [1,2][3,4] counter x2. 3,4 is 9 higher than 1,2 is needed? [5-7] static
CAN_frame BMWiX_C0 = {
.FD = true,
.ext_ID = false,
.DLC = 2,
.ID = 0xC0,
.data = {
0xF0,
0x00}}; // BDC output - Keep Alive 2 BDC>SME 200ms First byte cycles F0 > FE second byte 00 - MINIMUM ID TO KEEP SME AWAKE
//Vehicle CAN END
//Request Data CAN START
CAN_frame BMWiX_6F4 = {
.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0xC7}}; // Generic UDS Request data from SME. byte 4 selects requested value
CAN_frame BMWiX_6F4_REQUEST_SLEEPMODE = {
.FD = true,
.ext_ID = false,
.DLC = 4,
.ID = 0x6F4,
.data = {0x07, 0x02, 0x11, 0x04}}; // UDS Request Request BMS/SME goes to Sleep Mode
CAN_frame BMWiX_6F4_REQUEST_HARD_RESET = {.FD = true,
.ext_ID = false,
.DLC = 4,
.ID = 0x6F4,
.data = {0x07, 0x02, 0x11, 0x01}}; // UDS Request Hard reset of BMS/SME
CAN_frame BMWiX_6F4_REQUEST_CELL_TEMP = {.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xDD, 0xC0}}; // UDS Request Cell Temperatures
CAN_frame BMWiX_6F4_REQUEST_SOC = {.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0xCE}}; // Min/Avg/Max SOC%
CAN_frame BMWiX_6F4_REQUEST_CAPACITY = {
.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5,
0xC7}}; //Current and max capacity kWh. Stored in kWh as 0.01 scale with -50 bias
CAN_frame BMWiX_6F4_REQUEST_MINMAXCELLV = {
.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x53}}; //Min and max cell voltage 10V = Qualifier Invalid
CAN_frame BMWiX_6F4_REQUEST_MAINVOLTAGE_POSTCONTACTOR = {
.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x4A}}; //Main Battery Voltage (After Contactor)
CAN_frame BMWiX_6F4_REQUEST_MAINVOLTAGE_PRECONTACTOR = {
.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x4D}}; //Main Battery Voltage (Pre Contactor)
CAN_frame BMWiX_6F4_REQUEST_BATTERYCURRENT = {
.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x61}}; //Current amps 32bit signed MSB. dA . negative is discharge
CAN_frame BMWiX_6F4_REQUEST_CELL_VOLTAGE = {
.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x54}}; //MultiFrameIndividual Cell Voltages
CAN_frame BMWiX_6F4_REQUEST_T30VOLTAGE = {
.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0xA7}}; //Terminal 30 Voltage (12V SME supply)
CAN_frame BMWiX_6F4_REQUEST_EOL_ISO = {.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xA8, 0x60}}; //Request EOL Reading including ISO
CAN_frame BMWiX_6F4_REQUEST_SOH = {.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x45}}; //SOH Max Min Mean Request
CAN_frame BMWiX_6F4_REQUEST_DATASUMMARY = {
.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {
0x07, 0x03, 0x22, 0xE5,
0x45}}; //MultiFrame Summary Request, includes SOC/SOH/MinMax/MaxCapac/RemainCapac/max v and t at last charge. slow refreshrate
CAN_frame BMWiX_6F4_REQUEST_PYRO = {.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xAC, 0x93}}; //Pyro Status
CAN_frame BMWiX_6F4_REQUEST_UPTIME = {.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE4, 0xC0}}; // Uptime and Vehicle Time Status
CAN_frame BMWiX_6F4_REQUEST_HVIL = {.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x69}}; // Request HVIL State
CAN_frame BMWiX_6F4_REQUEST_BALANCINGSTATUS = {.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE4, 0xCA}}; // Request Balancing Data
CAN_frame BMWiX_6F4_REQUEST_MAX_CHARGE_DISCHARGE_AMPS = {
.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x62}}; // Request allowable charge discharge amps
CAN_frame BMWiX_6F4_REQUEST_VOLTAGE_QUALIFIER_CHECK = {
.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x4B}}; // Request HV Voltage Qualifier
CAN_frame BMWiX_6F4_REQUEST_CONTACTORS_CLOSE = {
.FD = true,
.ext_ID = false,
.DLC = 6,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x51, 0x01}}; // Request Contactors Close - Unconfirmed
CAN_frame BMWiX_6F4_REQUEST_CONTACTORS_OPEN = {
.FD = true,
.ext_ID = false,
.DLC = 6,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x51, 0x01}}; // Request Contactors Open - Unconfirmed
CAN_frame BMWiX_6F4_REQUEST_BALANCING_START = {
.FD = true,
.ext_ID = false,
.DLC = 6,
.ID = 0x6F4,
.data = {0xF4, 0x04, 0x71, 0x01, 0xAE, 0x77}}; // Request Balancing command?
CAN_frame BMWiX_6F4_REQUEST_BALANCING_START2 = {
.FD = true,
.ext_ID = false,
.DLC = 6,
.ID = 0x6F4,
.data = {0xF4, 0x04, 0x31, 0x01, 0xAE, 0x77}}; // Request Balancing command?
CAN_frame BMWiX_6F4_REQUEST_PACK_VOLTAGE_LIMITS = {
.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x4C}}; // Request pack voltage limits
CAN_frame BMWiX_6F4_CONTINUE_DATA = {.FD = true,
.ext_ID = false,
.DLC = 4,
.ID = 0x6F4,
.data = {0x07, 0x30, 0x00, 0x02}};
//Action Requests:
CAN_frame BMWiX_6F4_CELL_SOC = {.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x9A}};
CAN_frame BMWiX_6F4_CELL_TEMP = {.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0xCA}};
//Request Data CAN End
//Setup UDS values to poll for
CAN_frame* UDS_REQUESTS100MS[17] = {&BMWiX_6F4_REQUEST_CELL_TEMP,
&BMWiX_6F4_REQUEST_SOC,
&BMWiX_6F4_REQUEST_CAPACITY,
&BMWiX_6F4_REQUEST_MINMAXCELLV,
&BMWiX_6F4_REQUEST_MAINVOLTAGE_POSTCONTACTOR,
&BMWiX_6F4_REQUEST_MAINVOLTAGE_PRECONTACTOR,
&BMWiX_6F4_REQUEST_BATTERYCURRENT,
&BMWiX_6F4_REQUEST_CELL_VOLTAGE,
&BMWiX_6F4_REQUEST_T30VOLTAGE,
&BMWiX_6F4_REQUEST_SOH,
&BMWiX_6F4_REQUEST_UPTIME,
&BMWiX_6F4_REQUEST_PYRO,
&BMWiX_6F4_REQUEST_EOL_ISO,
&BMWiX_6F4_REQUEST_HVIL,
&BMWiX_6F4_REQUEST_MAX_CHARGE_DISCHARGE_AMPS,
&BMWiX_6F4_REQUEST_BALANCINGSTATUS,
&BMWiX_6F4_REQUEST_PACK_VOLTAGE_LIMITS};
int numUDSreqs = sizeof(UDS_REQUESTS100MS) / sizeof(UDS_REQUESTS100MS[0]); // Number of elements in the array
//iX Intermediate vars
bool battery_info_available = false;
uint32_t battery_serial_number = 0;
int32_t battery_current = 0;
int16_t battery_voltage = 370; //Startup with valid values - needs fixing in future
int16_t terminal30_12v_voltage = 0;
int16_t battery_voltage_after_contactor = 0;
int16_t min_soc_state = 5000;
int16_t avg_soc_state = 5000;
int16_t max_soc_state = 5000;
int16_t min_soh_state = 9900; // Uses E5 45, also available in 78 73
int16_t avg_soh_state = 9900; // Uses E5 45, also available in 78 73
int16_t max_soh_state = 9900; // Uses E5 45, also available in 78 73
uint16_t max_design_voltage = 0;
uint16_t min_design_voltage = 0;
int32_t remaining_capacity = 0;
int32_t max_capacity = 0;
int16_t min_battery_temperature = 0;
int16_t avg_battery_temperature = 0;
int16_t max_battery_temperature = 0;
int16_t main_contactor_temperature = 0;
int16_t min_cell_voltage = 3700; //Startup with valid values - needs fixing in future
int16_t max_cell_voltage = 3700; //Startup with valid values - needs fixing in future
unsigned long min_cell_voltage_lastchanged = 0;
unsigned long max_cell_voltage_lastchanged = 0;
unsigned min_cell_voltage_lastreceived = 0;
unsigned max_cell_voltage_lastreceived = 0;
uint32_t sme_uptime = 0; //Uses E4 C0
int16_t allowable_charge_amps = 0; //E5 62
int16_t allowable_discharge_amps = 0; //E5 62
int32_t iso_safety_positive = 0; //Uses A8 60
int32_t iso_safety_negative = 0; //Uses A8 60
int32_t iso_safety_parallel = 0; //Uses A8 60
int16_t count_full_charges = 0; //TODO 42
int16_t count_charges = 0; //TODO 42
int16_t hvil_status = 0;
int16_t voltage_qualifier_status = 0; //0 = Valid, 1 = Invalid
int16_t balancing_status = 0; //4 = not active
uint8_t contactors_closed = 0; //TODO E5 BF or E5 51
uint8_t contactor_status_precharge = 0; //TODO E5 BF
uint8_t contactor_status_negative = 0; //TODO E5 BF
uint8_t contactor_status_positive = 0; //TODO E5 BF
uint8_t pyro_status_pss1 = 0; //Using AC 93
uint8_t pyro_status_pss4 = 0; //Using AC 93
uint8_t pyro_status_pss6 = 0; //Using AC 93
uint8_t uds_req_id_counter = 0;
uint8_t detected_number_of_cells = 108;
const unsigned long STALE_PERIOD =
STALE_PERIOD_CONFIG; // Time in milliseconds to check for staleness (e.g., 5000 ms = 5 seconds)
//End iX Intermediate vars
uint8_t current_cell_polled = 0;
uint16_t counter_10ms = 0; // max 65535 --> 655.35 seconds
uint8_t counter_100ms = 0; // max 255 --> 25.5 seconds
bool isStale(int16_t currentValue, uint16_t& lastValue, unsigned long& lastChangeTime);
uint8_t increment_uds_req_id_counter(uint8_t index);
uint8_t increment_alive_counter(uint8_t counter);
/**
* @brief Handle incoming user request to close or open contactors
*
* @param[in] void
*
* @return void
*/
void HandleIncomingUserRequest(void);
/**
* @brief Handle incoming inverter request to close or open contactors.alignas
*
* This function uses the "inverter_allows_contactor_closing" flag from the datalayer, to determine if CAN messages shall be sent to the battery to close or open the contactors.
*
* @param[in] void
*
* @return void
*/
void HandleIncomingInverterRequest(void);
/**
* @brief Close contactors of the BMW iX battery
*
* @param[in] void
*
* @return void
*/
void BmwIxCloseContactors(void);
/**
* @brief Handle close contactors requests for the BMW iX battery
*
* @param[in] counter_10ms Counter that increments by 1, every 10ms
*
* @return void
*/
void HandleBmwIxCloseContactorsRequest(uint16_t counter_10ms);
/**
* @brief Keep contactors of the BMW iX battery closed
*
* @param[in] counter_100ms Counter that increments by 1, every 100 ms
*
* @return void
*/
void BmwIxKeepContactorsClosed(uint8_t counter_100ms);
/**
* @brief Open contactors of the BMW iX battery
*
* @param[in] void
*
* @return void
*/
void BmwIxOpenContactors(void);
/**
* @brief Handle open contactors requests for the BMW iX battery
*
* @param[in] counter_10ms Counter that increments by 1, every 10ms
*
* @return void
*/
void HandleBmwIxOpenContactorsRequest(uint16_t counter_10ms);
};
#endif #endif

View file

@ -1,40 +1,11 @@
#include "../include.h" #include "../include.h"
#ifdef BMW_PHEV_BATTERY #ifdef BMW_PHEV_BATTERY
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h" #include "../datalayer/datalayer_extended.h"
#include "../devboard/utils/events.h" #include "../devboard/utils/events.h"
#include "BMW-PHEV-BATTERY.h" #include "BMW-PHEV-BATTERY.h"
/* Do not change code below unless you are sure what you are doing */
static unsigned long previousMillis20 = 0; // will store last time a 20ms CAN Message was send
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
static unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was send
static unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was send
static unsigned long previousMillis640 = 0; // will store last time a 600ms CAN Message was send
static unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was send
static unsigned long previousMillis5000 = 0; // will store last time a 5000ms CAN Message was send
static unsigned long previousMillis10000 = 0; // will store last time a 10000ms CAN Message was send
#define ALIVE_MAX_VALUE 14 // BMW CAN messages contain alive counter, goes from 0...14
enum CmdState { SOH, CELL_VOLTAGE_MINMAX, SOC, CELL_VOLTAGE_CELLNO, CELL_VOLTAGE_CELLNO_LAST };
static CmdState cmdState = SOC;
// A structure to keep track of the ongoing multi-frame UDS response
typedef struct {
bool UDS_inProgress; // Are we currently receiving a multi-frame message?
uint16_t UDS_expectedLength; // Expected total payload length
uint16_t UDS_bytesReceived; // How many bytes have been stored so far
uint8_t UDS_moduleID; // The "module" indicated by the first frame
uint8_t receivedInBatch; // Number of CFs received in the current batch
uint8_t UDS_buffer[256]; // Buffer for the reassembled data
unsigned long UDS_lastFrameMillis; // Timestamp of last frame (for timeouts, if desired)
} UDS_RxContext;
// A single global UDS context, since only one module can respond at a time
static UDS_RxContext gUDSContext;
const unsigned char crc8_table[256] = const unsigned char crc8_table[256] =
{ // CRC8_SAE_J1850_ZER0 formula,0x1D Poly,initial value 0x3F,Final XOR value varies { // CRC8_SAE_J1850_ZER0 formula,0x1D Poly,initial value 0x3F,Final XOR value varies
0x00, 0x1D, 0x3A, 0x27, 0x74, 0x69, 0x4E, 0x53, 0xE8, 0xF5, 0xD2, 0xCF, 0x9C, 0x81, 0xA6, 0xBB, 0xCD, 0xD0, 0x00, 0x1D, 0x3A, 0x27, 0x74, 0x69, 0x4E, 0x53, 0xE8, 0xF5, 0xD2, 0xCF, 0x9C, 0x81, 0xA6, 0xBB, 0xCD, 0xD0,
@ -102,349 +73,8 @@ TODO:
*/ */
//Vehicle CAN START
CAN_frame BMWiX_0C0 = {
.FD = false,
.ext_ID = false,
.DLC = 2,
.ID = 0x0C0,
.data = {
0xF0,
0x08}}; // Keep Alive 2 BDC>SME 200ms First byte cycles F0 > FE second byte 08 static - MINIMUM ID TO KEEP SME AWAKE
CAN_frame BMW_13E = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x13E,
.data = {0xFF, 0x31, 0xFA, 0xFA, 0xFA, 0xFA, 0x0C, 0x00}};
//Vehicle CAN END
//Request Data CAN START
CAN_frame BMW_PHEV_BUS_WAKEUP_REQUEST = {
.FD = false,
.ext_ID = false,
.DLC = 4,
.ID = 0x554,
.data = {
0x5A, 0xA5, 0x5A,
0xA5}}; // Won't work at 500kbps! Ideally sent at 50kbps - but can also achieve wakeup at 100kbps (helps with library support but might not be as reliable). Might need to be sent twice + clear buffer
CAN_frame BMWPHEV_6F1_REQUEST_SOC = {.FD = false,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F1,
.data = {0x07, 0x03, 0x22, 0xDD, 0xC4}}; // SOC%
CAN_frame BMWPHEV_6F1_REQUEST_SOH = {.FD = false,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F1,
.data = {0x07, 0x03, 0x22, 0xDD, 0x7B}}; // SOH%
CAN_frame BMWPHEV_6F1_REQUEST_CURRENT = {.FD = false,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F1,
.data = {0x07, 0x03, 0x22, 0xDD, 0x69}}; // SOH%
CAN_frame BMWPHEV_6F1_REQUEST_VOLTAGE_LIMITS = {
.FD = false,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F1,
.data = {0x07, 0x03, 0x22, 0xDD, 0x7E}}; // Pack Voltage Limits Multi Frame
CAN_frame BMWPHEV_6F1_REQUEST_PAIRED_VIN = {.FD = false,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F1,
.data = {0x07, 0x03, 0x22, 0xF1, 0x90}}; // SME Paired VIN
CAN_frame BMWPHEV_6F1_REQUEST_ISO_READING1 = {
.FD = false,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F1,
.data = {
0x07, 0x03, 0x22, 0xDD,
0x6A}}; // MULTI FRAME ISOLATIONSWIDERSTAND 62 DD 6A [07 D0] [07 D0] [07 D0] [01] [01] [01] 00 00 00 00 00 [EXT Reading] [INT reading] [ EXT - 0 not plausible, 1 plausible]
CAN_frame BMWPHEV_6F1_REQUEST_ISO_READING2 = {
.FD = false,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F1,
.data = {0x07, 0x03, 0x22, 0xD6,
0xD9}}; // R_ISO_ROH 62 D6 D9 [07 FF] [13] (2047kohm) quality of reading 0-21 (19)
CAN_frame BMWPHEV_6F1_REQUEST_PACK_INFO = {
.FD = false,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F1,
.data = {0x07, 0x03, 0x22, 0xDF, 0x71}}; // 62 DF 71 00 60 1C 25 1C? Cell Count, Module Count
CAN_frame BMWPHEV_6F1_REQUEST_CURRENT_LIMITS = {
.FD = false,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F1,
.data = {0x07, 0x03, 0x22, 0xDD, 0x7D}}; // Pack Current Limits Multi Frame
CAN_frame BMWPHEV_6F1_REQUEST_MAINVOLTAGE_PRECONTACTOR = {
.FD = false,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F1,
.data = {0x07, 0x03, 0x22, 0xDD, 0xB4}}; //Main Battery Voltage (Pre Contactor)
CAN_frame BMWPHEV_6F1_REQUEST_MAINVOLTAGE_POSTCONTACTOR = {
.FD = false,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F1,
.data = {0x07, 0x03, 0x22, 0xDD, 0x66}}; //Main Battery Voltage (After Contactor)
CAN_frame BMWPHEV_6F1_REQUEST_CELLSUMMARY = {
.FD = false,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F1,
.data = {0x07, 0x03, 0x22, 0xDF, 0xA0}}; //Min and max cell voltage + temps 6.55V = Qualifier Invalid?
CAN_frame BMWPHEV_6F1_REQUEST_CELLS_INDIVIDUAL_VOLTS = {
.FD = false,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F1,
.data = {0x07, 0x03, 0x22, 0xDF, 0xA5}}; //All individual cell voltages
CAN_frame BMWPHEV_6F1_REQUEST_CELL_TEMP = {
.FD = false,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F1,
.data = {
0x07, 0x03, 0x22, 0xDD,
0xC0}}; // UDS Request Cell Temperatures min max avg. Has continue frame min in first, then max + avg in second frame
CAN_frame BMW_6F1_REQUEST_CONTINUE_MULTIFRAME = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x6F1,
.data = {
0x07, 0x30, 0x03, 0x00, 0x00, 0x00, 0x00,
0x00}}; //Request continued frames from UDS Multiframe request byte[2] is the request messages to return per continue. default 0x03, all is 0x00
CAN_frame BMW_6F1_REQUEST_HARD_RESET = {.FD = false,
.ext_ID = false,
.DLC = 4,
.ID = 0x6F1,
.data = {0x07, 0x03, 0x11, 0x01}}; // Reset BMS - TBC
CAN_frame BMWPHEV_6F1_REQUEST_CONTACTORS_CLOSE = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x6F1,
.data = {0x07, 0x04, 0x2E, 0xDD, 0x61, 0x01, 0x00, 0x00}}; // Request Contactors Close - Unconfirmed
CAN_frame BMWPHEV_6F1_REQUEST_CONTACTORS_OPEN = {
.FD = false,
.ext_ID = false,
.DLC = 6,
.ID = 0x6F1,
.data = {0x07, 0x04, 0x2E, 0xDD, 0x61, 0x00, 0x00, 0x00}}; // Request Contactors Open - Unconfirmed
CAN_frame BMWPHEV_6F1_REQUEST_BALANCING_STATUS = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x6F1,
.data = {0x07, 0x04, 0x31, 0x03, 0xAD, 0x6B, 0x00,
0x00}}; // Balancing status. Response 7DLC F1 05 71 03 AD 6B 01 (01 = active) (03 not active)
CAN_frame BMWPHEV_6F1_REQUEST_ISOLATION_TEST = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x6F1,
.data = {0x07, 0x04, 0x31, 0x01, 0xAD, 0x61, 0x00, 0x00}}; // Start Isolation Test
CAN_frame BMWPHEV_6F1_REQUEST_BALANCING_START = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x6F1,
.data = {0x07, 0x04, 0x31, 0x01, 0xAD, 0x6B, 0x00, 0x00}}; // Balancing start request
CAN_frame BMWPHEV_6F1_REQUEST_BALANCING_STOP = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x6F1,
.data = {0x07, 0x04, 0x31, 0x02, 0xAD, 0x6B, 0x00, 0x00}}; // Balancing stop request
//Action Requests:
CAN_frame BMW_10B = {.FD = false,
.ext_ID = false,
.DLC = 3,
.ID = 0x10B,
.data = {0xCD, 0x00, 0xFC}}; // Contactor closing command?
CAN_frame BMWPHEV_6F1_CELL_SOC = {.FD = false,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F1,
.data = {0x07, 0x03, 0x22, 0xE5, 0x9A}};
CAN_frame BMWPHEV_6F1_CELL_TEMP = {.FD = false,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F1,
.data = {0x07, 0x03, 0x22, 0xE5, 0xCA}};
//Request Data CAN End
static bool battery_awake = false;
//Setup Fast UDS values to poll for
CAN_frame* UDS_REQUESTS_FAST[] = {&BMWPHEV_6F1_REQUEST_CELLSUMMARY,
&BMWPHEV_6F1_REQUEST_SOC,
&BMWPHEV_6F1_REQUEST_CURRENT,
&BMWPHEV_6F1_REQUEST_VOLTAGE_LIMITS,
&BMWPHEV_6F1_REQUEST_MAINVOLTAGE_PRECONTACTOR,
&BMWPHEV_6F1_REQUEST_MAINVOLTAGE_POSTCONTACTOR};
int numFastUDSreqs = sizeof(UDS_REQUESTS_FAST) / sizeof(UDS_REQUESTS_FAST[0]); //Store Number of elements in the array
//Setup Slow UDS values to poll for
CAN_frame* UDS_REQUESTS_SLOW[] = {&BMWPHEV_6F1_REQUEST_ISO_READING1, &BMWPHEV_6F1_REQUEST_ISO_READING2,
&BMWPHEV_6F1_REQUEST_CURRENT_LIMITS, &BMWPHEV_6F1_REQUEST_SOH,
&BMWPHEV_6F1_REQUEST_CELLS_INDIVIDUAL_VOLTS, &BMWPHEV_6F1_REQUEST_CELL_TEMP,
&BMWPHEV_6F1_REQUEST_BALANCING_STATUS, &BMWPHEV_6F1_REQUEST_PAIRED_VIN};
int numSlowUDSreqs = sizeof(UDS_REQUESTS_SLOW) / sizeof(UDS_REQUESTS_SLOW[0]); // Store Number of elements in the array
//PHEV intermediate vars
//#define UDS_LOG //Useful for logging multiframe handling
static uint16_t battery_max_charge_voltage = 0;
static int16_t battery_max_charge_amperage = 0;
static uint16_t battery_min_discharge_voltage = 0;
static int16_t battery_max_discharge_amperage = 0;
static uint8_t startup_counter_contactor = 0;
static uint8_t alive_counter_20ms = 0;
static uint8_t BMW_13E_counter = 0;
static uint32_t battery_BEV_available_power_shortterm_charge = 0;
static uint32_t battery_BEV_available_power_shortterm_discharge = 0;
static uint32_t battery_BEV_available_power_longterm_charge = 0;
static uint32_t battery_BEV_available_power_longterm_discharge = 0;
static uint16_t battery_predicted_energy_charge_condition = 0;
static uint16_t battery_predicted_energy_charging_target = 0;
static uint16_t battery_prediction_voltage_shortterm_charge = 0;
static uint16_t battery_prediction_voltage_shortterm_discharge = 0;
static uint16_t battery_prediction_voltage_longterm_charge = 0;
static uint16_t battery_prediction_voltage_longterm_discharge = 0;
static uint8_t battery_status_service_disconnection_plug = 0;
static uint8_t battery_status_measurement_isolation = 0;
static uint8_t battery_request_abort_charging = 0;
static uint16_t battery_prediction_duration_charging_minutes = 0;
static uint8_t battery_prediction_time_end_of_charging_minutes = 0;
static uint16_t battery_energy_content_maximum_kWh = 0;
static uint8_t battery_request_operating_mode = 0;
static uint16_t battery_target_voltage_in_CV_mode = 0;
static uint8_t battery_request_charging_condition_minimum = 0;
static uint8_t battery_request_charging_condition_maximum = 0;
static uint16_t battery_display_SOC = 0;
static uint8_t battery_status_error_isolation_external_Bordnetz = 0;
static uint8_t battery_status_error_isolation_internal_Bordnetz = 0;
static uint8_t battery_request_cooling = 0;
static uint8_t battery_status_valve_cooling = 0;
static uint8_t battery_status_error_locking = 0;
static uint8_t battery_status_precharge_locked = 0;
static uint8_t battery_status_disconnecting_switch = 0;
static uint8_t battery_status_emergency_mode = 0;
static uint8_t battery_request_service = 0;
static uint8_t battery_error_emergency_mode = 0;
static uint8_t battery_status_error_disconnecting_switch = 0;
static uint8_t battery_status_warning_isolation = 0;
static uint8_t battery_status_cold_shutoff_valve = 0;
static int16_t battery_temperature_HV = 0;
static int16_t battery_temperature_heat_exchanger = 0;
static int16_t battery_temperature_max = 0;
static int16_t battery_temperature_min = 0;
static bool pack_limit_info_available = false;
static bool cell_limit_info_available = false;
//iX Intermediate vars
static uint32_t battery_serial_number = 0;
static int32_t battery_current = 0;
static int16_t battery_voltage = 3700; //Initialize as valid - should be fixed in future
static int16_t terminal30_12v_voltage = 0;
static int16_t battery_voltage_after_contactor = 0;
static int16_t min_soc_state = 5000;
static int16_t avg_soc_state = 5000;
static int16_t max_soc_state = 5000;
static int16_t min_soh_state = 9999; // Uses E5 45, also available in 78 73
static int16_t avg_soh_state = 9999; // Uses E5 45, also available in 78 73
static int16_t max_soh_state = 9999; // Uses E5 45, also available in 78 73
static uint16_t max_design_voltage = 0;
static uint16_t min_design_voltage = 0;
static int32_t remaining_capacity = 0;
static int32_t max_capacity = 0;
static int16_t main_contactor_temperature = 0;
static int16_t min_cell_voltage = 3700; //Initialize as valid - should be fixed in future
static int16_t max_cell_voltage = 3700; //Initialize as valid - should be fixed in future
static unsigned long min_cell_voltage_lastchanged = 0;
static unsigned long max_cell_voltage_lastchanged = 0;
static unsigned min_cell_voltage_lastreceived = 0;
static unsigned max_cell_voltage_lastreceived = 0;
static int16_t allowable_charge_amps = 0; //E5 62
static int16_t allowable_discharge_amps = 0; //E5 62
static int32_t iso_safety_int_kohm = 0; //STAT_ISOWIDERSTAND_INT_WERT
static int32_t iso_safety_ext_kohm = 0; //STAT_ISOWIDERSTAND_EXT_STD_WERT
static int32_t iso_safety_trg_kohm = 0;
static int32_t iso_safety_ext_plausible = 0; //STAT_ISOWIDERSTAND_EXT_TRG_PLAUS
static int32_t iso_safety_int_plausible = 0; //STAT_ISOWIDERSTAND_EXT_TRG_WERT
static int32_t iso_safety_trg_plausible = 0;
static int32_t iso_safety_kohm = 0; //STAT_R_ISO_ROH_01_WERT
static int32_t iso_safety_kohm_quality = 0; //STAT_R_ISO_ROH_QAL_01_INFO Quality of measurement 0-21 (higher better)
static uint8_t paired_vin[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; //17 Byte array for paired VIN
static int16_t count_full_charges = 0; //TODO 42
static int16_t count_charges = 0; //TODO 42
static int16_t hvil_status = 0;
static int16_t voltage_qualifier_status = 0; //0 = Valid, 1 = Invalid
static int16_t balancing_status = 0; //4 = not active
static uint8_t contactors_closed = 0; //TODO E5 BF or E5 51
static uint8_t contactor_status_precharge = 0; //TODO E5 BF
static uint8_t contactor_status_negative = 0; //TODO E5 BF
static uint8_t contactor_status_positive = 0; //TODO E5 BF
static uint8_t uds_fast_req_id_counter = 0;
static uint8_t uds_slow_req_id_counter = 0;
static uint8_t detected_number_of_cells = 96;
const unsigned long STALE_PERIOD =
STALE_PERIOD_CONFIG; // Time in milliseconds to check for staleness (e.g., 5000 ms = 5 seconds)
static byte iX_0C0_counter = 0xF0; // Initialize to 0xF0
//End iX Intermediate vars
static uint8_t current_cell_polled = 0;
// Function to check if a value has gone stale over a specified time period // Function to check if a value has gone stale over a specified time period
bool isStale(int16_t currentValue, uint16_t& lastValue, unsigned long& lastChangeTime) { bool BmwPhevBattery::isStale(int16_t currentValue, uint16_t& lastValue, unsigned long& lastChangeTime) {
unsigned long currentTime = millis(); unsigned long currentTime = millis();
// Check if the value has changed // Check if the value has changed
@ -479,7 +109,7 @@ static uint8_t increment_uds_req_id_counter(uint8_t index, int numReqs) {
UDS Multi-Frame Helpers UDS Multi-Frame Helpers
-------------------------------------------------------------------------- */ -------------------------------------------------------------------------- */
void startUDSMultiFrameReception(uint16_t totalLength, uint8_t moduleID) { void BmwPhevBattery::startUDSMultiFrameReception(uint16_t totalLength, uint8_t moduleID) {
gUDSContext.UDS_inProgress = true; gUDSContext.UDS_inProgress = true;
gUDSContext.UDS_expectedLength = totalLength; gUDSContext.UDS_expectedLength = totalLength;
gUDSContext.UDS_bytesReceived = 0; gUDSContext.UDS_bytesReceived = 0;
@ -488,7 +118,7 @@ void startUDSMultiFrameReception(uint16_t totalLength, uint8_t moduleID) {
gUDSContext.UDS_lastFrameMillis = millis(); // if you want to track timeouts gUDSContext.UDS_lastFrameMillis = millis(); // if you want to track timeouts
} }
bool storeUDSPayload(const uint8_t* payload, uint8_t length) { bool BmwPhevBattery::storeUDSPayload(const uint8_t* payload, uint8_t length) {
if (gUDSContext.UDS_bytesReceived + length > sizeof(gUDSContext.UDS_buffer)) { if (gUDSContext.UDS_bytesReceived + length > sizeof(gUDSContext.UDS_buffer)) {
// Overflow => abort // Overflow => abort
gUDSContext.UDS_inProgress = false; gUDSContext.UDS_inProgress = false;
@ -511,11 +141,11 @@ bool storeUDSPayload(const uint8_t* payload, uint8_t length) {
return true; return true;
} }
bool isUDSMessageComplete() { bool BmwPhevBattery::isUDSMessageComplete() {
return (!gUDSContext.UDS_inProgress && gUDSContext.UDS_bytesReceived > 0); return (!gUDSContext.UDS_inProgress && gUDSContext.UDS_bytesReceived > 0);
} }
static uint8_t increment_alive_counter(uint8_t counter) { uint8_t BmwPhevBattery::increment_alive_counter(uint8_t counter) {
counter++; counter++;
if (counter > ALIVE_MAX_VALUE) { if (counter > ALIVE_MAX_VALUE) {
counter = 0; counter = 0;
@ -532,7 +162,7 @@ static byte increment_0C0_counter(byte counter) {
return counter; return counter;
} }
void processCellVoltages() { void BmwPhevBattery::processCellVoltages() {
const int startByte = 3; // Start reading at byte 3 const int startByte = 3; // Start reading at byte 3
const int numVoltages = 96; // Number of cell voltage values to process const int numVoltages = 96; // Number of cell voltage values to process
int voltage_index = 0; // Starting index for the destination array int voltage_index = 0; // Starting index for the destination array
@ -553,7 +183,7 @@ void processCellVoltages() {
} }
} }
void wake_battery_via_canbus() { void BmwPhevBattery::wake_battery_via_canbus() {
//TJA1055 transceiver remote wake requires pulses on the bus of //TJA1055 transceiver remote wake requires pulses on the bus of
// Dominant for at least ~7µs (min) and at most ~38µs (max) // Dominant for at least ~7µs (min) and at most ~38µs (max)
// Followed by a Recessive interval of at least ~3µs (min) and at most ~10µs (max) // Followed by a Recessive interval of at least ~3µs (min) and at most ~10µs (max)
@ -570,7 +200,8 @@ void wake_battery_via_canbus() {
logging.println("Sent magic wakeup packet to SME at 100kbps..."); logging.println("Sent magic wakeup packet to SME at 100kbps...");
#endif #endif
} }
void update_values_battery() { //This function maps all the values fetched via CAN to the battery datalayer
void BmwPhevBattery::update_values() { //This function maps all the values fetched via CAN to the battery datalayer
datalayer.battery.status.real_soc = avg_soc_state; datalayer.battery.status.real_soc = avg_soc_state;
@ -677,7 +308,7 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV; datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
} }
} }
void handle_incoming_can_frame_battery(CAN_frame rx_frame) { void BmwPhevBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
battery_awake = true; battery_awake = true;
switch (rx_frame.ID) { switch (rx_frame.ID) {
@ -1005,7 +636,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
} }
} }
void transmit_can_battery(unsigned long currentMillis) { void BmwPhevBattery::transmit_can(unsigned long currentMillis) {
//if (battery_awake) { //We can always send CAN as the PHEV BMS will wake up on vehicle comms //if (battery_awake) { //We can always send CAN as the PHEV BMS will wake up on vehicle comms
@ -1067,7 +698,7 @@ void transmit_can_battery(unsigned long currentMillis) {
} }
} }
void setup_battery(void) { // Performs one time setup at startup void BmwPhevBattery::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "BMW PHEV Battery", 63); strncpy(datalayer.system.info.battery_protocol, "BMW PHEV Battery", 63);
datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.info.battery_protocol[63] = '\0';
//Wakeup the SME //Wakeup the SME

View file

@ -2,21 +2,411 @@
#define BMW_PHEV_BATTERY_H #define BMW_PHEV_BATTERY_H
#include <Arduino.h> #include <Arduino.h>
#include "../include.h" #include "../include.h"
#include "CanBattery.h"
#define BATTERY_SELECTED #define BATTERY_SELECTED
#define SELECTED_BATTERY_CLASS BmwPhevBattery
#define MAX_PACK_VOLTAGE_DV 4650 //4650 = 465.0V class BmwPhevBattery : public CanBattery {
#define MIN_PACK_VOLTAGE_DV 3000 public:
#define MAX_CELL_DEVIATION_MV 250 virtual void setup(void);
#define MAX_CELL_VOLTAGE_MV 4300 //Battery is put into emergency stop if one cell goes over this value virtual void handle_incoming_can_frame(CAN_frame rx_frame);
#define MIN_CELL_VOLTAGE_MV 2800 //Battery is put into emergency stop if one cell goes below this value virtual void update_values();
#define MAX_DISCHARGE_POWER_ALLOWED_W 10000 virtual void transmit_can(unsigned long currentMillis);
#define MAX_CHARGE_POWER_ALLOWED_W 10000
#define MAX_CHARGE_POWER_WHEN_TOPBALANCING_W 500 private:
#define RAMPDOWN_SOC 9000 // (90.00) SOC% to start ramping down from max charge power towards 0 at 100.00% static const int MAX_PACK_VOLTAGE_DV = 4650; //4650 = 465.0V
#define STALE_PERIOD_CONFIG \ static const int MIN_PACK_VOLTAGE_DV = 3000;
static const int MAX_CELL_DEVIATION_MV = 250;
static const int MAX_CELL_VOLTAGE_MV = 4300; //Battery is put into emergency stop if one cell goes over this value
static const int MIN_CELL_VOLTAGE_MV = 2800; //Battery is put into emergency stop if one cell goes below this value
static const int MAX_DISCHARGE_POWER_ALLOWED_W = 10000;
static const int MAX_CHARGE_POWER_ALLOWED_W = 10000;
static const int MAX_CHARGE_POWER_WHEN_TOPBALANCING_W = 500;
static const int RAMPDOWN_SOC =
9000; // (90.00) SOC% to start ramping down from max charge power towards 0 at 100.00%
static const int STALE_PERIOD_CONFIG =
3600000; //Number of milliseconds before critical values are classed as stale/stuck 1800000 = 3600 seconds / 60mins 3600000; //Number of milliseconds before critical values are classed as stale/stuck 1800000 = 3600 seconds / 60mins
void setup_battery(void);
void transmit_can_frame(CAN_frame* tx_frame, int interface); bool isStale(int16_t currentValue, uint16_t& lastValue, unsigned long& lastChangeTime);
void startUDSMultiFrameReception(uint16_t totalLength, uint8_t moduleID);
bool storeUDSPayload(const uint8_t* payload, uint8_t length);
bool isUDSMessageComplete();
void processCellVoltages();
void wake_battery_via_canbus();
uint8_t increment_alive_counter(uint8_t counter);
unsigned long previousMillis20 = 0; // will store last time a 20ms CAN Message was send
unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was send
unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was send
unsigned long previousMillis640 = 0; // will store last time a 600ms CAN Message was send
unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was send
unsigned long previousMillis5000 = 0; // will store last time a 5000ms CAN Message was send
unsigned long previousMillis10000 = 0; // will store last time a 10000ms CAN Message was send
static const int ALIVE_MAX_VALUE = 14; // BMW CAN messages contain alive counter, goes from 0...14
enum CmdState { SOH, CELL_VOLTAGE_MINMAX, SOC, CELL_VOLTAGE_CELLNO, CELL_VOLTAGE_CELLNO_LAST };
CmdState cmdState = SOC;
// A structure to keep track of the ongoing multi-frame UDS response
typedef struct {
bool UDS_inProgress; // Are we currently receiving a multi-frame message?
uint16_t UDS_expectedLength; // Expected total payload length
uint16_t UDS_bytesReceived; // How many bytes have been stored so far
uint8_t UDS_moduleID; // The "module" indicated by the first frame
uint8_t receivedInBatch; // Number of CFs received in the current batch
uint8_t UDS_buffer[256]; // Buffer for the reassembled data
unsigned long UDS_lastFrameMillis; // Timestamp of last frame (for timeouts, if desired)
} UDS_RxContext;
// A single global UDS context, since only one module can respond at a time
UDS_RxContext gUDSContext;
//Vehicle CAN START
CAN_frame BMWiX_0C0 = {
.FD = false,
.ext_ID = false,
.DLC = 2,
.ID = 0x0C0,
.data = {
0xF0,
0x08}}; // Keep Alive 2 BDC>SME 200ms First byte cycles F0 > FE second byte 08 - MINIMUM ID TO KEEP SME AWAKE
CAN_frame BMW_13E = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x13E,
.data = {0xFF, 0x31, 0xFA, 0xFA, 0xFA, 0xFA, 0x0C, 0x00}};
//Vehicle CAN END
//Request Data CAN START
CAN_frame BMW_PHEV_BUS_WAKEUP_REQUEST = {
.FD = false,
.ext_ID = false,
.DLC = 4,
.ID = 0x554,
.data = {
0x5A, 0xA5, 0x5A,
0xA5}}; // Won't work at 500kbps! Ideally sent at 50kbps - but can also achieve wakeup at 100kbps (helps with library support but might not be as reliable). Might need to be sent twice + clear buffer
CAN_frame BMWPHEV_6F1_REQUEST_SOC = {.FD = false,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F1,
.data = {0x07, 0x03, 0x22, 0xDD, 0xC4}}; // SOC%
CAN_frame BMWPHEV_6F1_REQUEST_SOH = {.FD = false,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F1,
.data = {0x07, 0x03, 0x22, 0xDD, 0x7B}}; // SOH%
CAN_frame BMWPHEV_6F1_REQUEST_CURRENT = {.FD = false,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F1,
.data = {0x07, 0x03, 0x22, 0xDD, 0x69}}; // SOH%
CAN_frame BMWPHEV_6F1_REQUEST_VOLTAGE_LIMITS = {
.FD = false,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F1,
.data = {0x07, 0x03, 0x22, 0xDD, 0x7E}}; // Pack Voltage Limits Multi Frame
CAN_frame BMWPHEV_6F1_REQUEST_PAIRED_VIN = {.FD = false,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F1,
.data = {0x07, 0x03, 0x22, 0xF1, 0x90}}; // SME Paired VIN
CAN_frame BMWPHEV_6F1_REQUEST_ISO_READING1 = {
.FD = false,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F1,
.data = {
0x07, 0x03, 0x22, 0xDD,
0x6A}}; // MULTI FRAME ISOLATIONSWIDERSTAND 62 DD 6A [07 D0] [07 D0] [07 D0] [01] [01] [01] 00 00 00 00 00 [EXT Reading] [INT reading] [ EXT - 0 not plausible, 1 plausible]
CAN_frame BMWPHEV_6F1_REQUEST_ISO_READING2 = {
.FD = false,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F1,
.data = {0x07, 0x03, 0x22, 0xD6,
0xD9}}; // R_ISO_ROH 62 D6 D9 [07 FF] [13] (2047kohm) quality of reading 0-21 (19)
CAN_frame BMWPHEV_6F1_REQUEST_PACK_INFO = {
.FD = false,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F1,
.data = {0x07, 0x03, 0x22, 0xDF, 0x71}}; // 62 DF 71 00 60 1C 25 1C? Cell Count, Module Count
CAN_frame BMWPHEV_6F1_REQUEST_CURRENT_LIMITS = {
.FD = false,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F1,
.data = {0x07, 0x03, 0x22, 0xDD, 0x7D}}; // Pack Current Limits Multi Frame
CAN_frame BMWPHEV_6F1_REQUEST_MAINVOLTAGE_PRECONTACTOR = {
.FD = false,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F1,
.data = {0x07, 0x03, 0x22, 0xDD, 0xB4}}; //Main Battery Voltage (Pre Contactor)
CAN_frame BMWPHEV_6F1_REQUEST_MAINVOLTAGE_POSTCONTACTOR = {
.FD = false,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F1,
.data = {0x07, 0x03, 0x22, 0xDD, 0x66}}; //Main Battery Voltage (After Contactor)
CAN_frame BMWPHEV_6F1_REQUEST_CELLSUMMARY = {
.FD = false,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F1,
.data = {0x07, 0x03, 0x22, 0xDF, 0xA0}}; //Min and max cell voltage + temps 6.55V = Qualifier Invalid?
CAN_frame BMWPHEV_6F1_REQUEST_CELLS_INDIVIDUAL_VOLTS = {
.FD = false,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F1,
.data = {0x07, 0x03, 0x22, 0xDF, 0xA5}}; //All individual cell voltages
CAN_frame BMWPHEV_6F1_REQUEST_CELL_TEMP = {
.FD = false,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F1,
.data = {
0x07, 0x03, 0x22, 0xDD,
0xC0}}; // UDS Request Cell Temperatures min max avg. Has continue frame min in first, then max + avg in second frame
CAN_frame BMW_6F1_REQUEST_CONTINUE_MULTIFRAME = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x6F1,
.data = {
0x07, 0x30, 0x03, 0x00, 0x00, 0x00, 0x00,
0x00}}; //Request continued frames from UDS Multiframe request byte[2] is the request messages to return per continue. default 0x03, all is 0x00
CAN_frame BMW_6F1_REQUEST_HARD_RESET = {.FD = false,
.ext_ID = false,
.DLC = 4,
.ID = 0x6F1,
.data = {0x07, 0x03, 0x11, 0x01}}; // Reset BMS - TBC
CAN_frame BMWPHEV_6F1_REQUEST_CONTACTORS_CLOSE = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x6F1,
.data = {0x07, 0x04, 0x2E, 0xDD, 0x61, 0x01, 0x00, 0x00}}; // Request Contactors Close - Unconfirmed
CAN_frame BMWPHEV_6F1_REQUEST_CONTACTORS_OPEN = {
.FD = false,
.ext_ID = false,
.DLC = 6,
.ID = 0x6F1,
.data = {0x07, 0x04, 0x2E, 0xDD, 0x61, 0x00, 0x00, 0x00}}; // Request Contactors Open - Unconfirmed
CAN_frame BMWPHEV_6F1_REQUEST_BALANCING_STATUS = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x6F1,
.data = {0x07, 0x04, 0x31, 0x03, 0xAD, 0x6B, 0x00,
0x00}}; // Balancing status. Response 7DLC F1 05 71 03 AD 6B 01 (01 = active) (03 not active)
CAN_frame BMWPHEV_6F1_REQUEST_ISOLATION_TEST = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x6F1,
.data = {0x07, 0x04, 0x31, 0x01, 0xAD, 0x61, 0x00, 0x00}}; // Start Isolation Test
CAN_frame BMWPHEV_6F1_REQUEST_BALANCING_START = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x6F1,
.data = {0x07, 0x04, 0x31, 0x01, 0xAD, 0x6B, 0x00, 0x00}}; // Balancing start request
CAN_frame BMWPHEV_6F1_REQUEST_BALANCING_STOP = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x6F1,
.data = {0x07, 0x04, 0x31, 0x02, 0xAD, 0x6B, 0x00, 0x00}}; // Balancing stop request
//Action Requests:
CAN_frame BMW_10B = {.FD = false,
.ext_ID = false,
.DLC = 3,
.ID = 0x10B,
.data = {0xCD, 0x00, 0xFC}}; // Contactor closing command?
CAN_frame BMWPHEV_6F1_CELL_SOC = {.FD = false,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F1,
.data = {0x07, 0x03, 0x22, 0xE5, 0x9A}};
CAN_frame BMWPHEV_6F1_CELL_TEMP = {.FD = false,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F1,
.data = {0x07, 0x03, 0x22, 0xE5, 0xCA}};
//Request Data CAN End
bool battery_awake = false;
//Setup Fast UDS values to poll for
CAN_frame* UDS_REQUESTS_FAST[6] = {&BMWPHEV_6F1_REQUEST_CELLSUMMARY,
&BMWPHEV_6F1_REQUEST_SOC,
&BMWPHEV_6F1_REQUEST_CURRENT,
&BMWPHEV_6F1_REQUEST_VOLTAGE_LIMITS,
&BMWPHEV_6F1_REQUEST_MAINVOLTAGE_PRECONTACTOR,
&BMWPHEV_6F1_REQUEST_MAINVOLTAGE_POSTCONTACTOR};
int numFastUDSreqs =
sizeof(UDS_REQUESTS_FAST) / sizeof(UDS_REQUESTS_FAST[0]); //Store Number of elements in the array
//Setup Slow UDS values to poll for
CAN_frame* UDS_REQUESTS_SLOW[8] = {&BMWPHEV_6F1_REQUEST_ISO_READING1, &BMWPHEV_6F1_REQUEST_ISO_READING2,
&BMWPHEV_6F1_REQUEST_CURRENT_LIMITS, &BMWPHEV_6F1_REQUEST_SOH,
&BMWPHEV_6F1_REQUEST_CELLS_INDIVIDUAL_VOLTS, &BMWPHEV_6F1_REQUEST_CELL_TEMP,
&BMWPHEV_6F1_REQUEST_BALANCING_STATUS, &BMWPHEV_6F1_REQUEST_PAIRED_VIN};
int numSlowUDSreqs =
sizeof(UDS_REQUESTS_SLOW) / sizeof(UDS_REQUESTS_SLOW[0]); // Store Number of elements in the array
//PHEV intermediate vars
//#define UDS_LOG //Useful for logging multiframe handling
uint16_t battery_max_charge_voltage = 0;
int16_t battery_max_charge_amperage = 0;
uint16_t battery_min_discharge_voltage = 0;
int16_t battery_max_discharge_amperage = 0;
uint8_t startup_counter_contactor = 0;
uint8_t alive_counter_20ms = 0;
uint8_t BMW_13E_counter = 0;
uint32_t battery_BEV_available_power_shortterm_charge = 0;
uint32_t battery_BEV_available_power_shortterm_discharge = 0;
uint32_t battery_BEV_available_power_longterm_charge = 0;
uint32_t battery_BEV_available_power_longterm_discharge = 0;
uint16_t battery_predicted_energy_charge_condition = 0;
uint16_t battery_predicted_energy_charging_target = 0;
uint16_t battery_prediction_voltage_shortterm_charge = 0;
uint16_t battery_prediction_voltage_shortterm_discharge = 0;
uint16_t battery_prediction_voltage_longterm_charge = 0;
uint16_t battery_prediction_voltage_longterm_discharge = 0;
uint8_t battery_status_service_disconnection_plug = 0;
uint8_t battery_status_measurement_isolation = 0;
uint8_t battery_request_abort_charging = 0;
uint16_t battery_prediction_duration_charging_minutes = 0;
uint8_t battery_prediction_time_end_of_charging_minutes = 0;
uint16_t battery_energy_content_maximum_kWh = 0;
uint8_t battery_request_operating_mode = 0;
uint16_t battery_target_voltage_in_CV_mode = 0;
uint8_t battery_request_charging_condition_minimum = 0;
uint8_t battery_request_charging_condition_maximum = 0;
uint16_t battery_display_SOC = 0;
uint8_t battery_status_error_isolation_external_Bordnetz = 0;
uint8_t battery_status_error_isolation_internal_Bordnetz = 0;
uint8_t battery_request_cooling = 0;
uint8_t battery_status_valve_cooling = 0;
uint8_t battery_status_error_locking = 0;
uint8_t battery_status_precharge_locked = 0;
uint8_t battery_status_disconnecting_switch = 0;
uint8_t battery_status_emergency_mode = 0;
uint8_t battery_request_service = 0;
uint8_t battery_error_emergency_mode = 0;
uint8_t battery_status_error_disconnecting_switch = 0;
uint8_t battery_status_warning_isolation = 0;
uint8_t battery_status_cold_shutoff_valve = 0;
int16_t battery_temperature_HV = 0;
int16_t battery_temperature_heat_exchanger = 0;
int16_t battery_temperature_max = 0;
int16_t battery_temperature_min = 0;
bool pack_limit_info_available = false;
bool cell_limit_info_available = false;
//iX Intermediate vars
uint32_t battery_serial_number = 0;
int32_t battery_current = 0;
int16_t battery_voltage = 3700; //Initialize as valid - should be fixed in future
int16_t terminal30_12v_voltage = 0;
int16_t battery_voltage_after_contactor = 0;
int16_t min_soc_state = 5000;
int16_t avg_soc_state = 5000;
int16_t max_soc_state = 5000;
int16_t min_soh_state = 9999; // Uses E5 45, also available in 78 73
int16_t avg_soh_state = 9999; // Uses E5 45, also available in 78 73
int16_t max_soh_state = 9999; // Uses E5 45, also available in 78 73
uint16_t max_design_voltage = 0;
uint16_t min_design_voltage = 0;
int32_t remaining_capacity = 0;
int32_t max_capacity = 0;
int16_t main_contactor_temperature = 0;
int16_t min_cell_voltage = 3700; //Initialize as valid - should be fixed in future
int16_t max_cell_voltage = 3700; //Initialize as valid - should be fixed in future
unsigned long min_cell_voltage_lastchanged = 0;
unsigned long max_cell_voltage_lastchanged = 0;
unsigned min_cell_voltage_lastreceived = 0;
unsigned max_cell_voltage_lastreceived = 0;
int16_t allowable_charge_amps = 0; //E5 62
int16_t allowable_discharge_amps = 0; //E5 62
int32_t iso_safety_int_kohm = 0; //STAT_ISOWIDERSTAND_INT_WERT
int32_t iso_safety_ext_kohm = 0; //STAT_ISOWIDERSTAND_EXT_STD_WERT
int32_t iso_safety_trg_kohm = 0;
int32_t iso_safety_ext_plausible = 0; //STAT_ISOWIDERSTAND_EXT_TRG_PLAUS
int32_t iso_safety_int_plausible = 0; //STAT_ISOWIDERSTAND_EXT_TRG_WERT
int32_t iso_safety_trg_plausible = 0;
int32_t iso_safety_kohm = 0; //STAT_R_ISO_ROH_01_WERT
int32_t iso_safety_kohm_quality = 0; //STAT_R_ISO_ROH_QAL_01_INFO Quality of measurement 0-21 (higher better)
uint8_t paired_vin[17] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; //17 Byte array for paired VIN
int16_t count_full_charges = 0; //TODO 42
int16_t count_charges = 0; //TODO 42
int16_t hvil_status = 0;
int16_t voltage_qualifier_status = 0; //0 = Valid, 1 = Invalid
int16_t balancing_status = 0; //4 = not active
uint8_t contactors_closed = 0; //TODO E5 BF or E5 51
uint8_t contactor_status_precharge = 0; //TODO E5 BF
uint8_t contactor_status_negative = 0; //TODO E5 BF
uint8_t contactor_status_positive = 0; //TODO E5 BF
uint8_t uds_fast_req_id_counter = 0;
uint8_t uds_slow_req_id_counter = 0;
uint8_t detected_number_of_cells = 96;
const unsigned long STALE_PERIOD =
STALE_PERIOD_CONFIG; // Time in milliseconds to check for staleness (e.g., 5000 ms = 5 seconds)
byte iX_0C0_counter = 0xF0; // Initialize to 0xF0
//End iX Intermediate vars
uint8_t current_cell_polled = 0;
};
#endif #endif

View file

@ -1,5 +1,6 @@
#include "../include.h" #include "../include.h"
#ifdef BOLT_AMPERA_BATTERY #ifdef BOLT_AMPERA_BATTERY
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h" #include "../datalayer/datalayer_extended.h"
#include "../devboard/utils/events.h" #include "../devboard/utils/events.h"
@ -39,170 +40,7 @@ TODOs left for this implementation
0x460 Energy Storage System Temp HV (Who sends this? Battery?) 0x460 Energy Storage System Temp HV (Who sends this? Battery?)
*/ */
/* Do not change code below unless you are sure what you are doing */ void BoltAmperaBattery::update_values() { //This function maps all the values fetched via CAN to the battery datalayer
static unsigned long previousMillis20ms = 0; // will store last time a 20ms CAN Message was send
static unsigned long previousMillis100ms = 0; // will store last time a 100ms CAN Message was send
static unsigned long previousMillis120ms = 0; // will store last time a 120ms CAN Message was send
CAN_frame BOLT_778 = {.FD = false, // Unsure of what this message is, added only as example
.ext_ID = false,
.DLC = 7,
.ID = 0x778,
.data = {0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame BOLT_POLL_7E4 = {.FD = false, // VICM_HV poll
.ext_ID = false,
.DLC = 8,
.ID = 0x7E4,
.data = {0x03, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame BOLT_ACK_7E4 = {.FD = false, //VICM_HV ack
.ext_ID = false,
.DLC = 8,
.ID = 0x7E4,
.data = {0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame BOLT_POLL_7E7 = {.FD = false, //VITM_HV poll
.ext_ID = false,
.DLC = 8,
.ID = 0x7E7,
.data = {0x03, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame BOLT_ACK_7E7 = {.FD = false, //VITM_HV ack
.ext_ID = false,
.DLC = 8,
.ID = 0x7E7,
.data = {0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
// Other PID requests in the vehicle
// All HV ECUs - 0x101
// HPCC HV - 0x243 replies on 0x643
// OBCM HV - 0x244 replies on 0x644
// VICM_HV - 0x7E4 replies 0x7EC (This is battery)
// VICM2_HV - 0x7E6 replies 0x7EF (Tis is battery also)
// VITM_HV - 0x7E7 replies on 7EF (This is battery)
static uint16_t battery_cell_voltages[96]; //array with all the cellvoltages
static uint16_t battery_capacity_my17_18 = 0;
static uint16_t battery_capacity_my19plus = 0;
static uint16_t battery_SOC_display = 0;
static uint16_t battery_SOC_raw_highprec = 0;
static uint16_t battery_max_temperature = 0;
static uint16_t battery_min_temperature = 0;
static uint16_t battery_min_cell_voltage = 0;
static uint16_t battery_max_cell_voltage = 0;
static uint16_t battery_internal_resistance = 0;
static uint16_t battery_lowest_cell = 0;
static uint16_t battery_highest_cell = 0;
static uint16_t battery_voltage_polled = 0;
static uint16_t battery_voltage_periodic = 0;
static uint16_t battery_vehicle_isolation = 0;
static uint16_t battery_isolation_kohm = 0;
static uint16_t battery_HV_locked = 0;
static uint16_t battery_crash_event = 0;
static uint16_t battery_HVIL = 0;
static uint16_t battery_HVIL_status = 0;
static uint16_t battery_5V_ref = 0;
static int16_t battery_current_7E4 = 0;
static int16_t battery_module_temp_1 = 0;
static int16_t battery_module_temp_2 = 0;
static int16_t battery_module_temp_3 = 0;
static int16_t battery_module_temp_4 = 0;
static int16_t battery_module_temp_5 = 0;
static int16_t battery_module_temp_6 = 0;
static uint16_t battery_cell_average_voltage = 0;
static uint16_t battery_cell_average_voltage_2 = 0;
static uint16_t battery_terminal_voltage = 0;
static uint16_t battery_ignition_power_mode = 0;
static int16_t battery_current_7E7 = 0;
static int16_t temperature_1 = 0;
static int16_t temperature_2 = 0;
static int16_t temperature_3 = 0;
static int16_t temperature_4 = 0;
static int16_t temperature_5 = 0;
static int16_t temperature_6 = 0;
static int16_t temperature_highest = 0;
static int16_t temperature_lowest = 0;
static uint8_t mux = 0;
static uint8_t poll_index_7E4 = 0;
static uint16_t currentpoll_7E4 = POLL_7E4_CAPACITY_EST_GEN1;
static uint16_t reply_poll_7E4 = 0;
static uint8_t poll_index_7E7 = 0;
static uint16_t currentpoll_7E7 = POLL_7E7_CURRENT;
static uint16_t reply_poll_7E7 = 0;
const uint16_t poll_commands_7E4[19] = {POLL_7E4_CAPACITY_EST_GEN1,
POLL_7E4_CAPACITY_EST_GEN2,
POLL_7E4_SOC_DISPLAY,
POLL_7E4_SOC_RAW_HIGHPREC,
POLL_7E4_MAX_TEMPERATURE,
POLL_7E4_MIN_TEMPERATURE,
POLL_7E4_MIN_CELL_V,
POLL_7E4_MAX_CELL_V,
POLL_7E4_INTERNAL_RES,
POLL_7E4_LOWEST_CELL_NUMBER,
POLL_7E4_HIGHEST_CELL_NUMBER,
POLL_7E4_VOLTAGE,
POLL_7E4_VEHICLE_ISOLATION,
POLL_7E4_ISOLATION_TEST_KOHM,
POLL_7E4_HV_LOCKED_OUT,
POLL_7E4_CRASH_EVENT,
POLL_7E4_HVIL,
POLL_7E4_HVIL_STATUS,
POLL_7E4_CURRENT};
const uint16_t poll_commands_7E7[108] = {POLL_7E7_CURRENT, POLL_7E7_5V_REF,
POLL_7E7_MODULE_TEMP_1, POLL_7E7_MODULE_TEMP_2,
POLL_7E7_MODULE_TEMP_3, POLL_7E7_MODULE_TEMP_4,
POLL_7E7_MODULE_TEMP_5, POLL_7E7_MODULE_TEMP_6,
POLL_7E7_CELL_AVG_VOLTAGE, POLL_7E7_CELL_AVG_VOLTAGE_2,
POLL_7E7_TERMINAL_VOLTAGE, POLL_7E7_IGNITION_POWER_MODE,
POLL_7E7_CELL_01, POLL_7E7_CELL_02,
POLL_7E7_CELL_03, POLL_7E7_CELL_04,
POLL_7E7_CELL_05, POLL_7E7_CELL_06,
POLL_7E7_CELL_07, POLL_7E7_CELL_08,
POLL_7E7_CELL_09, POLL_7E7_CELL_10,
POLL_7E7_CELL_11, POLL_7E7_CELL_12,
POLL_7E7_CELL_13, POLL_7E7_CELL_14,
POLL_7E7_CELL_15, POLL_7E7_CELL_16,
POLL_7E7_CELL_17, POLL_7E7_CELL_18,
POLL_7E7_CELL_19, POLL_7E7_CELL_20,
POLL_7E7_CELL_21, POLL_7E7_CELL_22,
POLL_7E7_CELL_23, POLL_7E7_CELL_24,
POLL_7E7_CELL_25, POLL_7E7_CELL_26,
POLL_7E7_CELL_27, POLL_7E7_CELL_28,
POLL_7E7_CELL_29, POLL_7E7_CELL_30,
POLL_7E7_CELL_31, POLL_7E7_CELL_32,
POLL_7E7_CELL_33, POLL_7E7_CELL_34,
POLL_7E7_CELL_35, POLL_7E7_CELL_36,
POLL_7E7_CELL_37, POLL_7E7_CELL_38,
POLL_7E7_CELL_39, POLL_7E7_CELL_40,
POLL_7E7_CELL_41, POLL_7E7_CELL_42,
POLL_7E7_CELL_43, POLL_7E7_CELL_44,
POLL_7E7_CELL_45, POLL_7E7_CELL_46,
POLL_7E7_CELL_47, POLL_7E7_CELL_48,
POLL_7E7_CELL_49, POLL_7E7_CELL_50,
POLL_7E7_CELL_51, POLL_7E7_CELL_52,
POLL_7E7_CELL_53, POLL_7E7_CELL_54,
POLL_7E7_CELL_55, POLL_7E7_CELL_56,
POLL_7E7_CELL_57, POLL_7E7_CELL_58,
POLL_7E7_CELL_59, POLL_7E7_CELL_60,
POLL_7E7_CELL_61, POLL_7E7_CELL_62,
POLL_7E7_CELL_63, POLL_7E7_CELL_64,
POLL_7E7_CELL_65, POLL_7E7_CELL_66,
POLL_7E7_CELL_67, POLL_7E7_CELL_68,
POLL_7E7_CELL_69, POLL_7E7_CELL_70,
POLL_7E7_CELL_71, POLL_7E7_CELL_72,
POLL_7E7_CELL_73, POLL_7E7_CELL_74,
POLL_7E7_CELL_75, POLL_7E7_CELL_76,
POLL_7E7_CELL_77, POLL_7E7_CELL_78,
POLL_7E7_CELL_79, POLL_7E7_CELL_80,
POLL_7E7_CELL_81, POLL_7E7_CELL_82,
POLL_7E7_CELL_83, POLL_7E7_CELL_84,
POLL_7E7_CELL_85, POLL_7E7_CELL_86,
POLL_7E7_CELL_87, POLL_7E7_CELL_88,
POLL_7E7_CELL_89, POLL_7E7_CELL_90,
POLL_7E7_CELL_91, POLL_7E7_CELL_92,
POLL_7E7_CELL_93, POLL_7E7_CELL_94,
POLL_7E7_CELL_95, POLL_7E7_CELL_96};
void update_values_battery() { //This function maps all the values fetched via CAN to the battery datalayer
datalayer.battery.status.real_soc = battery_SOC_display; datalayer.battery.status.real_soc = battery_SOC_display;
@ -279,7 +117,7 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer_extended.boltampera.battery_current_7E4 = battery_current_7E4; datalayer_extended.boltampera.battery_current_7E4 = battery_current_7E4;
} }
void handle_incoming_can_frame_battery(CAN_frame rx_frame) { void BoltAmperaBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
switch (rx_frame.ID) { switch (rx_frame.ID) {
case 0x200: //High voltage Battery Cell Voltage Matrix 1 case 0x200: //High voltage Battery Cell Voltage Matrix 1
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
@ -769,7 +607,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
} }
} }
void transmit_can_battery(unsigned long currentMillis) { void BoltAmperaBattery::transmit_can(unsigned long currentMillis) {
//Send 20ms message //Send 20ms message
if (currentMillis - previousMillis20ms >= INTERVAL_20_MS) { if (currentMillis - previousMillis20ms >= INTERVAL_20_MS) {
@ -806,7 +644,7 @@ void transmit_can_battery(unsigned long currentMillis) {
} }
} }
void setup_battery(void) { // Performs one time setup at startup void BoltAmperaBattery::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "Chevrolet Bolt EV/Opel Ampera-e", 63); strncpy(datalayer.system.info.battery_protocol, "Chevrolet Bolt EV/Opel Ampera-e", 63);
datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.number_of_cells = 96; datalayer.battery.info.number_of_cells = 96;

View file

@ -3,180 +3,349 @@
#include <Arduino.h> #include <Arduino.h>
#include "../include.h" #include "../include.h"
#include "CanBattery.h"
#define BATTERY_SELECTED #define BATTERY_SELECTED
#define SELECTED_BATTERY_CLASS BoltAmperaBattery
#define MAX_PACK_VOLTAGE_DV 4150 //5000 = 500.0V class BoltAmperaBattery : public CanBattery {
#define MIN_PACK_VOLTAGE_DV 2500 public:
#define MAX_CELL_DEVIATION_MV 150 virtual void setup(void);
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value virtual void handle_incoming_can_frame(CAN_frame rx_frame);
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value virtual void update_values();
virtual void transmit_can(unsigned long currentMillis);
#define POLL_7E4_CAPACITY_EST_GEN1 0x41A3 private:
#define POLL_7E4_CAPACITY_EST_GEN2 0x45F9 static const int MAX_PACK_VOLTAGE_DV = 4150; //5000 = 500.0V
#define POLL_7E4_SOC_DISPLAY 0x8334 static const int MIN_PACK_VOLTAGE_DV = 2500;
#define POLL_7E4_SOC_RAW_HIGHPREC 0x43AF static const int MAX_CELL_DEVIATION_MV = 150;
#define POLL_7E4_MAX_TEMPERATURE 0x4349 static const int MAX_CELL_VOLTAGE_MV = 4250; //Battery is put into emergency stop if one cell goes over this value
#define POLL_7E4_MIN_TEMPERATURE 0x434A static const int MIN_CELL_VOLTAGE_MV = 2700; //Battery is put into emergency stop if one cell goes below this value
#define POLL_7E4_MIN_CELL_V 0x4329 static const int POLL_7E4_CAPACITY_EST_GEN1 = 0x41A3;
#define POLL_7E4_MAX_CELL_V 0x432B static const int POLL_7E4_CAPACITY_EST_GEN2 = 0x45F9;
#define POLL_7E4_INTERNAL_RES 0x40E9 static const int POLL_7E4_SOC_DISPLAY = 0x8334;
#define POLL_7E4_LOWEST_CELL_NUMBER 0x433B static const int POLL_7E4_SOC_RAW_HIGHPREC = 0x43AF;
#define POLL_7E4_HIGHEST_CELL_NUMBER 0x433C static const int POLL_7E4_MAX_TEMPERATURE = 0x4349;
#define POLL_7E4_VOLTAGE 0x432D static const int POLL_7E4_MIN_TEMPERATURE = 0x434A;
#define POLL_7E4_VEHICLE_ISOLATION 0x41EC static const int POLL_7E4_MIN_CELL_V = 0x4329;
#define POLL_7E4_ISOLATION_TEST_KOHM 0x43A6 static const int POLL_7E4_MAX_CELL_V = 0x432B;
#define POLL_7E4_HV_LOCKED_OUT 0x44F8 static const int POLL_7E4_INTERNAL_RES = 0x40E9;
#define POLL_7E4_CRASH_EVENT 0x4522 static const int POLL_7E4_LOWEST_CELL_NUMBER = 0x433B;
#define POLL_7E4_HVIL 0x4310 static const int POLL_7E4_HIGHEST_CELL_NUMBER = 0x433C;
#define POLL_7E4_HVIL_STATUS 0x4311 static const int POLL_7E4_VOLTAGE = 0x432D;
#define POLL_7E4_CURRENT 0x4356 static const int POLL_7E4_VEHICLE_ISOLATION = 0x41EC;
static const int POLL_7E4_ISOLATION_TEST_KOHM = 0x43A6;
static const int POLL_7E4_HV_LOCKED_OUT = 0x44F8;
static const int POLL_7E4_CRASH_EVENT = 0x4522;
static const int POLL_7E4_HVIL = 0x4310;
static const int POLL_7E4_HVIL_STATUS = 0x4311;
static const int POLL_7E4_CURRENT = 0x4356;
static const int POLL_7E7_CURRENT = 0x40D4;
static const int POLL_7E7_5V_REF = 0x40D3;
static const int POLL_7E7_MODULE_TEMP_1 = 0x40D7;
static const int POLL_7E7_MODULE_TEMP_2 = 0x40D9;
static const int POLL_7E7_MODULE_TEMP_3 = 0x40DB;
static const int POLL_7E7_MODULE_TEMP_4 = 0x40DD;
static const int POLL_7E7_MODULE_TEMP_5 = 0x40DF;
static const int POLL_7E7_MODULE_TEMP_6 = 0x40E1;
static const int POLL_7E7_CELL_AVG_VOLTAGE = 0xC218;
static const int POLL_7E7_CELL_AVG_VOLTAGE_2 = 0x44B9;
static const int POLL_7E7_TERMINAL_VOLTAGE = 0x82A3;
static const int POLL_7E7_IGNITION_POWER_MODE = 0x8002;
static const int POLL_7E7_GMLAN_HIGH_SPEED_STATUS = 0x8043;
static const int POLL_7E7_HV_ISOLATION_RESISTANCE = 0x43A6;
static const int POLL_7E7_HV_BUS_VOLTAGE = 0x438B;
static const int POLL_7E7_HYBRID_CELL_BALANCING_ID_1 = 0x4323;
static const int POLL_7E7_HYBRID_CELL_BALANCING_ID_2 = 0x4324;
static const int POLL_7E7_HYBRID_CELL_BALANCING_ID_3 = 0x4325;
static const int POLL_7E7_HYBRID_CELL_BALANCING_ID_4 = 0x4326;
static const int POLL_7E7_HYBRID_CELL_BALANCING_ID_5 = 0x4327;
static const int POLL_7E7_HYBRID_CELL_BALANCING_ID_6 = 0x4340;
static const int POLL_7E7_HYBRID_BATTERY_CELL_BALANCE_STATUS = 0x431C;
static const int POLL_7E7_5V_REF_VOLTAGE_1 = 0x428F;
static const int POLL_7E7_5V_REF_VOLTAGE_2 = 0x4290;
static const int POLL_7E7_CELL_01 = 0x4181;
static const int POLL_7E7_CELL_02 = 0x4182;
static const int POLL_7E7_CELL_03 = 0x4183;
static const int POLL_7E7_CELL_04 = 0x4184;
static const int POLL_7E7_CELL_05 = 0x4185;
static const int POLL_7E7_CELL_06 = 0x4186;
static const int POLL_7E7_CELL_07 = 0x4187;
static const int POLL_7E7_CELL_08 = 0x4188;
static const int POLL_7E7_CELL_09 = 0x4189;
static const int POLL_7E7_CELL_10 = 0x418A;
static const int POLL_7E7_CELL_11 = 0x418B;
static const int POLL_7E7_CELL_12 = 0x418C;
static const int POLL_7E7_CELL_13 = 0x418D;
static const int POLL_7E7_CELL_14 = 0x418E;
static const int POLL_7E7_CELL_15 = 0x418F;
static const int POLL_7E7_CELL_16 = 0x4190;
static const int POLL_7E7_CELL_17 = 0x4191;
static const int POLL_7E7_CELL_18 = 0x4192;
static const int POLL_7E7_CELL_19 = 0x4193;
static const int POLL_7E7_CELL_20 = 0x4194;
static const int POLL_7E7_CELL_21 = 0x4195;
static const int POLL_7E7_CELL_22 = 0x4196;
static const int POLL_7E7_CELL_23 = 0x4197;
static const int POLL_7E7_CELL_24 = 0x4198;
static const int POLL_7E7_CELL_25 = 0x4199;
static const int POLL_7E7_CELL_26 = 0x419A;
static const int POLL_7E7_CELL_27 = 0x419B;
static const int POLL_7E7_CELL_28 = 0x419C;
static const int POLL_7E7_CELL_29 = 0x419D;
static const int POLL_7E7_CELL_30 = 0x419E;
static const int POLL_7E7_CELL_31 = 0x419F;
static const int POLL_7E7_CELL_32 = 0x4200;
static const int POLL_7E7_CELL_33 = 0x4201;
static const int POLL_7E7_CELL_34 = 0x4202;
static const int POLL_7E7_CELL_35 = 0x4203;
static const int POLL_7E7_CELL_36 = 0x4204;
static const int POLL_7E7_CELL_37 = 0x4205;
static const int POLL_7E7_CELL_38 = 0x4206;
static const int POLL_7E7_CELL_39 = 0x4207;
static const int POLL_7E7_CELL_40 = 0x4208;
static const int POLL_7E7_CELL_41 = 0x4209;
static const int POLL_7E7_CELL_42 = 0x420A;
static const int POLL_7E7_CELL_43 = 0x420B;
static const int POLL_7E7_CELL_44 = 0x420C;
static const int POLL_7E7_CELL_45 = 0x420D;
static const int POLL_7E7_CELL_46 = 0x420E;
static const int POLL_7E7_CELL_47 = 0x420F;
static const int POLL_7E7_CELL_48 = 0x4210;
static const int POLL_7E7_CELL_49 = 0x4211;
static const int POLL_7E7_CELL_50 = 0x4212;
static const int POLL_7E7_CELL_51 = 0x4213;
static const int POLL_7E7_CELL_52 = 0x4214;
static const int POLL_7E7_CELL_53 = 0x4215;
static const int POLL_7E7_CELL_54 = 0x4216;
static const int POLL_7E7_CELL_55 = 0x4217;
static const int POLL_7E7_CELL_56 = 0x4218;
static const int POLL_7E7_CELL_57 = 0x4219;
static const int POLL_7E7_CELL_58 = 0x421A;
static const int POLL_7E7_CELL_59 = 0x421B;
static const int POLL_7E7_CELL_60 = 0x421C;
static const int POLL_7E7_CELL_61 = 0x421D;
static const int POLL_7E7_CELL_62 = 0x421E;
static const int POLL_7E7_CELL_63 = 0x421F;
static const int POLL_7E7_CELL_64 = 0x4220;
static const int POLL_7E7_CELL_65 = 0x4221;
static const int POLL_7E7_CELL_66 = 0x4222;
static const int POLL_7E7_CELL_67 = 0x4223;
static const int POLL_7E7_CELL_68 = 0x4224;
static const int POLL_7E7_CELL_69 = 0x4225;
static const int POLL_7E7_CELL_70 = 0x4226;
static const int POLL_7E7_CELL_71 = 0x4227;
static const int POLL_7E7_CELL_72 = 0x4228;
static const int POLL_7E7_CELL_73 = 0x4229;
static const int POLL_7E7_CELL_74 = 0x422A;
static const int POLL_7E7_CELL_75 = 0x422B;
static const int POLL_7E7_CELL_76 = 0x422C;
static const int POLL_7E7_CELL_77 = 0x422D;
static const int POLL_7E7_CELL_78 = 0x422E;
static const int POLL_7E7_CELL_79 = 0x422F;
static const int POLL_7E7_CELL_80 = 0x4230;
static const int POLL_7E7_CELL_81 = 0x4231;
static const int POLL_7E7_CELL_82 = 0x4232;
static const int POLL_7E7_CELL_83 = 0x4233;
static const int POLL_7E7_CELL_84 = 0x4234;
static const int POLL_7E7_CELL_85 = 0x4235;
static const int POLL_7E7_CELL_86 = 0x4236;
static const int POLL_7E7_CELL_87 = 0x4237;
static const int POLL_7E7_CELL_88 = 0x4238;
static const int POLL_7E7_CELL_89 = 0x4239;
static const int POLL_7E7_CELL_90 = 0x423A;
static const int POLL_7E7_CELL_91 = 0x423B;
static const int POLL_7E7_CELL_92 = 0x423C;
static const int POLL_7E7_CELL_93 = 0x423D;
static const int POLL_7E7_CELL_94 = 0x423E;
static const int POLL_7E7_CELL_95 = 0x423F;
static const int POLL_7E7_CELL_96 = 0x4240;
static const int POLL_7E7_CELL_97 = 0x4241; // Normal pack ends at 96, these cells might be unpopulated
static const int POLL_7E7_CELL_98 = 0x4242;
static const int POLL_7E7_CELL_99 = 0x4243;
static const int POLL_7E7_CELL_100 = 0x4244;
static const int POLL_7E7_CELL_101 = 0x4245;
static const int POLL_7E7_CELL_102 = 0x4246;
static const int POLL_7E7_CELL_103 = 0x4247;
static const int POLL_7E7_CELL_104 = 0x4248;
static const int POLL_7E7_CELL_105 = 0x4249;
static const int POLL_7E7_CELL_106 = 0x424A;
static const int POLL_7E7_CELL_107 = 0x424B;
static const int POLL_7E7_CELL_108 = 0x424C;
static const int POLL_7E7_CELL_109 = 0x424D;
static const int POLL_7E7_CELL_110 = 0x424E;
static const int POLL_7E7_CELL_111 = 0x424F;
static const int POLL_7E7_CELL_112 = 0x4250;
static const int POLL_7E7_CELL_113 = 0x4251;
static const int POLL_7E7_CELL_114 = 0x4252;
static const int POLL_7E7_CELL_115 = 0x4253;
static const int POLL_7E7_CELL_116 = 0x4254;
static const int POLL_7E7_CELL_117 = 0x4255;
static const int POLL_7E7_CELL_118 = 0x4256;
static const int POLL_7E7_CELL_119 = 0x4257;
static const int POLL_7E7_CELL_120 = 0x4258;
#define POLL_7E7_CURRENT 0x40D4 unsigned long previousMillis20ms = 0; // will store last time a 20ms CAN Message was send
#define POLL_7E7_5V_REF 0x40D3 unsigned long previousMillis100ms = 0; // will store last time a 100ms CAN Message was send
#define POLL_7E7_MODULE_TEMP_1 0x40D7 unsigned long previousMillis120ms = 0; // will store last time a 120ms CAN Message was send
#define POLL_7E7_MODULE_TEMP_2 0x40D9
#define POLL_7E7_MODULE_TEMP_3 0x40DB
#define POLL_7E7_MODULE_TEMP_4 0x40DD
#define POLL_7E7_MODULE_TEMP_5 0x40DF
#define POLL_7E7_MODULE_TEMP_6 0x40E1
#define POLL_7E7_CELL_AVG_VOLTAGE 0xC218
#define POLL_7E7_CELL_AVG_VOLTAGE_2 0x44B9
#define POLL_7E7_TERMINAL_VOLTAGE 0x82A3
#define POLL_7E7_IGNITION_POWER_MODE 0x8002
#define POLL_7E7_GMLAN_HIGH_SPEED_STATUS 0x8043
#define POLL_7E7_HV_ISOLATION_RESISTANCE 0x43A6
#define POLL_7E7_HV_BUS_VOLTAGE 0x438B
#define POLL_7E7_HYBRID_CELL_BALANCING_ID_1 0x4323
#define POLL_7E7_HYBRID_CELL_BALANCING_ID_2 0x4324
#define POLL_7E7_HYBRID_CELL_BALANCING_ID_3 0x4325
#define POLL_7E7_HYBRID_CELL_BALANCING_ID_4 0x4326
#define POLL_7E7_HYBRID_CELL_BALANCING_ID_5 0x4327
#define POLL_7E7_HYBRID_CELL_BALANCING_ID_6 0x4340
#define POLL_7E7_HYBRID_BATTERY_CELL_BALANCE_STATUS 0x431C
#define POLL_7E7_5V_REF_VOLTAGE_1 0x428F
#define POLL_7E7_5V_REF_VOLTAGE_2 0x4290
#define POLL_7E7_CELL_01 0x4181
#define POLL_7E7_CELL_02 0x4182
#define POLL_7E7_CELL_03 0x4183
#define POLL_7E7_CELL_04 0x4184
#define POLL_7E7_CELL_05 0x4185
#define POLL_7E7_CELL_06 0x4186
#define POLL_7E7_CELL_07 0x4187
#define POLL_7E7_CELL_08 0x4188
#define POLL_7E7_CELL_09 0x4189
#define POLL_7E7_CELL_10 0x418A
#define POLL_7E7_CELL_11 0x418B
#define POLL_7E7_CELL_12 0x418C
#define POLL_7E7_CELL_13 0x418D
#define POLL_7E7_CELL_14 0x418E
#define POLL_7E7_CELL_15 0x418F
#define POLL_7E7_CELL_16 0x4190
#define POLL_7E7_CELL_17 0x4191
#define POLL_7E7_CELL_18 0x4192
#define POLL_7E7_CELL_19 0x4193
#define POLL_7E7_CELL_20 0x4194
#define POLL_7E7_CELL_21 0x4195
#define POLL_7E7_CELL_22 0x4196
#define POLL_7E7_CELL_23 0x4197
#define POLL_7E7_CELL_24 0x4198
#define POLL_7E7_CELL_25 0x4199
#define POLL_7E7_CELL_26 0x419A
#define POLL_7E7_CELL_27 0x419B
#define POLL_7E7_CELL_28 0x419C
#define POLL_7E7_CELL_29 0x419D
#define POLL_7E7_CELL_30 0x419E
#define POLL_7E7_CELL_31 0x419F
#define POLL_7E7_CELL_32 0x4200
#define POLL_7E7_CELL_33 0x4201
#define POLL_7E7_CELL_34 0x4202
#define POLL_7E7_CELL_35 0x4203
#define POLL_7E7_CELL_36 0x4204
#define POLL_7E7_CELL_37 0x4205
#define POLL_7E7_CELL_38 0x4206
#define POLL_7E7_CELL_39 0x4207
#define POLL_7E7_CELL_40 0x4208
#define POLL_7E7_CELL_41 0x4209
#define POLL_7E7_CELL_42 0x420A
#define POLL_7E7_CELL_43 0x420B
#define POLL_7E7_CELL_44 0x420C
#define POLL_7E7_CELL_45 0x420D
#define POLL_7E7_CELL_46 0x420E
#define POLL_7E7_CELL_47 0x420F
#define POLL_7E7_CELL_48 0x4210
#define POLL_7E7_CELL_49 0x4211
#define POLL_7E7_CELL_50 0x4212
#define POLL_7E7_CELL_51 0x4213
#define POLL_7E7_CELL_52 0x4214
#define POLL_7E7_CELL_53 0x4215
#define POLL_7E7_CELL_54 0x4216
#define POLL_7E7_CELL_55 0x4217
#define POLL_7E7_CELL_56 0x4218
#define POLL_7E7_CELL_57 0x4219
#define POLL_7E7_CELL_58 0x421A
#define POLL_7E7_CELL_59 0x421B
#define POLL_7E7_CELL_60 0x421C
#define POLL_7E7_CELL_61 0x421D
#define POLL_7E7_CELL_62 0x421E
#define POLL_7E7_CELL_63 0x421F
#define POLL_7E7_CELL_64 0x4220
#define POLL_7E7_CELL_65 0x4221
#define POLL_7E7_CELL_66 0x4222
#define POLL_7E7_CELL_67 0x4223
#define POLL_7E7_CELL_68 0x4224
#define POLL_7E7_CELL_69 0x4225
#define POLL_7E7_CELL_70 0x4226
#define POLL_7E7_CELL_71 0x4227
#define POLL_7E7_CELL_72 0x4228
#define POLL_7E7_CELL_73 0x4229
#define POLL_7E7_CELL_74 0x422A
#define POLL_7E7_CELL_75 0x422B
#define POLL_7E7_CELL_76 0x422C
#define POLL_7E7_CELL_77 0x422D
#define POLL_7E7_CELL_78 0x422E
#define POLL_7E7_CELL_79 0x422F
#define POLL_7E7_CELL_80 0x4230
#define POLL_7E7_CELL_81 0x4231
#define POLL_7E7_CELL_82 0x4232
#define POLL_7E7_CELL_83 0x4233
#define POLL_7E7_CELL_84 0x4234
#define POLL_7E7_CELL_85 0x4235
#define POLL_7E7_CELL_86 0x4236
#define POLL_7E7_CELL_87 0x4237
#define POLL_7E7_CELL_88 0x4238
#define POLL_7E7_CELL_89 0x4239
#define POLL_7E7_CELL_90 0x423A
#define POLL_7E7_CELL_91 0x423B
#define POLL_7E7_CELL_92 0x423C
#define POLL_7E7_CELL_93 0x423D
#define POLL_7E7_CELL_94 0x423E
#define POLL_7E7_CELL_95 0x423F
#define POLL_7E7_CELL_96 0x4240
#define POLL_7E7_CELL_97 0x4241 // Normal pack ends at 96, these cells might be unpopulated
#define POLL_7E7_CELL_98 0x4242
#define POLL_7E7_CELL_99 0x4243
#define POLL_7E7_CELL_100 0x4244
#define POLL_7E7_CELL_101 0x4245
#define POLL_7E7_CELL_102 0x4246
#define POLL_7E7_CELL_103 0x4247
#define POLL_7E7_CELL_104 0x4248
#define POLL_7E7_CELL_105 0x4249
#define POLL_7E7_CELL_106 0x424A
#define POLL_7E7_CELL_107 0x424B
#define POLL_7E7_CELL_108 0x424C
#define POLL_7E7_CELL_109 0x424D
#define POLL_7E7_CELL_110 0x424E
#define POLL_7E7_CELL_111 0x424F
#define POLL_7E7_CELL_112 0x4250
#define POLL_7E7_CELL_113 0x4251
#define POLL_7E7_CELL_114 0x4252
#define POLL_7E7_CELL_115 0x4253
#define POLL_7E7_CELL_116 0x4254
#define POLL_7E7_CELL_117 0x4255
#define POLL_7E7_CELL_118 0x4256
#define POLL_7E7_CELL_119 0x4257
#define POLL_7E7_CELL_120 0x4258
void setup_battery(void); CAN_frame BOLT_778 = {.FD = false, // Unsure of what this message is, added only as example
void transmit_can_frame(CAN_frame* tx_frame, int interface); .ext_ID = false,
.DLC = 7,
.ID = 0x778,
.data = {0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame BOLT_POLL_7E4 = {.FD = false, // VICM_HV poll
.ext_ID = false,
.DLC = 8,
.ID = 0x7E4,
.data = {0x03, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame BOLT_ACK_7E4 = {.FD = false, //VICM_HV ack
.ext_ID = false,
.DLC = 8,
.ID = 0x7E4,
.data = {0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame BOLT_POLL_7E7 = {.FD = false, //VITM_HV poll
.ext_ID = false,
.DLC = 8,
.ID = 0x7E7,
.data = {0x03, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame BOLT_ACK_7E7 = {.FD = false, //VITM_HV ack
.ext_ID = false,
.DLC = 8,
.ID = 0x7E7,
.data = {0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
// Other PID requests in the vehicle
// All HV ECUs - 0x101
// HPCC HV - 0x243 replies on 0x643
// OBCM HV - 0x244 replies on 0x644
// VICM_HV - 0x7E4 replies 0x7EC (This is battery)
// VICM2_HV - 0x7E6 replies 0x7EF (Tis is battery also)
// VITM_HV - 0x7E7 replies on 7EF (This is battery)
uint16_t battery_cell_voltages[96]; //array with all the cellvoltages
uint16_t battery_capacity_my17_18 = 0;
uint16_t battery_capacity_my19plus = 0;
uint16_t battery_SOC_display = 0;
uint16_t battery_SOC_raw_highprec = 0;
uint16_t battery_max_temperature = 0;
uint16_t battery_min_temperature = 0;
uint16_t battery_min_cell_voltage = 0;
uint16_t battery_max_cell_voltage = 0;
uint16_t battery_internal_resistance = 0;
uint16_t battery_lowest_cell = 0;
uint16_t battery_highest_cell = 0;
uint16_t battery_voltage_polled = 0;
uint16_t battery_voltage_periodic = 0;
uint16_t battery_vehicle_isolation = 0;
uint16_t battery_isolation_kohm = 0;
uint16_t battery_HV_locked = 0;
uint16_t battery_crash_event = 0;
uint16_t battery_HVIL = 0;
uint16_t battery_HVIL_status = 0;
uint16_t battery_5V_ref = 0;
int16_t battery_current_7E4 = 0;
int16_t battery_module_temp_1 = 0;
int16_t battery_module_temp_2 = 0;
int16_t battery_module_temp_3 = 0;
int16_t battery_module_temp_4 = 0;
int16_t battery_module_temp_5 = 0;
int16_t battery_module_temp_6 = 0;
uint16_t battery_cell_average_voltage = 0;
uint16_t battery_cell_average_voltage_2 = 0;
uint16_t battery_terminal_voltage = 0;
uint16_t battery_ignition_power_mode = 0;
int16_t battery_current_7E7 = 0;
int16_t temperature_1 = 0;
int16_t temperature_2 = 0;
int16_t temperature_3 = 0;
int16_t temperature_4 = 0;
int16_t temperature_5 = 0;
int16_t temperature_6 = 0;
int16_t temperature_highest = 0;
int16_t temperature_lowest = 0;
uint8_t mux = 0;
uint8_t poll_index_7E4 = 0;
uint16_t currentpoll_7E4 = POLL_7E4_CAPACITY_EST_GEN1;
uint16_t reply_poll_7E4 = 0;
uint8_t poll_index_7E7 = 0;
uint16_t currentpoll_7E7 = POLL_7E7_CURRENT;
uint16_t reply_poll_7E7 = 0;
const uint16_t poll_commands_7E4[19] = {POLL_7E4_CAPACITY_EST_GEN1,
POLL_7E4_CAPACITY_EST_GEN2,
POLL_7E4_SOC_DISPLAY,
POLL_7E4_SOC_RAW_HIGHPREC,
POLL_7E4_MAX_TEMPERATURE,
POLL_7E4_MIN_TEMPERATURE,
POLL_7E4_MIN_CELL_V,
POLL_7E4_MAX_CELL_V,
POLL_7E4_INTERNAL_RES,
POLL_7E4_LOWEST_CELL_NUMBER,
POLL_7E4_HIGHEST_CELL_NUMBER,
POLL_7E4_VOLTAGE,
POLL_7E4_VEHICLE_ISOLATION,
POLL_7E4_ISOLATION_TEST_KOHM,
POLL_7E4_HV_LOCKED_OUT,
POLL_7E4_CRASH_EVENT,
POLL_7E4_HVIL,
POLL_7E4_HVIL_STATUS,
POLL_7E4_CURRENT};
const uint16_t poll_commands_7E7[108] = {POLL_7E7_CURRENT, POLL_7E7_5V_REF,
POLL_7E7_MODULE_TEMP_1, POLL_7E7_MODULE_TEMP_2,
POLL_7E7_MODULE_TEMP_3, POLL_7E7_MODULE_TEMP_4,
POLL_7E7_MODULE_TEMP_5, POLL_7E7_MODULE_TEMP_6,
POLL_7E7_CELL_AVG_VOLTAGE, POLL_7E7_CELL_AVG_VOLTAGE_2,
POLL_7E7_TERMINAL_VOLTAGE, POLL_7E7_IGNITION_POWER_MODE,
POLL_7E7_CELL_01, POLL_7E7_CELL_02,
POLL_7E7_CELL_03, POLL_7E7_CELL_04,
POLL_7E7_CELL_05, POLL_7E7_CELL_06,
POLL_7E7_CELL_07, POLL_7E7_CELL_08,
POLL_7E7_CELL_09, POLL_7E7_CELL_10,
POLL_7E7_CELL_11, POLL_7E7_CELL_12,
POLL_7E7_CELL_13, POLL_7E7_CELL_14,
POLL_7E7_CELL_15, POLL_7E7_CELL_16,
POLL_7E7_CELL_17, POLL_7E7_CELL_18,
POLL_7E7_CELL_19, POLL_7E7_CELL_20,
POLL_7E7_CELL_21, POLL_7E7_CELL_22,
POLL_7E7_CELL_23, POLL_7E7_CELL_24,
POLL_7E7_CELL_25, POLL_7E7_CELL_26,
POLL_7E7_CELL_27, POLL_7E7_CELL_28,
POLL_7E7_CELL_29, POLL_7E7_CELL_30,
POLL_7E7_CELL_31, POLL_7E7_CELL_32,
POLL_7E7_CELL_33, POLL_7E7_CELL_34,
POLL_7E7_CELL_35, POLL_7E7_CELL_36,
POLL_7E7_CELL_37, POLL_7E7_CELL_38,
POLL_7E7_CELL_39, POLL_7E7_CELL_40,
POLL_7E7_CELL_41, POLL_7E7_CELL_42,
POLL_7E7_CELL_43, POLL_7E7_CELL_44,
POLL_7E7_CELL_45, POLL_7E7_CELL_46,
POLL_7E7_CELL_47, POLL_7E7_CELL_48,
POLL_7E7_CELL_49, POLL_7E7_CELL_50,
POLL_7E7_CELL_51, POLL_7E7_CELL_52,
POLL_7E7_CELL_53, POLL_7E7_CELL_54,
POLL_7E7_CELL_55, POLL_7E7_CELL_56,
POLL_7E7_CELL_57, POLL_7E7_CELL_58,
POLL_7E7_CELL_59, POLL_7E7_CELL_60,
POLL_7E7_CELL_61, POLL_7E7_CELL_62,
POLL_7E7_CELL_63, POLL_7E7_CELL_64,
POLL_7E7_CELL_65, POLL_7E7_CELL_66,
POLL_7E7_CELL_67, POLL_7E7_CELL_68,
POLL_7E7_CELL_69, POLL_7E7_CELL_70,
POLL_7E7_CELL_71, POLL_7E7_CELL_72,
POLL_7E7_CELL_73, POLL_7E7_CELL_74,
POLL_7E7_CELL_75, POLL_7E7_CELL_76,
POLL_7E7_CELL_77, POLL_7E7_CELL_78,
POLL_7E7_CELL_79, POLL_7E7_CELL_80,
POLL_7E7_CELL_81, POLL_7E7_CELL_82,
POLL_7E7_CELL_83, POLL_7E7_CELL_84,
POLL_7E7_CELL_85, POLL_7E7_CELL_86,
POLL_7E7_CELL_87, POLL_7E7_CELL_88,
POLL_7E7_CELL_89, POLL_7E7_CELL_90,
POLL_7E7_CELL_91, POLL_7E7_CELL_92,
POLL_7E7_CELL_93, POLL_7E7_CELL_94,
POLL_7E7_CELL_95, POLL_7E7_CELL_96};
};
#endif #endif

View file

@ -1,5 +1,6 @@
#include "../include.h" #include "../include.h"
#ifdef BYD_ATTO_3_BATTERY #ifdef BYD_ATTO_3_BATTERY
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h" #include "../datalayer/datalayer_extended.h"
#include "../devboard/utils/events.h" #include "../devboard/utils/events.h"
@ -11,76 +12,6 @@ If you have a crash-locked pack, See the Wiki for more info on how to attempt an
After battery has been unlocked, you can remove the "USE_ESTIMATED_SOC" from the BYD-ATTO-3-BATTERY.h file After battery has been unlocked, you can remove the "USE_ESTIMATED_SOC" from the BYD-ATTO-3-BATTERY.h file
*/ */
/* Do not change code below unless you are sure what you are doing */
#define NOT_DETERMINED_YET 0
#define STANDARD_RANGE 1
#define EXTENDED_RANGE 2
#define NOT_RUNNING 0xFF
#define STARTED 0
#define RUNNING_STEP_1 1
#define RUNNING_STEP_2 2
static uint8_t battery_type = NOT_DETERMINED_YET;
static uint8_t stateMachineClearCrash = NOT_RUNNING;
static unsigned long previousMillis50 = 0; // will store last time a 50ms CAN Message was send
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
static unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was send
static bool SOC_method = false;
static uint8_t counter_50ms = 0;
static uint8_t counter_100ms = 0;
static uint8_t frame6_counter = 0xB;
static uint8_t frame7_counter = 0x5;
static uint16_t battery_voltage = 0;
static int16_t battery_temperature_ambient = 0;
static int16_t battery_daughterboard_temperatures[10];
static int16_t battery_lowest_temperature = 0;
static int16_t battery_highest_temperature = 0;
static int16_t battery_calc_min_temperature = 0;
static int16_t battery_calc_max_temperature = 0;
static uint16_t battery_highprecision_SOC = 0;
static uint16_t BMS_SOC = 0;
static uint16_t BMS_voltage = 0;
static int16_t BMS_current = 0;
static int16_t BMS_lowest_cell_temperature = 0;
static int16_t BMS_highest_cell_temperature = 0;
static int16_t BMS_average_cell_temperature = 0;
static uint16_t BMS_lowest_cell_voltage_mV = 3300;
static uint16_t BMS_highest_cell_voltage_mV = 3300;
static uint32_t BMS_unknown0 = 0;
static uint32_t BMS_unknown1 = 0;
static uint16_t BMS_allowed_charge_power = 0;
static uint16_t BMS_unknown3 = 0;
static uint16_t BMS_unknown4 = 0;
static uint16_t BMS_unknown5 = 0;
static uint16_t BMS_unknown6 = 0;
static uint16_t BMS_unknown7 = 0;
static uint16_t BMS_unknown8 = 0;
static uint16_t BMS_unknown9 = 0;
static uint8_t BMS_unknown10 = 0;
static uint8_t BMS_unknown11 = 0;
static uint8_t BMS_unknown12 = 0;
static uint8_t BMS_unknown13 = 0;
static uint8_t battery_frame_index = 0;
static uint16_t battery_cellvoltages[CELLCOUNT_EXTENDED] = {0};
#ifdef DOUBLE_BATTERY
static int16_t battery2_temperature_ambient = 0;
static int16_t battery2_daughterboard_temperatures[10];
static int16_t battery2_lowest_temperature = 0;
static int16_t battery2_highest_temperature = 0;
static int16_t battery2_calc_min_temperature = 0;
static int16_t battery2_calc_max_temperature = 0;
static uint16_t battery2_highprecision_SOC = 0;
static uint16_t BMS2_SOC = 0;
static uint16_t BMS2_voltage = 0;
static int16_t BMS2_current = 0;
static int16_t BMS2_lowest_cell_temperature = 0;
static int16_t BMS2_highest_cell_temperature = 0;
static int16_t BMS2_average_cell_temperature = 0;
static uint16_t BMS2_lowest_cell_voltage_mV = 3300;
static uint16_t BMS2_highest_cell_voltage_mV = 3300;
static uint8_t battery2_frame_index = 0;
static uint16_t battery2_cellvoltages[CELLCOUNT_EXTENDED] = {0};
#endif //DOUBLE_BATTERY
#define POLL_FOR_BATTERY_SOC 0x0005
#define POLL_FOR_BATTERY_VOLTAGE 0x0008 #define POLL_FOR_BATTERY_VOLTAGE 0x0008
#define POLL_FOR_BATTERY_CURRENT 0x0009 #define POLL_FOR_BATTERY_CURRENT 0x0009
#define POLL_FOR_LOWEST_TEMP_CELL 0x002f #define POLL_FOR_LOWEST_TEMP_CELL 0x002f
@ -165,34 +96,6 @@ static uint16_t battery2_cellvoltages[CELLCOUNT_EXTENDED] = {0};
#define ESTIMATED 0 #define ESTIMATED 0
#define MEASURED 1 #define MEASURED 1
static uint16_t poll_state = POLL_FOR_BATTERY_SOC;
static uint16_t pid_reply = 0;
CAN_frame ATTO_3_12D = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x12D,
.data = {0xA0, 0x28, 0x02, 0xA0, 0x0C, 0x71, 0xCF, 0x49}};
CAN_frame ATTO_3_441 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x441,
.data = {0x98, 0x3A, 0x88, 0x13, 0x07, 0x00, 0xFF, 0x8C}};
CAN_frame ATTO_3_7E7_POLL = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E7, //Poll PID 03 22 00 05 (POLL_FOR_BATTERY_SOC)
.data = {0x03, 0x22, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00}};
CAN_frame ATTO_3_7E7_ACK = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E7, //ACK frame for long PIDs
.data = {0x30, 0x08, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame ATTO_3_7E7_CLEAR_CRASH = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E7,
.data = {0x02, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}};
// Define the data points for %SOC depending on pack voltage // Define the data points for %SOC depending on pack voltage
const uint8_t numPoints = 28; const uint8_t numPoints = 28;
@ -241,10 +144,11 @@ uint16_t estimateSOCstandard(uint16_t packVoltage) { // Linear interpolation fu
return 0; // Default return for safety, should never reach here return 0; // Default return for safety, should never reach here
} }
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus void BydAttoBattery::
update_values() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
if (BMS_voltage > 0) { if (BMS_voltage > 0) {
datalayer.battery.status.voltage_dV = BMS_voltage * 10; datalayer_battery->status.voltage_dV = BMS_voltage * 10;
} }
#ifdef USE_ESTIMATED_SOC #ifdef USE_ESTIMATED_SOC
@ -252,32 +156,32 @@ void update_values_battery() { //This function maps all the values fetched via
// We instead estimate the SOC% based on the battery voltage. // We instead estimate the SOC% based on the battery voltage.
// This is a bad solution, you wont be able to use 100% of the battery // This is a bad solution, you wont be able to use 100% of the battery
if (battery_type == EXTENDED_RANGE) { if (battery_type == EXTENDED_RANGE) {
datalayer.battery.status.real_soc = estimateSOCextended(datalayer.battery.status.voltage_dV); datalayer_battery->status.real_soc = estimateSOCextended(datalayer_battery->status.voltage_dV);
} }
if (battery_type == STANDARD_RANGE) { if (battery_type == STANDARD_RANGE) {
datalayer.battery.status.real_soc = estimateSOCstandard(datalayer.battery.status.voltage_dV); datalayer_battery->status.real_soc = estimateSOCstandard(datalayer_battery->status.voltage_dV);
} }
SOC_method = ESTIMATED; SOC_method = ESTIMATED;
#else // Pack is not crashed, we can use periodically transmitted SOC #else // Pack is not crashed, we can use periodically transmitted SOC
datalayer.battery.status.real_soc = battery_highprecision_SOC * 10; datalayer_battery->status.real_soc = battery_highprecision_SOC * 10;
SOC_method = MEASURED; SOC_method = MEASURED;
#endif #endif
datalayer.battery.status.current_dA = -BMS_current; datalayer_battery->status.current_dA = -BMS_current;
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>( datalayer_battery->status.remaining_capacity_Wh = static_cast<uint32_t>(
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh); (static_cast<double>(datalayer_battery->status.real_soc) / 10000) * datalayer_battery->info.total_capacity_Wh);
datalayer.battery.status.max_discharge_power_W = MAXPOWER_DISCHARGE_W; //TODO: Map from CAN later on datalayer_battery->status.max_discharge_power_W = MAXPOWER_DISCHARGE_W; //TODO: Map from CAN later on
datalayer.battery.status.max_charge_power_W = BMS_allowed_charge_power * 10; //TODO: Scaling unknown, *10 best guess datalayer_battery->status.max_charge_power_W = BMS_allowed_charge_power * 10; //TODO: Scaling unknown, *10 best guess
datalayer.battery.status.cell_max_voltage_mV = BMS_highest_cell_voltage_mV; datalayer_battery->status.cell_max_voltage_mV = BMS_highest_cell_voltage_mV;
datalayer.battery.status.cell_min_voltage_mV = BMS_lowest_cell_voltage_mV; datalayer_battery->status.cell_min_voltage_mV = BMS_lowest_cell_voltage_mV;
//Map all cell voltages to the global array //Map all cell voltages to the global array
memcpy(datalayer.battery.status.cell_voltages_mV, battery_cellvoltages, CELLCOUNT_EXTENDED * sizeof(uint16_t)); memcpy(datalayer_battery->status.cell_voltages_mV, battery_cellvoltages, CELLCOUNT_EXTENDED * sizeof(uint16_t));
// Check if we are on Standard range or Extended range battery. // Check if we are on Standard range or Extended range battery.
// We use a variety of checks to ensure we catch a potential Standard range battery // We use a variety of checks to ensure we catch a potential Standard range battery
@ -296,16 +200,16 @@ void update_values_battery() { //This function maps all the values fetched via
switch (battery_type) { switch (battery_type) {
case STANDARD_RANGE: case STANDARD_RANGE:
datalayer.battery.info.total_capacity_Wh = 50000; datalayer_battery->info.total_capacity_Wh = 50000;
datalayer.battery.info.number_of_cells = CELLCOUNT_STANDARD; datalayer_battery->info.number_of_cells = CELLCOUNT_STANDARD;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_STANDARD_DV; datalayer_battery->info.max_design_voltage_dV = MAX_PACK_VOLTAGE_STANDARD_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_STANDARD_DV; datalayer_battery->info.min_design_voltage_dV = MIN_PACK_VOLTAGE_STANDARD_DV;
break; break;
case EXTENDED_RANGE: case EXTENDED_RANGE:
datalayer.battery.info.total_capacity_Wh = 60000; datalayer_battery->info.total_capacity_Wh = 60000;
datalayer.battery.info.number_of_cells = CELLCOUNT_EXTENDED; datalayer_battery->info.number_of_cells = CELLCOUNT_EXTENDED;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_EXTENDED_DV; datalayer_battery->info.max_design_voltage_dV = MAX_PACK_VOLTAGE_EXTENDED_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_EXTENDED_DV; datalayer_battery->info.min_design_voltage_dV = MIN_PACK_VOLTAGE_EXTENDED_DV;
break; break;
case NOT_DETERMINED_YET: case NOT_DETERMINED_YET:
default: default:
@ -338,106 +242,108 @@ void update_values_battery() { //This function maps all the values fetched via
//Write the result to datalayer //Write the result to datalayer
if ((battery_calc_min_temperature != 0) && (battery_calc_max_temperature != 0)) { if ((battery_calc_min_temperature != 0) && (battery_calc_max_temperature != 0)) {
//Avoid triggering high delta if only one of the values is available //Avoid triggering high delta if only one of the values is available
datalayer.battery.status.temperature_min_dC = battery_calc_min_temperature * 10; datalayer_battery->status.temperature_min_dC = battery_calc_min_temperature * 10;
datalayer.battery.status.temperature_max_dC = battery_calc_max_temperature * 10; datalayer_battery->status.temperature_max_dC = battery_calc_max_temperature * 10;
} }
#else //User does not need filtering out a broken sensor, just use the min-max the BMS sends #else //User does not need filtering out a broken sensor, just use the min-max the BMS sends
if ((BMS_lowest_cell_temperature != 0) && (BMS_highest_cell_temperature != 0)) { if ((BMS_lowest_cell_temperature != 0) && (BMS_highest_cell_temperature != 0)) {
//Avoid triggering high delta if only one of the values is available //Avoid triggering high delta if only one of the values is available
datalayer.battery.status.temperature_min_dC = BMS_lowest_cell_temperature * 10; datalayer_battery->status.temperature_min_dC = BMS_lowest_cell_temperature * 10;
datalayer.battery.status.temperature_max_dC = BMS_highest_cell_temperature * 10; datalayer_battery->status.temperature_max_dC = BMS_highest_cell_temperature * 10;
} }
#endif //!SKIP_TEMPERATURE_SENSOR_NUMBER #endif //!SKIP_TEMPERATURE_SENSOR_NUMBER
// Update webserver datalayer // Update webserver datalayer
datalayer_extended.bydAtto3.SOC_method = SOC_method; if (datalayer_bydatto) {
datalayer_extended.bydAtto3.SOC_estimated = datalayer.battery.status.real_soc; datalayer_bydatto->SOC_method = SOC_method;
datalayer_bydatto->SOC_estimated = datalayer_battery->status.real_soc;
//Once we implement switching logic, remember to change from where the estimated is taken //Once we implement switching logic, remember to change from where the estimated is taken
datalayer_extended.bydAtto3.SOC_highprec = battery_highprecision_SOC; datalayer_bydatto->SOC_highprec = battery_highprecision_SOC;
datalayer_extended.bydAtto3.SOC_polled = BMS_SOC; datalayer_bydatto->SOC_polled = BMS_SOC;
datalayer_extended.bydAtto3.voltage_periodic = battery_voltage; datalayer_bydatto->voltage_periodic = battery_voltage;
datalayer_extended.bydAtto3.voltage_polled = BMS_voltage; datalayer_bydatto->voltage_polled = BMS_voltage;
datalayer_extended.bydAtto3.battery_temperatures[0] = battery_daughterboard_temperatures[0]; datalayer_bydatto->battery_temperatures[0] = battery_daughterboard_temperatures[0];
datalayer_extended.bydAtto3.battery_temperatures[1] = battery_daughterboard_temperatures[1]; datalayer_bydatto->battery_temperatures[1] = battery_daughterboard_temperatures[1];
datalayer_extended.bydAtto3.battery_temperatures[2] = battery_daughterboard_temperatures[2]; datalayer_bydatto->battery_temperatures[2] = battery_daughterboard_temperatures[2];
datalayer_extended.bydAtto3.battery_temperatures[3] = battery_daughterboard_temperatures[3]; datalayer_bydatto->battery_temperatures[3] = battery_daughterboard_temperatures[3];
datalayer_extended.bydAtto3.battery_temperatures[4] = battery_daughterboard_temperatures[4]; datalayer_bydatto->battery_temperatures[4] = battery_daughterboard_temperatures[4];
datalayer_extended.bydAtto3.battery_temperatures[5] = battery_daughterboard_temperatures[5]; datalayer_bydatto->battery_temperatures[5] = battery_daughterboard_temperatures[5];
datalayer_extended.bydAtto3.battery_temperatures[6] = battery_daughterboard_temperatures[6]; datalayer_bydatto->battery_temperatures[6] = battery_daughterboard_temperatures[6];
datalayer_extended.bydAtto3.battery_temperatures[7] = battery_daughterboard_temperatures[7]; datalayer_bydatto->battery_temperatures[7] = battery_daughterboard_temperatures[7];
datalayer_extended.bydAtto3.battery_temperatures[8] = battery_daughterboard_temperatures[8]; datalayer_bydatto->battery_temperatures[8] = battery_daughterboard_temperatures[8];
datalayer_extended.bydAtto3.battery_temperatures[9] = battery_daughterboard_temperatures[9]; datalayer_bydatto->battery_temperatures[9] = battery_daughterboard_temperatures[9];
datalayer_extended.bydAtto3.unknown0 = BMS_unknown0; datalayer_bydatto->unknown0 = BMS_unknown0;
datalayer_extended.bydAtto3.unknown1 = BMS_unknown1; datalayer_bydatto->unknown1 = BMS_unknown1;
datalayer_extended.bydAtto3.chargePower = BMS_allowed_charge_power; datalayer_bydatto->chargePower = BMS_allowed_charge_power;
datalayer_extended.bydAtto3.unknown3 = BMS_unknown3; datalayer_bydatto->unknown3 = BMS_unknown3;
datalayer_extended.bydAtto3.unknown4 = BMS_unknown4; datalayer_bydatto->unknown4 = BMS_unknown4;
datalayer_extended.bydAtto3.unknown5 = BMS_unknown5; datalayer_bydatto->unknown5 = BMS_unknown5;
datalayer_extended.bydAtto3.unknown6 = BMS_unknown6; datalayer_bydatto->unknown6 = BMS_unknown6;
datalayer_extended.bydAtto3.unknown7 = BMS_unknown7; datalayer_bydatto->unknown7 = BMS_unknown7;
datalayer_extended.bydAtto3.unknown8 = BMS_unknown8; datalayer_bydatto->unknown8 = BMS_unknown8;
datalayer_extended.bydAtto3.unknown9 = BMS_unknown9; datalayer_bydatto->unknown9 = BMS_unknown9;
datalayer_extended.bydAtto3.unknown10 = BMS_unknown10; datalayer_bydatto->unknown10 = BMS_unknown10;
datalayer_extended.bydAtto3.unknown11 = BMS_unknown11; datalayer_bydatto->unknown11 = BMS_unknown11;
datalayer_extended.bydAtto3.unknown12 = BMS_unknown12; datalayer_bydatto->unknown12 = BMS_unknown12;
datalayer_extended.bydAtto3.unknown13 = BMS_unknown13; datalayer_bydatto->unknown13 = BMS_unknown13;
// Update requests from webserver datalayer // Update requests from webserver datalayer
if (datalayer_extended.bydAtto3.UserRequestCrashReset && stateMachineClearCrash == NOT_RUNNING) { if (datalayer_bydatto->UserRequestCrashReset && stateMachineClearCrash == NOT_RUNNING) {
stateMachineClearCrash = STARTED; stateMachineClearCrash = STARTED;
datalayer_extended.bydAtto3.UserRequestCrashReset = false; datalayer_bydatto->UserRequestCrashReset = false;
}
} }
} }
void handle_incoming_can_frame_battery(CAN_frame rx_frame) { void BydAttoBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
switch (rx_frame.ID) { switch (rx_frame.ID) {
case 0x244: case 0x244:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break; break;
case 0x245: case 0x245:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
if (rx_frame.data.u8[0] == 0x01) { if (rx_frame.data.u8[0] == 0x01) {
battery_temperature_ambient = (rx_frame.data.u8[4] - 40); battery_temperature_ambient = (rx_frame.data.u8[4] - 40);
} }
break; break;
case 0x286: case 0x286:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break; break;
case 0x334: case 0x334:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break; break;
case 0x338: case 0x338:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break; break;
case 0x344: case 0x344:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break; break;
case 0x345: case 0x345:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break; break;
case 0x347: case 0x347:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break; break;
case 0x34A: case 0x34A:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break; break;
case 0x35E: case 0x35E:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break; break;
case 0x360: case 0x360:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break; break;
case 0x36C: case 0x36C:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break; break;
case 0x438: case 0x438:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break; break;
case 0x43A: case 0x43A:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break; break;
case 0x43B: case 0x43B:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break; break;
case 0x43C: case 0x43C:
if (rx_frame.data.u8[0] == 0x00) { if (rx_frame.data.u8[0] == 0x00) {
@ -456,7 +362,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
} }
break; break;
case 0x43D: case 0x43D:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery_frame_index = rx_frame.data.u8[0]; battery_frame_index = rx_frame.data.u8[0];
if (battery_frame_index < (CELLCOUNT_EXTENDED / 3)) { if (battery_frame_index < (CELLCOUNT_EXTENDED / 3)) {
@ -468,31 +374,31 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
} }
break; break;
case 0x444: case 0x444:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery_voltage = ((rx_frame.data.u8[1] & 0x0F) << 8) | rx_frame.data.u8[0]; battery_voltage = ((rx_frame.data.u8[1] & 0x0F) << 8) | rx_frame.data.u8[0];
//battery_temperature_something = rx_frame.data.u8[7] - 40; resides in frame 7 //battery_temperature_something = rx_frame.data.u8[7] - 40; resides in frame 7
break; break;
case 0x445: case 0x445:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break; break;
case 0x446: case 0x446:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break; break;
case 0x447: case 0x447:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery_highprecision_SOC = ((rx_frame.data.u8[5] & 0x0F) << 8) | rx_frame.data.u8[4]; // 03 E0 = 992 = 99.2% battery_highprecision_SOC = ((rx_frame.data.u8[5] & 0x0F) << 8) | rx_frame.data.u8[4]; // 03 E0 = 992 = 99.2%
battery_lowest_temperature = (rx_frame.data.u8[1] - 40); //Best guess for now battery_lowest_temperature = (rx_frame.data.u8[1] - 40); //Best guess for now
battery_highest_temperature = (rx_frame.data.u8[3] - 40); //Best guess for now battery_highest_temperature = (rx_frame.data.u8[3] - 40); //Best guess for now
break; break;
case 0x47B: case 0x47B:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break; break;
case 0x524: case 0x524:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break; break;
case 0x7EF: //OBD2 PID reply from battery case 0x7EF: //OBD2 PID reply from battery
if (rx_frame.data.u8[0] == 0x10) { if (rx_frame.data.u8[0] == 0x10) {
transmit_can_frame(&ATTO_3_7E7_ACK, can_config.battery); //Send next line request transmit_can_frame(&ATTO_3_7E7_ACK, can_interface); //Send next line request
} }
pid_reply = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]); pid_reply = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]);
switch (pid_reply) { switch (pid_reply) {
@ -572,16 +478,19 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
break; break;
} }
} }
void transmit_can_battery(unsigned long currentMillis) {
void BydAttoBattery::transmit_can(unsigned long currentMillis) {
//Send 50ms message //Send 50ms message
if (currentMillis - previousMillis50 >= INTERVAL_50_MS) { if (currentMillis - previousMillis50 >= INTERVAL_50_MS) {
previousMillis50 = currentMillis; previousMillis50 = currentMillis;
// Set close contactors to allowed (Useful for crashed packs, started via contactor control thru GPIO) // Set close contactors to allowed (Useful for crashed packs, started via contactor control thru GPIO)
if (datalayer.battery.status.bms_status == ACTIVE) { if (allows_contactor_closing) {
datalayer.system.status.battery_allows_contactor_closing = true; if (datalayer_battery->status.bms_status == ACTIVE) {
*allows_contactor_closing = true;
} else { // Fault state, open contactors! } else { // Fault state, open contactors!
datalayer.system.status.battery_allows_contactor_closing = false; *allows_contactor_closing = false;
}
} }
counter_50ms++; counter_50ms++;
@ -606,10 +515,7 @@ void transmit_can_battery(unsigned long currentMillis) {
ATTO_3_12D.data.u8[6] = (0x0F | (frame6_counter << 4)); ATTO_3_12D.data.u8[6] = (0x0F | (frame6_counter << 4));
ATTO_3_12D.data.u8[7] = (0x09 | (frame7_counter << 4)); ATTO_3_12D.data.u8[7] = (0x09 | (frame7_counter << 4));
transmit_can_frame(&ATTO_3_12D, can_config.battery); transmit_can_frame(&ATTO_3_12D, can_interface);
#ifdef DOUBLE_BATTERY
transmit_can_frame(&ATTO_3_12D, can_config.battery_double);
#endif //DOUBLE_BATTERY
} }
// Send 100ms CAN Message // Send 100ms CAN Message
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) { if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
@ -626,24 +532,21 @@ void transmit_can_battery(unsigned long currentMillis) {
ATTO_3_441.data.u8[7] = 0xF5; ATTO_3_441.data.u8[7] = 0xF5;
} }
transmit_can_frame(&ATTO_3_441, can_config.battery); transmit_can_frame(&ATTO_3_441, can_interface);
#ifdef DOUBLE_BATTERY
transmit_can_frame(&ATTO_3_441, can_config.battery_double);
#endif //DOUBLE_BATTERY
switch (stateMachineClearCrash) { switch (stateMachineClearCrash) {
case STARTED: case STARTED:
ATTO_3_7E7_CLEAR_CRASH.data = {0x02, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}; ATTO_3_7E7_CLEAR_CRASH.data = {0x02, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00};
transmit_can_frame(&ATTO_3_7E7_CLEAR_CRASH, can_config.battery); transmit_can_frame(&ATTO_3_7E7_CLEAR_CRASH, can_interface);
stateMachineClearCrash = RUNNING_STEP_1; stateMachineClearCrash = RUNNING_STEP_1;
break; break;
case RUNNING_STEP_1: case RUNNING_STEP_1:
ATTO_3_7E7_CLEAR_CRASH.data = {0x04, 0x14, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00}; ATTO_3_7E7_CLEAR_CRASH.data = {0x04, 0x14, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00};
transmit_can_frame(&ATTO_3_7E7_CLEAR_CRASH, can_config.battery); transmit_can_frame(&ATTO_3_7E7_CLEAR_CRASH, can_interface);
stateMachineClearCrash = RUNNING_STEP_2; stateMachineClearCrash = RUNNING_STEP_2;
break; break;
case RUNNING_STEP_2: case RUNNING_STEP_2:
ATTO_3_7E7_CLEAR_CRASH.data = {0x03, 0x19, 0x02, 0x09, 0x00, 0x00, 0x00, 0x00}; ATTO_3_7E7_CLEAR_CRASH.data = {0x03, 0x19, 0x02, 0x09, 0x00, 0x00, 0x00, 0x00};
transmit_can_frame(&ATTO_3_7E7_CLEAR_CRASH, can_config.battery); transmit_can_frame(&ATTO_3_7E7_CLEAR_CRASH, can_interface);
stateMachineClearCrash = NOT_RUNNING; stateMachineClearCrash = NOT_RUNNING;
break; break;
case NOT_RUNNING: case NOT_RUNNING:
@ -773,210 +676,21 @@ void transmit_can_battery(unsigned long currentMillis) {
} }
if (stateMachineClearCrash == NOT_RUNNING) { //Don't poll battery for data if clear crash running if (stateMachineClearCrash == NOT_RUNNING) { //Don't poll battery for data if clear crash running
transmit_can_frame(&ATTO_3_7E7_POLL, can_config.battery); transmit_can_frame(&ATTO_3_7E7_POLL, can_interface);
#ifdef DOUBLE_BATTERY
transmit_can_frame(&ATTO_3_7E7_POLL, can_config.battery_double);
#endif //DOUBLE_BATTERY
} }
} }
} }
void setup_battery(void) { // Performs one time setup at startup void BydAttoBattery::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "BYD Atto 3", 63); strncpy(datalayer.system.info.battery_protocol, "BYD Atto 3", 63);
datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.chemistry = battery_chemistry_enum::LFP; datalayer_battery->info.number_of_cells = CELLCOUNT_STANDARD;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_EXTENDED_DV; //Startup in extremes datalayer_battery->info.chemistry = battery_chemistry_enum::LFP;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_STANDARD_DV; //We later determine range datalayer_battery->info.max_design_voltage_dV = MAX_PACK_VOLTAGE_EXTENDED_DV; //Startup in extremes
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; datalayer_battery->info.min_design_voltage_dV = MIN_PACK_VOLTAGE_STANDARD_DV; //We later determine range
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV; datalayer_battery->info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV; datalayer_battery->info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
#ifdef DOUBLE_BATTERY datalayer_battery->info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
datalayer.battery2.info.number_of_cells = CELLCOUNT_STANDARD;
datalayer.battery2.info.chemistry = battery_chemistry_enum::LFP;
datalayer.battery2.info.max_design_voltage_dV = datalayer.battery.info.max_design_voltage_dV;
datalayer.battery2.info.min_design_voltage_dV = datalayer.battery.info.min_design_voltage_dV;
datalayer.battery2.info.max_cell_voltage_mV = datalayer.battery.info.max_cell_voltage_mV;
datalayer.battery2.info.min_cell_voltage_mV = datalayer.battery.info.min_cell_voltage_mV;
datalayer.battery2.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
#endif //DOUBLE_BATTERY
} }
#ifdef DOUBLE_BATTERY
void update_values_battery2() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
if (BMS2_voltage > 0) {
datalayer.battery2.status.voltage_dV = BMS2_voltage * 10;
}
// We instead estimate the SOC% based on the battery2 voltage
// This is a very bad solution, and as soon as an usable SOC% value has been found on CAN, we should switch to that!
if (battery_type == EXTENDED_RANGE) {
datalayer.battery2.status.real_soc = estimateSOCextended(datalayer.battery2.status.voltage_dV);
}
if (battery_type == STANDARD_RANGE) {
datalayer.battery2.status.real_soc = estimateSOCstandard(datalayer.battery2.status.voltage_dV);
}
datalayer.battery2.status.current_dA = -BMS2_current;
datalayer.battery2.status.remaining_capacity_Wh = static_cast<uint32_t>(
(static_cast<double>(datalayer.battery2.status.real_soc) / 10000) * datalayer.battery2.info.total_capacity_Wh);
datalayer.battery2.status.max_discharge_power_W = 10000; //TODO: Map from CAN later on
datalayer.battery2.status.max_charge_power_W = 10000; //TODO: Map from CAN later on
datalayer.battery2.status.cell_max_voltage_mV = BMS2_highest_cell_voltage_mV;
datalayer.battery2.status.cell_min_voltage_mV = BMS2_lowest_cell_voltage_mV;
datalayer.battery2.status.temperature_min_dC = BMS2_lowest_cell_temperature * 10; // Add decimals
datalayer.battery2.status.temperature_max_dC = BMS2_highest_cell_temperature * 10;
//Map all cell voltages to the global array
memcpy(datalayer.battery2.status.cell_voltages_mV, battery2_cellvoltages, CELLCOUNT_EXTENDED * sizeof(uint16_t));
datalayer.battery2.info.total_capacity_Wh = datalayer.battery.info.total_capacity_Wh;
datalayer.battery2.info.number_of_cells = datalayer.battery.info.number_of_cells;
datalayer.battery2.info.max_design_voltage_dV = datalayer.battery.info.max_design_voltage_dV;
datalayer.battery2.info.min_design_voltage_dV = datalayer.battery.info.min_design_voltage_dV;
}
void handle_incoming_can_frame_battery2(CAN_frame rx_frame) {
switch (rx_frame.ID) {
case 0x244:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x245:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
if (rx_frame.data.u8[0] == 0x01) {
battery2_temperature_ambient = (rx_frame.data.u8[4] - 40);
}
break;
case 0x286:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x334:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x338:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x344:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x345:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x347:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x34A:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x35E:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x360:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x36C:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x438:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x43A:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x43B:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x43C: // Daughterboard temperatures reside in this CAN message
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
if (rx_frame.data.u8[0] == 0x00) {
battery2_daughterboard_temperatures[0] = (rx_frame.data.u8[1] - 40);
battery2_daughterboard_temperatures[1] = (rx_frame.data.u8[2] - 40);
battery2_daughterboard_temperatures[2] = (rx_frame.data.u8[3] - 40);
battery2_daughterboard_temperatures[3] = (rx_frame.data.u8[4] - 40);
battery2_daughterboard_temperatures[4] = (rx_frame.data.u8[5] - 40);
battery2_daughterboard_temperatures[5] = (rx_frame.data.u8[6] - 40);
}
if (rx_frame.data.u8[0] == 0x01) {
battery2_daughterboard_temperatures[6] = (rx_frame.data.u8[1] - 40);
battery2_daughterboard_temperatures[7] = (rx_frame.data.u8[2] - 40);
battery2_daughterboard_temperatures[8] = (rx_frame.data.u8[3] - 40);
battery2_daughterboard_temperatures[9] = (rx_frame.data.u8[4] - 40);
}
break;
case 0x43D:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery2_frame_index = rx_frame.data.u8[0];
if (battery2_frame_index < (CELLCOUNT_EXTENDED / 3)) {
uint8_t base2_index = battery2_frame_index * 3;
for (uint8_t i = 0; i < 3; i++) {
battery2_cellvoltages[base2_index + i] =
(((rx_frame.data.u8[2 * (i + 1)] & 0x0F) << 8) | rx_frame.data.u8[2 * i + 1]);
}
}
break;
case 0x444:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x445:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x446:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x447:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery2_highprecision_SOC = ((rx_frame.data.u8[5] & 0x0F) << 8) | rx_frame.data.u8[4];
battery2_lowest_temperature = (rx_frame.data.u8[1] - 40);
battery2_highest_temperature = (rx_frame.data.u8[3] - 40);
break;
case 0x47B:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x524:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x7EF: //OBD2 PID reply from battery2
switch (rx_frame.data.u8[3]) {
case POLL_FOR_BATTERY_SOC:
BMS2_SOC = rx_frame.data.u8[4];
break;
case POLL_FOR_BATTERY_VOLTAGE:
BMS2_voltage = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4];
break;
case POLL_FOR_BATTERY_CURRENT:
BMS2_current = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]) - 5000;
break;
case POLL_FOR_LOWEST_TEMP_CELL:
BMS2_lowest_cell_temperature = (rx_frame.data.u8[4] - 40);
break;
case POLL_FOR_HIGHEST_TEMP_CELL:
BMS2_highest_cell_temperature = (rx_frame.data.u8[4] - 40);
break;
case POLL_FOR_BATTERY_PACK_AVG_TEMP:
BMS2_average_cell_temperature = (rx_frame.data.u8[4] - 40);
break;
case POLL_FOR_BATTERY_CELL_MV_MAX:
BMS2_highest_cell_voltage_mV = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4];
break;
case POLL_FOR_BATTERY_CELL_MV_MIN:
BMS2_lowest_cell_voltage_mV = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4];
break;
default: //Unrecognized reply
break;
}
break;
default:
break;
}
}
#endif //DOUBLE_BATTERY
#endif #endif

View file

@ -1,8 +1,12 @@
#ifndef ATTO_3_BATTERY_H #ifndef ATTO_3_BATTERY_H
#define ATTO_3_BATTERY_H #define ATTO_3_BATTERY_H
#include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h"
#include "../include.h" #include "../include.h"
#include "CanBattery.h"
#define USE_ESTIMATED_SOC // If enabled, SOC is estimated from pack voltage. Useful for locked packs. \ #define USE_ESTIMATED_SOC // If enabled, SOC is estimated from pack voltage. Useful for locked packs. \
// Comment out this only if you know your BMS is unlocked and able to send SOC% // Comment out this only if you know your BMS is unlocked and able to send SOC%
#define MAXPOWER_CHARGE_W 10000 #define MAXPOWER_CHARGE_W 10000
@ -14,6 +18,8 @@
/* Do not modify the rows below */ /* Do not modify the rows below */
#define BATTERY_SELECTED #define BATTERY_SELECTED
#define SELECTED_BATTERY_CLASS BydAttoBattery
#define CELLCOUNT_EXTENDED 126 #define CELLCOUNT_EXTENDED 126
#define CELLCOUNT_STANDARD 104 #define CELLCOUNT_STANDARD 104
#define MAX_PACK_VOLTAGE_EXTENDED_DV 4410 //Extended range #define MAX_PACK_VOLTAGE_EXTENDED_DV 4410 //Extended range
@ -24,7 +30,117 @@
#define MAX_CELL_VOLTAGE_MV 3650 //Charging stops if one cell exceeds this value #define MAX_CELL_VOLTAGE_MV 3650 //Charging stops if one cell exceeds this value
#define MIN_CELL_VOLTAGE_MV 2800 //Discharging stops if one cell goes below this value #define MIN_CELL_VOLTAGE_MV 2800 //Discharging stops if one cell goes below this value
void setup_battery(void); class BydAttoBattery : public CanBattery {
void transmit_can_frame(CAN_frame* tx_frame, int interface); public:
// Use this constructor for the second battery.
BydAttoBattery(DATALAYER_BATTERY_TYPE* datalayer_ptr, DATALAYER_INFO_BYDATTO3* extended, int targetCan) {
datalayer_battery = datalayer_ptr;
datalayer_bydatto = extended;
allows_contactor_closing = nullptr;
can_interface = targetCan;
}
// Use the default constructor to create the first or single battery.
BydAttoBattery() {
datalayer_battery = &datalayer.battery;
allows_contactor_closing = &datalayer.system.status.battery_allows_contactor_closing;
can_interface = can_config.battery;
datalayer_bydatto = &datalayer_extended.bydAtto3;
}
virtual void setup(void);
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
virtual void update_values();
virtual void transmit_can(unsigned long currentMillis);
private:
DATALAYER_BATTERY_TYPE* datalayer_battery;
DATALAYER_INFO_BYDATTO3* datalayer_bydatto;
bool* allows_contactor_closing;
int can_interface;
static const int POLL_FOR_BATTERY_SOC = 0x0005;
static const uint8_t NOT_DETERMINED_YET = 0;
static const uint8_t STANDARD_RANGE = 1;
static const uint8_t EXTENDED_RANGE = 2;
static const uint8_t NOT_RUNNING = 0xFF;
static const uint8_t STARTED = 0;
static const uint8_t RUNNING_STEP_1 = 1;
static const uint8_t RUNNING_STEP_2 = 2;
uint8_t battery_type = NOT_DETERMINED_YET;
uint8_t stateMachineClearCrash = NOT_RUNNING;
unsigned long previousMillis50 = 0; // will store last time a 50ms CAN Message was send
unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was send
bool SOC_method = false;
uint8_t counter_50ms = 0;
uint8_t counter_100ms = 0;
uint8_t frame6_counter = 0xB;
uint8_t frame7_counter = 0x5;
uint16_t battery_voltage = 0;
int16_t battery_temperature_ambient = 0;
int16_t battery_daughterboard_temperatures[10];
int16_t battery_lowest_temperature = 0;
int16_t battery_highest_temperature = 0;
int16_t battery_calc_min_temperature = 0;
int16_t battery_calc_max_temperature = 0;
uint16_t battery_highprecision_SOC = 0;
uint16_t BMS_SOC = 0;
uint16_t BMS_voltage = 0;
int16_t BMS_current = 0;
int16_t BMS_lowest_cell_temperature = 0;
int16_t BMS_highest_cell_temperature = 0;
int16_t BMS_average_cell_temperature = 0;
uint16_t BMS_lowest_cell_voltage_mV = 3300;
uint16_t BMS_highest_cell_voltage_mV = 3300;
uint32_t BMS_unknown0 = 0;
uint32_t BMS_unknown1 = 0;
uint16_t BMS_allowed_charge_power = 0;
uint16_t BMS_unknown3 = 0;
uint16_t BMS_unknown4 = 0;
uint16_t BMS_unknown5 = 0;
uint16_t BMS_unknown6 = 0;
uint16_t BMS_unknown7 = 0;
uint16_t BMS_unknown8 = 0;
uint16_t BMS_unknown9 = 0;
uint8_t BMS_unknown10 = 0;
uint8_t BMS_unknown11 = 0;
uint8_t BMS_unknown12 = 0;
uint8_t BMS_unknown13 = 0;
uint8_t battery_frame_index = 0;
uint16_t battery_cellvoltages[CELLCOUNT_EXTENDED] = {0};
uint16_t poll_state = POLL_FOR_BATTERY_SOC;
uint16_t pid_reply = 0;
CAN_frame ATTO_3_12D = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x12D,
.data = {0xA0, 0x28, 0x02, 0xA0, 0x0C, 0x71, 0xCF, 0x49}};
CAN_frame ATTO_3_441 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x441,
.data = {0x98, 0x3A, 0x88, 0x13, 0x07, 0x00, 0xFF, 0x8C}};
CAN_frame ATTO_3_7E7_POLL = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E7, //Poll PID 03 22 00 05 (POLL_FOR_BATTERY_SOC)
.data = {0x03, 0x22, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00}};
CAN_frame ATTO_3_7E7_ACK = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E7, //ACK frame for long PIDs
.data = {0x30, 0x08, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame ATTO_3_7E7_CLEAR_CRASH = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E7,
.data = {0x02, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}};
};
#endif #endif

View file

@ -0,0 +1,12 @@
#ifndef BATTERY_H
#define BATTERY_H
// Abstract base class for next-generation battery implementations.
// Defines the interface to call battery specific functionality.
class Battery {
public:
virtual void setup(void) = 0;
virtual void update_values() = 0;
};
#endif

View file

@ -1,115 +1,12 @@
#include "../include.h" #include "../include.h"
#ifdef CELLPOWER_BMS #ifdef CELLPOWER_BMS
#include "../communication/can/comm_can.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"
#include "CELLPOWER-BMS.h" #include "CELLPOWER-BMS.h"
/* Do not change code below unless you are sure what you are doing */ void CellPowerBms::update_values() {
static unsigned long previousMillis1s = 0; // will store last time a 1s CAN Message was sent
//Actual content messages
// Optional add-on charger module. Might not be needed to send these towards the BMS to keep it happy.
CAN_frame CELLPOWER_18FF50E9 = {.FD = false,
.ext_ID = true,
.DLC = 5,
.ID = 0x18FF50E9,
.data = {0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame CELLPOWER_18FF50E8 = {.FD = false,
.ext_ID = true,
.DLC = 5,
.ID = 0x18FF50E8,
.data = {0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame CELLPOWER_18FF50E7 = {.FD = false,
.ext_ID = true,
.DLC = 5,
.ID = 0x18FF50E7,
.data = {0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame CELLPOWER_18FF50E5 = {.FD = false,
.ext_ID = true,
.DLC = 5,
.ID = 0x18FF50E5,
.data = {0x00, 0x00, 0x00, 0x00, 0x00}};
static bool system_state_discharge = false;
static bool system_state_charge = false;
static bool system_state_cellbalancing = false;
static bool system_state_tricklecharge = false;
static bool system_state_idle = false;
static bool system_state_chargecompleted = false;
static bool system_state_maintenancecharge = false;
static bool IO_state_main_positive_relay = false;
static bool IO_state_main_negative_relay = false;
static bool IO_state_charge_enable = false;
static bool IO_state_precharge_relay = false;
static bool IO_state_discharge_enable = false;
static bool IO_state_IO_6 = false;
static bool IO_state_IO_7 = false;
static bool IO_state_IO_8 = false;
static bool error_Cell_overvoltage = false;
static bool error_Cell_undervoltage = false;
static bool error_Cell_end_of_life_voltage = false;
static bool error_Cell_voltage_misread = false;
static bool error_Cell_over_temperature = false;
static bool error_Cell_under_temperature = false;
static bool error_Cell_unmanaged = false;
static bool error_LMU_over_temperature = false;
static bool error_LMU_under_temperature = false;
static bool error_Temp_sensor_open_circuit = false;
static bool error_Temp_sensor_short_circuit = false;
static bool error_SUB_communication = false;
static bool error_LMU_communication = false;
static bool error_Over_current_IN = false;
static bool error_Over_current_OUT = false;
static bool error_Short_circuit = false;
static bool error_Leak_detected = false;
static bool error_Leak_detection_failed = false;
static bool error_Voltage_difference = false;
static bool error_BMCU_supply_over_voltage = false;
static bool error_BMCU_supply_under_voltage = false;
static bool error_Main_positive_contactor = false;
static bool error_Main_negative_contactor = false;
static bool error_Precharge_contactor = false;
static bool error_Midpack_contactor = false;
static bool error_Precharge_timeout = false;
static bool error_Emergency_connector_override = false;
static bool warning_High_cell_voltage = false;
static bool warning_Low_cell_voltage = false;
static bool warning_High_cell_temperature = false;
static bool warning_Low_cell_temperature = false;
static bool warning_High_LMU_temperature = false;
static bool warning_Low_LMU_temperature = false;
static bool warning_SUB_communication_interfered = false;
static bool warning_LMU_communication_interfered = false;
static bool warning_High_current_IN = false;
static bool warning_High_current_OUT = false;
static bool warning_Pack_resistance_difference = false;
static bool warning_High_pack_resistance = false;
static bool warning_Cell_resistance_difference = false;
static bool warning_High_cell_resistance = false;
static bool warning_High_BMCU_supply_voltage = false;
static bool warning_Low_BMCU_supply_voltage = false;
static bool warning_Low_SOC = false;
static bool warning_Balancing_required_OCV_model = false;
static bool warning_Charger_not_responding = false;
static uint16_t cell_voltage_max_mV = 3700;
static uint16_t cell_voltage_min_mV = 3700;
static int8_t pack_temperature_high_C = 0;
static int8_t pack_temperature_low_C = 0;
static uint16_t battery_pack_voltage_dV = 3700;
static int16_t battery_pack_current_dA = 0;
static uint8_t battery_SOH_percentage = 99;
static uint8_t battery_SOC_percentage = 50;
static uint16_t battery_remaining_dAh = 0;
static uint8_t cell_with_highest_voltage = 0;
static uint8_t cell_with_lowest_voltage = 0;
static uint16_t requested_charge_current_dA = 0;
static uint16_t average_charge_current_dA = 0;
static uint16_t actual_charge_current_dA = 0;
static bool requested_exceeding_average_current = 0;
static bool error_state = false;
void update_values_battery() {
/* Update values from CAN */ /* Update values from CAN */
@ -213,7 +110,8 @@ void update_values_battery() {
//TODO, shall we react on this? //TODO, shall we react on this?
} }
} }
void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
void CellPowerBms::handle_incoming_can_frame(CAN_frame rx_frame) {
switch (rx_frame.ID) { switch (rx_frame.ID) {
case 0x1A4: //PDO1_TX - 200ms case 0x1A4: //PDO1_TX - 200ms
@ -316,7 +214,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
} }
} }
void transmit_can_battery(unsigned long currentMillis) { void CellPowerBms::transmit_can(unsigned long currentMillis) {
// Send 1s CAN Message // Send 1s CAN Message
if (currentMillis - previousMillis1s >= INTERVAL_1_S) { if (currentMillis - previousMillis1s >= INTERVAL_1_S) {
@ -331,7 +229,7 @@ void transmit_can_battery(unsigned long currentMillis) {
} }
} }
void setup_battery(void) { // Performs one time setup at startup void CellPowerBms::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "Cellpower BMS", 63); strncpy(datalayer.system.info.battery_protocol, "Cellpower BMS", 63);
datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.info.battery_protocol[63] = '\0';
datalayer.system.status.battery_allows_contactor_closing = true; datalayer.system.status.battery_allows_contactor_closing = true;

View file

@ -2,18 +2,130 @@
#define CELLPOWER_BMS_H #define CELLPOWER_BMS_H
#include <Arduino.h> #include <Arduino.h>
#include "../include.h" #include "../include.h"
#include "CanBattery.h"
/* Tweak these according to your battery build */ #define BATTERY_SELECTED
#define MAX_PACK_VOLTAGE_DV 5000 //5000 = 500.0V #define SELECTED_BATTERY_CLASS CellPowerBms
#define MIN_PACK_VOLTAGE_DV 1500
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value class CellPowerBms : public CanBattery {
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value public:
virtual void setup(void);
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
virtual void update_values();
virtual void transmit_can(unsigned long currentMillis);
private:
/* Tweak these according to your battery build */
static const int MAX_PACK_VOLTAGE_DV = 5000; //5000 = 500.0V
static const int MIN_PACK_VOLTAGE_DV = 1500;
static const int MAX_CELL_VOLTAGE_MV = 4250; //Battery is put into emergency stop if one cell goes over this value
static const int MIN_CELL_VOLTAGE_MV = 2700; //Battery is put into emergency stop if one cell goes below this value
unsigned long previousMillis1s = 0; // will store last time a 1s CAN Message was sent
//Actual content messages
// Optional add-on charger module. Might not be needed to send these towards the BMS to keep it happy.
CAN_frame CELLPOWER_18FF50E9 = {.FD = false,
.ext_ID = true,
.DLC = 5,
.ID = 0x18FF50E9,
.data = {0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame CELLPOWER_18FF50E8 = {.FD = false,
.ext_ID = true,
.DLC = 5,
.ID = 0x18FF50E8,
.data = {0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame CELLPOWER_18FF50E7 = {.FD = false,
.ext_ID = true,
.DLC = 5,
.ID = 0x18FF50E7,
.data = {0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame CELLPOWER_18FF50E5 = {.FD = false,
.ext_ID = true,
.DLC = 5,
.ID = 0x18FF50E5,
.data = {0x00, 0x00, 0x00, 0x00, 0x00}};
bool system_state_discharge = false;
bool system_state_charge = false;
bool system_state_cellbalancing = false;
bool system_state_tricklecharge = false;
bool system_state_idle = false;
bool system_state_chargecompleted = false;
bool system_state_maintenancecharge = false;
bool IO_state_main_positive_relay = false;
bool IO_state_main_negative_relay = false;
bool IO_state_charge_enable = false;
bool IO_state_precharge_relay = false;
bool IO_state_discharge_enable = false;
bool IO_state_IO_6 = false;
bool IO_state_IO_7 = false;
bool IO_state_IO_8 = false;
bool error_Cell_overvoltage = false;
bool error_Cell_undervoltage = false;
bool error_Cell_end_of_life_voltage = false;
bool error_Cell_voltage_misread = false;
bool error_Cell_over_temperature = false;
bool error_Cell_under_temperature = false;
bool error_Cell_unmanaged = false;
bool error_LMU_over_temperature = false;
bool error_LMU_under_temperature = false;
bool error_Temp_sensor_open_circuit = false;
bool error_Temp_sensor_short_circuit = false;
bool error_SUB_communication = false;
bool error_LMU_communication = false;
bool error_Over_current_IN = false;
bool error_Over_current_OUT = false;
bool error_Short_circuit = false;
bool error_Leak_detected = false;
bool error_Leak_detection_failed = false;
bool error_Voltage_difference = false;
bool error_BMCU_supply_over_voltage = false;
bool error_BMCU_supply_under_voltage = false;
bool error_Main_positive_contactor = false;
bool error_Main_negative_contactor = false;
bool error_Precharge_contactor = false;
bool error_Midpack_contactor = false;
bool error_Precharge_timeout = false;
bool error_Emergency_connector_override = false;
bool warning_High_cell_voltage = false;
bool warning_Low_cell_voltage = false;
bool warning_High_cell_temperature = false;
bool warning_Low_cell_temperature = false;
bool warning_High_LMU_temperature = false;
bool warning_Low_LMU_temperature = false;
bool warning_SUB_communication_interfered = false;
bool warning_LMU_communication_interfered = false;
bool warning_High_current_IN = false;
bool warning_High_current_OUT = false;
bool warning_Pack_resistance_difference = false;
bool warning_High_pack_resistance = false;
bool warning_Cell_resistance_difference = false;
bool warning_High_cell_resistance = false;
bool warning_High_BMCU_supply_voltage = false;
bool warning_Low_BMCU_supply_voltage = false;
bool warning_Low_SOC = false;
bool warning_Balancing_required_OCV_model = false;
bool warning_Charger_not_responding = false;
uint16_t cell_voltage_max_mV = 3700;
uint16_t cell_voltage_min_mV = 3700;
int8_t pack_temperature_high_C = 0;
int8_t pack_temperature_low_C = 0;
uint16_t battery_pack_voltage_dV = 3700;
int16_t battery_pack_current_dA = 0;
uint8_t battery_SOH_percentage = 99;
uint8_t battery_SOC_percentage = 50;
uint16_t battery_remaining_dAh = 0;
uint8_t cell_with_highest_voltage = 0;
uint8_t cell_with_lowest_voltage = 0;
uint16_t requested_charge_current_dA = 0;
uint16_t average_charge_current_dA = 0;
uint16_t actual_charge_current_dA = 0;
bool requested_exceeding_average_current = 0;
bool error_state = false;
};
/* Do not modify any rows below*/ /* Do not modify any rows below*/
#define BATTERY_SELECTED
#define NATIVECAN_250KBPS #define NATIVECAN_250KBPS
void setup_battery(void);
void transmit_can_frame(CAN_frame* tx_frame, int interface);
#endif #endif

View file

@ -1,233 +0,0 @@
#ifndef CHADEMO_BATTERY_TYPES_H
#define CHADEMO_BATTERY_TYPES_H
#define MAX_EVSE_POWER_CHARGING 3300
#define MAX_EVSE_OUTPUT_VOLTAGE 410
#define MAX_EVSE_OUTPUT_CURRENT 11
enum CHADEMO_STATE {
CHADEMO_FAULT,
CHADEMO_STOP,
CHADEMO_IDLE,
CHADEMO_CONNECTED,
CHADEMO_INIT, // intermediate state indicating CAN from Vehicle not yet received after connection
CHADEMO_NEGOTIATE,
CHADEMO_EV_ALLOWED,
CHADEMO_EVSE_PREPARE,
CHADEMO_EVSE_START,
CHADEMO_EVSE_CONTACTORS_ENABLED,
CHADEMO_POWERFLOW,
};
enum Mode { CHADEMO_CHARGE, CHADEMO_DISCHARGE, CHADEMO_BIDIRECTIONAL };
/* Charge/discharge sequence, indicating applicable V2H guideline
* If sequence number is not agreed upon via H201/H209 between EVSE and Vehicle,
* V2H 1.1 is assumed, which..is somehow between 0x0 and 0x1 ? TODO: better understanding here
* Use CHADEMO_seq to decide whether emitting 209 is necessary
* 0x0 1.0 and earlier
* 0x1 2.0 appendix A
* 0x2 2.0 appendix B
* TODO: is this influenced by x109->CHADEMO_protocol_number, or x102->ControlProtocolNumberEV ??
* Unused for now.
uint8_t CHADEMO_seq = 0x0;
*/
/*----------- CHARGING SUPPORT V2X --------------------------------------------------------------*/
/* ---------- VEHICLE Data structures */
//H100 - Vehicle - Minimum charging expectations
//TODO decide whether default values for vehicle-origin frames is even appropriate
struct x100_Vehicle_Charging_Limits {
uint8_t MinimumChargeCurrent = 0;
uint16_t MinimumBatteryVoltage = 300;
uint16_t MaximumBatteryVoltage = 402;
uint8_t ConstantOfChargingRateIndication = 0;
};
//H101 - Vehicle - Maximum charging expectations
struct x101_Vehicle_Charging_Estimate {
uint8_t MaxChargingTime10sBit = 0;
uint8_t MaxChargingTime1minBit = 0;
uint8_t EstimatedChargingTime = 0;
uint16_t RatedBatteryCapacity = 0;
};
//H102 - Vehicle - Charging targets and Status
// peer to x109 from EVSE
// termination triggers in both
// TODO see also Table A.26—Charge control termination command patterns
struct x102_Vehicle_Charging_Session { //Frame byte
uint8_t ControlProtocolNumberEV = 0; // 0
uint16_t TargetBatteryVoltage = 0; // 1-2
uint8_t ChargingCurrentRequest = 0; // 3 Note: per spec, units for this changed from kWh --> %
union {
uint8_t faults;
struct {
bool unused_3 : 1;
bool unused_2 : 1;
bool unused_1 : 1;
bool FaultBatteryVoltageDeviation : 1; // 4
bool FaultHighBatteryTemperature : 1; // 3
bool FaultBatteryCurrentDeviation : 1; // 2
bool FaultBatteryUnderVoltage : 1; // 1
bool FaultBatteryOverVoltage : 1; // 0
} fault;
} f;
union {
uint8_t packed;
struct {
bool StatusVehicleDischargeCompatible : 1; //5.7
bool unused_2 : 1; //5.6
bool unused_1 : 1; //5.5
bool StatusNormalStopRequest : 1; //5.4
bool StatusVehicle : 1; //5.3
bool StatusChargingError : 1; //5.2
bool StatusVehicleShifterPosition : 1; //5.1
bool StatusVehicleChargingEnabled : 1; //5.0 - bit zero is TODO. Vehicle charging enabled ==1 *AND* charge
// permission signal k needs to be active for charging to be
// permitted -- TODO document bits per byte for these flags
// and update variables to be more appropriate
} status;
} s;
uint8_t StateOfCharge = 0; //6 state of charge?
};
/* ---------- CHARGING: EVSE Data structures */
struct x108_EVSE_Capabilities { // Frame byte
bool contactor_weld_detection = 1; // 0
uint16_t available_output_voltage = MAX_EVSE_OUTPUT_VOLTAGE; // 1,2
uint8_t available_output_current = MAX_EVSE_OUTPUT_CURRENT; // 3
uint16_t threshold_voltage = 297; // 4,5 voltage that EVSE will stop if car fails to
// perhaps vehicle minus 3%, hardcoded initially to 96*2.95
// 6,7 = unused
};
/* Does double duty for charging and discharging */
struct x109_EVSE_Status { // Frame byte
uint8_t CHADEMO_protocol_number = 0x02; // 0
uint16_t setpoint_HV_VDC =
0; // 1,2 NOTE: charger_setpoint_HV_VDC elsewhere is a float. THIS is protocol-defined as an int. cast float->int and lose some precision for protocol adherence
uint8_t setpoint_HV_IDC = 0; // 3
//
bool discharge_compatible = true; // 4, bit 0. bits
// 4, bit 7-6 (?) unused. spec typo? maybe 1-7 unused
union {
uint8_t packed;
struct {
bool EVSE_status : 1; // 5, bit 0
bool EVSE_error : 1; // 5, bit 1
bool connector_locked : 1; // 5, bit 2 //NOTE: treated as connector_lock during discharge, but
// seen as 'energizing' during charging mode
bool battery_incompatible : 1; // 5, bit 3
bool ChgDischError : 1; // 5, bit 4
bool ChgDischStopControl : 1; // 5, bit 5 - set to false for initialization to indicate 'preparing to charge'
// set to false when ready to charge/discharge
} status;
} s;
// Either, or; not both.
// seconds field set to 0xFF by default
// indicating only the minutes field is used instead
// BOTH observed initially set to 0xFF in logs, so use
// that as the initialzed value
uint8_t remaining_time_10s = 0xFF; // 6
uint8_t remaining_time_1m = 0xFF; // 7
};
/*----------- DISCHARGING SUPPORT V2X --------------------------------------------------------------*/
/* ---------- VEHICLE Data structures */
//H200 - Vehicle - Discharge limits
struct x200_Vehicle_Discharge_Limits {
uint8_t MaximumDischargeCurrent = 0xFF;
uint16_t MinimumDischargeVoltage = 0;
uint16_t MinimumBatteryDischargeLevel = 0;
uint16_t MaxRemainingCapacityForCharging = 0;
};
/* TODO When charge/discharge sequence control number (ID201/209) is not received, the vehicle or the EVSE
should determine that the other is the EVSE or the vehicle of the model before the V2H guideline 1.1. */
//H201 - Vehicle - Estimated capacity available
// Intended primarily for display purposes.
// Peer to H209
// NOTE: in available CAN logs from a Leaf, 209 is sent with no 201 reply, so < 1.1 must be the inferred version
struct x201_Vehicle_Discharge_Estimate {
uint8_t V2HchargeDischargeSequenceNum = 0;
uint16_t ApproxDischargeCompletionTime = 0;
uint16_t AvailableVehicleEnergy = 0;
};
/* ---------- EVSE Data structures */
struct x208_EVSE_Discharge_Capability { // Frame byte
uint8_t present_discharge_current = 0xFF; // 0
uint16_t available_input_voltage = 500; // 1,2 -- poorly named as both 'available' and minimum input voltage
uint16_t available_input_current = 250; // 3 -- poorly named as both 'available' and maximum input current
// spec idiosyncracy in naming/description
// 4,5 = unused
uint16_t lower_threshold_voltage = 0; // 6,7
};
// H209 - EVSE - Estimated Discharge Duration
// peer to Vehicle's 201 event (Note: 209 seen
// in CAN logs even when 201 is not)
struct x209_EVSE_Discharge_Estimate { // Frame byte
uint8_t sequence_control_number = 0x2; // 0
uint16_t remaining_discharge_time = 0x0000; // 0x0000 == unused
};
/*----------- DYNAMIC CONTROL SUPPORT --------------------------------------------------------------*/
/* ---------- VEHICLE Data structures */
struct x110_Vehicle_Dynamic_Control { //Frame byte
union {
uint8_t packed;
struct {
bool PermissionResetMaxChgTime : 1; // bit 5 or 6? is this only x118 not x110?
bool unused_3 : 1;
bool unused_2 : 1;
bool unused_1 : 1;
bool HighVoltageControlStatus : 1; // bit 2 = High voltage control support
bool HighCurrentControlStatus : 1; // bit 1 = High current control support
// rate of change is -20A/s to 20A/s relative to 102.3
bool DynamicControlStatus : 1; // bit 0 = Dynamic Control support
} status;
} u;
};
/* ---------- EVSE Data structures */
// TODO 118
//H118
//see also table a.59 page 104 IEEE
struct x118_EVSE_Dynamic_Control { // Frame byte
union {
uint8_t packed;
struct {
bool PermissionResetMaxChgTime : 1; // bit 5 or 6?
bool unused_3 : 1;
bool unused_2 : 1;
bool unused_1 : 1;
bool HighVoltageControlStatus : 1; // bit 2 = High voltage control support
bool HighCurrentControlStatus : 1; // bit 1 = High current control support
// rate of change is -20A/s to 20A/s relative to 102.3
bool DynamicControlStatus : 1; // bit 0 = Dynamic Control support
} status;
} u;
};
/*----------- MANUFACTURER ID SUPPORT --------------------------------------------------------------*/
/* ---------- VEHICLE Data structures */
//H700 - Vehicle - Manufacturer identification
//Peer to H708
//Used to adapt to manufacturer-prescribed optional specification
struct x700_Vehicle_Vendor_ID {
uint8_t AutomakerCode = 0; // 0 = set to 0x0 to indicate incompatibility. Best as a starting place
uint8_t OptionalContent = 0; // 1-7, variable per vendor spec
};
void handle_chademo_sequence();
#endif

View file

@ -2,7 +2,6 @@
#ifdef CHADEMO_BATTERY #ifdef CHADEMO_BATTERY
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "../devboard/utils/events.h" #include "../devboard/utils/events.h"
#include "CHADEMO-BATTERY-INTERNAL.h"
#include "CHADEMO-BATTERY.h" #include "CHADEMO-BATTERY.h"
#include "CHADEMO-SHUNTS.h" #include "CHADEMO-SHUNTS.h"
@ -14,101 +13,8 @@
#define CAN_STILL_ALIVE 75 #define CAN_STILL_ALIVE 75
//#define CH_CAN_DEBUG //#define CH_CAN_DEBUG
static unsigned long setupMillis = 0;
static unsigned long handlerBeforeMillis = 0;
static unsigned long handlerAfterMillis = 0;
/* Do not change code below unless you are sure what you are doing */
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
static unsigned long previousMillis5000 =
0; // will store last time a 5s threshold was reached for display during debug
bool plug_inserted = false;
bool vehicle_can_initialized = false;
bool vehicle_can_received = false;
bool vehicle_permission = false;
bool evse_permission = false;
bool precharge_low = false;
bool positive_high = false;
bool contactors_ready = false;
uint8_t framecount = 0;
uint8_t max_discharge_current = 0; //TODO not sure on this one, but really influenced by inverter capability
bool high_current_control_enabled = false; // set to true when high current control is operating
// if true, values from 110.1 and 110.2 should be used instead of 102.3
// and 118 should be used for evse responses
// permissible rate of change is -20A/s to 20A/s relative to 102.3
Mode EVSE_mode = CHADEMO_DISCHARGE;
CHADEMO_STATE CHADEMO_Status = CHADEMO_IDLE;
/* Charge/discharge sequence, indicating applicable V2H guideline
* If sequence number is not agreed upon via H201/H209 between EVSE and Vehicle,
* V2H 1.1 is assumed
* Use CHADEMO_seq to decide whether emitting 209 is necessary
* 0x0 1.0 and earlier
* 0x1 2.0 appendix A
* 0x2 2.0 appendix B
* Unused for now.
uint8_t CHADEMO_seq = 0x0;
*/
bool x201_received = false;
bool x209_sent = false;
struct x100_Vehicle_Charging_Limits x100_chg_lim = {};
struct x101_Vehicle_Charging_Estimate x101_chg_est = {};
struct x102_Vehicle_Charging_Session x102_chg_session = {};
struct x110_Vehicle_Dynamic_Control x110_vehicle_dyn = {};
struct x200_Vehicle_Discharge_Limits x200_discharge_limits = {};
struct x201_Vehicle_Discharge_Estimate x201_discharge_estimate = {};
struct x700_Vehicle_Vendor_ID x700_vendor_id = {};
struct x209_EVSE_Discharge_Estimate x209_evse_dischg_est;
struct x108_EVSE_Capabilities x108_evse_cap;
struct x109_EVSE_Status x109_evse_state;
struct x118_EVSE_Dynamic_Control x118_evse_dyn;
struct x208_EVSE_Discharge_Capability x208_evse_dischg_cap;
CAN_frame CHADEMO_108 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x108,
.data = {0x01, 0xF4, 0x01, 0x0F, 0xB3, 0x01, 0x00, 0x00}};
CAN_frame CHADEMO_109 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x109,
.data = {0x02, 0x00, 0x00, 0x00, 0x01, 0x20, 0xFF, 0xFF}};
//For chademo v2.0 only
CAN_frame CHADEMO_118 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x118,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
// OLD value from skeleton implementation, indicates dynamic control is possible.
// Hardcode above as being incompatible for simplicity in current incarnation.
// .data = {0x10, 0x64, 0x00, 0xB0, 0x00, 0x1E, 0x00, 0x8F}};
// 0x200 : From vehicle-side. A V2X-ready vehicle will send this message to broadcast its “Maximum discharger current”. (It is a similar logic to the limits set in 0x100 or 0x102 during a DC charging session)
// 0x208 : From EVSE-side. A V2X EVSE will use this to send the “present discharger current” during the session, and the “available input current”. (uses similar logic to 0x108 and 0x109 during a DC charging session)
CAN_frame CHADEMO_208 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x208,
.data = {0xFF, 0xF4, 0x01, 0xF0, 0x00, 0x00, 0xFA, 0x00}};
CAN_frame CHADEMO_209 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x209,
.data = {0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
//This function maps all the values fetched via CAN to the correct parameters used for the inverter //This function maps all the values fetched via CAN to the correct parameters used for the inverter
void update_values_battery() { void ChademoBattery::update_values() {
datalayer.battery.status.real_soc = x102_chg_session.StateOfCharge; datalayer.battery.status.real_soc = x102_chg_session.StateOfCharge;
@ -147,21 +53,21 @@ void update_values_battery() {
//see IEEE Table A.26—Charge control termination command pattern on pg58 //see IEEE Table A.26—Charge control termination command pattern on pg58
//for stop conditions //for stop conditions
inline void process_vehicle_charging_minimums(CAN_frame rx_frame) { void ChademoBattery::process_vehicle_charging_minimums(CAN_frame rx_frame) {
x100_chg_lim.MinimumChargeCurrent = rx_frame.data.u8[0]; x100_chg_lim.MinimumChargeCurrent = rx_frame.data.u8[0];
x100_chg_lim.MinimumBatteryVoltage = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]); x100_chg_lim.MinimumBatteryVoltage = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]);
x100_chg_lim.MaximumBatteryVoltage = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]); x100_chg_lim.MaximumBatteryVoltage = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]);
x100_chg_lim.ConstantOfChargingRateIndication = rx_frame.data.u8[6]; x100_chg_lim.ConstantOfChargingRateIndication = rx_frame.data.u8[6];
} }
inline void process_vehicle_charging_maximums(CAN_frame rx_frame) { void ChademoBattery::process_vehicle_charging_maximums(CAN_frame rx_frame) {
x101_chg_est.MaxChargingTime10sBit = rx_frame.data.u8[1]; x101_chg_est.MaxChargingTime10sBit = rx_frame.data.u8[1];
x101_chg_est.MaxChargingTime1minBit = rx_frame.data.u8[2]; x101_chg_est.MaxChargingTime1minBit = rx_frame.data.u8[2];
x101_chg_est.EstimatedChargingTime = rx_frame.data.u8[3]; x101_chg_est.EstimatedChargingTime = rx_frame.data.u8[3];
x101_chg_est.RatedBatteryCapacity = ((rx_frame.data.u8[6] << 8) | rx_frame.data.u8[5]); x101_chg_est.RatedBatteryCapacity = ((rx_frame.data.u8[6] << 8) | rx_frame.data.u8[5]);
} }
inline void process_vehicle_charging_session(CAN_frame rx_frame) { void ChademoBattery::process_vehicle_charging_session(CAN_frame rx_frame) {
uint16_t newTargetBatteryVoltage = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[1]); uint16_t newTargetBatteryVoltage = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[1]);
uint16_t priorTargetBatteryVoltage = x102_chg_session.TargetBatteryVoltage; uint16_t priorTargetBatteryVoltage = x102_chg_session.TargetBatteryVoltage;
uint8_t newChargingCurrentRequest = rx_frame.data.u8[3]; uint8_t newChargingCurrentRequest = rx_frame.data.u8[3];
@ -303,7 +209,7 @@ inline void process_vehicle_charging_session(CAN_frame rx_frame) {
} }
/* x200 Vehicle, peer to x208 EVSE */ /* x200 Vehicle, peer to x208 EVSE */
inline void process_vehicle_charging_limits(CAN_frame rx_frame) { void ChademoBattery::process_vehicle_charging_limits(CAN_frame rx_frame) {
x200_discharge_limits.MaximumDischargeCurrent = rx_frame.data.u8[0]; x200_discharge_limits.MaximumDischargeCurrent = rx_frame.data.u8[0];
x200_discharge_limits.MinimumDischargeVoltage = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]); x200_discharge_limits.MinimumDischargeVoltage = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]);
@ -332,7 +238,7 @@ inline void process_vehicle_charging_limits(CAN_frame rx_frame) {
/* Vehicle 0x201, peer to EVSE 0x209 /* Vehicle 0x201, peer to EVSE 0x209
* HOWEVER, 201 isn't even emitted in any of the v2x canlogs available * HOWEVER, 201 isn't even emitted in any of the v2x canlogs available
*/ */
inline void process_vehicle_discharge_estimate(CAN_frame rx_frame) { void ChademoBattery::process_vehicle_discharge_estimate(CAN_frame rx_frame) {
unsigned long currentMillis = millis(); unsigned long currentMillis = millis();
x201_discharge_estimate.V2HchargeDischargeSequenceNum = rx_frame.data.u8[0]; x201_discharge_estimate.V2HchargeDischargeSequenceNum = rx_frame.data.u8[0];
@ -350,7 +256,7 @@ inline void process_vehicle_discharge_estimate(CAN_frame rx_frame) {
#endif #endif
} }
inline void process_vehicle_dynamic_control(CAN_frame rx_frame) { void ChademoBattery::process_vehicle_dynamic_control(CAN_frame rx_frame) {
//SM Dynamic Control = Charging station can increase of decrease "available output current" during charging. //SM Dynamic Control = Charging station can increase of decrease "available output current" during charging.
//If you set 0x110 byte 0, bit 0 to 1 you say you can do dynamic control. //If you set 0x110 byte 0, bit 0 to 1 you say you can do dynamic control.
//Charging station communicates this in 0x118 byte 0, bit 0 //Charging station communicates this in 0x118 byte 0, bit 0
@ -359,13 +265,13 @@ inline void process_vehicle_dynamic_control(CAN_frame rx_frame) {
x110_vehicle_dyn.u.status.DynamicControlStatus = bitRead(rx_frame.data.u8[0], 0); x110_vehicle_dyn.u.status.DynamicControlStatus = bitRead(rx_frame.data.u8[0], 0);
} }
inline void process_vehicle_vendor_ID(CAN_frame rx_frame) { void ChademoBattery::process_vehicle_vendor_ID(CAN_frame rx_frame) {
x700_vendor_id.AutomakerCode = rx_frame.data.u8[0]; x700_vendor_id.AutomakerCode = rx_frame.data.u8[0];
x700_vendor_id.OptionalContent = x700_vendor_id.OptionalContent =
((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[1]); //Actually more bytes, but not needed for our purpose ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[1]); //Actually more bytes, but not needed for our purpose
} }
void handle_incoming_can_frame_battery(CAN_frame rx_frame) { void ChademoBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
#ifdef CH_CAN_DEBUG #ifdef CH_CAN_DEBUG
logging.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D logging.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D
logging.print(" "); logging.print(" ");
@ -438,7 +344,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
} }
/* (re)initialize evse structures to pre-charge/discharge states */ /* (re)initialize evse structures to pre-charge/discharge states */
void evse_init() { void ChademoBattery::evse_init() {
// Held at 1 until start of charge when set to 0 // Held at 1 until start of charge when set to 0
// returns to 1 when ceasing power flow // returns to 1 when ceasing power flow
// mutually exclusive values // mutually exclusive values
@ -457,7 +363,7 @@ void evse_init() {
} }
/* updates for x108 */ /* updates for x108 */
void update_evse_capabilities(CAN_frame& f) { void ChademoBattery::update_evse_capabilities(CAN_frame& f) {
/* TODO use charger defines/runtime config? /* TODO use charger defines/runtime config?
* for now..leave as a future tweak. * for now..leave as a future tweak.
@ -495,7 +401,7 @@ void update_evse_capabilities(CAN_frame& f) {
} }
/* updates for x109 */ /* updates for x109 */
void update_evse_status(CAN_frame& f) { void ChademoBattery::update_evse_status(CAN_frame& f) {
x109_evse_state.s.status.EVSE_status = 1; x109_evse_state.s.status.EVSE_status = 1;
x109_evse_state.s.status.EVSE_error = 0; x109_evse_state.s.status.EVSE_error = 0;
@ -586,7 +492,7 @@ void update_evse_status(CAN_frame& f) {
* NOTE: x209 is emitted in CAN logs when x201 isn't even present * NOTE: x209 is emitted in CAN logs when x201 isn't even present
* it may not be understood by leaf (or ignored unless >= a certain protocol version or v2h sequence number * it may not be understood by leaf (or ignored unless >= a certain protocol version or v2h sequence number
*/ */
void update_evse_discharge_estimate(CAN_frame& f) { void ChademoBattery::update_evse_discharge_estimate(CAN_frame& f) {
//x209_evse_dischg_est.remaining_discharge_time_1m = x201_discharge_estimate.ApproxDischargeCompletionTime; //x209_evse_dischg_est.remaining_discharge_time_1m = x201_discharge_estimate.ApproxDischargeCompletionTime;
@ -605,7 +511,7 @@ void update_evse_discharge_estimate(CAN_frame& f) {
} }
/* x208 EVSE, peer to 0x200 Vehicle */ /* x208 EVSE, peer to 0x200 Vehicle */
void update_evse_discharge_capabilities(CAN_frame& f) { void ChademoBattery::update_evse_discharge_capabilities(CAN_frame& f) {
//present discharge current is a measured value //present discharge current is a measured value
x208_evse_dischg_cap.present_discharge_current = 0xFF - get_measured_current(); x208_evse_dischg_cap.present_discharge_current = 0xFF - get_measured_current();
@ -655,7 +561,7 @@ void update_evse_discharge_capabilities(CAN_frame& f) {
CHADEMO_208.data.u8[7] = highByte(x208_evse_dischg_cap.lower_threshold_voltage); CHADEMO_208.data.u8[7] = highByte(x208_evse_dischg_cap.lower_threshold_voltage);
} }
void transmit_can_battery(unsigned long currentMillis) { void ChademoBattery::transmit_can(unsigned long currentMillis) {
handlerBeforeMillis = currentMillis; handlerBeforeMillis = currentMillis;
handle_chademo_sequence(); handle_chademo_sequence();
@ -733,7 +639,7 @@ void transmit_can_battery(unsigned long currentMillis) {
* 5) Emergency stop stage * 5) Emergency stop stage
* CHADEMO_FAULT * CHADEMO_FAULT
*/ */
void handle_chademo_sequence() { void ChademoBattery::handle_chademo_sequence() {
precharge_low = digitalRead(PRECHARGE_PIN) == LOW; precharge_low = digitalRead(PRECHARGE_PIN) == LOW;
positive_high = digitalRead(POSITIVE_CONTACTOR_PIN) == HIGH; positive_high = digitalRead(POSITIVE_CONTACTOR_PIN) == HIGH;
@ -1020,7 +926,7 @@ void handle_chademo_sequence() {
return; return;
} }
void setup_battery(void) { // Performs one time setup at startup void ChademoBattery::setup(void) { // Performs one time setup at startup
pinMode(CHADEMO_PIN_2, OUTPUT); pinMode(CHADEMO_PIN_2, OUTPUT);
digitalWrite(CHADEMO_PIN_2, LOW); digitalWrite(CHADEMO_PIN_2, LOW);

View file

@ -2,8 +2,7 @@
#define CHADEMO_BATTERY_H #define CHADEMO_BATTERY_H
#include <Arduino.h> #include <Arduino.h>
#include "../include.h" #include "../include.h"
#include "CanBattery.h"
#define BATTERY_SELECTED
//Contactor control is required for CHADEMO support //Contactor control is required for CHADEMO support
#define CONTACTOR_CONTROL #define CONTACTOR_CONTROL
@ -12,7 +11,349 @@
// other measurement sources may be added in the future // other measurement sources may be added in the future
#define ISA_SHUNT #define ISA_SHUNT
void setup_battery(void); #define BATTERY_SELECTED
void transmit_can_frame(CAN_frame* tx_frame, int interface); #define SELECTED_BATTERY_CLASS ChademoBattery
class ChademoBattery : public CanBattery {
public:
virtual void setup(void);
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
virtual void update_values();
virtual void transmit_can(unsigned long currentMillis);
private:
void process_vehicle_charging_minimums(CAN_frame rx_frame);
void process_vehicle_charging_maximums(CAN_frame rx_frame);
void process_vehicle_charging_session(CAN_frame rx_frame);
void process_vehicle_charging_limits(CAN_frame rx_frame);
void process_vehicle_discharge_estimate(CAN_frame rx_frame);
void process_vehicle_dynamic_control(CAN_frame rx_frame);
void process_vehicle_vendor_ID(CAN_frame rx_frame);
void evse_init();
void update_evse_capabilities(CAN_frame& f);
void update_evse_status(CAN_frame& f);
void update_evse_discharge_estimate(CAN_frame& f);
void update_evse_discharge_capabilities(CAN_frame& f);
void handle_chademo_sequence();
static const int MAX_EVSE_POWER_CHARGING = 3300;
static const int MAX_EVSE_OUTPUT_VOLTAGE = 410;
static const int MAX_EVSE_OUTPUT_CURRENT = 11;
enum CHADEMO_STATE {
CHADEMO_FAULT,
CHADEMO_STOP,
CHADEMO_IDLE,
CHADEMO_CONNECTED,
CHADEMO_INIT, // intermediate state indicating CAN from Vehicle not yet received after connection
CHADEMO_NEGOTIATE,
CHADEMO_EV_ALLOWED,
CHADEMO_EVSE_PREPARE,
CHADEMO_EVSE_START,
CHADEMO_EVSE_CONTACTORS_ENABLED,
CHADEMO_POWERFLOW,
};
enum Mode { CHADEMO_CHARGE, CHADEMO_DISCHARGE, CHADEMO_BIDIRECTIONAL };
/* Charge/discharge sequence, indicating applicable V2H guideline
* If sequence number is not agreed upon via H201/H209 between EVSE and Vehicle,
* V2H 1.1 is assumed, which..is somehow between 0x0 and 0x1 ? TODO: better understanding here
* Use CHADEMO_seq to decide whether emitting 209 is necessary
* 0x0 1.0 and earlier
* 0x1 2.0 appendix A
* 0x2 2.0 appendix B
* TODO: is this influenced by x109->CHADEMO_protocol_number, or x102->ControlProtocolNumberEV ??
* Unused for now.
uint8_t CHADEMO_seq = 0x0;
*/
/*----------- CHARGING SUPPORT V2X --------------------------------------------------------------*/
/* ---------- VEHICLE Data structures */
//H100 - Vehicle - Minimum charging expectations
//TODO decide whether default values for vehicle-origin frames is even appropriate
struct x100_Vehicle_Charging_Limits {
uint8_t MinimumChargeCurrent = 0;
uint16_t MinimumBatteryVoltage = 300;
uint16_t MaximumBatteryVoltage = 402;
uint8_t ConstantOfChargingRateIndication = 0;
};
//H101 - Vehicle - Maximum charging expectations
struct x101_Vehicle_Charging_Estimate {
uint8_t MaxChargingTime10sBit = 0;
uint8_t MaxChargingTime1minBit = 0;
uint8_t EstimatedChargingTime = 0;
uint16_t RatedBatteryCapacity = 0;
};
//H102 - Vehicle - Charging targets and Status
// peer to x109 from EVSE
// termination triggers in both
// TODO see also Table A.26—Charge control termination command patterns
struct x102_Vehicle_Charging_Session { //Frame byte
uint8_t ControlProtocolNumberEV = 0; // 0
uint16_t TargetBatteryVoltage = 0; // 1-2
uint8_t ChargingCurrentRequest = 0; // 3 Note: per spec, units for this changed from kWh --> %
union {
uint8_t faults;
struct {
bool unused_3 : 1;
bool unused_2 : 1;
bool unused_1 : 1;
bool FaultBatteryVoltageDeviation : 1; // 4
bool FaultHighBatteryTemperature : 1; // 3
bool FaultBatteryCurrentDeviation : 1; // 2
bool FaultBatteryUnderVoltage : 1; // 1
bool FaultBatteryOverVoltage : 1; // 0
} fault;
} f;
union {
uint8_t packed;
struct {
bool StatusVehicleDischargeCompatible : 1; //5.7
bool unused_2 : 1; //5.6
bool unused_1 : 1; //5.5
bool StatusNormalStopRequest : 1; //5.4
bool StatusVehicle : 1; //5.3
bool StatusChargingError : 1; //5.2
bool StatusVehicleShifterPosition : 1; //5.1
bool StatusVehicleChargingEnabled : 1; //5.0 - bit zero is TODO. Vehicle charging enabled ==1 *AND* charge
// permission signal k needs to be active for charging to be
// permitted -- TODO document bits per byte for these flags
// and update variables to be more appropriate
} status;
} s;
uint8_t StateOfCharge = 0; //6 state of charge?
};
/* ---------- CHARGING: EVSE Data structures */
struct x108_EVSE_Capabilities { // Frame byte
bool contactor_weld_detection = 1; // 0
uint16_t available_output_voltage = MAX_EVSE_OUTPUT_VOLTAGE; // 1,2
uint8_t available_output_current = MAX_EVSE_OUTPUT_CURRENT; // 3
uint16_t threshold_voltage = 297; // 4,5 voltage that EVSE will stop if car fails to
// perhaps vehicle minus 3%, hardcoded initially to 96*2.95
// 6,7 = unused
};
/* Does double duty for charging and discharging */
struct x109_EVSE_Status { // Frame byte
uint8_t CHADEMO_protocol_number = 0x02; // 0
uint16_t setpoint_HV_VDC =
0; // 1,2 NOTE: charger_setpoint_HV_VDC elsewhere is a float. THIS is protocol-defined as an int. cast float->int and lose some precision for protocol adherence
uint8_t setpoint_HV_IDC = 0; // 3
//
bool discharge_compatible = true; // 4, bit 0. bits
// 4, bit 7-6 (?) unused. spec typo? maybe 1-7 unused
union {
uint8_t packed;
struct {
bool EVSE_status : 1; // 5, bit 0
bool EVSE_error : 1; // 5, bit 1
bool connector_locked : 1; // 5, bit 2 //NOTE: treated as connector_lock during discharge, but
// seen as 'energizing' during charging mode
bool battery_incompatible : 1; // 5, bit 3
bool ChgDischError : 1; // 5, bit 4
bool ChgDischStopControl : 1; // 5, bit 5 - set to false for initialization to indicate 'preparing to charge'
// set to false when ready to charge/discharge
} status;
} s;
// Either, or; not both.
// seconds field set to 0xFF by default
// indicating only the minutes field is used instead
// BOTH observed initially set to 0xFF in logs, so use
// that as the initialzed value
uint8_t remaining_time_10s = 0xFF; // 6
uint8_t remaining_time_1m = 0xFF; // 7
};
/*----------- DISCHARGING SUPPORT V2X --------------------------------------------------------------*/
/* ---------- VEHICLE Data structures */
//H200 - Vehicle - Discharge limits
struct x200_Vehicle_Discharge_Limits {
uint8_t MaximumDischargeCurrent = 0xFF;
uint16_t MinimumDischargeVoltage = 0;
uint16_t MinimumBatteryDischargeLevel = 0;
uint16_t MaxRemainingCapacityForCharging = 0;
};
/* TODO When charge/discharge sequence control number (ID201/209) is not received, the vehicle or the EVSE
should determine that the other is the EVSE or the vehicle of the model before the V2H guideline 1.1. */
//H201 - Vehicle - Estimated capacity available
// Intended primarily for display purposes.
// Peer to H209
// NOTE: in available CAN logs from a Leaf, 209 is sent with no 201 reply, so < 1.1 must be the inferred version
struct x201_Vehicle_Discharge_Estimate {
uint8_t V2HchargeDischargeSequenceNum = 0;
uint16_t ApproxDischargeCompletionTime = 0;
uint16_t AvailableVehicleEnergy = 0;
};
/* ---------- EVSE Data structures */
struct x208_EVSE_Discharge_Capability { // Frame byte
uint8_t present_discharge_current = 0xFF; // 0
uint16_t available_input_voltage = 500; // 1,2 -- poorly named as both 'available' and minimum input voltage
uint16_t available_input_current = 250; // 3 -- poorly named as both 'available' and maximum input current
// spec idiosyncracy in naming/description
// 4,5 = unused
uint16_t lower_threshold_voltage = 0; // 6,7
};
// H209 - EVSE - Estimated Discharge Duration
// peer to Vehicle's 201 event (Note: 209 seen
// in CAN logs even when 201 is not)
struct x209_EVSE_Discharge_Estimate { // Frame byte
uint8_t sequence_control_number = 0x2; // 0
uint16_t remaining_discharge_time = 0x0000; // 0x0000 == unused
};
/*----------- DYNAMIC CONTROL SUPPORT --------------------------------------------------------------*/
/* ---------- VEHICLE Data structures */
struct x110_Vehicle_Dynamic_Control { //Frame byte
union {
uint8_t packed;
struct {
bool PermissionResetMaxChgTime : 1; // bit 5 or 6? is this only x118 not x110?
bool unused_3 : 1;
bool unused_2 : 1;
bool unused_1 : 1;
bool HighVoltageControlStatus : 1; // bit 2 = High voltage control support
bool HighCurrentControlStatus : 1; // bit 1 = High current control support
// rate of change is -20A/s to 20A/s relative to 102.3
bool DynamicControlStatus : 1; // bit 0 = Dynamic Control support
} status;
} u;
};
/* ---------- EVSE Data structures */
// TODO 118
//H118
//see also table a.59 page 104 IEEE
struct x118_EVSE_Dynamic_Control { // Frame byte
union {
uint8_t packed;
struct {
bool PermissionResetMaxChgTime : 1; // bit 5 or 6?
bool unused_3 : 1;
bool unused_2 : 1;
bool unused_1 : 1;
bool HighVoltageControlStatus : 1; // bit 2 = High voltage control support
bool HighCurrentControlStatus : 1; // bit 1 = High current control support
// rate of change is -20A/s to 20A/s relative to 102.3
bool DynamicControlStatus : 1; // bit 0 = Dynamic Control support
} status;
} u;
};
/*----------- MANUFACTURER ID SUPPORT --------------------------------------------------------------*/
/* ---------- VEHICLE Data structures */
//H700 - Vehicle - Manufacturer identification
//Peer to H708
//Used to adapt to manufacturer-prescribed optional specification
struct x700_Vehicle_Vendor_ID {
uint8_t AutomakerCode = 0; // 0 = set to 0x0 to indicate incompatibility. Best as a starting place
uint8_t OptionalContent = 0; // 1-7, variable per vendor spec
};
unsigned long setupMillis = 0;
unsigned long handlerBeforeMillis = 0;
unsigned long handlerAfterMillis = 0;
/* Do not change code below unless you are sure what you are doing */
unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
unsigned long previousMillis5000 = 0; // will store last time a 5s threshold was reached for display during debug
bool plug_inserted = false;
bool vehicle_can_initialized = false;
bool vehicle_can_received = false;
bool vehicle_permission = false;
bool evse_permission = false;
bool precharge_low = false;
bool positive_high = false;
bool contactors_ready = false;
uint8_t framecount = 0;
uint8_t max_discharge_current = 0; //TODO not sure on this one, but really influenced by inverter capability
bool high_current_control_enabled = false; // set to true when high current control is operating
// if true, values from 110.1 and 110.2 should be used instead of 102.3
// and 118 should be used for evse responses
// permissible rate of change is -20A/s to 20A/s relative to 102.3
Mode EVSE_mode = CHADEMO_DISCHARGE;
CHADEMO_STATE CHADEMO_Status = CHADEMO_IDLE;
/* Charge/discharge sequence, indicating applicable V2H guideline
* If sequence number is not agreed upon via H201/H209 between EVSE and Vehicle,
* V2H 1.1 is assumed
* Use CHADEMO_seq to decide whether emitting 209 is necessary
* 0x0 1.0 and earlier
* 0x1 2.0 appendix A
* 0x2 2.0 appendix B
* Unused for now.
uint8_t CHADEMO_seq = 0x0;
*/
bool x201_received = false;
bool x209_sent = false;
struct x100_Vehicle_Charging_Limits x100_chg_lim = {};
struct x101_Vehicle_Charging_Estimate x101_chg_est = {};
struct x102_Vehicle_Charging_Session x102_chg_session = {};
struct x110_Vehicle_Dynamic_Control x110_vehicle_dyn = {};
struct x200_Vehicle_Discharge_Limits x200_discharge_limits = {};
struct x201_Vehicle_Discharge_Estimate x201_discharge_estimate = {};
struct x700_Vehicle_Vendor_ID x700_vendor_id = {};
struct x209_EVSE_Discharge_Estimate x209_evse_dischg_est;
struct x108_EVSE_Capabilities x108_evse_cap;
struct x109_EVSE_Status x109_evse_state;
struct x118_EVSE_Dynamic_Control x118_evse_dyn;
struct x208_EVSE_Discharge_Capability x208_evse_dischg_cap;
CAN_frame CHADEMO_108 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x108,
.data = {0x01, 0xF4, 0x01, 0x0F, 0xB3, 0x01, 0x00, 0x00}};
CAN_frame CHADEMO_109 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x109,
.data = {0x02, 0x00, 0x00, 0x00, 0x01, 0x20, 0xFF, 0xFF}};
//For chademo v2.0 only
CAN_frame CHADEMO_118 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x118,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
// OLD value from skeleton implementation, indicates dynamic control is possible.
// Hardcode above as being incompatible for simplicity in current incarnation.
// .data = {0x10, 0x64, 0x00, 0xB0, 0x00, 0x1E, 0x00, 0x8F}};
// 0x200 : From vehicle-side. A V2X-ready vehicle will send this message to broadcast its “Maximum discharger current”. (It is a similar logic to the limits set in 0x100 or 0x102 during a DC charging session)
// 0x208 : From EVSE-side. A V2X EVSE will use this to send the “present discharger current” during the session, and the “available input current”. (uses similar logic to 0x108 and 0x109 during a DC charging session)
CAN_frame CHADEMO_208 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x208,
.data = {0xFF, 0xF4, 0x01, 0xF0, 0x00, 0x00, 0xFA, 0x00}};
CAN_frame CHADEMO_209 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x209,
.data = {0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
};
#endif #endif

View file

@ -22,7 +22,6 @@
#ifdef CHADEMO_BATTERY #ifdef CHADEMO_BATTERY
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "../devboard/utils/events.h" #include "../devboard/utils/events.h"
#include "CHADEMO-BATTERY-INTERNAL.h"
#include "CHADEMO-BATTERY.h" #include "CHADEMO-BATTERY.h"
#include "CHADEMO-SHUNTS.h" #include "CHADEMO-SHUNTS.h"

View file

@ -1,92 +1,15 @@
#include "../include.h" #include "../include.h"
#ifdef CMFA_EV_BATTERY #ifdef CMFA_EV_BATTERY
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h" #include "../datalayer/datalayer_extended.h"
#include "../devboard/utils/events.h" #include "../devboard/utils/events.h"
#include "CMFA-EV-BATTERY.h" #include "CMFA-EV-BATTERY.h"
/* Do not change code below unless you are sure what you are doing */
CAN_frame CMFA_1EA = {.FD = false, .ext_ID = false, .DLC = 1, .ID = 0x1EA, .data = {0x00}};
CAN_frame CMFA_125 = {.FD = false,
.ext_ID = false,
.DLC = 7,
.ID = 0x125,
.data = {0x7D, 0x7D, 0x7D, 0x07, 0x82, 0x6A, 0x8A}};
CAN_frame CMFA_134 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x134,
.data = {0x90, 0x8A, 0x7E, 0x3E, 0xB2, 0x4C, 0x80, 0x00}};
CAN_frame CMFA_135 = {.FD = false, .ext_ID = false, .DLC = 5, .ID = 0x135, .data = {0xD5, 0x85, 0x38, 0x80, 0x01}};
CAN_frame CMFA_3D3 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x3D3,
.data = {0x47, 0x30, 0x00, 0x02, 0x5D, 0x80, 0x5D, 0xE7}};
CAN_frame CMFA_59B = {.FD = false, .ext_ID = false, .DLC = 3, .ID = 0x59B, .data = {0x00, 0x02, 0x00}};
CAN_frame CMFA_ACK = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x79B,
.data = {0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame CMFA_POLLING_FRAME = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x79B,
.data = {0x03, 0x22, 0x90, 0x01, 0x00, 0x00, 0x00, 0x00}};
static bool end_of_charge = false;
static bool interlock_flag = false;
static uint16_t soc_z = 0;
static uint16_t soc_u = 0;
static uint16_t max_regen_power = 0;
static uint16_t max_discharge_power = 0;
static int16_t average_temperature = 0;
static int16_t minimum_temperature = 0;
static int16_t maximum_temperature = 0;
static uint16_t maximum_charge_power = 0;
static uint16_t SOH_available_power = 0;
static uint16_t SOH_generated_power = 0;
static uint32_t average_voltage_of_cells = 270000;
static uint16_t highest_cell_voltage_mv = 3700;
static uint16_t lowest_cell_voltage_mv = 3700;
static uint16_t lead_acid_voltage = 12000;
static uint8_t highest_cell_voltage_number = 0;
static uint8_t lowest_cell_voltage_number = 0;
static uint64_t cumulative_energy_when_discharging = 0;
static uint64_t cumulative_energy_when_charging = 0;
static uint64_t cumulative_energy_in_regen = 0;
static uint16_t soh_average = 10000;
static uint16_t cellvoltages_mv[72];
static uint32_t poll_pid = PID_POLL_SOH_AVERAGE;
static uint16_t pid_reply = 0;
static uint8_t counter_10ms = 0;
static uint8_t content_125[16] = {0x07, 0x0C, 0x01, 0x06, 0x0B, 0x00, 0x05, 0x0A,
0x0F, 0x04, 0x09, 0x0E, 0x03, 0x08, 0x0D, 0x02};
static uint8_t content_135[16] = {0x85, 0xD5, 0x25, 0x75, 0xC5, 0x15, 0x65, 0xB5,
0x05, 0x55, 0xA5, 0xF5, 0x45, 0x95, 0xE5, 0x35};
static unsigned long previousMillis200ms = 0;
static unsigned long previousMillis100ms = 0;
static unsigned long previousMillis10ms = 0;
#define MAXSOC 9000 //90.00 Raw SOC displays this value when battery is at 100%
#define MINSOC 500 //5.00 Raw SOC displays this value when battery is at 0%
static uint8_t heartbeat = 0; //Alternates between 0x55 and 0xAA every 5th frame
static uint8_t heartbeat2 = 0; //Alternates between 0x55 and 0xAA every 5th frame
static uint32_t SOC_raw = 0;
static uint16_t SOH = 99;
static int16_t current = 0;
static uint16_t pack_voltage = 2700;
static int16_t highest_cell_temperature = 0;
static int16_t lowest_cell_temperature = 0;
static uint32_t discharge_power_w = 0;
static uint32_t charge_power_w = 0;
/* The raw SOC value sits at 90% when the battery is full, so we should report back 100% once this value is reached /* The raw SOC value sits at 90% when the battery is full, so we should report back 100% once this value is reached
Same goes for low point, when 10% is reached we report 0% */ Same goes for low point, when 10% is reached we report 0% */
uint16_t rescale_raw_SOC(uint32_t raw_SOC) { uint16_t CmfaEvBattery::rescale_raw_SOC(uint32_t raw_SOC) {
uint32_t calc_soc; uint32_t calc_soc;
calc_soc = (raw_SOC * 0.25); calc_soc = (raw_SOC * 0.25);
@ -103,7 +26,8 @@ uint16_t rescale_raw_SOC(uint32_t raw_SOC) {
return (uint16_t)calc_soc; return (uint16_t)calc_soc;
} }
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus void CmfaEvBattery::
update_values() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
datalayer.battery.status.soh_pptt = (SOH * 100); datalayer.battery.status.soh_pptt = (SOH * 100);
datalayer.battery.status.real_soc = rescale_raw_SOC(SOC_raw); datalayer.battery.status.real_soc = rescale_raw_SOC(SOC_raw);
@ -157,7 +81,7 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer_extended.CMFAEV.soh_average = soh_average; datalayer_extended.CMFAEV.soh_average = soh_average;
} }
void handle_incoming_can_frame_battery(CAN_frame rx_frame) { void CmfaEvBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
switch (rx_frame.ID) { //These frames are transmitted by the battery switch (rx_frame.ID) { //These frames are transmitted by the battery
case 0x127: //10ms , Same structure as old Zoe 0x155 message! case 0x127: //10ms , Same structure as old Zoe 0x155 message!
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
@ -513,7 +437,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
} }
} }
void transmit_can_battery(unsigned long currentMillis) { void CmfaEvBattery::transmit_can(unsigned long currentMillis) {
// Send 10ms CAN Message // Send 10ms CAN Message
if (currentMillis - previousMillis10ms >= INTERVAL_10_MS) { if (currentMillis - previousMillis10ms >= INTERVAL_10_MS) {
previousMillis10ms = currentMillis; previousMillis10ms = currentMillis;
@ -1016,7 +940,7 @@ void transmit_can_battery(unsigned long currentMillis) {
} }
} }
void setup_battery(void) { // Performs one time setup at startup void CmfaEvBattery::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "CMFA platform, 27 kWh battery", 63); strncpy(datalayer.system.info.battery_protocol, "CMFA platform, 27 kWh battery", 63);
datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.info.battery_protocol[63] = '\0';
datalayer.system.status.battery_allows_contactor_closing = true; datalayer.system.status.battery_allows_contactor_closing = true;

View file

@ -2,157 +2,211 @@
#define CMFA_EV_BATTERY_H #define CMFA_EV_BATTERY_H
#include "../include.h" #include "../include.h"
#include "CanBattery.h"
#define BATTERY_SELECTED #define BATTERY_SELECTED
#define MAX_PACK_VOLTAGE_DV 3040 //5000 = 500.0V #define SELECTED_BATTERY_CLASS CmfaEvBattery
#define MIN_PACK_VOLTAGE_DV 2185
#define MAX_CELL_DEVIATION_MV 100
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
// OBD2 PID polls. Some of these have been reverse engineered, but there are many unknown values still class CmfaEvBattery : public CanBattery {
#define PID_POLL_SOCZ 0x9001 //122 in log public:
#define PID_POLL_USOC 0x9002 //5531 (Possible SOC candidate) virtual void setup(void);
#define PID_POLL_SOH_AVERAGE 0x9003 virtual void handle_incoming_can_frame(CAN_frame rx_frame);
#define PID_POLL_AVERAGE_VOLTAGE_OF_CELLS 0x9006 virtual void update_values();
#define PID_POLL_HIGHEST_CELL_VOLTAGE 0x9007 virtual void transmit_can(unsigned long currentMillis);
#define PID_POLL_CELL_NUMBER_HIGHEST_VOLTAGE 0x9008
#define PID_POLL_LOWEST_CELL_VOLTAGE 0x9009
#define PID_POLL_CELL_NUMBER_LOWEST_VOLTAGE 0x900A
#define PID_POLL_CURRENT_OFFSET 0x900C
#define PID_POLL_INSTANT_CURRENT 0x900D
#define PID_POLL_MAX_REGEN 0x900E
#define PID_POLL_MAX_DISCHARGE_POWER 0x900F
#define PID_POLL_12V_BATTERY 0x9011
#define PID_POLL_AVERAGE_TEMPERATURE 0x9012 //749 in log
#define PID_POLL_MIN_TEMPERATURE 0x9013 //736 in log
#define PID_POLL_MAX_TEMPERATURE 0x9014 //752 in log
#define PID_POLL_MAX_CHARGE_POWER 0x9018
#define PID_POLL_END_OF_CHARGE_FLAG 0x9019
#define PID_POLL_INTERLOCK_FLAG 0x901A
#define PID_POLL_BATTERY_IDENTIFICATION 0x901B // Multi frame message
#define PID_POLL_CELL_1 0x9021
#define PID_POLL_CELL_2 0x9022
#define PID_POLL_CELL_3 0x9023
#define PID_POLL_CELL_4 0x9024
#define PID_POLL_CELL_5 0x9025
#define PID_POLL_CELL_6 0x9026
#define PID_POLL_CELL_7 0x9027
#define PID_POLL_CELL_8 0x9028
#define PID_POLL_CELL_9 0x9029
#define PID_POLL_CELL_10 0x902A
#define PID_POLL_CELL_11 0x902B
#define PID_POLL_CELL_12 0x902C
#define PID_POLL_CELL_13 0x902D
#define PID_POLL_CELL_14 0x902E
#define PID_POLL_CELL_15 0x902F
#define PID_POLL_CELL_16 0x9030
#define PID_POLL_CELL_17 0x9031
#define PID_POLL_CELL_18 0x9032
#define PID_POLL_CELL_19 0x9033
#define PID_POLL_CELL_20 0x9034
#define PID_POLL_CELL_21 0x9035
#define PID_POLL_CELL_22 0x9036
#define PID_POLL_CELL_23 0x9037
#define PID_POLL_CELL_24 0x9038
#define PID_POLL_CELL_25 0x9039
#define PID_POLL_CELL_26 0x903A
#define PID_POLL_CELL_27 0x903B
#define PID_POLL_CELL_28 0x903C
#define PID_POLL_CELL_29 0x903D
#define PID_POLL_CELL_30 0x903E
#define PID_POLL_CELL_31 0x903F
#define PID_POLL_DIDS_SUPPORTED_IN_RANGE_9041_9060 0x9040
#define PID_POLL_CELL_32 0x9041
#define PID_POLL_CELL_33 0x9042
#define PID_POLL_CELL_34 0x9043
#define PID_POLL_CELL_35 0x9044
#define PID_POLL_CELL_36 0x9045
#define PID_POLL_CELL_37 0x9046
#define PID_POLL_CELL_38 0x9047
#define PID_POLL_CELL_39 0x9048
#define PID_POLL_CELL_40 0x9049
#define PID_POLL_CELL_41 0x904A
#define PID_POLL_CELL_42 0x904B
#define PID_POLL_CELL_43 0x904C
#define PID_POLL_CELL_44 0x904D
#define PID_POLL_CELL_45 0x904E
#define PID_POLL_CELL_46 0x904F
#define PID_POLL_CELL_47 0x9050
#define PID_POLL_CELL_48 0x9051
#define PID_POLL_CELL_49 0x9052
#define PID_POLL_CELL_50 0x9053
#define PID_POLL_CELL_51 0x9054
#define PID_POLL_CELL_52 0x9055
#define PID_POLL_CELL_53 0x9056
#define PID_POLL_CELL_54 0x9057
#define PID_POLL_CELL_55 0x9058
#define PID_POLL_CELL_56 0x9059
#define PID_POLL_CELL_57 0x905A
#define PID_POLL_CELL_58 0x905B
#define PID_POLL_CELL_59 0x905C
#define PID_POLL_CELL_60 0x905D
#define PID_POLL_CELL_61 0x905E
#define PID_POLL_CELL_62 0x905F
#define PID_POLL_DIDS_SUPPORTED_IN_RANGE_9061_9080 0x9060
#define PID_POLL_CELL_63 0x9061
#define PID_POLL_CELL_64 0x9062
#define PID_POLL_CELL_65 0x9063
#define PID_POLL_CELL_66 0x9064
#define PID_POLL_CELL_67 0x9065
#define PID_POLL_CELL_68 0x9066
#define PID_POLL_CELL_69 0x9067
#define PID_POLL_CELL_70 0x9068
#define PID_POLL_CELL_71 0x9069
#define PID_POLL_CELL_72 0x906A
/*
#define PID_POLL_UNKNOWNX 0x912F // Multi frame message, empty
#define PID_POLL_UNKNOWNX 0x9129
#define PID_POLL_UNKNOWNX 0x9131
#define PID_POLL_UNKNOWNX 0x9132
#define PID_POLL_UNKNOWNX 0x9133
#define PID_POLL_UNKNOWNX 0x9134
#define PID_POLL_UNKNOWNX 0x9135
#define PID_POLL_UNKNOWNX 0x9136
#define PID_POLL_UNKNOWNX 0x9137
#define PID_POLL_UNKNOWNX 0x9138
#define PID_POLL_UNKNOWNX 0x9139
#define PID_POLL_UNKNOWNX 0x913A
#define PID_POLL_UNKNOWNX 0x913B
#define PID_POLL_UNKNOWNX 0x913C
#define PID_POLL_UNKNOWN5 0x912F
#define PID_POLL_UNKNOWNX 0x91B7
*/
#define PID_POLL_SOH_AVAILABLE_POWER_CALCULATION 0x91BC // 0-100%
#define PID_POLL_SOH_GENERATED_POWER_CALCULATION 0x91BD // 0-100%
/*
#define PID_POLL_UNKNOWNX 0x91C1
#define PID_POLL_UNKNOWNX 0x91CD
#define PID_POLL_UNKNOWNX 0x91CF
#define PID_POLL_UNKNOWNX 0x91F6
#define PID_POLL_UNKNOWNX 0x91F7
#define PID_POLL_UNKNOWNX 0x920F
#define PID_POLL_UNKNOWNx 0x9242
*/
#define PID_POLL_CUMULATIVE_ENERGY_WHEN_CHARGING 0x9243
#define PID_POLL_CUMULATIVE_ENERGY_WHEN_DISCHARGING 0x9245
#define PID_POLL_CUMULATIVE_ENERGY_IN_REGEN 0x9247
/*
#define PID_POLL_UNKNOWNx 0x9256
#define PID_POLL_UNKNOWNx 0x9261
#define PID_POLL_UNKNOWN7 0x9284
#define PID_POLL_UNKNOWNx 0xF012
#define PID_POLL_UNKNOWNx 0xF1A0
#define PID_POLL_UNKNOWNx 0xF182
#define PID_POLL_UNKNOWNx 0xF187
#define PID_POLL_UNKNOWNx 0xF188
#define PID_POLL_UNKNOWNx 0xF18A
#define PID_POLL_UNKNOWNx 0xF18C
#define PID_POLL_UNKNOWNx 0xF191
#define PID_POLL_UNKNOWNx 0xF194
#define PID_POLL_UNKNOWNx 0xF195
*/
void setup_battery(void); private:
void transmit_can_frame(CAN_frame* tx_frame, int interface); uint16_t rescale_raw_SOC(uint32_t raw_SOC);
static const int MAX_PACK_VOLTAGE_DV = 3040; // 5000 = 500.0V
static const int MIN_PACK_VOLTAGE_DV = 2185;
static const int MAX_CELL_DEVIATION_MV = 100;
static const int MAX_CELL_VOLTAGE_MV = 4250; // Emergency stop if above
static const int MIN_CELL_VOLTAGE_MV = 2700; // Emergency stop if below
// OBD2 PID polls
static const int PID_POLL_SOCZ = 0x9001;
static const int PID_POLL_USOC = 0x9002;
static const int PID_POLL_SOH_AVERAGE = 0x9003;
static const int PID_POLL_AVERAGE_VOLTAGE_OF_CELLS = 0x9006;
static const int PID_POLL_HIGHEST_CELL_VOLTAGE = 0x9007;
static const int PID_POLL_CELL_NUMBER_HIGHEST_VOLTAGE = 0x9008;
static const int PID_POLL_LOWEST_CELL_VOLTAGE = 0x9009;
static const int PID_POLL_CELL_NUMBER_LOWEST_VOLTAGE = 0x900A;
static const int PID_POLL_CURRENT_OFFSET = 0x900C;
static const int PID_POLL_INSTANT_CURRENT = 0x900D;
static const int PID_POLL_MAX_REGEN = 0x900E;
static const int PID_POLL_MAX_DISCHARGE_POWER = 0x900F;
static const int PID_POLL_12V_BATTERY = 0x9011;
static const int PID_POLL_AVERAGE_TEMPERATURE = 0x9012;
static const int PID_POLL_MIN_TEMPERATURE = 0x9013;
static const int PID_POLL_MAX_TEMPERATURE = 0x9014;
static const int PID_POLL_MAX_CHARGE_POWER = 0x9018;
static const int PID_POLL_END_OF_CHARGE_FLAG = 0x9019;
static const int PID_POLL_INTERLOCK_FLAG = 0x901A;
static const int PID_POLL_BATTERY_IDENTIFICATION = 0x901B;
static const int PID_POLL_CELL_1 = 0x9021;
static const int PID_POLL_CELL_2 = 0x9022;
static const int PID_POLL_CELL_3 = 0x9023;
static const int PID_POLL_CELL_4 = 0x9024;
static const int PID_POLL_CELL_5 = 0x9025;
static const int PID_POLL_CELL_6 = 0x9026;
static const int PID_POLL_CELL_7 = 0x9027;
static const int PID_POLL_CELL_8 = 0x9028;
static const int PID_POLL_CELL_9 = 0x9029;
static const int PID_POLL_CELL_10 = 0x902A;
static const int PID_POLL_CELL_11 = 0x902B;
static const int PID_POLL_CELL_12 = 0x902C;
static const int PID_POLL_CELL_13 = 0x902D;
static const int PID_POLL_CELL_14 = 0x902E;
static const int PID_POLL_CELL_15 = 0x902F;
static const int PID_POLL_CELL_16 = 0x9030;
static const int PID_POLL_CELL_17 = 0x9031;
static const int PID_POLL_CELL_18 = 0x9032;
static const int PID_POLL_CELL_19 = 0x9033;
static const int PID_POLL_CELL_20 = 0x9034;
static const int PID_POLL_CELL_21 = 0x9035;
static const int PID_POLL_CELL_22 = 0x9036;
static const int PID_POLL_CELL_23 = 0x9037;
static const int PID_POLL_CELL_24 = 0x9038;
static const int PID_POLL_CELL_25 = 0x9039;
static const int PID_POLL_CELL_26 = 0x903A;
static const int PID_POLL_CELL_27 = 0x903B;
static const int PID_POLL_CELL_28 = 0x903C;
static const int PID_POLL_CELL_29 = 0x903D;
static const int PID_POLL_CELL_30 = 0x903E;
static const int PID_POLL_CELL_31 = 0x903F;
static const int PID_POLL_DIDS_SUPPORTED_IN_RANGE_9041_9060 = 0x9040;
static const int PID_POLL_CELL_32 = 0x9041;
static const int PID_POLL_CELL_33 = 0x9042;
static const int PID_POLL_CELL_34 = 0x9043;
static const int PID_POLL_CELL_35 = 0x9044;
static const int PID_POLL_CELL_36 = 0x9045;
static const int PID_POLL_CELL_37 = 0x9046;
static const int PID_POLL_CELL_38 = 0x9047;
static const int PID_POLL_CELL_39 = 0x9048;
static const int PID_POLL_CELL_40 = 0x9049;
static const int PID_POLL_CELL_41 = 0x904A;
static const int PID_POLL_CELL_42 = 0x904B;
static const int PID_POLL_CELL_43 = 0x904C;
static const int PID_POLL_CELL_44 = 0x904D;
static const int PID_POLL_CELL_45 = 0x904E;
static const int PID_POLL_CELL_46 = 0x904F;
static const int PID_POLL_CELL_47 = 0x9050;
static const int PID_POLL_CELL_48 = 0x9051;
static const int PID_POLL_CELL_49 = 0x9052;
static const int PID_POLL_CELL_50 = 0x9053;
static const int PID_POLL_CELL_51 = 0x9054;
static const int PID_POLL_CELL_52 = 0x9055;
static const int PID_POLL_CELL_53 = 0x9056;
static const int PID_POLL_CELL_54 = 0x9057;
static const int PID_POLL_CELL_55 = 0x9058;
static const int PID_POLL_CELL_56 = 0x9059;
static const int PID_POLL_CELL_57 = 0x905A;
static const int PID_POLL_CELL_58 = 0x905B;
static const int PID_POLL_CELL_59 = 0x905C;
static const int PID_POLL_CELL_60 = 0x905D;
static const int PID_POLL_CELL_61 = 0x905E;
static const int PID_POLL_CELL_62 = 0x905F;
static const int PID_POLL_DIDS_SUPPORTED_IN_RANGE_9061_9080 = 0x9060;
static const int PID_POLL_CELL_63 = 0x9061;
static const int PID_POLL_CELL_64 = 0x9062;
static const int PID_POLL_CELL_65 = 0x9063;
static const int PID_POLL_CELL_66 = 0x9064;
static const int PID_POLL_CELL_67 = 0x9065;
static const int PID_POLL_CELL_68 = 0x9066;
static const int PID_POLL_CELL_69 = 0x9067;
static const int PID_POLL_CELL_70 = 0x9068;
static const int PID_POLL_CELL_71 = 0x9069;
static const int PID_POLL_CELL_72 = 0x906A;
static const int PID_POLL_SOH_AVAILABLE_POWER_CALCULATION = 0x91BC;
static const int PID_POLL_SOH_GENERATED_POWER_CALCULATION = 0x91BD;
static const int PID_POLL_CUMULATIVE_ENERGY_WHEN_CHARGING = 0x9243;
static const int PID_POLL_CUMULATIVE_ENERGY_WHEN_DISCHARGING = 0x9245;
static const int PID_POLL_CUMULATIVE_ENERGY_IN_REGEN = 0x9247;
CAN_frame CMFA_1EA = {.FD = false, .ext_ID = false, .DLC = 1, .ID = 0x1EA, .data = {0x00}};
CAN_frame CMFA_125 = {.FD = false,
.ext_ID = false,
.DLC = 7,
.ID = 0x125,
.data = {0x7D, 0x7D, 0x7D, 0x07, 0x82, 0x6A, 0x8A}};
CAN_frame CMFA_134 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x134,
.data = {0x90, 0x8A, 0x7E, 0x3E, 0xB2, 0x4C, 0x80, 0x00}};
CAN_frame CMFA_135 = {.FD = false, .ext_ID = false, .DLC = 5, .ID = 0x135, .data = {0xD5, 0x85, 0x38, 0x80, 0x01}};
CAN_frame CMFA_3D3 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x3D3,
.data = {0x47, 0x30, 0x00, 0x02, 0x5D, 0x80, 0x5D, 0xE7}};
CAN_frame CMFA_59B = {.FD = false, .ext_ID = false, .DLC = 3, .ID = 0x59B, .data = {0x00, 0x02, 0x00}};
CAN_frame CMFA_ACK = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x79B,
.data = {0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame CMFA_POLLING_FRAME = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x79B,
.data = {0x03, 0x22, 0x90, 0x01, 0x00, 0x00, 0x00, 0x00}};
bool end_of_charge = false;
bool interlock_flag = false;
uint16_t soc_z = 0;
uint16_t soc_u = 0;
uint16_t max_regen_power = 0;
uint16_t max_discharge_power = 0;
int16_t average_temperature = 0;
int16_t minimum_temperature = 0;
int16_t maximum_temperature = 0;
uint16_t maximum_charge_power = 0;
uint16_t SOH_available_power = 0;
uint16_t SOH_generated_power = 0;
uint32_t average_voltage_of_cells = 270000;
uint16_t highest_cell_voltage_mv = 3700;
uint16_t lowest_cell_voltage_mv = 3700;
uint16_t lead_acid_voltage = 12000;
uint8_t highest_cell_voltage_number = 0;
uint8_t lowest_cell_voltage_number = 0;
uint64_t cumulative_energy_when_discharging = 0;
uint64_t cumulative_energy_when_charging = 0;
uint64_t cumulative_energy_in_regen = 0;
uint16_t soh_average = 10000;
uint16_t cellvoltages_mv[72];
uint32_t poll_pid = PID_POLL_SOH_AVERAGE;
uint16_t pid_reply = 0;
uint8_t counter_10ms = 0;
uint8_t content_125[16] = {0x07, 0x0C, 0x01, 0x06, 0x0B, 0x00, 0x05, 0x0A,
0x0F, 0x04, 0x09, 0x0E, 0x03, 0x08, 0x0D, 0x02};
uint8_t content_135[16] = {0x85, 0xD5, 0x25, 0x75, 0xC5, 0x15, 0x65, 0xB5,
0x05, 0x55, 0xA5, 0xF5, 0x45, 0x95, 0xE5, 0x35};
unsigned long previousMillis200ms = 0;
unsigned long previousMillis100ms = 0;
unsigned long previousMillis10ms = 0;
static const int MAXSOC = 9000; //90.00 Raw SOC displays this value when battery is at 100%
static const int MINSOC = 500; //5.00 Raw SOC displays this value when battery is at 0%
uint8_t heartbeat = 0; //Alternates between 0x55 and 0xAA every 5th frame
uint8_t heartbeat2 = 0; //Alternates between 0x55 and 0xAA every 5th frame
uint32_t SOC_raw = 0;
uint16_t SOH = 99;
int16_t current = 0;
uint16_t pack_voltage = 2700;
int16_t highest_cell_temperature = 0;
int16_t lowest_cell_temperature = 0;
uint32_t discharge_power_w = 0;
uint32_t charge_power_w = 0;
};
#endif #endif

View file

@ -1,16 +1,14 @@
#ifndef CAN_BATTERY_H #ifndef CAN_BATTERY_H
#define CAN_BATTERY_H #define CAN_BATTERY_H
#include "Battery.h"
#include "src/devboard/utils/types.h" #include "src/devboard/utils/types.h"
// Abstract base class for next-generation battery implementations. // Abstract base class for batteries using the CAN bus
// Defines the interface to call battery specific functionality. class CanBattery : public Battery {
// No support for double battery yet.
class CanBattery {
public: public:
virtual void setup(void) = 0;
virtual void handle_incoming_can_frame(CAN_frame rx_frame) = 0; virtual void handle_incoming_can_frame(CAN_frame rx_frame) = 0;
virtual void update_values() = 0;
virtual void transmit_can(unsigned long currentMillis) = 0; virtual void transmit_can(unsigned long currentMillis) = 0;
}; };

View file

@ -19,7 +19,7 @@ static uint16_t cellvoltage_max_mV = 0;
static uint16_t SOC = 0; static uint16_t SOC = 0;
static bool has_fault = false; static bool has_fault = false;
void update_values_battery() { void DalyBms::update_values() {
datalayer.battery.status.real_soc = SOC; datalayer.battery.status.real_soc = SOC;
datalayer.battery.status.voltage_dV = voltage_dV; //value is *10 (3700 = 370.0) datalayer.battery.status.voltage_dV = voltage_dV; //value is *10 (3700 = 370.0)
datalayer.battery.status.current_dA = current_dA; //value is *10 (150 = 15.0) datalayer.battery.status.current_dA = current_dA; //value is *10 (150 = 15.0)
@ -60,7 +60,7 @@ void update_values_battery() {
datalayer.battery.status.real_bms_status = has_fault ? BMS_FAULT : BMS_ACTIVE; datalayer.battery.status.real_bms_status = has_fault ? BMS_FAULT : BMS_ACTIVE;
} }
void setup_battery(void) { // Performs one time setup at startup void DalyBms::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "DALY RS485", 63); strncpy(datalayer.system.info.battery_protocol, "DALY RS485", 63);
datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.number_of_cells = CELL_COUNT; datalayer.battery.info.number_of_cells = CELL_COUNT;
@ -70,6 +70,8 @@ void setup_battery(void) { // Performs one time setup at startup
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV; datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.battery.info.total_capacity_Wh = BATTERY_WH_MAX; datalayer.battery.info.total_capacity_Wh = BATTERY_WH_MAX;
datalayer.system.status.battery_allows_contactor_closing = true; datalayer.system.status.battery_allows_contactor_closing = true;
Serial2.begin(baud_rate(), SERIAL_8N1, RS485_RX_PIN, RS485_TX_PIN);
} }
uint8_t calculate_checksum(uint8_t buff[12]) { uint8_t calculate_checksum(uint8_t buff[12]) {
@ -154,31 +156,29 @@ void decode_packet(uint8_t command, uint8_t data[8]) {
} }
} }
void transmit_rs485() { void DalyBms::transmit_rs485(unsigned long currentMillis) {
static uint8_t nextCommand = 0x90; static uint8_t nextCommand = 0x90;
if (millis() - lastPacket > 60) { if (currentMillis - lastPacket > 60) {
lastPacket = currentMillis;
uint8_t tx_buff[13] = {0}; uint8_t tx_buff[13] = {0};
tx_buff[0] = 0xA5; tx_buff[0] = 0xA5;
tx_buff[1] = 0x40; tx_buff[1] = 0x40;
tx_buff[2] = nextCommand; tx_buff[2] = nextCommand;
tx_buff[3] = 8; tx_buff[3] = 8;
tx_buff[12] = calculate_checksum(tx_buff); tx_buff[12] = calculate_checksum(tx_buff);
#ifdef DEBUG_VIA_USB #ifdef DEBUG_VIA_USB
dump_buff("transmitting: ", tx_buff, 13); dump_buff("transmitting: ", tx_buff, 13);
#endif #endif
Serial2.write(tx_buff, 13); Serial2.write(tx_buff, 13);
lastPacket = millis();
nextCommand++; nextCommand++;
if (nextCommand > 0x98) if (nextCommand > 0x98)
nextCommand = 0x90; nextCommand = 0x90;
} }
} }
void receive_RS485() { void DalyBms::receive_RS485() {
static uint8_t recv_buff[13] = {0}; static uint8_t recv_buff[13] = {0};
static uint8_t recv_len = 0; static uint8_t recv_len = 0;

View file

@ -1,6 +1,8 @@
#ifndef DALY_BMS_H #ifndef DALY_BMS_H
#define DALY_BMS_H #define DALY_BMS_H
#include "RS485Battery.h"
/* Tweak these according to your battery build */ /* Tweak these according to your battery build */
#define CELL_COUNT 14 #define CELL_COUNT 14
#define MAX_PACK_VOLTAGE_DV 580 //580 = 58.0V #define MAX_PACK_VOLTAGE_DV 580 //580 = 58.0V
@ -14,6 +16,17 @@
/* Do not modify any rows below*/ /* Do not modify any rows below*/
#define BATTERY_SELECTED #define BATTERY_SELECTED
#define RS485_BATTERY_SELECTED #define RS485_BATTERY_SELECTED
#define RS485_BAUDRATE 9600 #define SELECTED_BATTERY_CLASS DalyBms
class DalyBms : public RS485Battery {
public:
void setup();
void update_values();
void transmit_rs485(unsigned long currentMillis);
void receive_RS485();
private:
int baud_rate() { return 9600; }
};
#endif #endif

View file

@ -1,6 +1,6 @@
#include "../include.h" #include "../include.h"
#ifdef STELLANTIS_ECMP_BATTERY #ifdef STELLANTIS_ECMP_BATTERY
#include <algorithm> // For std::min and std::max #include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h" //For More Battery Info page #include "../datalayer/datalayer_extended.h" //For More Battery Info page
#include "../devboard/utils/events.h" #include "../devboard/utils/events.h"
@ -14,39 +14,7 @@ This integration is still ongoing. Here is what still needs to be done in order
*/ */
/* Do not change code below unless you are sure what you are doing */ /* Do not change code below unless you are sure what you are doing */
static unsigned long previousMillis20 = 0; // will store last time a 20ms CAN Message was sent void EcmpBattery::update_values() {
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was sent
//Actual content messages
CAN_frame ECMP_382 = {
.FD = false, //BSI_Info (VCU) PSA specific
.ext_ID = false,
.DLC = 8,
.ID = 0x382,
.data = {0x09, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; //09 20 on AC charge. 0A 20 on DC charge
CAN_frame ECMP_0F0 = {.FD = false, //VCU (Common)
.ext_ID = false,
.DLC = 8,
.ID = 0x0F0,
.data = {0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF}};
static uint8_t data_0F0_20[16] = {0xFF, 0x0E, 0x1D, 0x2C, 0x3B, 0x4A, 0x59, 0x68,
0x77, 0x86, 0x95, 0xA4, 0xB3, 0xC2, 0xD1, 0xE0};
static uint8_t data_0F0_00[16] = {0xF1, 0x00, 0x1F, 0x2E, 0x3D, 0x4C, 0x5B, 0x6A,
0x79, 0x88, 0x97, 0xA6, 0xB5, 0xC4, 0xD3, 0xE2};
static uint8_t counter_20ms = 0;
static uint16_t battery_voltage = 370;
static uint16_t battery_soc = 0;
static int16_t battery_current = 0;
static uint8_t battery_MainConnectorState = 0;
static bool battery_RelayOpenRequest = false;
static uint16_t battery_AllowedMaxChargeCurrent = 0;
static uint16_t battery_AllowedMaxDischargeCurrent = 0;
static uint16_t battery_insulationResistanceKOhm = 0;
static int16_t battery_highestTemperature = 0;
static int16_t battery_lowestTemperature = 0;
static uint16_t cellvoltages[108];
void update_values_battery() {
datalayer.battery.status.real_soc = battery_soc * 10; datalayer.battery.status.real_soc = battery_soc * 10;
@ -92,7 +60,7 @@ void update_values_battery() {
datalayer_extended.stellantisECMP.InsulationResistance = battery_insulationResistanceKOhm; datalayer_extended.stellantisECMP.InsulationResistance = battery_insulationResistanceKOhm;
} }
void handle_incoming_can_frame_battery(CAN_frame rx_frame) { void EcmpBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
switch (rx_frame.ID) { switch (rx_frame.ID) {
case 0x125: //Common case 0x125: //Common
@ -326,7 +294,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
} }
} }
void transmit_can_battery(unsigned long currentMillis) { void EcmpBattery::transmit_can(unsigned long currentMillis) {
// Send 20ms CAN Message // Send 20ms CAN Message
if (currentMillis - previousMillis20 >= INTERVAL_20_MS) { if (currentMillis - previousMillis20 >= INTERVAL_20_MS) {
previousMillis20 = currentMillis; previousMillis20 = currentMillis;
@ -352,12 +320,15 @@ void transmit_can_battery(unsigned long currentMillis) {
} }
} }
void setup_battery(void) { // Performs one time setup at startup void EcmpBattery::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "Stellantis ECMP battery", 63); strncpy(datalayer.system.info.battery_protocol, "Stellantis ECMP battery", 63);
datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.number_of_cells = 108; datalayer.battery.info.number_of_cells = 108;
datalayer.battery.info.max_design_voltage_dV = 4546; // 454.6V, charging over this is not possible datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_design_voltage_dV = 3210; // 321.0V, under this, discharging further is disabled datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.system.status.battery_allows_contactor_closing = true; datalayer.system.status.battery_allows_contactor_closing = true;
} }

View file

@ -3,10 +3,56 @@
#include <Arduino.h> #include <Arduino.h>
#include "../include.h" #include "../include.h"
#define BATTERY_SELECTED #include "CanBattery.h"
#define MAX_CELL_DEVIATION_MV 250
void setup_battery(void); #define BATTERY_SELECTED
void transmit_can_frame(CAN_frame* tx_frame, int interface); #define SELECTED_BATTERY_CLASS EcmpBattery
class EcmpBattery : public CanBattery {
public:
virtual void setup(void);
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
virtual void update_values();
virtual void transmit_can(unsigned long currentMillis);
private:
static const int MAX_PACK_VOLTAGE_DV = 4546;
static const int MIN_PACK_VOLTAGE_DV = 3210;
static const int MAX_CELL_DEVIATION_MV = 100;
static const int MAX_CELL_VOLTAGE_MV = 4250;
static const int MIN_CELL_VOLTAGE_MV = 2700;
bool battery_RelayOpenRequest = false;
uint8_t counter_20ms = 0;
uint8_t battery_MainConnectorState = 0;
int16_t battery_current = 0;
uint16_t battery_voltage = 370;
uint16_t battery_soc = 0;
uint16_t cellvoltages[108];
uint16_t battery_AllowedMaxChargeCurrent = 0;
uint16_t battery_AllowedMaxDischargeCurrent = 0;
uint16_t battery_insulationResistanceKOhm = 0;
int16_t battery_highestTemperature = 0;
int16_t battery_lowestTemperature = 0;
unsigned long previousMillis20 = 0; // will store last time a 20ms CAN Message was sent
unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was sent
CAN_frame ECMP_382 = {
.FD = false, //BSI_Info (VCU) PSA specific
.ext_ID = false,
.DLC = 8,
.ID = 0x382,
.data = {0x09, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; //09 20 on AC charge. 0A 20 on DC charge
CAN_frame ECMP_0F0 = {.FD = false, //VCU (Common)
.ext_ID = false,
.DLC = 8,
.ID = 0x0F0,
.data = {0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF}};
uint8_t data_0F0_20[16] = {0xFF, 0x0E, 0x1D, 0x2C, 0x3B, 0x4A, 0x59, 0x68,
0x77, 0x86, 0x95, 0xA4, 0xB3, 0xC2, 0xD1, 0xE0};
uint8_t data_0F0_00[16] = {0xF1, 0x00, 0x1F, 0x2E, 0x3D, 0x4C, 0x5B, 0x6A,
0x79, 0x88, 0x97, 0xA6, 0xB5, 0xC4, 0xD3, 0xE2};
};
#endif #endif

View file

@ -0,0 +1,675 @@
#include "../include.h"
#ifdef GEELY_GEOMETRY_C_BATTERY
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h" //For "More battery info" webpage
#include "../devboard/utils/events.h"
#include "GEELY-GEOMETRY-C-BATTERY.h"
/* TODO
- Contactor closing: CAN log needed from complete H-CAN of Geely Geometry C vehicle. We are not sure what needs to be sent towards the battery yet to get contactor closing working. DTC readout complains that a "Power CAN BUS Data Missing" message is still missing
- Unsure if the current CAN sending routine is enough to keep BMS alive 24/7. Testing needed
- There are a few UNKNOWN PID polls, these need to be decoded
- Some critical values are still missing:
- Current sensor (Mandatory!)
- Max charge power (can be estimated)
- Max discharge power (can be estimated)
- Cell voltage min/max (not mandatory, but very nice to have)
- All cell voltages (not mandatory, but very nice to have)
Node descriptions, these can send CAN messages in the Geely Geometry C
DSCU (Drivers Seat Control Unit)
OBC (On Board Charger)
FRS (Front Radar System)
IPU (Integrated Power Unit Control)
EGSM (Electronic Gear Shifter)
MMI
T-BOX (Electrocar Communication Control Module)
IPK
FCS
FRS
TCM(SAS)
RPS
ESC
ACU(YRS)
DSCU
PEPS
ESCL
BCM (Body Control Module)
AC
BMSH (Battery Management System)
VCU (Vehicle Control Unit)
AVAS
IB
RSRS
RML
There are 4 CAN buses in the Geometry C, we are interested in the Hybrid CAN (HB-CAN)
- Hybrid, HB CAN: gateway, electronic shifter, VCU, T-BOX, BMS, high and low voltage charging system, integrated power controller
- Infotainent, IF CAN: gateway, diagnostic interface, combined instrument, controller, head-up display, audio host, T-BOX
- Comfort, CF CAN: gateway, diagnostic interface, low-speed alarm controller, thermal management control module, electronic
steering column lock, BCM, seat module
- Chassis, CS CAN: gateway, steering wheel angle sensor, front monocular camera, VCU, millimeter wave radar probe, EPS,
smart booster, ESC, airbag control module, automatic parking module
*/
/* Do not change code below unless you are sure what you are doing */
void GeelyGeometryCBattery::update_values() {
datalayer_battery->status.soh_pptt;
datalayer_battery->status.real_soc = poll_soc * 10;
datalayer_battery->status.voltage_dV = battery_voltage;
datalayer_battery->status.current_dA;
if (poll_amount_cells == 102) { // We have determined that we are on 70kWh pack
datalayer_battery->info.total_capacity_Wh = 70000;
datalayer_battery->info.max_design_voltage_dV = MAX_PACK_VOLTAGE_70_DV;
datalayer_battery->info.min_design_voltage_dV = MIN_PACK_VOLTAGE_70_DV;
}
//Calculate the remaining Wh amount from SOC% and max Wh value.
datalayer_battery->status.remaining_capacity_Wh = static_cast<uint32_t>(
(static_cast<double>(datalayer_battery->status.real_soc) / 10000) * datalayer_battery->info.total_capacity_Wh);
datalayer_battery->status.max_discharge_power_W = discharge_power_allowed * 100;
datalayer_battery->status.max_charge_power_W;
datalayer_battery->status.cell_min_voltage_mV = maximum_cell_voltage - 10; //TODO: Fix once we have min value
datalayer_battery->status.cell_max_voltage_mV = maximum_cell_voltage;
// Initialize highest and lowest to the first element
maximum_temperature = poll_temperature[0];
minimum_temperature = poll_temperature[0];
// Iterate through the array to find the highest and lowest values
for (uint8_t i = 1; i < 6; ++i) {
if (poll_temperature[i] > maximum_temperature) {
maximum_temperature = poll_temperature[i];
}
if (poll_temperature[i] < minimum_temperature) {
minimum_temperature = poll_temperature[i];
}
}
datalayer_battery->status.temperature_min_dC = minimum_temperature * 10;
datalayer_battery->status.temperature_max_dC = maximum_temperature * 10;
if (HVIL_signal > 0) {
set_event(EVENT_HVIL_FAILURE, HVIL_signal);
} else {
clear_event(EVENT_HVIL_FAILURE);
}
//Update webserver more battery info page
memcpy(datalayer_geometryc->BatterySerialNumber, serialnumbers, sizeof(serialnumbers));
memcpy(datalayer_geometryc->ModuleTemperatures, poll_temperature, sizeof(poll_temperature));
memcpy(datalayer_geometryc->BatterySoftwareVersion, poll_software_version, sizeof(poll_software_version));
memcpy(datalayer_geometryc->BatteryHardwareVersion, poll_hardware_version, sizeof(poll_hardware_version));
datalayer_geometryc->soc = poll_soc;
datalayer_geometryc->CC2voltage = poll_cc2_voltage;
datalayer_geometryc->cellMaxVoltageNumber = poll_cell_max_voltage_number;
datalayer_geometryc->cellMinVoltageNumber = poll_cell_min_voltage_number;
datalayer_geometryc->cellTotalAmount = poll_amount_cells;
datalayer_geometryc->specificialVoltage = poll_specificial_voltage;
datalayer_geometryc->unknown1 = poll_unknown1;
datalayer_geometryc->rawSOCmax = poll_raw_soc_max;
datalayer_geometryc->rawSOCmin = poll_raw_soc_min;
datalayer_geometryc->unknown4 = poll_unknown4;
datalayer_geometryc->capModMax = poll_cap_module_max;
datalayer_geometryc->capModMin = poll_cap_module_min;
datalayer_geometryc->unknown7 = poll_unknown7;
datalayer_geometryc->unknown8 = poll_unknown8;
}
const unsigned char crctable[256] = { // CRC8_SAE_J1850_ZER0 formula,0x2F Poly,initial value 0xFF,Final XOR value 0xFF
0x00, 0x2F, 0x5E, 0x71, 0xBC, 0x93, 0xE2, 0xCD, 0x57, 0x78, 0x09, 0x26, 0xEB, 0xC4, 0xB5, 0x9A, 0xAE, 0x81, 0xF0,
0xDF, 0x12, 0x3D, 0x4C, 0x63, 0xF9, 0xD6, 0xA7, 0x88, 0x45, 0x6A, 0x1B, 0x34, 0x73, 0x5C, 0x2D, 0x02, 0xCF, 0xE0,
0x91, 0xBE, 0x24, 0x0B, 0x7A, 0x55, 0x98, 0xB7, 0xC6, 0xE9, 0xDD, 0xF2, 0x83, 0xAC, 0x61, 0x4E, 0x3F, 0x10, 0x8A,
0xA5, 0xD4, 0xFB, 0x36, 0x19, 0x68, 0x47, 0xE6, 0xC9, 0xB8, 0x97, 0x5A, 0x75, 0x04, 0x2B, 0xB1, 0x9E, 0xEF, 0xC0,
0x0D, 0x22, 0x53, 0x7C, 0x48, 0x67, 0x16, 0x39, 0xF4, 0xDB, 0xAA, 0x85, 0x1F, 0x30, 0x41, 0x6E, 0xA3, 0x8C, 0xFD,
0xD2, 0x95, 0xBA, 0xCB, 0xE4, 0x29, 0x06, 0x77, 0x58, 0xC2, 0xED, 0x9C, 0xB3, 0x7E, 0x51, 0x20, 0x0F, 0x3B, 0x14,
0x65, 0x4A, 0x87, 0xA8, 0xD9, 0xF6, 0x6C, 0x43, 0x32, 0x1D, 0xD0, 0xFF, 0x8E, 0xA1, 0xE3, 0xCC, 0xBD, 0x92, 0x5F,
0x70, 0x01, 0x2E, 0xB4, 0x9B, 0xEA, 0xC5, 0x08, 0x27, 0x56, 0x79, 0x4D, 0x62, 0x13, 0x3C, 0xF1, 0xDE, 0xAF, 0x80,
0x1A, 0x35, 0x44, 0x6B, 0xA6, 0x89, 0xF8, 0xD7, 0x90, 0xBF, 0xCE, 0xE1, 0x2C, 0x03, 0x72, 0x5D, 0xC7, 0xE8, 0x99,
0xB6, 0x7B, 0x54, 0x25, 0x0A, 0x3E, 0x11, 0x60, 0x4F, 0x82, 0xAD, 0xDC, 0xF3, 0x69, 0x46, 0x37, 0x18, 0xD5, 0xFA,
0x8B, 0xA4, 0x05, 0x2A, 0x5B, 0x74, 0xB9, 0x96, 0xE7, 0xC8, 0x52, 0x7D, 0x0C, 0x23, 0xEE, 0xC1, 0xB0, 0x9F, 0xAB,
0x84, 0xF5, 0xDA, 0x17, 0x38, 0x49, 0x66, 0xFC, 0xD3, 0xA2, 0x8D, 0x40, 0x6F, 0x1E, 0x31, 0x76, 0x59, 0x28, 0x07,
0xCA, 0xE5, 0x94, 0xBB, 0x21, 0x0E, 0x7F, 0x50, 0x9D, 0xB2, 0xC3, 0xEC, 0xD8, 0xF7, 0x86, 0xA9, 0x64, 0x4B, 0x3A,
0x15, 0x8F, 0xA0, 0xD1, 0xFE, 0x33, 0x1C, 0x6D, 0x42};
bool is_message_corrupt(CAN_frame* rx_frame) {
uint8_t crc = 0xFF; // Initial value
for (uint8_t j = 0; j < 7; j++) {
crc = crctable[crc ^ rx_frame->data.u8[j]];
}
crc = (crc ^ 0xFF); // Final XOR
return crc != rx_frame->data.u8[7];
}
void GeelyGeometryCBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
switch (rx_frame.ID) {
case 0x0B0: //10ms
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
if (is_message_corrupt(&rx_frame)) {
datalayer.battery2.status.CAN_error_counter++;
break; //Message content malformed, abort reading data from it
}
//Contains:
//HVIL Signal
// - HVIL not connected: 000000B0 00 8 10 06 00 00 00 00 8E 31
// - HVIL connected: 000000B0 00 8 10 00 00 00 00 00 82 0D
// Based on this, the two HVIL is most likely frame1
HVIL_signal = (rx_frame.data.u8[1] & 0x0F);
//BatteryDchgSysFaultLevel
//ChgFaultLevel
//frame7, CRC
//frame6, low byte counter 0-F
break;
case 0x178: //10ms (64 13 88 00 0E 30 0A 85)
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
if (is_message_corrupt(&rx_frame)) {
datalayer.battery2.status.CAN_error_counter++;
break; //Message content malformed, abort reading data from it
}
battery_voltage = ((rx_frame.data.u8[4] & 0x1F) << 8) | rx_frame.data.u8[5];
//frame7, CRC
//frame6, low byte counter 0-F
break;
case 0x179: //20ms (3E 52 BA 5D A4 3F 0C D9)
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
if (is_message_corrupt(&rx_frame)) {
datalayer.battery2.status.CAN_error_counter++;
break; //Message content malformed, abort reading data from it
}
//2BA = 69.8 //Potentially charge power allowed
//frame7, CRC
//frame6, low byte counter 0-F
break;
case 0x17A: //100ms (Battery 01 B4 52 28 4A 46 6E AE)
//(Car log 0A 3D EE F1 BD C6 67 F7)
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
if (is_message_corrupt(&rx_frame)) {
datalayer.battery2.status.CAN_error_counter++;
break; //Message content malformed, abort reading data from it
}
//frame7, CRC
//frame6, low byte counter 0-F
break;
case 0x17B: //20ms (00 00 10 00 0F FE 03 C9) (car is the same, static)
if (is_message_corrupt(&rx_frame)) {
datalayer.battery2.status.CAN_error_counter++;
break; //Message content malformed, abort reading data from it
}
//frame7, CRC
//frame6, low byte counter 0-F
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x210: //100ms (38 04 3A 01 38 22 22 39)
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
if (is_message_corrupt(&rx_frame)) {
datalayer.battery2.status.CAN_error_counter++;
break; //Message content malformed, abort reading data from it
}
discharge_power_allowed = ((rx_frame.data.u8[1] & 0x0F) << 8) | rx_frame.data.u8[2]; //TODO, not confirmed
//43A = 108.2kW potentially discharge power allowed
//frame7, CRC
//frame6, counter 0 - 0x22
break;
case 0x211: //100ms (00 D8 C6 00 00 00 0F 3A)
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
if (is_message_corrupt(&rx_frame)) {
datalayer.battery2.status.CAN_error_counter++;
break; //Message content malformed, abort reading data from it
}
//frame7, CRC
//frame6, low byte counter 0-F
break;
case 0x212: //500ms (Completely empty)
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x351: //100ms (4A 31 71 B8 6E F8 84 00)
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x352: //500ms (76 78 00 00 82 FF FF 00)
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x353: //500ms (00 00 00 00 62 00 EA 5D) (car 00 00 00 00 00 00 E6 04)
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x354: //500ms (Completely empty)
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x355: //500ms (89 4A 03 5C 39 06 04 00)
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x356: //1s (6B 09 0C 69 0A F1 D3 86)
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x357: //20ms (18 17 6F 20 00 00 00 00)
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//Frame 0 and 1 seems to count something large
break;
case 0x358: //1s (03 DF 10 3C DA 0E 20 DE)
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x359: //1s (1F 40 00 00 00 00 00 36)
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//Frame7 loops 01-21-22-36-01-21...
break;
case 0x35B: //200ms (Serialnumbers)
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
mux = rx_frame.data.u8[0];
switch (mux) {
case 0x01:
serialnumbers[0] = rx_frame.data.u8[1];
serialnumbers[1] = rx_frame.data.u8[2];
serialnumbers[2] = rx_frame.data.u8[3];
serialnumbers[3] = rx_frame.data.u8[4];
serialnumbers[4] = rx_frame.data.u8[5];
serialnumbers[5] = rx_frame.data.u8[6];
serialnumbers[6] = rx_frame.data.u8[7];
break;
case 0x02:
serialnumbers[7] = rx_frame.data.u8[1];
serialnumbers[8] = rx_frame.data.u8[2];
serialnumbers[9] = rx_frame.data.u8[3];
serialnumbers[10] = rx_frame.data.u8[4];
serialnumbers[11] = rx_frame.data.u8[5];
serialnumbers[12] = rx_frame.data.u8[6];
serialnumbers[13] = rx_frame.data.u8[7];
break;
case 0x03:
serialnumbers[14] = rx_frame.data.u8[1];
serialnumbers[15] = rx_frame.data.u8[2];
serialnumbers[16] = rx_frame.data.u8[3];
serialnumbers[17] = rx_frame.data.u8[4];
serialnumbers[18] = rx_frame.data.u8[5];
serialnumbers[19] = rx_frame.data.u8[6];
serialnumbers[20] = rx_frame.data.u8[7];
break;
case 0x04:
serialnumbers[21] = rx_frame.data.u8[1];
serialnumbers[22] = rx_frame.data.u8[2];
serialnumbers[23] = rx_frame.data.u8[3];
serialnumbers[24] = rx_frame.data.u8[4];
serialnumbers[25] = rx_frame.data.u8[5];
serialnumbers[26] = rx_frame.data.u8[6];
serialnumbers[27] = rx_frame.data.u8[7];
break;
default:
break;
}
break;
case 0x424: //500ms (24 10 01 01 02 00 00 00)
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x7EA:
if (rx_frame.data.u8[0] == 0x10) { //Multiframe response, send ACK
transmit_can_frame(&GEELY_ACK, can_config.battery);
//Multiframe has the poll reply slightly different location
incoming_poll = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
}
if (rx_frame.data.u8[0] < 0x10) { //One line response
incoming_poll = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
switch (incoming_poll) {
case POLL_SOC:
poll_soc = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CC2_VOLTAGE:
poll_cc2_voltage = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_MAX_VOLTAGE_NUMBER:
poll_cell_max_voltage_number = rx_frame.data.u8[4];
break;
case POLL_CELL_MIN_VOLTAGE_NUMBER:
poll_cell_min_voltage_number = rx_frame.data.u8[4];
break;
case POLL_AMOUNT_CELLS:
poll_amount_cells = rx_frame.data.u8[4];
datalayer_battery->info.number_of_cells = poll_amount_cells;
break;
case POLL_SPECIFICIAL_VOLTAGE:
poll_specificial_voltage = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_UNKNOWN_1:
poll_unknown1 = rx_frame.data.u8[4];
break;
case POLL_RAW_SOC_MAX:
poll_raw_soc_max = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_RAW_SOC_MIN:
poll_raw_soc_min = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_UNKNOWN_4:
poll_unknown4 = rx_frame.data.u8[4];
break;
case POLL_CAPACITY_MODULE_MAX:
poll_cap_module_max = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CAPACITY_MODULE_MIN:
poll_cap_module_min = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_UNKNOWN_7:
poll_unknown7 = rx_frame.data.u8[4];
break;
case POLL_UNKNOWN_8:
poll_unknown8 = rx_frame.data.u8[4];
break;
default:
break;
}
}
switch (incoming_poll) //Multiframe response
{
case POLL_MULTI_TEMPS:
switch (rx_frame.data.u8[0]) {
case 0x10:
poll_temperature[0] = (rx_frame.data.u8[5] - TEMP_OFFSET);
poll_temperature[1] = (rx_frame.data.u8[6] - TEMP_OFFSET);
poll_temperature[2] = (rx_frame.data.u8[7] - TEMP_OFFSET);
break;
case 0x21:
poll_temperature[3] = (rx_frame.data.u8[1] - TEMP_OFFSET);
poll_temperature[4] = (rx_frame.data.u8[2] - TEMP_OFFSET);
poll_temperature[5] = (rx_frame.data.u8[3] - TEMP_OFFSET);
break;
default:
break;
}
break;
case POLL_MULTI_SOFTWARE_VERSION:
switch (rx_frame.data.u8[0]) {
case 0x10:
poll_software_version[0] = rx_frame.data.u8[5];
poll_software_version[1] = rx_frame.data.u8[6];
poll_software_version[2] = rx_frame.data.u8[7];
break;
case 0x21:
poll_software_version[3] = rx_frame.data.u8[1];
poll_software_version[4] = rx_frame.data.u8[2];
poll_software_version[5] = rx_frame.data.u8[3];
poll_software_version[6] = rx_frame.data.u8[4];
poll_software_version[7] = rx_frame.data.u8[5];
poll_software_version[8] = rx_frame.data.u8[6];
poll_software_version[9] = rx_frame.data.u8[7];
break;
case 0x22:
poll_software_version[10] = rx_frame.data.u8[1];
poll_software_version[11] = rx_frame.data.u8[2];
poll_software_version[12] = rx_frame.data.u8[3];
poll_software_version[13] = rx_frame.data.u8[4];
poll_software_version[14] = rx_frame.data.u8[5];
poll_software_version[15] = rx_frame.data.u8[6];
break;
case 0x23:
break;
default:
break;
}
break;
case POLL_MULTI_HARDWARE_VERSION:
switch (rx_frame.data.u8[0]) {
case 0x10:
poll_hardware_version[0] = rx_frame.data.u8[5];
poll_hardware_version[1] = rx_frame.data.u8[6];
poll_hardware_version[2] = rx_frame.data.u8[7];
break;
case 0x21:
poll_hardware_version[3] = rx_frame.data.u8[1];
poll_hardware_version[4] = rx_frame.data.u8[2];
poll_hardware_version[5] = rx_frame.data.u8[3];
poll_hardware_version[6] = rx_frame.data.u8[4];
poll_hardware_version[7] = rx_frame.data.u8[5];
poll_hardware_version[8] = rx_frame.data.u8[6];
poll_hardware_version[9] = rx_frame.data.u8[7];
break;
case 0x22:
poll_hardware_version[10] = rx_frame.data.u8[1];
poll_hardware_version[11] = rx_frame.data.u8[2];
poll_hardware_version[12] = rx_frame.data.u8[3];
poll_hardware_version[13] = rx_frame.data.u8[4];
poll_hardware_version[14] = rx_frame.data.u8[5];
poll_hardware_version[15] = rx_frame.data.u8[6];
break;
case 0x23:
break;
default:
break;
}
break;
default:
//Not a multiframe response, do nothing
break;
}
break;
default:
break;
}
}
uint8_t calc_crc8_geely(CAN_frame* rx_frame) {
uint8_t crc = 0xFF; // Initial value
for (uint8_t j = 0; j < 7; j++) {
crc = crctable[crc ^ rx_frame->data.u8[j]];
}
return crc ^ 0xFF; // Final XOR
}
void GeelyGeometryCBattery::transmit_can(unsigned long currentMillis) {
// Send 10ms CAN Message
if (currentMillis - previousMillis10 >= INTERVAL_10_MS) {
previousMillis10 = currentMillis;
GEELY_191.data.u8[6] = ((GEELY_191.data.u8[6] & 0xF0) | counter_10ms);
GEELY_191.data.u8[7] = calc_crc8_geely(&GEELY_191);
GEELY_0A6.data.u8[6] = ((GEELY_0A6.data.u8[6] & 0xF0) | counter_10ms);
GEELY_0A6.data.u8[7] = calc_crc8_geely(&GEELY_0A6);
GEELY_165.data.u8[6] = ((GEELY_165.data.u8[6] & 0xF0) | counter_10ms);
GEELY_165.data.u8[7] = calc_crc8_geely(&GEELY_165);
GEELY_1A4.data.u8[6] = ((GEELY_1A4.data.u8[6] & 0xF0) | counter_10ms);
GEELY_1A4.data.u8[7] = calc_crc8_geely(&GEELY_1A4);
GEELY_162.data.u8[6] = ((GEELY_162.data.u8[6] & 0xF0) | counter_10ms);
GEELY_162.data.u8[7] = calc_crc8_geely(&GEELY_162);
GEELY_1A5.data.u8[6] = ((GEELY_1A5.data.u8[6] & 0xF0) | counter_10ms);
GEELY_1A5.data.u8[7] = calc_crc8_geely(&GEELY_1A5);
GEELY_220.data.u8[6] = ((GEELY_220.data.u8[6] & 0xF0) | counter_10ms);
GEELY_220.data.u8[7] = calc_crc8_geely(&GEELY_220);
GEELY_0E0.data.u8[4] = ((GEELY_0E0.data.u8[4] & 0xF0) | counter_10ms); //unique
GEELY_0E0.data.u8[5] = calc_crc8_geely(&GEELY_0E0); //unique
counter_10ms = (counter_10ms + 1) % 17; // 0-1-...F-0-1 etc.
transmit_can_frame(&GEELY_191, can_config.battery);
transmit_can_frame(&GEELY_0A6, can_config.battery);
transmit_can_frame(&GEELY_160, can_config.battery);
transmit_can_frame(&GEELY_165, can_config.battery);
transmit_can_frame(&GEELY_1A4, can_config.battery);
transmit_can_frame(&GEELY_162, can_config.battery); //CONFIRMED MANDATORY! VCU message
transmit_can_frame(&GEELY_1A5, can_config.battery);
transmit_can_frame(&GEELY_220, can_config.battery); //CONFIRMED MANDATORY! OBC message
transmit_can_frame(&GEELY_0E0, can_config.battery);
}
if (currentMillis - previousMillis20 >= INTERVAL_20_MS) {
previousMillis20 = currentMillis;
GEELY_145.data.u8[6] = ((GEELY_145.data.u8[6] & 0xF0) | counter_10ms);
GEELY_145.data.u8[7] = calc_crc8_geely(&GEELY_145);
GEELY_150.data.u8[6] = ((GEELY_150.data.u8[6] & 0xF0) | counter_10ms);
GEELY_150.data.u8[7] = calc_crc8_geely(&GEELY_150);
counter_20ms = (counter_20ms + 1) % 17; // 0-1-...F-0-1 etc.
transmit_can_frame(&GEELY_145, can_config.battery); //CONFIRMED MANDATORY! shifter
transmit_can_frame(&GEELY_0F9, can_config.battery); //CONFIRMED MANDATORY! shifter
transmit_can_frame(&GEELY_0FA, can_config.battery); //Might be unnecessary, not in workshop manual
transmit_can_frame(&GEELY_197, can_config.battery); //Might be unnecessary, not in workshop manual
transmit_can_frame(&GEELY_150, can_config.battery);
}
if (currentMillis - previousMillis50 >= INTERVAL_50_MS) {
previousMillis50 = currentMillis;
GEELY_1A3.data.u8[6] = ((GEELY_1A3.data.u8[6] & 0xF0) | counter_10ms);
GEELY_1A3.data.u8[7] = calc_crc8_geely(&GEELY_1A3);
GEELY_0A8.data.u8[6] = ((GEELY_0A8.data.u8[6] & 0xF0) | counter_10ms);
GEELY_0A8.data.u8[7] = calc_crc8_geely(&GEELY_0A8);
counter_50ms = (counter_50ms + 1) % 17; // 0-1-...F-0-1 etc.
transmit_can_frame(&GEELY_1B2, can_config.battery);
transmit_can_frame(&GEELY_221, can_config.battery); //CONFIRMED MANDATORY! OBC message
//transmit_can_frame(&GEELY_1A3, can_config.battery); //Might be unnecessary, radar info
transmit_can_frame(&GEELY_1A7, can_config.battery); //Might be unnecessary
transmit_can_frame(&GEELY_0A8, can_config.battery); //CONFIRMED MANDATORY! IPU message
transmit_can_frame(&GEELY_1F2, can_config.battery); //Might be unnecessary, not in manual
transmit_can_frame(&GEELY_1A6, can_config.battery); //Might be unnecessary, not in manual
}
// Send 100ms CAN Message
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
previousMillis100 = currentMillis;
GEELY_0A8.data.u8[6] = ((GEELY_0A8.data.u8[6] & 0x0F) | (counter_10ms << 4)); //unique bitshift
GEELY_0A8.data.u8[7] = calc_crc8_geely(&GEELY_0A8);
counter_100ms = (counter_100ms + 1) % 17; // 0-1-...F-0-1 etc.
transmit_can_frame(&GEELY_222, can_config.battery); //CONFIRMED MANDATORY! OBC message
//transmit_can_frame(&GEELY_2D2, can_config.battery); //Might be unnecessary, seat info
transmit_can_frame(&GEELY_292, can_config.battery); //CONFIRMED MANDATORY! T-BOX
}
// Send 200ms CAN Message
if (currentMillis - previousMillis200 >= INTERVAL_200_MS) {
previousMillis200 = currentMillis;
switch (poll_pid) {
case POLL_SOC:
GEELY_POLL.data.u8[2] = (uint8_t)(POLL_SOC >> 8);
GEELY_POLL.data.u8[3] = (uint8_t)POLL_SOC;
poll_pid = POLL_CC2_VOLTAGE;
break;
case POLL_CC2_VOLTAGE:
GEELY_POLL.data.u8[2] = (uint8_t)(POLL_CC2_VOLTAGE >> 8);
GEELY_POLL.data.u8[3] = (uint8_t)POLL_CC2_VOLTAGE;
poll_pid = POLL_CELL_MAX_VOLTAGE_NUMBER;
break;
case POLL_CELL_MAX_VOLTAGE_NUMBER:
GEELY_POLL.data.u8[2] = (uint8_t)(POLL_CELL_MAX_VOLTAGE_NUMBER >> 8);
GEELY_POLL.data.u8[3] = (uint8_t)POLL_CELL_MAX_VOLTAGE_NUMBER;
poll_pid = POLL_CELL_MIN_VOLTAGE_NUMBER;
break;
case POLL_CELL_MIN_VOLTAGE_NUMBER:
GEELY_POLL.data.u8[2] = (uint8_t)(POLL_CELL_MIN_VOLTAGE_NUMBER >> 8);
GEELY_POLL.data.u8[3] = (uint8_t)POLL_CELL_MIN_VOLTAGE_NUMBER;
poll_pid = POLL_AMOUNT_CELLS;
break;
case POLL_AMOUNT_CELLS:
GEELY_POLL.data.u8[2] = (uint8_t)(POLL_AMOUNT_CELLS >> 8);
GEELY_POLL.data.u8[3] = (uint8_t)POLL_AMOUNT_CELLS;
poll_pid = POLL_SPECIFICIAL_VOLTAGE;
break;
case POLL_SPECIFICIAL_VOLTAGE:
GEELY_POLL.data.u8[2] = (uint8_t)(POLL_SPECIFICIAL_VOLTAGE >> 8);
GEELY_POLL.data.u8[3] = (uint8_t)POLL_SPECIFICIAL_VOLTAGE;
poll_pid = POLL_UNKNOWN_1;
break;
case POLL_UNKNOWN_1:
GEELY_POLL.data.u8[2] = (uint8_t)(POLL_UNKNOWN_1 >> 8);
GEELY_POLL.data.u8[3] = (uint8_t)POLL_UNKNOWN_1;
poll_pid = POLL_RAW_SOC_MAX;
break;
case POLL_RAW_SOC_MAX:
GEELY_POLL.data.u8[2] = (uint8_t)(POLL_RAW_SOC_MAX >> 8);
GEELY_POLL.data.u8[3] = (uint8_t)POLL_RAW_SOC_MAX;
poll_pid = POLL_RAW_SOC_MIN;
break;
case POLL_RAW_SOC_MIN:
GEELY_POLL.data.u8[2] = (uint8_t)(POLL_RAW_SOC_MIN >> 8);
GEELY_POLL.data.u8[3] = (uint8_t)POLL_RAW_SOC_MIN;
poll_pid = POLL_UNKNOWN_4;
break;
case POLL_UNKNOWN_4:
GEELY_POLL.data.u8[2] = (uint8_t)(POLL_UNKNOWN_4 >> 8);
GEELY_POLL.data.u8[3] = (uint8_t)POLL_UNKNOWN_4;
poll_pid = POLL_CAPACITY_MODULE_MAX;
break;
case POLL_CAPACITY_MODULE_MAX:
GEELY_POLL.data.u8[2] = (uint8_t)(POLL_CAPACITY_MODULE_MAX >> 8);
GEELY_POLL.data.u8[3] = (uint8_t)POLL_CAPACITY_MODULE_MAX;
poll_pid = POLL_CAPACITY_MODULE_MIN;
break;
case POLL_CAPACITY_MODULE_MIN:
GEELY_POLL.data.u8[2] = (uint8_t)(POLL_CAPACITY_MODULE_MIN >> 8);
GEELY_POLL.data.u8[3] = (uint8_t)POLL_CAPACITY_MODULE_MIN;
poll_pid = POLL_UNKNOWN_7;
break;
case POLL_UNKNOWN_7:
GEELY_POLL.data.u8[2] = (uint8_t)(POLL_UNKNOWN_7 >> 8);
GEELY_POLL.data.u8[3] = (uint8_t)POLL_UNKNOWN_7;
poll_pid = POLL_UNKNOWN_8;
break;
case POLL_UNKNOWN_8:
GEELY_POLL.data.u8[2] = (uint8_t)(POLL_UNKNOWN_8 >> 8);
GEELY_POLL.data.u8[3] = (uint8_t)POLL_UNKNOWN_8;
poll_pid = POLL_MULTI_TEMPS;
break;
case POLL_MULTI_TEMPS:
GEELY_POLL.data.u8[2] = (uint8_t)(POLL_MULTI_TEMPS >> 8);
GEELY_POLL.data.u8[3] = (uint8_t)POLL_MULTI_TEMPS;
poll_pid = POLL_MULTI_UNKNOWN_2;
break;
case POLL_MULTI_UNKNOWN_2:
GEELY_POLL.data.u8[2] = (uint8_t)(POLL_MULTI_UNKNOWN_2 >> 8);
GEELY_POLL.data.u8[3] = (uint8_t)POLL_MULTI_UNKNOWN_2;
poll_pid = POLL_MULTI_UNKNOWN_3;
break;
case POLL_MULTI_UNKNOWN_3:
GEELY_POLL.data.u8[2] = (uint8_t)(POLL_MULTI_UNKNOWN_3 >> 8);
GEELY_POLL.data.u8[3] = (uint8_t)POLL_MULTI_UNKNOWN_3;
poll_pid = POLL_MULTI_UNKNOWN_4;
break;
case POLL_MULTI_UNKNOWN_4:
GEELY_POLL.data.u8[2] = (uint8_t)(POLL_MULTI_UNKNOWN_4 >> 8);
GEELY_POLL.data.u8[3] = (uint8_t)POLL_MULTI_UNKNOWN_4;
poll_pid = POLL_MULTI_HARDWARE_VERSION;
break;
case POLL_MULTI_HARDWARE_VERSION:
GEELY_POLL.data.u8[2] = (uint8_t)(POLL_MULTI_HARDWARE_VERSION >> 8);
GEELY_POLL.data.u8[3] = (uint8_t)POLL_MULTI_HARDWARE_VERSION;
poll_pid = POLL_MULTI_SOFTWARE_VERSION;
break;
case POLL_MULTI_SOFTWARE_VERSION:
GEELY_POLL.data.u8[2] = (uint8_t)(POLL_MULTI_SOFTWARE_VERSION >> 8);
GEELY_POLL.data.u8[3] = (uint8_t)POLL_MULTI_SOFTWARE_VERSION;
poll_pid = POLL_SOC;
break;
default:
poll_pid = POLL_SOC;
break;
}
transmit_can_frame(&GEELY_POLL, can_config.battery);
}
}
void GeelyGeometryCBattery::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "Geely Geometry C", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.system.status.battery_allows_contactor_closing = true;
datalayer_battery->info.number_of_cells = 102; //70kWh pack has 102S, startup in this mode
datalayer_battery->info.max_design_voltage_dV = MAX_PACK_VOLTAGE_70_DV; //Startup in extreme ends
datalayer_battery->info.min_design_voltage_dV = MIN_PACK_VOLTAGE_53_DV; //Before pack size determined
datalayer_battery->info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer_battery->info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer_battery->info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
}
#endif

View file

@ -0,0 +1,217 @@
#ifndef GEELY_GEOMETRY_C_BATTERY_H
#define GEELY_GEOMETRY_C_BATTERY_H
#include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h"
#include "../include.h"
#include "CanBattery.h"
#define BATTERY_SELECTED
#define SELECTED_BATTERY_CLASS GeelyGeometryCBattery
#define POLL_SOC 0x4B35
#define POLL_CC2_VOLTAGE 0x4BCF
#define POLL_CELL_MAX_VOLTAGE_NUMBER 0x4B1E
#define POLL_CELL_MIN_VOLTAGE_NUMBER 0x4B20
#define POLL_AMOUNT_CELLS 0x4B07
#define POLL_SPECIFICIAL_VOLTAGE 0x4B05
#define POLL_UNKNOWN_1 0x4BDA //245 on two batteries
#define POLL_RAW_SOC_MAX 0x4BC3
#define POLL_RAW_SOC_MIN 0x4BC4
#define POLL_UNKNOWN_4 0xDF00 //144 (the other battery 143)
#define POLL_CAPACITY_MODULE_MAX 0x4B3D
#define POLL_CAPACITY_MODULE_MIN 0x4B3E
#define POLL_UNKNOWN_7 0x4B24 //1 (the other battery 23)
#define POLL_UNKNOWN_8 0x4B26 //16 (the other battery 33)
#define POLL_MULTI_TEMPS 0x4B0F
#define POLL_MULTI_UNKNOWN_2 0x4B10
#define POLL_MULTI_UNKNOWN_3 0x4B53
#define POLL_MULTI_UNKNOWN_4 0x4B54
#define POLL_MULTI_HARDWARE_VERSION 0x4B6B
#define POLL_MULTI_SOFTWARE_VERSION 0x4B6C
class GeelyGeometryCBattery : public CanBattery {
public:
virtual void setup(void);
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
virtual void update_values();
virtual void transmit_can(unsigned long currentMillis);
private:
const int MAX_PACK_VOLTAGE_70_DV 4420 //70kWh
const int MIN_PACK_VOLTAGE_70_DV 2860 const int MAX_PACK_VOLTAGE_53_DV 4160 //53kWh
const int MIN_PACK_VOLTAGE_53_DV 2700 const int MAX_CELL_DEVIATION_MV 150 const int
MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
const int MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
DATALAYER_BATTERY_TYPE* datalayer_battery;
DATALAYER_INFO_GEELY_GEOMETRY_C* datalayer_geometryc;
CAN_frame GEELY_191 = {.FD = false, //PAS_APA_Status , 10ms
.ext_ID = false,
.DLC = 8,
.ID = 0x191,
.data = {0x00, 0x00, 0x81, 0x20, 0x00, 0x00, 0x00, 0x01}};
CAN_frame GEELY_2D2 = {.FD = false, //DSCU 100ms
.ext_ID = false,
.DLC = 8,
.ID = 0x2D2,
.data = {0x60, 0x8E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GEELY_0A6 = {.FD = false, //VCU 10ms
.ext_ID = false,
.DLC = 8,
.ID = 0x0A6,
.data = {0xFA, 0x0F, 0xA0, 0x00, 0x00, 0xFA, 0x00, 0xE4}};
CAN_frame GEELY_160 = {.FD = false, //VCU 10ms
.ext_ID = false,
.DLC = 8,
.ID = 0x160,
.data = {0x00, 0x01, 0x67, 0xF7, 0xC0, 0x19, 0x00, 0x20}};
CAN_frame GEELY_165 = {.FD = false, //VCU_ModeControl 10ms
.ext_ID = false,
.DLC = 8,
.ID = 0x165,
.data = {0x00, 0x81, 0xA1, 0x00, 0x00, 0x1E, 0x00, 0xD6}};
CAN_frame GEELY_1A4 = {.FD = false, //VCU 10ms
.ext_ID = false,
.DLC = 8,
.ID = 0x1A4,
.data = {0x17, 0x73, 0x17, 0x70, 0x02, 0x1C, 0x00, 0x56}};
CAN_frame GEELY_162 = {.FD = false, //VCU 10ms
.ext_ID = false,
.DLC = 8,
.ID = 0x162,
.data = {0x00, 0x05, 0x06, 0x81, 0x00, 0x09, 0x00, 0xC6}};
CAN_frame GEELY_1A5 = {.FD = false, //VCU 10ms
.ext_ID = false,
.DLC = 8,
.ID = 0x1A5,
.data = {0x17, 0x70, 0x24, 0x0B, 0x00, 0x00, 0x00, 0xF9}};
CAN_frame GEELY_1B2 = {.FD = false, //??? 50ms
.ext_ID = false,
.DLC = 8,
.ID = 0x1B2,
.data = {0x17, 0x70, 0x24, 0x0B, 0x00, 0x00, 0x00, 0xF9}};
CAN_frame GEELY_221 = {.FD = false, //OBC 50ms
.ext_ID = false,
.DLC = 8,
.ID = 0x221,
.data = {0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00}};
CAN_frame GEELY_220 = {.FD = false, //OBC 100ms
.ext_ID = false,
.DLC = 8,
.ID = 0x220,
.data = {0x0B, 0x43, 0x69, 0xF3, 0x3A, 0x10, 0x00, 0x31}};
CAN_frame GEELY_1A3 = {.FD = false, //FRS 50ms
.ext_ID = false,
.DLC = 8,
.ID = 0x1A3,
.data = {0xFF, 0x18, 0x20, 0x00, 0x00, 0x00, 0x00, 0x4F}};
CAN_frame GEELY_1A7 = {.FD = false, //??? 50ms
.ext_ID = false,
.DLC = 8,
.ID = 0x1A7,
.data = {0x00, 0x7F, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x00}};
CAN_frame GEELY_0A8 = {.FD = false, //IPU 100ms
.ext_ID = false,
.DLC = 8,
.ID = 0x0A8,
.data = {0x00, 0x2E, 0xDC, 0x4E, 0x20, 0x00, 0x20, 0xA2}};
CAN_frame GEELY_1F2 = {.FD = false, //??? 50ms
.ext_ID = false,
.DLC = 8,
.ID = 0x1F2,
.data = {0x9B, 0xA3, 0x99, 0xA2, 0x41, 0x42, 0x41, 0x42}};
CAN_frame GEELY_222 = {.FD = false, //OBC 100ms
.ext_ID = false,
.DLC = 8,
.ID = 0x222,
.data = {0x00, 0x00, 0x00, 0xFF, 0xF8, 0x00, 0x00, 0x00}};
CAN_frame GEELY_1A6 = {.FD = false, //OBC 100ms
.ext_ID = false,
.DLC = 8,
.ID = 0x1A6,
.data = {0x00, 0x7F, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x00}};
CAN_frame GEELY_145 = {.FD = false, //EGSM 20ms
.ext_ID = false,
.DLC = 8,
.ID = 0x145,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A}};
CAN_frame GEELY_0E0 = {.FD = false, //IPU 10ms
.ext_ID = false,
.DLC = 8,
.ID = 0x0E0,
.data = {0xFF, 0x09, 0x00, 0xE0, 0x00, 0x8F, 0x00, 0x00}};
CAN_frame GEELY_0F9 = {.FD = false, //??? 20ms
.ext_ID = false,
.DLC = 8,
.ID = 0x0F9,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GEELY_292 = {.FD = false, //T-BOX 100ms
.ext_ID = false,
.DLC = 8,
.ID = 0x292,
.data = {0x00, 0x00, 0x00, 0x1F, 0xE7, 0xE7, 0x00, 0xBC}};
CAN_frame GEELY_0FA = {.FD = false, //??? 20ms
.ext_ID = false,
.DLC = 8,
.ID = 0x0FA,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GEELY_197 = {.FD = false, //??? 20ms
.ext_ID = false,
.DLC = 8,
.ID = 0x197,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A}};
CAN_frame GEELY_150 = {.FD = false, //EPS 20ms
.ext_ID = false,
.DLC = 8,
.ID = 0x150,
.data = {0x7E, 0x00, 0x24, 0x00, 0x01, 0x01, 0x00, 0xA9}};
CAN_frame GEELY_POLL = {.FD = false, //Polling frame
.ext_ID = false,
.DLC = 8,
.ID = 0x7E2,
.data = {0x03, 0x22, 0x4B, 0xDA, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GEELY_ACK = {.FD = false, //Ack frame
.ext_ID = false,
.DLC = 8,
.ID = 0x7E2,
.data = {0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
uint16_t poll_pid = POLL_SOC;
uint16_t incoming_poll = 0;
uint8_t counter_10ms = 0;
uint8_t counter_20ms = 0;
uint8_t counter_50ms = 0;
uint8_t counter_100ms = 0;
unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was sent
unsigned long previousMillis20 = 0; // will store last time a 20ms CAN Message was sent
unsigned long previousMillis50 = 0; // will store last time a 50ms CAN Message was sent
unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was sent
unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was sent
uint8_t mux = 0;
uint16_t battery_voltage = 3700;
int16_t maximum_temperature = 0;
int16_t minimum_temperature = 0;
uint8_t HVIL_signal = 0;
uint8_t serialnumbers[28] = {0};
uint16_t maximum_cell_voltage = 3700;
uint16_t discharge_power_allowed = 0;
uint16_t poll_soc = 0;
uint16_t poll_cc2_voltage = 0;
uint16_t poll_cell_max_voltage_number = 0;
uint16_t poll_cell_min_voltage_number = 0;
uint16_t poll_amount_cells = 0;
uint16_t poll_specificial_voltage = 0;
uint16_t poll_unknown1 = 0;
uint16_t poll_raw_soc_max = 0;
uint16_t poll_raw_soc_min = 0;
uint16_t poll_unknown4 = 0;
uint16_t poll_cap_module_max = 0;
uint16_t poll_cap_module_min = 0;
uint16_t poll_unknown7 = 0;
uint16_t poll_unknown8 = 0;
int16_t poll_temperature[6] = {0};
#define TEMP_OFFSET 30 //TODO, not calibrated yet, best guess
uint8_t poll_software_version[16] = {0};
uint8_t poll_hardware_version[16] = {0};
};
#endif

View file

@ -1,14 +1,11 @@
#include "../include.h" #include "../include.h"
#ifdef KIA_E_GMP_BATTERY #ifdef KIA_E_GMP_BATTERY
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "../devboard/utils/events.h" #include "../devboard/utils/events.h"
#include "../lib/pierremolinaro-ACAN2517FD/ACAN2517FD.h" #include "../lib/pierremolinaro-ACAN2517FD/ACAN2517FD.h"
#include "KIA-E-GMP-BATTERY.h" #include "KIA-E-GMP-BATTERY.h"
/* Do not change code below unless you are sure what you are doing */
static unsigned long previousMillis200ms = 0; // will store last time a 200ms CAN Message was send
static unsigned long previousMillis10s = 0; // will store last time a 10s CAN Message was send
const unsigned char crc8_table[256] = const unsigned char crc8_table[256] =
{ // CRC8_SAE_J1850_ZER0 formula,0x1D Poly,initial value 0x3F,Final XOR value varies { // CRC8_SAE_J1850_ZER0 formula,0x1D Poly,initial value 0x3F,Final XOR value varies
0x00, 0x1D, 0x3A, 0x27, 0x74, 0x69, 0x4E, 0x53, 0xE8, 0xF5, 0xD2, 0xCF, 0x9C, 0x81, 0xA6, 0xBB, 0xCD, 0xD0, 0x00, 0x1D, 0x3A, 0x27, 0x74, 0x69, 0x4E, 0x53, 0xE8, 0xF5, 0xD2, 0xCF, 0x9C, 0x81, 0xA6, 0xBB, 0xCD, 0xD0,
@ -27,42 +24,6 @@ const unsigned char crc8_table[256] =
0x60, 0x7D, 0x2E, 0x33, 0x14, 0x09, 0x7F, 0x62, 0x45, 0x58, 0x0B, 0x16, 0x31, 0x2C, 0x97, 0x8A, 0xAD, 0xB0, 0x60, 0x7D, 0x2E, 0x33, 0x14, 0x09, 0x7F, 0x62, 0x45, 0x58, 0x0B, 0x16, 0x31, 0x2C, 0x97, 0x8A, 0xAD, 0xB0,
0xE3, 0xFE, 0xD9, 0xC4}; 0xE3, 0xFE, 0xD9, 0xC4};
static uint16_t inverterVoltageFrameHigh = 0;
static uint16_t inverterVoltage = 0;
static uint16_t soc_calculated = 0;
static uint16_t SOC_BMS = 0;
static uint16_t SOC_Display = 0;
static uint16_t SOC_estimated_lowest = 0;
static uint16_t SOC_estimated_highest = 0;
static uint16_t batterySOH = 1000;
static uint16_t CellVoltMax_mV = 3700;
static uint16_t CellVoltMin_mV = 3700;
static uint16_t batteryVoltage = 6700;
static int16_t leadAcidBatteryVoltage = 120;
static int16_t batteryAmps = 0;
static int16_t temperatureMax = 0;
static int16_t temperatureMin = 0;
static int16_t allowedDischargePower = 0;
static int16_t allowedChargePower = 0;
static int16_t poll_data_pid = 0;
static uint8_t CellVmaxNo = 0;
static uint8_t CellVminNo = 0;
static uint8_t batteryManagementMode = 0;
static uint8_t BMS_ign = 0;
static uint8_t batteryRelay = 0;
static uint8_t waterleakageSensor = 164;
static bool startedUp = false;
static bool ok_start_polling_battery = false;
static uint8_t counter_200 = 0;
static uint8_t KIA_7E4_COUNTER = 0x01;
static int8_t temperature_water_inlet = 0;
static int8_t powerRelayTemperature = 0;
static int8_t heatertemp = 0;
static bool set_voltage_limits = false;
static uint8_t ticks_200ms_counter = 0;
static uint8_t EGMP_1CF_counter = 0;
static uint8_t EGMP_3XF_counter = 0;
// Define the data points for %SOC depending on cell voltage // Define the data points for %SOC depending on cell voltage
const uint8_t numPoints = 100; const uint8_t numPoints = 100;
@ -107,7 +68,7 @@ uint16_t estimateSOCFromCell(uint16_t cellVoltage) {
} }
// Simplified version of the pack-based SOC estimation with compensation // Simplified version of the pack-based SOC estimation with compensation
uint16_t estimateSOC(uint16_t packVoltage, uint16_t cellCount, int16_t currentAmps) { uint16_t KiaEGmpBattery::estimateSOC(uint16_t packVoltage, uint16_t cellCount, int16_t currentAmps) {
// If cell count is still the default 192 but we haven't confirmed it yet // If cell count is still the default 192 but we haven't confirmed it yet
if (!set_voltage_limits && cellCount == 192) { if (!set_voltage_limits && cellCount == 192) {
// Fall back to BMS-reported SOC while cell count is uncertain // Fall back to BMS-reported SOC while cell count is uncertain
@ -161,7 +122,7 @@ uint16_t selectSOC(uint16_t SOC_low, uint16_t SOC_high) {
} }
/* These messages are needed for contactor closing */ /* These messages are needed for contactor closing */
unsigned long startMillis; unsigned long startMillis = 0;
uint8_t messageIndex = 0; uint8_t messageIndex = 0;
uint8_t messageDelays[63] = {0, 0, 5, 10, 10, 15, 19, 19, 20, 20, 25, 30, 30, 35, 40, 40, uint8_t messageDelays[63] = {0, 0, 5, 10, 10, 15, 19, 19, 20, 20, 25, 30, 30, 35, 40, 40,
45, 49, 49, 50, 50, 52, 53, 53, 54, 55, 60, 60, 65, 67, 67, 70, 45, 49, 49, 50, 50, 52, 53, 53, 54, 55, 60, 60, 65, 67, 67, 70,
@ -695,7 +656,7 @@ void set_cell_voltages(CAN_frame rx_frame, int start, int length, int startCell)
} }
} }
void set_voltage_minmax_limits() { void KiaEGmpBattery::set_voltage_minmax_limits() {
uint8_t valid_cell_count = 0; uint8_t valid_cell_count = 0;
for (int i = 0; i < MAX_AMOUNT_CELLS; ++i) { for (int i = 0; i < MAX_AMOUNT_CELLS; ++i) {
@ -729,7 +690,8 @@ static uint8_t calculateCRC(CAN_frame rx_frame, uint8_t length, uint8_t initial_
return crc; return crc;
} }
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus void KiaEGmpBattery::
update_values() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
#ifdef ESTIMATE_SOC_FROM_CELLVOLTAGE #ifdef ESTIMATE_SOC_FROM_CELLVOLTAGE
// Use the simplified pack-based SOC estimation with proper compensation // Use the simplified pack-based SOC estimation with proper compensation
@ -850,7 +812,7 @@ void update_values_battery() { //This function maps all the values fetched via
#endif #endif
} }
void handle_incoming_can_frame_battery(CAN_frame rx_frame) { void KiaEGmpBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
startedUp = true; startedUp = true;
switch (rx_frame.ID) { switch (rx_frame.ID) {
case 0x055: case 0x055:
@ -1082,7 +1044,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
} }
} }
void transmit_can_battery(unsigned long currentMillis) { void KiaEGmpBattery::transmit_can(unsigned long currentMillis) {
if (startedUp) { if (startedUp) {
//Send Contactor closing message loop //Send Contactor closing message loop
// Check if we still have messages to send // Check if we still have messages to send
@ -1100,7 +1062,7 @@ void transmit_can_battery(unsigned long currentMillis) {
} }
if (messageIndex >= 63) { if (messageIndex >= 63) {
startMillis = millis(); // Start over! startMillis = currentMillis; // Start over!
messageIndex = 0; messageIndex = 0;
} }
@ -1128,12 +1090,9 @@ void transmit_can_battery(unsigned long currentMillis) {
} }
} }
void setup_battery(void) { // Performs one time setup at startup void KiaEGmpBattery::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "Kia/Hyundai EGMP platform", 63); strncpy(datalayer.system.info.battery_protocol, "Kia/Hyundai EGMP platform", 63);
datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.info.battery_protocol[63] = '\0';
startMillis = millis(); // Record the starting time
datalayer.system.status.battery_allows_contactor_closing = true; datalayer.system.status.battery_allows_contactor_closing = true;
datalayer.battery.info.number_of_cells = 192; // TODO: will vary depending on battery datalayer.battery.info.number_of_cells = 192; // TODO: will vary depending on battery
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;

View file

@ -3,27 +3,78 @@
#include <Arduino.h> #include <Arduino.h>
#include "../include.h" #include "../include.h"
#include "../lib/pierremolinaro-ACAN2517FD/ACAN2517FD.h" #include "../lib/pierremolinaro-ACAN2517FD/ACAN2517FD.h"
#include "CanBattery.h"
extern ACAN2517FD canfd; extern ACAN2517FD canfd;
#define ESTIMATE_SOC_FROM_CELLVOLTAGE #define ESTIMATE_SOC_FROM_CELLVOLTAGE
#define BATTERY_SELECTED #define BATTERY_SELECTED
#define MAX_PACK_VOLTAGE_DV 8064 //5000 = 500.0V #define SELECTED_BATTERY_CLASS KiaEGmpBattery
#define MIN_PACK_VOLTAGE_DV 4320
#define MAX_CELL_DEVIATION_MV 150
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2950 //Battery is put into emergency stop if one cell goes below this value
#define MAXCHARGEPOWERALLOWED 10000
#define MAXDISCHARGEPOWERALLOWED 10000
#define RAMPDOWN_SOC 9000 // 90.00 SOC% to start ramping down from max charge power towards 0 at 100.00%
#define RAMPDOWNPOWERALLOWED 10000 // What power we ramp down from towards top balancing
// Used for SoC compensation - Define internal resistance value in milliohms for the entire pack class KiaEGmpBattery : public CanBattery {
// How to calculate: voltage_drop_under_known_load [Volts] / load [Amps] = Resistance public:
#define PACK_INTERNAL_RESISTANCE_MOHM 200 // 200 milliohms for the whole pack virtual void setup(void);
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
virtual void update_values();
virtual void transmit_can(unsigned long currentMillis);
void setup_battery(void); private:
void transmit_can_frame(CAN_frame* tx_frame, int interface); uint16_t estimateSOC(uint16_t packVoltage, uint16_t cellCount, int16_t currentAmps);
void set_voltage_minmax_limits();
static const int MAX_PACK_VOLTAGE_DV = 8064; //5000 = 500.0V
static const int MIN_PACK_VOLTAGE_DV = 4320;
static const int MAX_CELL_DEVIATION_MV = 150;
static const int MAX_CELL_VOLTAGE_MV = 4250; //Battery is put into emergency stop if one cell goes over this value
static const int MIN_CELL_VOLTAGE_MV = 2950; //Battery is put into emergency stop if one cell goes below this value
static const int MAXCHARGEPOWERALLOWED = 10000;
static const int MAXDISCHARGEPOWERALLOWED = 10000;
static const int RAMPDOWN_SOC = 9000; // 90.00 SOC% to start ramping down from max charge power towards 0 at 100.00%
static const int RAMPDOWNPOWERALLOWED = 10000; // What power we ramp down from towards top balancing
// Used for SoC compensation - Define internal resistance value in milliohms for the entire pack
// How to calculate: voltage_drop_under_known_load [Volts] / load [Amps] = Resistance
static const int PACK_INTERNAL_RESISTANCE_MOHM = 200; // 200 milliohms for the whole pack
unsigned long previousMillis200ms = 0; // will store last time a 200ms CAN Message was send
unsigned long previousMillis10s = 0; // will store last time a 10s CAN Message was send
uint16_t inverterVoltageFrameHigh = 0;
uint16_t inverterVoltage = 0;
uint16_t soc_calculated = 0;
uint16_t SOC_BMS = 0;
uint16_t SOC_Display = 0;
uint16_t SOC_estimated_lowest = 0;
uint16_t SOC_estimated_highest = 0;
uint16_t batterySOH = 1000;
uint16_t CellVoltMax_mV = 3700;
uint16_t CellVoltMin_mV = 3700;
uint16_t batteryVoltage = 6700;
int16_t leadAcidBatteryVoltage = 120;
int16_t batteryAmps = 0;
int16_t temperatureMax = 0;
int16_t temperatureMin = 0;
int16_t allowedDischargePower = 0;
int16_t allowedChargePower = 0;
int16_t poll_data_pid = 0;
uint8_t CellVmaxNo = 0;
uint8_t CellVminNo = 0;
uint8_t batteryManagementMode = 0;
uint8_t BMS_ign = 0;
uint8_t batteryRelay = 0;
uint8_t waterleakageSensor = 164;
bool startedUp = false;
bool ok_start_polling_battery = false;
uint8_t counter_200 = 0;
uint8_t KIA_7E4_COUNTER = 0x01;
int8_t temperature_water_inlet = 0;
int8_t powerRelayTemperature = 0;
int8_t heatertemp = 0;
bool set_voltage_limits = false;
uint8_t ticks_200ms_counter = 0;
uint8_t EGMP_1CF_counter = 0;
uint8_t EGMP_3XF_counter = 0;
};
#endif #endif

View file

@ -1,176 +1,36 @@
#include "../include.h" #include "../include.h"
#ifdef KIA_HYUNDAI_64_BATTERY #ifdef KIA_HYUNDAI_64_BATTERY
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h" #include "../datalayer/datalayer_extended.h"
#include "../devboard/utils/events.h" #include "../devboard/utils/events.h"
#include "KIA-HYUNDAI-64-BATTERY.h" #include "KIA-HYUNDAI-64-BATTERY.h"
/* Do not change code below unless you are sure what you are doing */ void KiaHyundai64Battery::
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send update_values() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
static unsigned long previousMillis10 = 0; // will store last time a 10s CAN Message was send
static uint16_t soc_calculated = 0; datalayer_battery->status.real_soc = (SOC_Display * 10); //increase SOC range from 0-100.0 -> 100.00
static uint16_t SOC_BMS = 0;
static uint16_t SOC_Display = 0;
static uint16_t batterySOH = 1000;
static uint16_t CellVoltMax_mV = 3700;
static uint16_t CellVoltMin_mV = 3700;
static uint16_t allowedDischargePower = 0;
static uint16_t allowedChargePower = 0;
static uint16_t batteryVoltage = 0;
static uint16_t inverterVoltageFrameHigh = 0;
static uint16_t inverterVoltage = 0;
static uint16_t cellvoltages_mv[98];
static int16_t leadAcidBatteryVoltage = 120;
static int16_t batteryAmps = 0;
static int16_t temperatureMax = 0;
static int16_t temperatureMin = 0;
static int16_t poll_data_pid = 0;
static bool holdPidCounter = false;
static uint8_t CellVmaxNo = 0;
static uint8_t CellVminNo = 0;
static uint8_t batteryManagementMode = 0;
static uint8_t BMS_ign = 0;
static uint8_t batteryRelay = 0;
static uint8_t waterleakageSensor = 164;
static uint8_t counter_200 = 0;
static int8_t temperature_water_inlet = 0;
static int8_t heatertemp = 0;
static int8_t powerRelayTemperature = 0;
static bool startedUp = false;
#ifdef DOUBLE_BATTERY datalayer_battery->status.soh_pptt = (batterySOH * 10); //Increase decimals from 100.0% -> 100.00%
static uint8_t counter_200_2 = 0;
static uint16_t battery2_soc_calculated = 0;
static uint16_t battery2_SOC_BMS = 0;
static uint16_t battery2_SOC_Display = 0;
static uint16_t battery2_batterySOH = 1000;
static uint16_t battery2_CellVoltMax_mV = 3700;
static uint16_t battery2_CellVoltMin_mV = 3700;
static uint16_t battery2_allowedDischargePower = 0;
static uint16_t battery2_allowedChargePower = 0;
static uint16_t battery2_batteryVoltage = 0;
static uint16_t battery2_inverterVoltageFrameHigh = 0;
static uint16_t battery2_inverterVoltage = 0;
static uint16_t battery2_cellvoltages_mv[98];
static int16_t battery2_leadAcidBatteryVoltage = 120;
static int16_t battery2_batteryAmps = 0;
static int16_t battery2_temperatureMax = 0;
static int16_t battery2_temperatureMin = 0;
static int16_t battery2_poll_data_pid = 0;
static bool battery2_holdPidCounter = false;
static uint8_t battery2_CellVmaxNo = 0;
static uint8_t battery2_CellVminNo = 0;
static uint8_t battery2_batteryManagementMode = 0;
static uint8_t battery2_BMS_ign = 0;
static uint8_t battery2_batteryRelay = 0;
static uint8_t battery2_waterleakageSensor = 164;
static uint8_t battery2_counter_200 = 0;
static int8_t battery2_temperature_water_inlet = 0;
static int8_t battery2_heatertemp = 0;
static int8_t battery2_powerRelayTemperature = 0;
static bool battery2_startedUp = false;
CAN_frame KIA_HYUNDAI_200_2 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x200,
.data = {0x00, 0x80, 0xD8, 0x04, 0x00, 0x17, 0xD0, 0x00}}; //2nd battery
#endif //DOUBLE_BATTERY
CAN_frame KIA_HYUNDAI_200 = {.FD = false, datalayer_battery->status.voltage_dV = batteryVoltage; //value is *10 (3700 = 370.0)
.ext_ID = false,
.DLC = 8,
.ID = 0x200,
.data = {0x00, 0x80, 0xD8, 0x04, 0x00, 0x17, 0xD0, 0x00}};
CAN_frame KIA_HYUNDAI_523 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x523,
.data = {0x08, 0x38, 0x36, 0x36, 0x33, 0x34, 0x00, 0x01}};
CAN_frame KIA_HYUNDAI_524 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x524,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
//553 Needed frame 200ms
CAN_frame KIA64_553 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x553,
.data = {0x04, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00}};
//57F Needed frame 100ms
CAN_frame KIA64_57F = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x57F,
.data = {0x80, 0x0A, 0x72, 0x00, 0x00, 0x00, 0x00, 0x72}};
//Needed frame 100ms
CAN_frame KIA64_2A1 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x2A1,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame KIA64_7E4_id1 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E4,
.data = {0x03, 0x22, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00}}; //Poll PID 03 22 01 01
CAN_frame KIA64_7E4_id2 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E4,
.data = {0x03, 0x22, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00}}; //Poll PID 03 22 01 02
CAN_frame KIA64_7E4_id3 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E4,
.data = {0x03, 0x22, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00}}; //Poll PID 03 22 01 03
CAN_frame KIA64_7E4_id4 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E4,
.data = {0x03, 0x22, 0x01, 0x04, 0x00, 0x00, 0x00, 0x00}}; //Poll PID 03 22 01 04
CAN_frame KIA64_7E4_id5 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E4,
.data = {0x03, 0x22, 0x01, 0x05, 0x00, 0x00, 0x00, 0x00}}; //Poll PID 03 22 01 05
CAN_frame KIA64_7E4_id6 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E4,
.data = {0x03, 0x22, 0x01, 0x06, 0x00, 0x00, 0x00, 0x00}}; //Poll PID 03 22 01 06
CAN_frame KIA64_7E4_ack = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E4,
.data = {0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; //Ack frame, correct PID is returned
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus datalayer_battery->status.current_dA = -batteryAmps; //value is *10 (150 = 15.0) , invert the sign
datalayer.battery.status.real_soc = (SOC_Display * 10); //increase SOC range from 0-100.0 -> 100.00 datalayer_battery->status.remaining_capacity_Wh = static_cast<uint32_t>(
(static_cast<double>(datalayer_battery->status.real_soc) / 10000) * datalayer_battery->info.total_capacity_Wh);
datalayer.battery.status.soh_pptt = (batterySOH * 10); //Increase decimals from 100.0% -> 100.00% datalayer_battery->status.max_charge_power_W = allowedChargePower * 10;
datalayer.battery.status.voltage_dV = batteryVoltage; //value is *10 (3700 = 370.0) datalayer_battery->status.max_discharge_power_W = allowedDischargePower * 10;
datalayer.battery.status.current_dA = -batteryAmps; //value is *10 (150 = 15.0) , invert the sign datalayer_battery->status.temperature_min_dC = (int8_t)temperatureMin * 10; //Increase decimals, 17C -> 17.0C
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>( datalayer_battery->status.temperature_max_dC = (int8_t)temperatureMax * 10; //Increase decimals, 18C -> 18.0C
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
datalayer.battery.status.max_charge_power_W = allowedChargePower * 10; datalayer_battery->status.cell_max_voltage_mV = CellVoltMax_mV;
datalayer.battery.status.max_discharge_power_W = allowedDischargePower * 10; datalayer_battery->status.cell_min_voltage_mV = CellVoltMin_mV;
datalayer.battery.status.temperature_min_dC = (int8_t)temperatureMin * 10; //Increase decimals, 17C -> 17.0C
datalayer.battery.status.temperature_max_dC = (int8_t)temperatureMax * 10; //Increase decimals, 18C -> 18.0C
datalayer.battery.status.cell_max_voltage_mV = CellVoltMax_mV;
datalayer.battery.status.cell_min_voltage_mV = CellVoltMin_mV;
if (waterleakageSensor == 0) { if (waterleakageSensor == 0) {
set_event(EVENT_WATER_INGRESS, 0); set_event(EVENT_WATER_INGRESS, 0);
@ -181,14 +41,14 @@ void update_values_battery() { //This function maps all the values fetched via
} }
// Update webserver datalayer // Update webserver datalayer
datalayer_extended.KiaHyundai64.total_cell_count = datalayer.battery.info.number_of_cells; datalayer_battery_extended->total_cell_count = datalayer_battery->info.number_of_cells;
datalayer_extended.KiaHyundai64.battery_12V = leadAcidBatteryVoltage; datalayer_battery_extended->battery_12V = leadAcidBatteryVoltage;
datalayer_extended.KiaHyundai64.waterleakageSensor = waterleakageSensor; datalayer_battery_extended->waterleakageSensor = waterleakageSensor;
datalayer_extended.KiaHyundai64.temperature_water_inlet = temperature_water_inlet; datalayer_battery_extended->temperature_water_inlet = temperature_water_inlet;
datalayer_extended.KiaHyundai64.powerRelayTemperature = powerRelayTemperature * 2; datalayer_battery_extended->powerRelayTemperature = powerRelayTemperature * 2;
datalayer_extended.KiaHyundai64.batteryManagementMode = batteryManagementMode; datalayer_battery_extended->batteryManagementMode = batteryManagementMode;
datalayer_extended.KiaHyundai64.BMS_ign = BMS_ign; datalayer_battery_extended->BMS_ign = BMS_ign;
datalayer_extended.KiaHyundai64.batteryRelay = batteryRelay; datalayer_battery_extended->batteryRelay = batteryRelay;
//Perform logging if configured to do so //Perform logging if configured to do so
#ifdef DEBUG_LOG #ifdef DEBUG_LOG
@ -205,7 +65,7 @@ void update_values_battery() { //This function maps all the values fetched via
logging.print(" Amps | "); logging.print(" Amps | ");
logging.print((uint16_t)batteryVoltage / 10.0, 1); logging.print((uint16_t)batteryVoltage / 10.0, 1);
logging.print(" Volts | "); logging.print(" Volts | ");
logging.print((int16_t)datalayer.battery.status.active_power_W); logging.print((int16_t)datalayer_battery->status.active_power_W);
logging.println(" Watts"); logging.println(" Watts");
logging.print("Allowed Charge "); logging.print("Allowed Charge ");
logging.print((uint16_t)allowedChargePower * 10); logging.print((uint16_t)allowedChargePower * 10);
@ -251,32 +111,32 @@ void update_values_battery() { //This function maps all the values fetched via
#endif #endif
} }
void update_number_of_cells() { void KiaHyundai64Battery::update_number_of_cells() {
//If we have cell values and number_of_cells not initialized yet //If we have cell values and number_of_cells not initialized yet
if (cellvoltages_mv[0] > 0 && datalayer.battery.info.number_of_cells == 0) { if (cellvoltages_mv[0] > 0 && datalayer_battery->info.number_of_cells == 0) {
// Check if we have 98S or 90S battery // Check if we have 98S or 90S battery
if (datalayer.battery.status.cell_voltages_mV[97] > 0) { if (datalayer_battery->status.cell_voltages_mV[97] > 0) {
datalayer.battery.info.number_of_cells = 98; datalayer_battery->info.number_of_cells = 98;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_98S_DV; datalayer_battery->info.max_design_voltage_dV = MAX_PACK_VOLTAGE_98S_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_98S_DV; datalayer_battery->info.min_design_voltage_dV = MIN_PACK_VOLTAGE_98S_DV;
datalayer.battery.info.total_capacity_Wh = 64000; datalayer_battery->info.total_capacity_Wh = 64000;
} else { } else {
datalayer.battery.info.number_of_cells = 90; datalayer_battery->info.number_of_cells = 90;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_90S_DV; datalayer_battery->info.max_design_voltage_dV = MAX_PACK_VOLTAGE_90S_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_90S_DV; datalayer_battery->info.min_design_voltage_dV = MIN_PACK_VOLTAGE_90S_DV;
datalayer.battery.info.total_capacity_Wh = 40000; datalayer_battery->info.total_capacity_Wh = 40000;
} }
} }
} }
void handle_incoming_can_frame_battery(CAN_frame rx_frame) { void KiaHyundai64Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
switch (rx_frame.ID) { switch (rx_frame.ID) {
case 0x4DE: case 0x4DE:
startedUp = true; startedUp = true;
break; break;
case 0x542: //BMS SOC case 0x542: //BMS SOC
startedUp = true; startedUp = true;
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
SOC_Display = rx_frame.data.u8[0] * 5; //100% = 200 ( 200 * 5 = 1000 ) SOC_Display = rx_frame.data.u8[0] * 5; //100% = 200 ( 200 * 5 = 1000 )
break; break;
case 0x594: case 0x594:
@ -321,17 +181,17 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
} }
poll_data_pid++; poll_data_pid++;
if (poll_data_pid == 1) { if (poll_data_pid == 1) {
transmit_can_frame(&KIA64_7E4_id1, can_config.battery); transmit_can_frame(&KIA64_7E4_id1, can_interface);
} else if (poll_data_pid == 2) { } else if (poll_data_pid == 2) {
transmit_can_frame(&KIA64_7E4_id2, can_config.battery); transmit_can_frame(&KIA64_7E4_id2, can_interface);
} else if (poll_data_pid == 3) { } else if (poll_data_pid == 3) {
transmit_can_frame(&KIA64_7E4_id3, can_config.battery); transmit_can_frame(&KIA64_7E4_id3, can_interface);
} else if (poll_data_pid == 4) { } else if (poll_data_pid == 4) {
transmit_can_frame(&KIA64_7E4_id4, can_config.battery); transmit_can_frame(&KIA64_7E4_id4, can_interface);
} else if (poll_data_pid == 5) { } else if (poll_data_pid == 5) {
transmit_can_frame(&KIA64_7E4_id5, can_config.battery); transmit_can_frame(&KIA64_7E4_id5, can_interface);
} else if (poll_data_pid == 6) { } else if (poll_data_pid == 6) {
transmit_can_frame(&KIA64_7E4_id6, can_config.battery); transmit_can_frame(&KIA64_7E4_id6, can_interface);
} }
} }
break; break;
@ -340,7 +200,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
case 0x10: //"PID Header" case 0x10: //"PID Header"
if (rx_frame.data.u8[4] == poll_data_pid) { if (rx_frame.data.u8[4] == poll_data_pid) {
transmit_can_frame(&KIA64_7E4_ack, transmit_can_frame(&KIA64_7E4_ack,
can_config.battery); //Send ack to BMS if the same frame is sent as polled can_interface); //Send ack to BMS if the same frame is sent as polled
} }
break; break;
case 0x21: //First frame in PID group case 0x21: //First frame in PID group
@ -511,7 +371,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
} }
} }
//Map all cell voltages to the global array //Map all cell voltages to the global array
memcpy(datalayer.battery.status.cell_voltages_mV, cellvoltages_mv, 98 * sizeof(uint16_t)); memcpy(datalayer_battery->status.cell_voltages_mV, cellvoltages_mv, 98 * sizeof(uint16_t));
//Update number of cells //Update number of cells
update_number_of_cells(); update_number_of_cells();
break; break;
@ -533,398 +393,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
} }
} }
#ifdef DOUBLE_BATTERY void KiaHyundai64Battery::transmit_can(unsigned long currentMillis) {
void update_values_battery2() { // Handle the values coming in from battery #2
/* Start with mapping all values */
datalayer.battery2.status.real_soc = (battery2_SOC_Display * 10); //increase SOC range from 0-100.0 -> 100.00
datalayer.battery2.status.soh_pptt = (battery2_batterySOH * 10); //Increase decimals from 100.0% -> 100.00%
datalayer.battery2.status.voltage_dV = battery2_batteryVoltage; //value is *10 (3700 = 370.0)
datalayer.battery2.status.current_dA = -battery2_batteryAmps; //value is *10 (150 = 15.0) , invert the sign
datalayer.battery2.status.remaining_capacity_Wh = static_cast<uint32_t>(
(static_cast<double>(datalayer.battery2.status.real_soc) / 10000) * datalayer.battery2.info.total_capacity_Wh);
datalayer.battery2.status.max_charge_power_W = battery2_allowedChargePower * 10;
datalayer.battery2.status.max_discharge_power_W = battery2_allowedDischargePower * 10;
datalayer.battery2.status.temperature_min_dC =
(int8_t)battery2_temperatureMin * 10; //Increase decimals, 17C -> 17.0C
datalayer.battery2.status.temperature_max_dC =
(int8_t)battery2_temperatureMax * 10; //Increase decimals, 18C -> 18.0C
datalayer.battery2.status.cell_max_voltage_mV = battery2_CellVoltMax_mV;
datalayer.battery2.status.cell_min_voltage_mV = battery2_CellVoltMin_mV;
if (battery2_waterleakageSensor == 0) {
set_event(EVENT_WATER_INGRESS, 0);
}
if (battery2_leadAcidBatteryVoltage < 110) {
set_event(EVENT_12V_LOW, leadAcidBatteryVoltage);
}
// Update webserver datalayer
datalayer_extended.KiaHyundai64.battery2_total_cell_count = datalayer.battery2.info.number_of_cells;
datalayer_extended.KiaHyundai64.battery2_battery_12V = battery2_leadAcidBatteryVoltage;
datalayer_extended.KiaHyundai64.battery2_waterleakageSensor = battery2_waterleakageSensor;
datalayer_extended.KiaHyundai64.battery2_temperature_water_inlet = battery2_temperature_water_inlet;
datalayer_extended.KiaHyundai64.battery2_powerRelayTemperature = battery2_powerRelayTemperature * 2;
datalayer_extended.KiaHyundai64.battery2_batteryManagementMode = battery2_batteryManagementMode;
datalayer_extended.KiaHyundai64.battery2_BMS_ign = battery2_BMS_ign;
datalayer_extended.KiaHyundai64.battery2_batteryRelay = battery2_batteryRelay;
//Perform logging if configured to do so
#ifdef DEBUG_LOG
logging.println(); //sepatator
logging.println("Values from battery: ");
logging.print("SOC BMS: ");
logging.print((uint16_t)battery2_SOC_BMS / 10.0, 1);
logging.print("% | SOC Display: ");
logging.print((uint16_t)battery2_SOC_Display / 10.0, 1);
logging.print("% | SOH ");
logging.print((uint16_t)battery2_batterySOH / 10.0, 1);
logging.println("%");
logging.print((int16_t)battery2_batteryAmps / 10.0, 1);
logging.print(" Amps | ");
logging.print((uint16_t)battery2_batteryVoltage / 10.0, 1);
logging.print(" Volts | ");
logging.print((int16_t)datalayer.battery2.status.active_power_W);
logging.println(" Watts");
logging.print("Allowed Charge ");
logging.print((uint16_t)battery2_allowedChargePower * 10);
logging.print(" W | Allowed Discharge ");
logging.print((uint16_t)battery2_allowedDischargePower * 10);
logging.println(" W");
logging.print("MaxCellVolt ");
logging.print(battery2_CellVoltMax_mV);
logging.print(" mV No ");
logging.print(battery2_CellVmaxNo);
logging.print(" | MinCellVolt ");
logging.print(battery2_CellVoltMin_mV);
logging.print(" mV No ");
logging.println(battery2_CellVminNo);
logging.print("TempHi ");
logging.print((int16_t)battery2_temperatureMax);
logging.print("°C TempLo ");
logging.print((int16_t)battery2_temperatureMin);
logging.print("°C WaterInlet ");
logging.print((int8_t)battery2_temperature_water_inlet);
logging.print("°C PowerRelay ");
logging.print((int8_t)battery2_powerRelayTemperature * 2);
logging.println("°C");
logging.print("Aux12volt: ");
logging.print((int16_t)battery2_leadAcidBatteryVoltage / 10.0, 1);
logging.println("V | ");
logging.print("BmsManagementMode ");
logging.print((uint8_t)battery2_batteryManagementMode, BIN);
if (bitRead((uint8_t)battery2_BMS_ign, 2) == 1) {
logging.print(" | BmsIgnition ON");
} else {
logging.print(" | BmsIgnition OFF");
}
if (bitRead((uint8_t)battery2_batteryRelay, 0) == 1) {
logging.print(" | PowerRelay ON");
} else {
logging.print(" | PowerRelay OFF");
}
logging.print(" | Inverter ");
logging.print(battery2_inverterVoltage);
logging.println(" Volts");
#endif
}
void update_number_of_cells_battery2() {
//If we have cell values and number_of_cells not initialized yet
if (battery2_cellvoltages_mv[0] > 0 && datalayer.battery2.info.number_of_cells == 0) {
// Check if we have 98S or 90S battery
if (datalayer.battery2.status.cell_voltages_mV[97] > 0) {
datalayer.battery2.info.number_of_cells = 98;
datalayer.battery2.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_98S_DV;
datalayer.battery2.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_98S_DV;
datalayer.battery2.info.total_capacity_Wh = 64000;
} else {
datalayer.battery2.info.number_of_cells = 90;
datalayer.battery2.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_90S_DV;
datalayer.battery2.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_90S_DV;
datalayer.battery2.info.total_capacity_Wh = 40000;
}
}
}
void handle_incoming_can_frame_battery2(CAN_frame rx_frame) {
switch (rx_frame.ID) {
case 0x4DE:
battery2_startedUp = true;
break;
case 0x542: //BMS SOC
battery2_startedUp = true;
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery2_SOC_Display = rx_frame.data.u8[0] * 5; //100% = 200 ( 200 * 5 = 1000 )
break;
case 0x594:
battery2_startedUp = true;
battery2_allowedChargePower = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]);
battery2_allowedDischargePower = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]);
battery2_SOC_BMS = rx_frame.data.u8[5] * 5; //100% = 200 ( 200 * 5 = 1000 )
break;
case 0x595:
battery2_startedUp = true;
battery2_batteryVoltage = (rx_frame.data.u8[7] << 8) + rx_frame.data.u8[6];
battery2_batteryAmps = (rx_frame.data.u8[5] << 8) + rx_frame.data.u8[4];
if (battery2_counter_200 > 3) {
//KIA_HYUNDAI_524.data.u8[0] = (uint8_t)(battery2_batteryVoltage / 10);
//KIA_HYUNDAI_524.data.u8[1] = (uint8_t)((battery2_batteryVoltage / 10) >> 8);
} //VCU measured voltage sent back to bms (Not required since battery1 writes this)
break;
case 0x596:
battery2_startedUp = true;
battery2_leadAcidBatteryVoltage = rx_frame.data.u8[1]; //12v Battery Volts
battery2_temperatureMin = rx_frame.data.u8[6]; //Lowest temp in battery
battery2_temperatureMax = rx_frame.data.u8[7]; //Highest temp in battery
break;
case 0x598:
battery2_startedUp = true;
break;
case 0x5D5:
battery2_startedUp = true;
battery2_waterleakageSensor =
rx_frame.data.u8[3]; //Water sensor inside pack, value 164 is no water --> 0 is short
battery2_powerRelayTemperature = rx_frame.data.u8[7];
break;
case 0x5D8:
battery2_startedUp = true;
//PID data is polled after last message sent from battery every other time:
if (battery2_holdPidCounter == true) {
battery2_holdPidCounter = false;
} else {
battery2_holdPidCounter = true;
if (battery2_poll_data_pid >= 6) { //polling one of six PIDs at 100ms*2, resolution = 1200ms
battery2_poll_data_pid = 0;
}
battery2_poll_data_pid++;
if (battery2_poll_data_pid == 1) {
transmit_can_frame(&KIA64_7E4_id1, can_config.battery_double);
} else if (battery2_poll_data_pid == 2) {
transmit_can_frame(&KIA64_7E4_id2, can_config.battery_double);
} else if (battery2_poll_data_pid == 3) {
transmit_can_frame(&KIA64_7E4_id3, can_config.battery_double);
} else if (battery2_poll_data_pid == 4) {
transmit_can_frame(&KIA64_7E4_id4, can_config.battery_double);
} else if (battery2_poll_data_pid == 5) {
transmit_can_frame(&KIA64_7E4_id5, can_config.battery_double);
} else if (battery2_poll_data_pid == 6) {
transmit_can_frame(&KIA64_7E4_id6, can_config.battery_double);
}
}
break;
case 0x7EC: //Data From polled PID group, BigEndian
switch (rx_frame.data.u8[0]) {
case 0x10: //"PID Header"
if (rx_frame.data.u8[4] == battery2_poll_data_pid) {
transmit_can_frame(&KIA64_7E4_ack,
can_config.battery_double); //Send ack to BMS if the same frame is sent as polled
}
break;
case 0x21: //First frame in PID group
if (battery2_poll_data_pid == 1) {
battery2_batteryRelay = rx_frame.data.u8[7];
} else if (battery2_poll_data_pid == 2) {
battery2_cellvoltages_mv[0] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[1] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[2] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[3] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[4] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[5] = (rx_frame.data.u8[7] * 20);
} else if (battery2_poll_data_pid == 3) {
battery2_cellvoltages_mv[32] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[33] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[34] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[35] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[36] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[37] = (rx_frame.data.u8[7] * 20);
} else if (battery2_poll_data_pid == 4) {
battery2_cellvoltages_mv[64] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[65] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[66] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[67] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[68] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[69] = (rx_frame.data.u8[7] * 20);
}
break;
case 0x22: //Second datarow in PID group
if (battery2_poll_data_pid == 2) {
battery2_cellvoltages_mv[6] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[7] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[8] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[9] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[10] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[11] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[12] = (rx_frame.data.u8[7] * 20);
} else if (battery2_poll_data_pid == 3) {
battery2_cellvoltages_mv[38] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[39] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[40] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[41] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[42] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[43] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[44] = (rx_frame.data.u8[7] * 20);
} else if (battery2_poll_data_pid == 4) {
battery2_cellvoltages_mv[70] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[71] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[72] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[73] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[74] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[75] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[76] = (rx_frame.data.u8[7] * 20);
} else if (battery2_poll_data_pid == 6) {
battery2_batteryManagementMode = rx_frame.data.u8[5];
}
break;
case 0x23: //Third datarow in PID group
if (battery2_poll_data_pid == 1) {
battery2_temperature_water_inlet = rx_frame.data.u8[6];
battery2_CellVoltMax_mV = (rx_frame.data.u8[7] * 20); //(volts *50) *20 =mV
} else if (battery2_poll_data_pid == 2) {
battery2_cellvoltages_mv[13] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[14] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[15] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[16] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[17] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[18] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[19] = (rx_frame.data.u8[7] * 20);
} else if (battery2_poll_data_pid == 3) {
battery2_cellvoltages_mv[45] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[46] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[47] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[48] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[49] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[50] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[51] = (rx_frame.data.u8[7] * 20);
} else if (battery2_poll_data_pid == 4) {
battery2_cellvoltages_mv[77] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[78] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[79] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[80] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[81] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[82] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[83] = (rx_frame.data.u8[7] * 20);
} else if (battery2_poll_data_pid == 5) {
battery2_heatertemp = rx_frame.data.u8[7];
}
break;
case 0x24: //Fourth datarow in PID group
if (battery2_poll_data_pid == 1) {
battery2_CellVmaxNo = rx_frame.data.u8[1];
battery2_CellVminNo = rx_frame.data.u8[3];
battery2_CellVoltMin_mV = (rx_frame.data.u8[2] * 20); //(volts *50) *20 =mV
} else if (battery2_poll_data_pid == 2) {
battery2_cellvoltages_mv[20] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[21] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[22] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[23] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[24] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[25] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[26] = (rx_frame.data.u8[7] * 20);
} else if (battery2_poll_data_pid == 3) {
battery2_cellvoltages_mv[52] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[53] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[54] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[55] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[56] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[57] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[58] = (rx_frame.data.u8[7] * 20);
} else if (battery2_poll_data_pid == 4) {
battery2_cellvoltages_mv[84] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[85] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[86] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[87] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[88] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[89] = (rx_frame.data.u8[6] * 20);
if (rx_frame.data.u8[7] > 4) { // Data only valid on 98S
battery2_cellvoltages_mv[90] = (rx_frame.data.u8[7] * 20); // Perform extra checks
}
} else if (battery2_poll_data_pid == 5) {
battery2_batterySOH = ((rx_frame.data.u8[2] << 8) + rx_frame.data.u8[3]);
}
break;
case 0x25: //Fifth datarow in PID group
if (battery2_poll_data_pid == 2) {
battery2_cellvoltages_mv[27] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[28] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[29] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[30] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[31] = (rx_frame.data.u8[5] * 20);
} else if (battery2_poll_data_pid == 3) {
battery2_cellvoltages_mv[59] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[60] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[61] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[62] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[63] = (rx_frame.data.u8[5] * 20);
} else if (battery2_poll_data_pid == 4) { // Data only valid on 98S
if (rx_frame.data.u8[1] > 4) { // Perform extra checks
battery2_cellvoltages_mv[91] = (rx_frame.data.u8[1] * 20);
}
if (rx_frame.data.u8[2] > 4) { // Perform extra checks
battery2_cellvoltages_mv[92] = (rx_frame.data.u8[2] * 20);
}
if (rx_frame.data.u8[3] > 4) { // Perform extra checks
battery2_cellvoltages_mv[93] = (rx_frame.data.u8[3] * 20);
}
if (rx_frame.data.u8[4] > 4) { // Perform extra checks
battery2_cellvoltages_mv[94] = (rx_frame.data.u8[4] * 20);
}
if (rx_frame.data.u8[5] > 4) { // Perform extra checks
battery2_cellvoltages_mv[95] = (rx_frame.data.u8[5] * 20);
}
} else if (battery2_poll_data_pid == 5) { // Data only valid on 98S
if (rx_frame.data.u8[4] > 4) { // Perform extra checks
battery2_cellvoltages_mv[96] = (rx_frame.data.u8[4] * 20);
}
if (rx_frame.data.u8[5] > 4) { // Perform extra checks
battery2_cellvoltages_mv[97] = (rx_frame.data.u8[5] * 20);
}
}
break;
case 0x26: //Sixth datarow in PID group
//We have read all cells, check that content is valid:
for (uint8_t i = 85; i < 97; ++i) {
if (battery2_cellvoltages_mv[i] < 300) { // Zero the value if it's below 300
battery2_cellvoltages_mv[i] = 0; // Some packs incorrectly report the last unpopulated cells as 20-60mV
}
}
//Map all cell voltages to the global array
memcpy(datalayer.battery2.status.cell_voltages_mV, battery2_cellvoltages_mv, 98 * sizeof(uint16_t));
//Update number of cells
update_number_of_cells_battery2();
break;
case 0x27: //Seventh datarow in PID group
if (battery2_poll_data_pid == 1) {
battery2_BMS_ign = rx_frame.data.u8[6];
battery2_inverterVoltageFrameHigh = rx_frame.data.u8[7];
}
break;
case 0x28: //Eighth datarow in PID group
if (battery2_poll_data_pid == 1) {
battery2_inverterVoltage = (battery2_inverterVoltageFrameHigh << 8) + rx_frame.data.u8[1];
}
break;
}
break;
default:
break;
}
}
#endif //DOUBLE_BATTERY
void transmit_can_battery(unsigned long currentMillis) {
if (!startedUp) { if (!startedUp) {
return; // Don't send any CAN messages towards battery until it has started up return; // Don't send any CAN messages towards battery until it has started up
@ -934,21 +403,19 @@ void transmit_can_battery(unsigned long currentMillis) {
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) { if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
previousMillis100 = currentMillis; previousMillis100 = currentMillis;
transmit_can_frame(&KIA64_553, can_config.battery); if (contactor_closing_allowed == nullptr || *contactor_closing_allowed) {
transmit_can_frame(&KIA64_57F, can_config.battery); transmit_can_frame(&KIA64_553, can_interface);
transmit_can_frame(&KIA64_2A1, can_config.battery); transmit_can_frame(&KIA64_57F, can_interface);
#ifdef DOUBLE_BATTERY transmit_can_frame(&KIA64_2A1, can_interface);
if (battery2_startedUp && datalayer.system.status.battery2_allows_contactor_closing) {
transmit_can_frame(&KIA64_553, can_config.battery_double);
transmit_can_frame(&KIA64_57F, can_config.battery_double);
transmit_can_frame(&KIA64_2A1, can_config.battery_double);
} }
#endif // DOUBLE_BATTERY
} }
// Send 10ms CAN Message // Send 10ms CAN Message
if (currentMillis - previousMillis10 >= INTERVAL_10_MS) { if (currentMillis - previousMillis10 >= INTERVAL_10_MS) {
previousMillis10 = currentMillis; previousMillis10 = currentMillis;
if (contactor_closing_allowed == nullptr || *contactor_closing_allowed) {
switch (counter_200) { switch (counter_200) {
case 0: case 0:
KIA_HYUNDAI_200.data.u8[5] = 0x17; KIA_HYUNDAI_200.data.u8[5] = 0x17;
@ -989,81 +456,24 @@ void transmit_can_battery(unsigned long currentMillis) {
break; break;
} }
transmit_can_frame(&KIA_HYUNDAI_200, can_config.battery); transmit_can_frame(&KIA_HYUNDAI_200, can_interface);
transmit_can_frame(&KIA_HYUNDAI_523, can_interface);
transmit_can_frame(&KIA_HYUNDAI_523, can_config.battery); transmit_can_frame(&KIA_HYUNDAI_524, can_interface);
transmit_can_frame(&KIA_HYUNDAI_524, can_config.battery);
#ifdef DOUBLE_BATTERY
if (battery2_startedUp && datalayer.system.status.battery2_allows_contactor_closing) {
switch (counter_200_2) {
case 0:
KIA_HYUNDAI_200_2.data.u8[5] = 0x17;
++counter_200_2;
break;
case 1:
KIA_HYUNDAI_200_2.data.u8[5] = 0x57;
++counter_200_2;
break;
case 2:
KIA_HYUNDAI_200_2.data.u8[5] = 0x97;
++counter_200_2;
break;
case 3:
KIA_HYUNDAI_200_2.data.u8[5] = 0xD7;
++counter_200_2;
break;
case 4:
KIA_HYUNDAI_200_2.data.u8[3] = 0x10;
KIA_HYUNDAI_200_2.data.u8[5] = 0xFF;
++counter_200_2;
break;
case 5:
KIA_HYUNDAI_200_2.data.u8[5] = 0x3B;
++counter_200_2;
break;
case 6:
KIA_HYUNDAI_200_2.data.u8[5] = 0x7B;
++counter_200_2;
break;
case 7:
KIA_HYUNDAI_200_2.data.u8[5] = 0xBB;
++counter_200_2;
break;
case 8:
KIA_HYUNDAI_200_2.data.u8[5] = 0xFB;
counter_200_2 = 5;
break;
} }
transmit_can_frame(&KIA_HYUNDAI_200_2, can_config.battery_double);
transmit_can_frame(&KIA_HYUNDAI_523, can_config.battery_double);
transmit_can_frame(&KIA_HYUNDAI_524, can_config.battery_double);
}
#endif // DOUBLE_BATTERY
} }
} }
void setup_battery(void) { // Performs one time setup at startup void KiaHyundai64Battery::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "Kia/Hyundai 64/40kWh battery", 63); strncpy(datalayer.system.info.battery_protocol, "Kia/Hyundai 64/40kWh battery", 63);
datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_98S_DV; //Start with 98S value. Precised later datalayer_battery->info.max_design_voltage_dV = MAX_PACK_VOLTAGE_98S_DV; //Start with 98S value. Precised later
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_90S_DV; //Start with 90S value. Precised later datalayer_battery->info.min_design_voltage_dV = MIN_PACK_VOLTAGE_90S_DV; //Start with 90S value. Precised later
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; datalayer_battery->info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV; datalayer_battery->info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV; datalayer_battery->info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
datalayer.system.status.battery_allows_contactor_closing = true; if (allows_contactor_closing) {
#ifdef DOUBLE_BATTERY *allows_contactor_closing = true;
datalayer.battery2.info.max_design_voltage_dV = datalayer.battery.info.max_design_voltage_dV; }
datalayer.battery2.info.min_design_voltage_dV = datalayer.battery.info.min_design_voltage_dV;
datalayer.battery2.info.max_cell_voltage_mV = datalayer.battery.info.max_cell_voltage_mV;
datalayer.battery2.info.min_cell_voltage_mV = datalayer.battery.info.min_cell_voltage_mV;
datalayer.battery2.info.max_cell_voltage_deviation_mV = datalayer.battery.info.max_cell_voltage_deviation_mV;
#endif //DOUBLE_BATTERY
} }
#endif #endif

View file

@ -1,19 +1,164 @@
#ifndef KIA_HYUNDAI_64_BATTERY_H #ifndef KIA_HYUNDAI_64_BATTERY_H
#define KIA_HYUNDAI_64_BATTERY_H #define KIA_HYUNDAI_64_BATTERY_H
#include <Arduino.h> #include <Arduino.h>
#include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h"
#include "../include.h" #include "../include.h"
#include "CanBattery.h"
#define BATTERY_SELECTED #define BATTERY_SELECTED
#define MAX_PACK_VOLTAGE_98S_DV 4110 //5000 = 500.0V #define SELECTED_BATTERY_CLASS KiaHyundai64Battery
#define MIN_PACK_VOLTAGE_98S_DV 2800
#define MAX_PACK_VOLTAGE_90S_DV 3870
#define MIN_PACK_VOLTAGE_90S_DV 2250
#define MAX_CELL_DEVIATION_MV 150
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2950 //Battery is put into emergency stop if one cell goes below this value
void setup_battery(void); class KiaHyundai64Battery : public CanBattery {
void update_number_of_cells(); public:
void transmit_can_frame(CAN_frame* tx_frame, int interface); // Use this constructor for the second battery.
KiaHyundai64Battery(DATALAYER_BATTERY_TYPE* datalayer_ptr, DATALAYER_INFO_KIAHYUNDAI64* extended_ptr,
bool* contactor_closing_allowed_ptr, int targetCan) {
datalayer_battery = datalayer_ptr;
contactor_closing_allowed = contactor_closing_allowed_ptr;
allows_contactor_closing = nullptr;
can_interface = targetCan;
datalayer_battery_extended = extended_ptr;
}
// Use the default constructor to create the first or single battery.
KiaHyundai64Battery() {
datalayer_battery = &datalayer.battery;
allows_contactor_closing = &datalayer.system.status.battery_allows_contactor_closing;
contactor_closing_allowed = nullptr;
can_interface = can_config.battery;
datalayer_battery_extended = &datalayer_extended.KiaHyundai64;
}
virtual void setup(void);
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
virtual void update_values();
virtual void transmit_can(unsigned long currentMillis);
private:
DATALAYER_BATTERY_TYPE* datalayer_battery;
DATALAYER_INFO_KIAHYUNDAI64* datalayer_battery_extended;
// If not null, this battery decides when the contactor can be closed and writes the value here.
bool* allows_contactor_closing;
// If not null, this battery listens to this boolean to determine whether contactor closing is allowed
bool* contactor_closing_allowed;
int can_interface;
void update_number_of_cells();
static const int MAX_PACK_VOLTAGE_98S_DV = 4110; //5000 = 500.0V
static const int MIN_PACK_VOLTAGE_98S_DV = 2800;
static const int MAX_PACK_VOLTAGE_90S_DV = 3870;
static const int MIN_PACK_VOLTAGE_90S_DV = 2250;
static const int MAX_CELL_DEVIATION_MV = 150;
static const int MAX_CELL_VOLTAGE_MV = 4250; //Battery is put into emergency stop if one cell goes over this value
static const int MIN_CELL_VOLTAGE_MV = 2950; //Battery is put into emergency stop if one cell goes below this value
unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
unsigned long previousMillis10 = 0; // will store last time a 10s CAN Message was send
uint16_t soc_calculated = 0;
uint16_t SOC_BMS = 0;
uint16_t SOC_Display = 0;
uint16_t batterySOH = 1000;
uint16_t CellVoltMax_mV = 3700;
uint16_t CellVoltMin_mV = 3700;
uint16_t allowedDischargePower = 0;
uint16_t allowedChargePower = 0;
uint16_t batteryVoltage = 0;
uint16_t inverterVoltageFrameHigh = 0;
uint16_t inverterVoltage = 0;
uint16_t cellvoltages_mv[98];
int16_t leadAcidBatteryVoltage = 120;
int16_t batteryAmps = 0;
int16_t temperatureMax = 0;
int16_t temperatureMin = 0;
int16_t poll_data_pid = 0;
bool holdPidCounter = false;
uint8_t CellVmaxNo = 0;
uint8_t CellVminNo = 0;
uint8_t batteryManagementMode = 0;
uint8_t BMS_ign = 0;
uint8_t batteryRelay = 0;
uint8_t waterleakageSensor = 164;
uint8_t counter_200 = 0;
int8_t temperature_water_inlet = 0;
int8_t heatertemp = 0;
int8_t powerRelayTemperature = 0;
bool startedUp = false;
CAN_frame KIA_HYUNDAI_200 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x200,
.data = {0x00, 0x80, 0xD8, 0x04, 0x00, 0x17, 0xD0, 0x00}};
CAN_frame KIA_HYUNDAI_523 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x523,
.data = {0x08, 0x38, 0x36, 0x36, 0x33, 0x34, 0x00, 0x01}};
CAN_frame KIA_HYUNDAI_524 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x524,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
//553 Needed frame 200ms
CAN_frame KIA64_553 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x553,
.data = {0x04, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00}};
//57F Needed frame 100ms
CAN_frame KIA64_57F = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x57F,
.data = {0x80, 0x0A, 0x72, 0x00, 0x00, 0x00, 0x00, 0x72}};
//Needed frame 100ms
CAN_frame KIA64_2A1 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x2A1,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame KIA64_7E4_id1 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E4,
.data = {0x03, 0x22, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00}}; //Poll PID 03 22 01 01
CAN_frame KIA64_7E4_id2 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E4,
.data = {0x03, 0x22, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00}}; //Poll PID 03 22 01 02
CAN_frame KIA64_7E4_id3 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E4,
.data = {0x03, 0x22, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00}}; //Poll PID 03 22 01 03
CAN_frame KIA64_7E4_id4 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E4,
.data = {0x03, 0x22, 0x01, 0x04, 0x00, 0x00, 0x00, 0x00}}; //Poll PID 03 22 01 04
CAN_frame KIA64_7E4_id5 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E4,
.data = {0x03, 0x22, 0x01, 0x05, 0x00, 0x00, 0x00, 0x00}}; //Poll PID 03 22 01 05
CAN_frame KIA64_7E4_id6 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E4,
.data = {0x03, 0x22, 0x01, 0x06, 0x00, 0x00, 0x00, 0x00}}; //Poll PID 03 22 01 06
CAN_frame KIA64_7E4_ack = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E4,
.data = {0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; //Ack frame, correct PID is returned
};
#endif #endif

View file

@ -1,5 +1,6 @@
#include "../include.h" #include "../include.h"
#ifdef KIA_HYUNDAI_HYBRID_BATTERY #ifdef KIA_HYUNDAI_HYBRID_BATTERY
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "../devboard/utils/events.h" #include "../devboard/utils/events.h"
#include "KIA-HYUNDAI-HYBRID-BATTERY.h" #include "KIA-HYUNDAI-HYBRID-BATTERY.h"
@ -9,51 +10,8 @@
- We need to figure out how to keep the BMS alive. Most likely we need to send a specific CAN message - We need to figure out how to keep the BMS alive. Most likely we need to send a specific CAN message
*/ */
/* Do not change code below unless you are sure what you are doing */ void KiaHyundaiHybridBattery::
static unsigned long previousMillis1000 = 0; // will store last time a 100ms CAN Message was send update_values() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
static uint16_t SOC = 0;
static uint16_t SOC_display = 0;
static bool interlock_missing = false;
static int16_t battery_current = 0;
static uint8_t battery_current_high_byte = 0;
static uint16_t battery_voltage = 0;
static uint32_t available_charge_power = 0;
static uint32_t available_discharge_power = 0;
static int8_t battery_module_max_temperature = 0;
static int8_t battery_module_min_temperature = 0;
static uint8_t poll_data_pid = 0;
static uint16_t cellvoltages_mv[98];
static uint16_t min_cell_voltage_mv = 3700;
static uint16_t max_cell_voltage_mv = 3700;
CAN_frame KIA_7E4_id1 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E4,
.data = {0x02, 0x21, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame KIA_7E4_id2 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E4,
.data = {0x02, 0x21, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame KIA_7E4_id3 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E4,
.data = {0x02, 0x21, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame KIA_7E4_id5 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E4,
.data = {0x02, 0x21, 0x05, 0x04, 0x00, 0x00, 0x00, 0x00}};
CAN_frame KIA_7E4_ack = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E4, //Ack frame, correct PID is returned. Flow control message
.data = {0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
datalayer.battery.status.real_soc = SOC * 50; datalayer.battery.status.real_soc = SOC * 50;
@ -86,7 +44,7 @@ void update_values_battery() { //This function maps all the values fetched via
} }
} }
void handle_incoming_can_frame_battery(CAN_frame rx_frame) { void KiaHyundaiHybridBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
switch (rx_frame.ID) { switch (rx_frame.ID) {
case 0x5F1: case 0x5F1:
@ -230,7 +188,8 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
break; break;
} }
} }
void transmit_can_battery(unsigned long currentMillis) {
void KiaHyundaiHybridBattery::transmit_can(unsigned long currentMillis) {
// Send 1000ms CAN Message // Send 1000ms CAN Message
if (currentMillis - previousMillis1000 >= INTERVAL_1_S) { if (currentMillis - previousMillis1000 >= INTERVAL_1_S) {
@ -255,7 +214,7 @@ void transmit_can_battery(unsigned long currentMillis) {
} }
} }
void setup_battery(void) { // Performs one time setup at startup void KiaHyundaiHybridBattery::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "Kia/Hyundai Hybrid", 63); strncpy(datalayer.system.info.battery_protocol, "Kia/Hyundai Hybrid", 63);
datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.info.battery_protocol[63] = '\0';
datalayer.system.status.battery_allows_contactor_closing = true; datalayer.system.status.battery_allows_contactor_closing = true;

View file

@ -3,14 +3,67 @@
#include <Arduino.h> #include <Arduino.h>
#include "../include.h" #include "../include.h"
#define BATTERY_SELECTED #include "CanBattery.h"
#define MAX_PACK_VOLTAGE_DV 2550 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_DV 1700
#define MAX_CELL_DEVIATION_MV 100
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
void setup_battery(void); #define BATTERY_SELECTED
void transmit_can_frame(CAN_frame* tx_frame, int interface); #define SELECTED_BATTERY_CLASS KiaHyundaiHybridBattery
class KiaHyundaiHybridBattery : public CanBattery {
public:
virtual void setup(void);
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
virtual void update_values();
virtual void transmit_can(unsigned long currentMillis);
private:
static const int MAX_PACK_VOLTAGE_DV = 2550; //5000 = 500.0V
static const int MIN_PACK_VOLTAGE_DV = 1700;
static const int MAX_CELL_DEVIATION_MV = 100;
static const int MAX_CELL_VOLTAGE_MV = 4250; //Battery is put into emergency stop if one cell goes over this value
static const int MIN_CELL_VOLTAGE_MV = 2700; //Battery is put into emergency stop if one cell goes below this value
unsigned long previousMillis1000 = 0; // will store last time a 100ms CAN Message was send
uint16_t SOC = 0;
uint16_t SOC_display = 0;
bool interlock_missing = false;
int16_t battery_current = 0;
uint8_t battery_current_high_byte = 0;
uint16_t battery_voltage = 0;
uint32_t available_charge_power = 0;
uint32_t available_discharge_power = 0;
int8_t battery_module_max_temperature = 0;
int8_t battery_module_min_temperature = 0;
uint8_t poll_data_pid = 0;
uint16_t cellvoltages_mv[98];
uint16_t min_cell_voltage_mv = 3700;
uint16_t max_cell_voltage_mv = 3700;
CAN_frame KIA_7E4_id1 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E4,
.data = {0x02, 0x21, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame KIA_7E4_id2 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E4,
.data = {0x02, 0x21, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame KIA_7E4_id3 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E4,
.data = {0x02, 0x21, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame KIA_7E4_id5 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E4,
.data = {0x02, 0x21, 0x05, 0x04, 0x00, 0x00, 0x00, 0x00}};
CAN_frame KIA_7E4_ack = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E4, //Ack frame, correct PID is returned. Flow control message
.data = {0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
};
#endif #endif

View file

@ -15,359 +15,20 @@ TODO list
- remaining_capacity_Wh is based on a lower limit of 5% soc. This means that at 5% soc, remaining_capacity_Wh returns 0. - remaining_capacity_Wh is based on a lower limit of 5% soc. This means that at 5% soc, remaining_capacity_Wh returns 0.
*/ */
/* Do not change code below unless you are sure what you are doing */ const int RX_0x17F0007B = 0x0001;
static unsigned long previousMillis10ms = 0; // will store last time a 10ms CAN Message was send const int RX_0x12DD54D0 = 0x0002;
static unsigned long previousMillis20ms = 0; // will store last time a 20ms CAN Message was send const int RX_0x12DD54D1 = 0x0004;
static unsigned long previousMillis40ms = 0; // will store last time a 40ms CAN Message was send const int RX_0x12DD54D2 = 0x0008;
static unsigned long previousMillis50ms = 0; // will store last time a 50ms CAN Message was send const int RX_0x1A555550 = 0x0010;
static unsigned long previousMillis100ms = 0; // will store last time a 100ms CAN Message was send const int RX_0x1A555551 = 0x0020;
static unsigned long previousMillis200ms = 0; // will store last time a 200ms CAN Message was send const int RX_0x1A5555B2 = 0x0040;
static unsigned long previousMillis500ms = 0; // will store last time a 200ms CAN Message was send const int RX_0x16A954A6 = 0x0080;
static unsigned long previousMillis1s = 0; // will store last time a 1s CAN Message was send const int RX_0x1A5555B0 = 0x0100;
const int RX_0x1A5555B1 = 0x0200;
static bool toggle = false; const int RX_0x5A2 = 0x0400;
static uint8_t counter_1000ms = 0; const int RX_0x5CA = 0x0800;
static uint8_t counter_200ms = 0; const int RX_0x0CF = 0x1000;
static uint8_t counter_100ms = 0; const int RX_DEFAULT = 0xE000;
static uint8_t counter_50ms = 0;
static uint8_t counter_40ms = 0;
static uint8_t counter_20ms = 0;
static uint8_t counter_10ms = 0;
static uint8_t counter_040 = 0;
static uint8_t counter_0F7 = 0;
static uint8_t counter_3b5 = 0;
static uint32_t poll_pid = PID_CELLVOLTAGE_CELL_85; // We start here to quickly determine the cell size of the pack.
static bool nof_cells_determined = false;
static uint32_t pid_reply = 0;
static uint16_t battery_soc_polled = 0;
static uint16_t battery_voltage_polled = 1480;
static int16_t battery_current_polled = 0;
static int16_t battery_max_temp = 600;
static int16_t battery_min_temp = 600;
static uint16_t battery_max_charge_voltage = 0;
static uint16_t battery_min_discharge_voltage = 0;
static uint16_t battery_allowed_charge_power = 0;
static uint16_t battery_allowed_discharge_power = 0;
static uint16_t cellvoltages_polled[108];
static uint16_t tempval = 0;
static uint8_t BMS_16A954A6_CRC = 0;
static uint8_t BMS_5A2_counter = 0;
static uint8_t BMS_5CA_counter = 0;
static uint8_t BMS_0CF_counter = 0;
static uint8_t BMS_0C0_counter = 0;
static uint8_t BMS_578_counter = 0;
static uint8_t BMS_16A954A6_counter = 0;
static bool BMS_ext_limits_active =
false; //The current current limits of the HV battery are expanded to start the combustion engine / confirmation of the request
static uint8_t BMS_mode =
0x07; //0: standby; Gates open; Communication active 1: Main contactor closed / HV network activated / normal driving operation
//2: assigned depending on the project (e.g. balancing, extended DC fast charging) //3: external charging
static uint8_t BMS_HVIL_status = 0; //0 init, 1 seated, 2 open, 3 fault
static bool BMS_fault_performance = false; //Error: Battery performance is limited (e.g. due to sensor or fan failure)
static uint16_t BMS_current = 16300;
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
static uint32_t BMS_voltage_intermediate = 2000;
static uint32_t BMS_voltage = 1480;
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
static bool BMS_OBD_MIL = false;
static uint8_t BMS_error_status =
0x7; //0 Component_IO, 1 Restricted_CompFkt_Isoerror_I, 2 Restricted_CompFkt_Isoerror_II, 3 Restricted_CompFkt_Interlock, 4 Restricted_CompFkt_SD, 5 Restricted_CompFkt_Performance red, 6 = No component function, 7 = Init
static uint16_t BMS_capacity_ah = 0;
static bool BMS_error_lamp_req = false;
static bool BMS_warning_lamp_req = false;
static uint8_t BMS_Kl30c_Status = 0; // 0 init, 1 closed, 2 open, 3 fault
static bool service_disconnect_switch_missing = false;
static bool pilotline_open = false;
static bool balancing_request = false;
static uint8_t battery_diagnostic = 0;
static uint16_t battery_Wh_left = 0;
static uint16_t battery_Wh_max = 1000;
static uint8_t battery_potential_status = 0;
static uint8_t battery_temperature_warning = 0;
static uint16_t max_discharge_power_watt = 0;
static uint16_t max_discharge_current_amp = 0;
static uint16_t max_charge_power_watt = 0;
static uint16_t max_charge_current_amp = 0;
static uint16_t battery_SOC = 1;
static uint16_t usable_energy_amount_Wh = 0;
static uint8_t status_HV_line = 0; //0 init, 1 No open HV line, 2 open HV line detected, 3 fault
static uint8_t warning_support = 0;
static bool battery_heating_active = false;
static uint16_t power_discharge_percentage = 0;
static uint16_t power_charge_percentage = 0;
static uint16_t actual_battery_voltage = 0;
static uint16_t regen_battery = 0;
static uint16_t energy_extracted_from_battery = 0;
static uint16_t max_fastcharging_current_amp = 0; //maximum DC charging current allowed
static uint8_t BMS_Status_DCLS =
0; //Status of the voltage monitoring on the DC charging interface. 0 inactive, 1 I_O , 2 N_I_O , 3 active
static uint16_t DC_voltage_DCLS =
0; //Factor 1, DC voltage of the charging station. Measurement between the DC HV lines.
static uint16_t DC_voltage_chargeport =
0; //Factor 0.5, Current voltage at the HV battery DC charging terminals; Outlet to the DC charging plug.
static uint8_t BMS_welded_contactors_status =
0; //0: Init no diagnostic result, 1: no contactor welded, 2: at least 1 contactor welded, 3: Protection status detection error
static bool BMS_error_shutdown_request =
false; // Fault: Fault condition, requires battery contactors to be opened internal battery error; Advance notification of an impending opening of the battery contactors by the BMS
static bool BMS_error_shutdown =
false; // Fault: Fault condition, requires battery contactors to be opened Internal battery error, battery contactors opened without notice by the BMS
static uint16_t power_battery_heating_watt = 0;
static uint16_t power_battery_heating_req_watt = 0;
static uint8_t cooling_request =
0; //0 = No cooling, 1 = Light cooling, cabin prio, 2= higher cooling, 3 = immediate cooling, 4 = emergency cooling
static uint8_t heating_request = 0; //0 = init, 1= maintain temp, 2=higher demand, 3 = immediate heat demand
static uint8_t balancing_active = false; //0 = init, 1 active, 2 not active
static bool charging_active = false;
static uint16_t max_energy_Wh = 0;
static uint16_t max_charge_percent = 0;
static uint16_t min_charge_percent = 0;
static uint16_t isolation_resistance_kOhm = 0;
static bool battery_heating_installed = false;
static bool error_NT_circuit = false;
static uint8_t pump_1_control = 0; //0x0D not installed, 0x0E init, 0x0F fault
static uint8_t pump_2_control = 0; //0x0D not installed, 0x0E init, 0x0F fault
static uint8_t target_flow_temperature_C = 0; //*0,5 -40
static uint8_t return_temperature_C = 0; //*0,5 -40
static uint8_t status_valve_1 = 0; //0 not active, 1 active, 5 not installed, 6 init, 7 fault
static uint8_t status_valve_2 = 0; //0 not active, 1 active, 5 not installed, 6 init, 7 fault
static uint8_t temperature_request =
0; //0 high cooling, 1 medium cooling, 2 low cooling, 3 no temp requirement init, 4 low heating , 5 medium heating, 6 high heating, 7 circulation
static uint16_t performance_index_discharge_peak_temperature_percentage = 0;
static uint16_t performance_index_charge_peak_temperature_percentage = 0;
static uint8_t temperature_status_discharge =
0; //0 init, 1 temp under optimal, 2 temp optimal, 3 temp over optimal, 7 fault
static uint8_t temperature_status_charge =
0; //0 init, 1 temp under optimal, 2 temp optimal, 3 temp over optimal, 7 fault
static uint8_t isolation_fault =
0; //0 init, 1 no iso fault, 2 iso fault threshold1, 3 iso fault threshold2, 4 IWU defective
static uint8_t isolation_status =
0; // 0 init, 1 = larger threshold1, 2 = smaller threshold1 total, 3 = smaller threshold1 intern, 4 = smaller threshold2 total, 5 = smaller threshold2 intern, 6 = no measurement, 7 = measurement active
static uint8_t actual_temperature_highest_C = 0; //*0,5 -40
static uint8_t actual_temperature_lowest_C = 0; //*0,5 -40
static uint16_t actual_cellvoltage_highest_mV = 0; //bias 1000
static uint16_t actual_cellvoltage_lowest_mV = 0; //bias 1000
static uint16_t predicted_power_dyn_standard_watt = 0;
static uint8_t predicted_time_dyn_standard_minutes = 0;
static uint8_t mux = 0;
static uint16_t cellvoltages[160] = {0};
static uint16_t duration_discharge_power_watt = 0;
static uint16_t duration_charge_power_watt = 0;
static uint16_t maximum_voltage = 0;
static uint16_t minimum_voltage = 0;
static uint8_t battery_serialnumber[26];
static uint8_t realtime_overcurrent_monitor = 0;
static uint8_t realtime_CAN_communication_fault = 0;
static uint8_t realtime_overcharge_warning = 0;
static uint8_t realtime_SOC_too_high = 0;
static uint8_t realtime_SOC_too_low = 0;
static uint8_t realtime_SOC_jumping_warning = 0;
static uint8_t realtime_temperature_difference_warning = 0;
static uint8_t realtime_cell_overtemperature_warning = 0;
static uint8_t realtime_cell_undertemperature_warning = 0;
static uint8_t realtime_battery_overvoltage_warning = 0;
static uint8_t realtime_battery_undervoltage_warning = 0;
static uint8_t realtime_cell_overvoltage_warning = 0;
static uint8_t realtime_cell_undervoltage_warning = 0;
static uint8_t realtime_cell_imbalance_warning = 0;
static uint8_t realtime_warning_battery_unathorized = 0;
static bool component_protection_active = false;
static bool shutdown_active = false;
static bool transportation_mode_active = false;
static uint8_t KL15_mode = 0;
static uint8_t bus_knockout_timer = 0;
static uint8_t hybrid_wakeup_reason = 0;
static uint8_t wakeup_type = 0;
static bool instrumentation_cluster_request = false;
static uint8_t seconds = 0;
static uint32_t first_can_msg = 0;
static uint32_t last_can_msg_timestamp = 0;
static bool hv_requested = false;
static int32_t kwh_charge = 0;
static int32_t kwh_discharge = 0;
#define TIME_YEAR 2024
#define TIME_MONTH 8
#define TIME_DAY 20
#define TIME_HOUR 10
#define TIME_MINUTE 5
#define TIME_SECOND 0
#define BMS_TARGET_HV_OFF 0
#define BMS_TARGET_HV_ON 1
#define BMS_TARGET_AC_CHARGING_EXT 3 //(HS + AC_2 contactors closed)
#define BMS_TARGET_AC_CHARGING 4 //(HS + AC contactors closed)
#define BMS_TARGET_DC_CHARGING 6 //(HS + DC contactors closed)
#define BMS_TARGET_INIT 7
#define DC_FASTCHARGE_NO_START_REQUEST 0x00
#define DC_FASTCHARGE_VEHICLE 0x40
#define DC_FASTCHARGE_LS1 0x80
#define DC_FASTCHARGE_LS2 0xC0
CAN_frame MEB_POLLING_FRAME = {.FD = true,
.ext_ID = true,
.DLC = 8,
.ID = 0x1C40007B, // SOC 02 8C
.data = {0x03, 0x22, 0x02, 0x8C, 0x55, 0x55, 0x55, 0x55}};
CAN_frame MEB_ACK_FRAME = {.FD = true,
.ext_ID = true,
.DLC = 8,
.ID = 0x1C40007B, // Ack
.data = {0x30, 0x00, 0x00, 0x55, 0x55, 0x55, 0x55, 0x55}};
//Messages needed for contactor closing
CAN_frame MEB_040 = {.FD = true, // Airbag
.ext_ID = false,
.DLC = 8,
.ID = 0x040, //Frame5 has HV deactivate request. Needs to be 0x00
.data = {0x7E, 0x83, 0x00, 0x01, 0x00, 0x00, 0x15, 0x00}};
CAN_frame MEB_0C0 = {
.FD = true, // EM1 message
.ext_ID = false,
.DLC = 32,
.ID = 0x0C0, //Frame 5-6 and maybe 7-8 important (external voltage at inverter)
.data = {0x77, 0x0A, 0xFE, 0xE7, 0x7F, 0x10, 0x27, 0x00, 0xE0, 0x7F, 0xFF, 0xF3, 0x3F, 0xFF, 0xF3, 0x3F,
0xFC, 0x0F, 0x00, 0x00, 0xC0, 0xFF, 0xFE, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame MEB_0FC = {
.FD = true, //
.ext_ID = false,
.DLC = 48,
.ID = 0x0FC, //This message contains emergency regen request?(byte19), battery needs to see this message
.data = {0x07, 0x08, 0x00, 0x00, 0x7E, 0x00, 0x40, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFE, 0xFE, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xF4, 0x01, 0x40, 0xFF, 0xEB, 0x7F, 0x0A, 0x88, 0xE3, 0x81, 0xAF, 0x42}};
CAN_frame MEB_6B2 = {.FD = true, // Diagnostics
.ext_ID = false,
.DLC = 8,
.ID = 0x6B2,
.data = {0x6A, 0xA7, 0x37, 0x80, 0xC9, 0xBD, 0xF6, 0xC2}};
CAN_frame MEB_17FC007B_poll = {.FD = true, // Non period request
.ext_ID = true,
.DLC = 8,
.ID = 0x17FC007B,
.data = {0x03, 0x22, 0x1E, 0x3D, 0x55, 0x55, 0x55, 0x55}};
CAN_frame MEB_1A5555A6 = {.FD = true,
.ext_ID = true,
.DLC = 8,
.ID = 0x1A5555A6,
.data = {0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame MEB_585 = {
.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x585,
.data = {0xCF, 0x38, 0xAF, 0x5B, 0x25, 0x00, 0x00, 0x00}}; // CF 38 AF 5B 25 00 00 00 in start4.log
// .data = {0xCF, 0x38, 0x20, 0x02, 0x25, 0xF7, 0x30, 0x00}}; // CF 38 AF 5B 25 00 00 00 in start4.log
CAN_frame MEB_5F5 = {.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x5F5,
.data = {0x23, 0x02, 0x39, 0xC0, 0x1B, 0x8B, 0xC8, 0x1B}};
CAN_frame MEB_641 = {.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x641,
.data = {0x37, 0x18, 0x00, 0x00, 0xF0, 0x00, 0xAA, 0x70}};
CAN_frame MEB_3C0 = {.FD = true,
.ext_ID = false,
.DLC = 4,
.ID = 0x3C0,
.data = {0x66, 0x00, 0x00, 0x00}}; // Klemmen_status_01
CAN_frame MEB_0FD = {.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x0FD, //CRC and counter, otherwise static
.data = {0x5F, 0xD0, 0x1F, 0x81, 0x00, 0x00, 0x00, 0x00}};
CAN_frame MEB_16A954FB = {.FD = true,
.ext_ID = true,
.DLC = 8,
.ID = 0x16A954FB,
.data = {0x00, 0xC0, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame MEB_1A555548 = {.FD = true,
.ext_ID = true,
.DLC = 8,
.ID = 0x1A555548,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame MEB_1A55552B = {.FD = true,
.ext_ID = true,
.DLC = 8,
.ID = 0x1A55552B,
.data = {0x00, 0x00, 0x00, 0xA0, 0x02, 0x04, 0x00, 0x30}};
CAN_frame MEB_569 = {.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x569, //HVEM
.data = {0x00, 0x00, 0x01, 0x3A, 0x00, 0x00, 0x00, 0x00}};
CAN_frame MEB_16A954B4 = {.FD = true,
.ext_ID = true,
.DLC = 8,
.ID = 0x16A954B4, //eTM
.data = {0xFE, 0xB6, 0x0D, 0x00, 0x00, 0xD5, 0x48, 0xFD}};
CAN_frame MEB_1B000046 = {.FD = false, // Not FD
.ext_ID = true,
.DLC = 8,
.ID = 0x1B000046, // Klima
.data = {0x00, 0x40, 0x08, 0x01, 0x00, 0x00, 0x00, 0x00}};
CAN_frame MEB_1B000010 = {.FD = false, // Not FD
.ext_ID = true,
.DLC = 8,
.ID = 0x1B000010, // Gateway
.data = {0x00, 0x50, 0x08, 0x50, 0x01, 0xFF, 0x30, 0x00}};
CAN_frame MEB_1B0000B9 = {.FD = false, // Not FD
.ext_ID = true,
.DLC = 8,
.ID = 0x1B0000B9, //DC/DC converter
.data = {0x00, 0x40, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00}};
CAN_frame MEB_153 = {.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x153, // Static content
.data = {0x00, 0x00, 0x00, 0xFF, 0xEF, 0xFE, 0xFF, 0xFF}};
CAN_frame MEB_5E1 = {.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x5E1, // Static content
.data = {0x7F, 0x2A, 0x00, 0x60, 0xFE, 0x00, 0x00, 0x00}};
CAN_frame MEB_3BE = {.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x3BE, // CRC, otherwise Static content
.data = {0x57, 0x0D, 0x00, 0x00, 0x00, 0x02, 0x04, 0x40}};
CAN_frame MEB_272 = {.FD = true, //HVLM_14
.ext_ID = false,
.DLC = 8,
.ID = 0x272, // Static content
.data = {0x00, 0x00, 0x00, 0x00, 0x48, 0x08, 0x00, 0x94}};
CAN_frame MEB_503 = {.FD = true, //HVK_01
.ext_ID = false,
.DLC = 8,
.ID = 0x503, // Content varies. Frame1 & 3 has HV req
.data = {0x5D, 0x61, 0x00, 0xFF, 0x7F, 0x80, 0xE3, 0x03}};
CAN_frame MEB_14C = {
.FD = true, //Motor message
.ext_ID = false,
.DLC = 32,
.ID = 0x14C, //CRC needed, static content otherwise
.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}};
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
* *
@ -538,7 +199,8 @@ uint8_t vw_crc_calc(uint8_t* inputBytes, uint8_t length, uint32_t address) {
return crc; return crc;
} }
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus void MebBattery::
update_values() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
datalayer.battery.status.real_soc = battery_SOC * 5; //*0.05*100 datalayer.battery.status.real_soc = battery_SOC * 5; //*0.05*100
@ -645,7 +307,7 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer_extended.meb.charging_active = charging_active; datalayer_extended.meb.charging_active = charging_active;
} }
void handle_incoming_can_frame_battery(CAN_frame rx_frame) { void MebBattery::handle_incoming_can_frame(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 #ifdef DEBUG_LOG
@ -1627,7 +1289,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
} }
} }
void transmit_can_battery(unsigned long currentMillis) { void MebBattery::transmit_can(unsigned long currentMillis) {
if (currentMillis > last_can_msg_timestamp + 500) { if (currentMillis > last_can_msg_timestamp + 500) {
#ifdef DEBUG_LOG #ifdef DEBUG_LOG
@ -2373,7 +2035,7 @@ void transmit_can_battery(unsigned long currentMillis) {
} }
} }
void setup_battery(void) { // Performs one time setup at startup void MebBattery::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "Volkswagen Group MEB platform via CAN-FD", 63); strncpy(datalayer.system.info.battery_protocol, "Volkswagen Group MEB platform via CAN-FD", 63);
datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.number_of_cells = 108; //Startup in 108S mode. We figure out the actual count later. datalayer.battery.info.number_of_cells = 108; //Startup in 108S mode. We figure out the actual count later.

View file

@ -2,156 +2,501 @@
#define MEB_BATTERY_H #define MEB_BATTERY_H
#include <Arduino.h> #include <Arduino.h>
#include "../include.h" #include "../include.h"
#include "CanBattery.h"
#define BATTERY_SELECTED #define BATTERY_SELECTED
#define MAX_PACK_VOLTAGE_84S_DV 3528 //5000 = 500.0V #define SELECTED_BATTERY_CLASS MebBattery
#define MIN_PACK_VOLTAGE_84S_DV 2520
#define MAX_PACK_VOLTAGE_96S_DV 4032
#define MIN_PACK_VOLTAGE_96S_DV 2880
#define MAX_PACK_VOLTAGE_108S_DV 4536
#define MIN_PACK_VOLTAGE_108S_DV 3240
#define MAX_CELL_DEVIATION_MV 150
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
#define PID_SOC 0x028C class MebBattery : public CanBattery {
#define PID_VOLTAGE 0x1E3B public:
#define PID_CURRENT 0x1E3D virtual void setup(void);
#define PID_MAX_TEMP 0x1E0E virtual void handle_incoming_can_frame(CAN_frame rx_frame);
#define PID_MIN_TEMP 0x1E0F virtual void update_values();
#define PID_MAX_CHARGE_VOLTAGE 0x5171 virtual void transmit_can(unsigned long currentMillis);
#define PID_MIN_DISCHARGE_VOLTAGE 0x5170
#define PID_ENERGY_COUNTERS 0x1E32
#define PID_ALLOWED_CHARGE_POWER 0x1E1B
#define PID_ALLOWED_DISCHARGE_POWER 0x1E1C
#define PID_CELLVOLTAGE_CELL_1 0x1E40
#define PID_CELLVOLTAGE_CELL_2 0x1E41
#define PID_CELLVOLTAGE_CELL_3 0x1E42
#define PID_CELLVOLTAGE_CELL_4 0x1E43
#define PID_CELLVOLTAGE_CELL_5 0x1E44
#define PID_CELLVOLTAGE_CELL_6 0x1E45
#define PID_CELLVOLTAGE_CELL_7 0x1E46
#define PID_CELLVOLTAGE_CELL_8 0x1E47
#define PID_CELLVOLTAGE_CELL_9 0x1E48
#define PID_CELLVOLTAGE_CELL_10 0x1E49
#define PID_CELLVOLTAGE_CELL_11 0x1E4A
#define PID_CELLVOLTAGE_CELL_12 0x1E4B
#define PID_CELLVOLTAGE_CELL_13 0x1E4C
#define PID_CELLVOLTAGE_CELL_14 0x1E4D
#define PID_CELLVOLTAGE_CELL_15 0x1E4E
#define PID_CELLVOLTAGE_CELL_16 0x1E4F
#define PID_CELLVOLTAGE_CELL_17 0x1E50
#define PID_CELLVOLTAGE_CELL_18 0x1E51
#define PID_CELLVOLTAGE_CELL_19 0x1E52
#define PID_CELLVOLTAGE_CELL_20 0x1E53
#define PID_CELLVOLTAGE_CELL_21 0x1E54
#define PID_CELLVOLTAGE_CELL_22 0x1E55
#define PID_CELLVOLTAGE_CELL_23 0x1E56
#define PID_CELLVOLTAGE_CELL_24 0x1E57
#define PID_CELLVOLTAGE_CELL_25 0x1E58
#define PID_CELLVOLTAGE_CELL_26 0x1E59
#define PID_CELLVOLTAGE_CELL_27 0x1E5A
#define PID_CELLVOLTAGE_CELL_28 0x1E5B
#define PID_CELLVOLTAGE_CELL_29 0x1E5C
#define PID_CELLVOLTAGE_CELL_30 0x1E5D
#define PID_CELLVOLTAGE_CELL_31 0x1E5E
#define PID_CELLVOLTAGE_CELL_32 0x1E5F
#define PID_CELLVOLTAGE_CELL_33 0x1E60
#define PID_CELLVOLTAGE_CELL_34 0x1E61
#define PID_CELLVOLTAGE_CELL_35 0x1E62
#define PID_CELLVOLTAGE_CELL_36 0x1E63
#define PID_CELLVOLTAGE_CELL_37 0x1E64
#define PID_CELLVOLTAGE_CELL_38 0x1E65
#define PID_CELLVOLTAGE_CELL_39 0x1E66
#define PID_CELLVOLTAGE_CELL_40 0x1E67
#define PID_CELLVOLTAGE_CELL_41 0x1E68
#define PID_CELLVOLTAGE_CELL_42 0x1E69
#define PID_CELLVOLTAGE_CELL_43 0x1E6A
#define PID_CELLVOLTAGE_CELL_44 0x1E6B
#define PID_CELLVOLTAGE_CELL_45 0x1E6C
#define PID_CELLVOLTAGE_CELL_46 0x1E6D
#define PID_CELLVOLTAGE_CELL_47 0x1E6E
#define PID_CELLVOLTAGE_CELL_48 0x1E6F
#define PID_CELLVOLTAGE_CELL_49 0x1E70
#define PID_CELLVOLTAGE_CELL_50 0x1E71
#define PID_CELLVOLTAGE_CELL_51 0x1E72
#define PID_CELLVOLTAGE_CELL_52 0x1E73
#define PID_CELLVOLTAGE_CELL_53 0x1E74
#define PID_CELLVOLTAGE_CELL_54 0x1E75
#define PID_CELLVOLTAGE_CELL_55 0x1E76
#define PID_CELLVOLTAGE_CELL_56 0x1E77
#define PID_CELLVOLTAGE_CELL_57 0x1E78
#define PID_CELLVOLTAGE_CELL_58 0x1E79
#define PID_CELLVOLTAGE_CELL_59 0x1E7A
#define PID_CELLVOLTAGE_CELL_60 0x1E7B
#define PID_CELLVOLTAGE_CELL_61 0x1E7C
#define PID_CELLVOLTAGE_CELL_62 0x1E7D
#define PID_CELLVOLTAGE_CELL_63 0x1E7E
#define PID_CELLVOLTAGE_CELL_64 0x1E7F
#define PID_CELLVOLTAGE_CELL_65 0x1E80
#define PID_CELLVOLTAGE_CELL_66 0x1E81
#define PID_CELLVOLTAGE_CELL_67 0x1E82
#define PID_CELLVOLTAGE_CELL_68 0x1E83
#define PID_CELLVOLTAGE_CELL_69 0x1E84
#define PID_CELLVOLTAGE_CELL_70 0x1E85
#define PID_CELLVOLTAGE_CELL_71 0x1E86
#define PID_CELLVOLTAGE_CELL_72 0x1E87
#define PID_CELLVOLTAGE_CELL_73 0x1E88
#define PID_CELLVOLTAGE_CELL_74 0x1E89
#define PID_CELLVOLTAGE_CELL_75 0x1E8A
#define PID_CELLVOLTAGE_CELL_76 0x1E8B
#define PID_CELLVOLTAGE_CELL_77 0x1E8C
#define PID_CELLVOLTAGE_CELL_78 0x1E8D
#define PID_CELLVOLTAGE_CELL_79 0x1E8E
#define PID_CELLVOLTAGE_CELL_80 0x1E8F
#define PID_CELLVOLTAGE_CELL_81 0x1E90
#define PID_CELLVOLTAGE_CELL_82 0x1E91
#define PID_CELLVOLTAGE_CELL_83 0x1E92
#define PID_CELLVOLTAGE_CELL_84 0x1E93
#define PID_CELLVOLTAGE_CELL_85 0x1E94
#define PID_CELLVOLTAGE_CELL_86 0x1E95
#define PID_CELLVOLTAGE_CELL_87 0x1E96
#define PID_CELLVOLTAGE_CELL_88 0x1E97
#define PID_CELLVOLTAGE_CELL_89 0x1E98
#define PID_CELLVOLTAGE_CELL_90 0x1E99
#define PID_CELLVOLTAGE_CELL_91 0x1E9A
#define PID_CELLVOLTAGE_CELL_92 0x1E9B
#define PID_CELLVOLTAGE_CELL_93 0x1E9C
#define PID_CELLVOLTAGE_CELL_94 0x1E9D
#define PID_CELLVOLTAGE_CELL_95 0x1E9E
#define PID_CELLVOLTAGE_CELL_96 0x1E9F
#define PID_CELLVOLTAGE_CELL_97 0x1EA0
#define PID_CELLVOLTAGE_CELL_98 0x1EA1
#define PID_CELLVOLTAGE_CELL_99 0x1EA2
#define PID_CELLVOLTAGE_CELL_100 0x1EA3
#define PID_CELLVOLTAGE_CELL_101 0x1EA4
#define PID_CELLVOLTAGE_CELL_102 0x1EA5
#define PID_CELLVOLTAGE_CELL_103 0x1EA6
#define PID_CELLVOLTAGE_CELL_104 0x1EA7
#define PID_CELLVOLTAGE_CELL_105 0x1EA8
#define PID_CELLVOLTAGE_CELL_106 0x1EA9
#define PID_CELLVOLTAGE_CELL_107 0x1EAA
#define PID_CELLVOLTAGE_CELL_108 0x1EAB
#define PID_TEMP_POINT_1 0x1EAE
#define PID_TEMP_POINT_2 0x1EAF
#define PID_TEMP_POINT_3 0x1EB0
#define PID_TEMP_POINT_4 0x1EB1
#define PID_TEMP_POINT_5 0x1EB2
#define PID_TEMP_POINT_6 0x1EB3
#define PID_TEMP_POINT_7 0x1EB4
#define PID_TEMP_POINT_8 0x1EB5
#define PID_TEMP_POINT_9 0x1EB6
#define PID_TEMP_POINT_10 0x1EB7
#define PID_TEMP_POINT_11 0x1EB8
#define PID_TEMP_POINT_12 0x1EB9
#define PID_TEMP_POINT_13 0x1EBA
#define PID_TEMP_POINT_14 0x1EBB
#define PID_TEMP_POINT_15 0x1EBC
#define PID_TEMP_POINT_16 0x1EBD
#define PID_TEMP_POINT_17 0x1EBE
#define PID_TEMP_POINT_18 0x1EBF
void setup_battery(void); private:
void transmit_can_frame(CAN_frame* tx_frame, int interface); static const int MAX_PACK_VOLTAGE_84S_DV = 3528; //5000 = 500.0V
static const int MIN_PACK_VOLTAGE_84S_DV = 2520;
static const int MAX_PACK_VOLTAGE_96S_DV = 4032;
static const int MIN_PACK_VOLTAGE_96S_DV = 2880;
static const int MAX_PACK_VOLTAGE_108S_DV = 4536;
static const int MIN_PACK_VOLTAGE_108S_DV = 3240;
static const int MAX_CELL_DEVIATION_MV = 150;
static const int MAX_CELL_VOLTAGE_MV = 4250; //Battery is put into emergency stop if one cell goes over this value
static const int MIN_CELL_VOLTAGE_MV = 2700; //Battery is put into emergency stop if one cell goes below this value
static const int PID_SOC = 0x028C;
static const int PID_VOLTAGE = 0x1E3B;
static const int PID_CURRENT = 0x1E3D;
static const int PID_MAX_TEMP = 0x1E0E;
static const int PID_MIN_TEMP = 0x1E0F;
static const int PID_MAX_CHARGE_VOLTAGE = 0x5171;
static const int PID_MIN_DISCHARGE_VOLTAGE = 0x5170;
static const int PID_ENERGY_COUNTERS = 0x1E32;
static const int PID_ALLOWED_CHARGE_POWER = 0x1E1B;
static const int PID_ALLOWED_DISCHARGE_POWER = 0x1E1C;
static const int PID_CELLVOLTAGE_CELL_1 = 0x1E40;
static const int PID_CELLVOLTAGE_CELL_2 = 0x1E41;
static const int PID_CELLVOLTAGE_CELL_3 = 0x1E42;
static const int PID_CELLVOLTAGE_CELL_4 = 0x1E43;
static const int PID_CELLVOLTAGE_CELL_5 = 0x1E44;
static const int PID_CELLVOLTAGE_CELL_6 = 0x1E45;
static const int PID_CELLVOLTAGE_CELL_7 = 0x1E46;
static const int PID_CELLVOLTAGE_CELL_8 = 0x1E47;
static const int PID_CELLVOLTAGE_CELL_9 = 0x1E48;
static const int PID_CELLVOLTAGE_CELL_10 = 0x1E49;
static const int PID_CELLVOLTAGE_CELL_11 = 0x1E4A;
static const int PID_CELLVOLTAGE_CELL_12 = 0x1E4B;
static const int PID_CELLVOLTAGE_CELL_13 = 0x1E4C;
static const int PID_CELLVOLTAGE_CELL_14 = 0x1E4D;
static const int PID_CELLVOLTAGE_CELL_15 = 0x1E4E;
static const int PID_CELLVOLTAGE_CELL_16 = 0x1E4F;
static const int PID_CELLVOLTAGE_CELL_17 = 0x1E50;
static const int PID_CELLVOLTAGE_CELL_18 = 0x1E51;
static const int PID_CELLVOLTAGE_CELL_19 = 0x1E52;
static const int PID_CELLVOLTAGE_CELL_20 = 0x1E53;
static const int PID_CELLVOLTAGE_CELL_21 = 0x1E54;
static const int PID_CELLVOLTAGE_CELL_22 = 0x1E55;
static const int PID_CELLVOLTAGE_CELL_23 = 0x1E56;
static const int PID_CELLVOLTAGE_CELL_24 = 0x1E57;
static const int PID_CELLVOLTAGE_CELL_25 = 0x1E58;
static const int PID_CELLVOLTAGE_CELL_26 = 0x1E59;
static const int PID_CELLVOLTAGE_CELL_27 = 0x1E5A;
static const int PID_CELLVOLTAGE_CELL_28 = 0x1E5B;
static const int PID_CELLVOLTAGE_CELL_29 = 0x1E5C;
static const int PID_CELLVOLTAGE_CELL_30 = 0x1E5D;
static const int PID_CELLVOLTAGE_CELL_31 = 0x1E5E;
static const int PID_CELLVOLTAGE_CELL_32 = 0x1E5F;
static const int PID_CELLVOLTAGE_CELL_33 = 0x1E60;
static const int PID_CELLVOLTAGE_CELL_34 = 0x1E61;
static const int PID_CELLVOLTAGE_CELL_35 = 0x1E62;
static const int PID_CELLVOLTAGE_CELL_36 = 0x1E63;
static const int PID_CELLVOLTAGE_CELL_37 = 0x1E64;
static const int PID_CELLVOLTAGE_CELL_38 = 0x1E65;
static const int PID_CELLVOLTAGE_CELL_39 = 0x1E66;
static const int PID_CELLVOLTAGE_CELL_40 = 0x1E67;
static const int PID_CELLVOLTAGE_CELL_41 = 0x1E68;
static const int PID_CELLVOLTAGE_CELL_42 = 0x1E69;
static const int PID_CELLVOLTAGE_CELL_43 = 0x1E6A;
static const int PID_CELLVOLTAGE_CELL_44 = 0x1E6B;
static const int PID_CELLVOLTAGE_CELL_45 = 0x1E6C;
static const int PID_CELLVOLTAGE_CELL_46 = 0x1E6D;
static const int PID_CELLVOLTAGE_CELL_47 = 0x1E6E;
static const int PID_CELLVOLTAGE_CELL_48 = 0x1E6F;
static const int PID_CELLVOLTAGE_CELL_49 = 0x1E70;
static const int PID_CELLVOLTAGE_CELL_50 = 0x1E71;
static const int PID_CELLVOLTAGE_CELL_51 = 0x1E72;
static const int PID_CELLVOLTAGE_CELL_52 = 0x1E73;
static const int PID_CELLVOLTAGE_CELL_53 = 0x1E74;
static const int PID_CELLVOLTAGE_CELL_54 = 0x1E75;
static const int PID_CELLVOLTAGE_CELL_55 = 0x1E76;
static const int PID_CELLVOLTAGE_CELL_56 = 0x1E77;
static const int PID_CELLVOLTAGE_CELL_57 = 0x1E78;
static const int PID_CELLVOLTAGE_CELL_58 = 0x1E79;
static const int PID_CELLVOLTAGE_CELL_59 = 0x1E7A;
static const int PID_CELLVOLTAGE_CELL_60 = 0x1E7B;
static const int PID_CELLVOLTAGE_CELL_61 = 0x1E7C;
static const int PID_CELLVOLTAGE_CELL_62 = 0x1E7D;
static const int PID_CELLVOLTAGE_CELL_63 = 0x1E7E;
static const int PID_CELLVOLTAGE_CELL_64 = 0x1E7F;
static const int PID_CELLVOLTAGE_CELL_65 = 0x1E80;
static const int PID_CELLVOLTAGE_CELL_66 = 0x1E81;
static const int PID_CELLVOLTAGE_CELL_67 = 0x1E82;
static const int PID_CELLVOLTAGE_CELL_68 = 0x1E83;
static const int PID_CELLVOLTAGE_CELL_69 = 0x1E84;
static const int PID_CELLVOLTAGE_CELL_70 = 0x1E85;
static const int PID_CELLVOLTAGE_CELL_71 = 0x1E86;
static const int PID_CELLVOLTAGE_CELL_72 = 0x1E87;
static const int PID_CELLVOLTAGE_CELL_73 = 0x1E88;
static const int PID_CELLVOLTAGE_CELL_74 = 0x1E89;
static const int PID_CELLVOLTAGE_CELL_75 = 0x1E8A;
static const int PID_CELLVOLTAGE_CELL_76 = 0x1E8B;
static const int PID_CELLVOLTAGE_CELL_77 = 0x1E8C;
static const int PID_CELLVOLTAGE_CELL_78 = 0x1E8D;
static const int PID_CELLVOLTAGE_CELL_79 = 0x1E8E;
static const int PID_CELLVOLTAGE_CELL_80 = 0x1E8F;
static const int PID_CELLVOLTAGE_CELL_81 = 0x1E90;
static const int PID_CELLVOLTAGE_CELL_82 = 0x1E91;
static const int PID_CELLVOLTAGE_CELL_83 = 0x1E92;
static const int PID_CELLVOLTAGE_CELL_84 = 0x1E93;
static const int PID_CELLVOLTAGE_CELL_85 = 0x1E94;
static const int PID_CELLVOLTAGE_CELL_86 = 0x1E95;
static const int PID_CELLVOLTAGE_CELL_87 = 0x1E96;
static const int PID_CELLVOLTAGE_CELL_88 = 0x1E97;
static const int PID_CELLVOLTAGE_CELL_89 = 0x1E98;
static const int PID_CELLVOLTAGE_CELL_90 = 0x1E99;
static const int PID_CELLVOLTAGE_CELL_91 = 0x1E9A;
static const int PID_CELLVOLTAGE_CELL_92 = 0x1E9B;
static const int PID_CELLVOLTAGE_CELL_93 = 0x1E9C;
static const int PID_CELLVOLTAGE_CELL_94 = 0x1E9D;
static const int PID_CELLVOLTAGE_CELL_95 = 0x1E9E;
static const int PID_CELLVOLTAGE_CELL_96 = 0x1E9F;
static const int PID_CELLVOLTAGE_CELL_97 = 0x1EA0;
static const int PID_CELLVOLTAGE_CELL_98 = 0x1EA1;
static const int PID_CELLVOLTAGE_CELL_99 = 0x1EA2;
static const int PID_CELLVOLTAGE_CELL_100 = 0x1EA3;
static const int PID_CELLVOLTAGE_CELL_101 = 0x1EA4;
static const int PID_CELLVOLTAGE_CELL_102 = 0x1EA5;
static const int PID_CELLVOLTAGE_CELL_103 = 0x1EA6;
static const int PID_CELLVOLTAGE_CELL_104 = 0x1EA7;
static const int PID_CELLVOLTAGE_CELL_105 = 0x1EA8;
static const int PID_CELLVOLTAGE_CELL_106 = 0x1EA9;
static const int PID_CELLVOLTAGE_CELL_107 = 0x1EAA;
static const int PID_CELLVOLTAGE_CELL_108 = 0x1EAB;
static const int PID_TEMP_POINT_1 = 0x1EAE;
static const int PID_TEMP_POINT_2 = 0x1EAF;
static const int PID_TEMP_POINT_3 = 0x1EB0;
static const int PID_TEMP_POINT_4 = 0x1EB1;
static const int PID_TEMP_POINT_5 = 0x1EB2;
static const int PID_TEMP_POINT_6 = 0x1EB3;
static const int PID_TEMP_POINT_7 = 0x1EB4;
static const int PID_TEMP_POINT_8 = 0x1EB5;
static const int PID_TEMP_POINT_9 = 0x1EB6;
static const int PID_TEMP_POINT_10 = 0x1EB7;
static const int PID_TEMP_POINT_11 = 0x1EB8;
static const int PID_TEMP_POINT_12 = 0x1EB9;
static const int PID_TEMP_POINT_13 = 0x1EBA;
static const int PID_TEMP_POINT_14 = 0x1EBB;
static const int PID_TEMP_POINT_15 = 0x1EBC;
static const int PID_TEMP_POINT_16 = 0x1EBD;
static const int PID_TEMP_POINT_17 = 0x1EBE;
static const int PID_TEMP_POINT_18 = 0x1EBF;
unsigned long previousMillis10ms = 0; // will store last time a 10ms CAN Message was send
unsigned long previousMillis20ms = 0; // will store last time a 20ms CAN Message was send
unsigned long previousMillis40ms = 0; // will store last time a 40ms CAN Message was send
unsigned long previousMillis50ms = 0; // will store last time a 50ms CAN Message was send
unsigned long previousMillis100ms = 0; // will store last time a 100ms CAN Message was send
unsigned long previousMillis200ms = 0; // will store last time a 200ms CAN Message was send
unsigned long previousMillis500ms = 0; // will store last time a 200ms CAN Message was send
unsigned long previousMillis1s = 0; // will store last time a 1s CAN Message was send
bool toggle = false;
uint8_t counter_1000ms = 0;
uint8_t counter_200ms = 0;
uint8_t counter_100ms = 0;
uint8_t counter_50ms = 0;
uint8_t counter_40ms = 0;
uint8_t counter_20ms = 0;
uint8_t counter_10ms = 0;
uint8_t counter_040 = 0;
uint8_t counter_0F7 = 0;
uint8_t counter_3b5 = 0;
uint32_t poll_pid = PID_CELLVOLTAGE_CELL_85; // We start here to quickly determine the cell size of the pack.
bool nof_cells_determined = false;
uint32_t pid_reply = 0;
uint16_t battery_soc_polled = 0;
uint16_t battery_voltage_polled = 1480;
int16_t battery_current_polled = 0;
int16_t battery_max_temp = 600;
int16_t battery_min_temp = 600;
uint16_t battery_max_charge_voltage = 0;
uint16_t battery_min_discharge_voltage = 0;
uint16_t battery_allowed_charge_power = 0;
uint16_t battery_allowed_discharge_power = 0;
uint16_t cellvoltages_polled[108];
uint16_t tempval = 0;
uint8_t BMS_16A954A6_CRC = 0;
uint8_t BMS_5A2_counter = 0;
uint8_t BMS_5CA_counter = 0;
uint8_t BMS_0CF_counter = 0;
uint8_t BMS_0C0_counter = 0;
uint8_t BMS_578_counter = 0;
uint8_t BMS_16A954A6_counter = 0;
bool BMS_ext_limits_active =
false; //The current current limits of the HV battery are expanded to start the combustion engine / confirmation of the request
uint8_t BMS_mode =
0x07; //0: standby; Gates open; Communication active 1: Main contactor closed / HV network activated / normal driving operation
//2: assigned depending on the project (e.g. balancing, extended DC fast charging) //3: external charging
uint8_t BMS_HVIL_status = 0; //0 init, 1 seated, 2 open, 3 fault
bool BMS_fault_performance = false; //Error: Battery performance is limited (e.g. due to sensor or fan failure)
uint16_t BMS_current = 16300;
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
uint32_t BMS_voltage_intermediate = 2000;
uint32_t BMS_voltage = 1480;
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
bool BMS_OBD_MIL = false;
uint8_t BMS_error_status =
0x7; //0 Component_IO, 1 Restricted_CompFkt_Isoerror_I, 2 Restricted_CompFkt_Isoerror_II, 3 Restricted_CompFkt_Interlock, 4 Restricted_CompFkt_SD, 5 Restricted_CompFkt_Performance red, 6 = No component function, 7 = Init
uint16_t BMS_capacity_ah = 0;
bool BMS_error_lamp_req = false;
bool BMS_warning_lamp_req = false;
uint8_t BMS_Kl30c_Status = 0; // 0 init, 1 closed, 2 open, 3 fault
bool service_disconnect_switch_missing = false;
bool pilotline_open = false;
bool balancing_request = false;
uint8_t battery_diagnostic = 0;
uint16_t battery_Wh_left = 0;
uint16_t battery_Wh_max = 1000;
uint8_t battery_potential_status = 0;
uint8_t battery_temperature_warning = 0;
uint16_t max_discharge_power_watt = 0;
uint16_t max_discharge_current_amp = 0;
uint16_t max_charge_power_watt = 0;
uint16_t max_charge_current_amp = 0;
uint16_t battery_SOC = 1;
uint16_t usable_energy_amount_Wh = 0;
uint8_t status_HV_line = 0; //0 init, 1 No open HV line, 2 open HV line detected, 3 fault
uint8_t warning_support = 0;
bool battery_heating_active = false;
uint16_t power_discharge_percentage = 0;
uint16_t power_charge_percentage = 0;
uint16_t actual_battery_voltage = 0;
uint16_t regen_battery = 0;
uint16_t energy_extracted_from_battery = 0;
uint16_t max_fastcharging_current_amp = 0; //maximum DC charging current allowed
uint8_t BMS_Status_DCLS =
0; //Status of the voltage monitoring on the DC charging interface. 0 inactive, 1 I_O , 2 N_I_O , 3 active
uint16_t DC_voltage_DCLS = 0; //Factor 1, DC voltage of the charging station. Measurement between the DC HV lines.
uint16_t DC_voltage_chargeport =
0; //Factor 0.5, Current voltage at the HV battery DC charging terminals; Outlet to the DC charging plug.
uint8_t BMS_welded_contactors_status =
0; //0: Init no diagnostic result, 1: no contactor welded, 2: at least 1 contactor welded, 3: Protection status detection error
bool BMS_error_shutdown_request =
false; // Fault: Fault condition, requires battery contactors to be opened internal battery error; Advance notification of an impending opening of the battery contactors by the BMS
bool BMS_error_shutdown =
false; // Fault: Fault condition, requires battery contactors to be opened Internal battery error, battery contactors opened without notice by the BMS
uint16_t power_battery_heating_watt = 0;
uint16_t power_battery_heating_req_watt = 0;
uint8_t cooling_request =
0; //0 = No cooling, 1 = Light cooling, cabin prio, 2= higher cooling, 3 = immediate cooling, 4 = emergency cooling
uint8_t heating_request = 0; //0 = init, 1= maintain temp, 2=higher demand, 3 = immediate heat demand
uint8_t balancing_active = false; //0 = init, 1 active, 2 not active
bool charging_active = false;
uint16_t max_energy_Wh = 0;
uint16_t max_charge_percent = 0;
uint16_t min_charge_percent = 0;
uint16_t isolation_resistance_kOhm = 0;
bool battery_heating_installed = false;
bool error_NT_circuit = false;
uint8_t pump_1_control = 0; //0x0D not installed, 0x0E init, 0x0F fault
uint8_t pump_2_control = 0; //0x0D not installed, 0x0E init, 0x0F fault
uint8_t target_flow_temperature_C = 0; //*0,5 -40
uint8_t return_temperature_C = 0; //*0,5 -40
uint8_t status_valve_1 = 0; //0 not active, 1 active, 5 not installed, 6 init, 7 fault
uint8_t status_valve_2 = 0; //0 not active, 1 active, 5 not installed, 6 init, 7 fault
uint8_t temperature_request =
0; //0 high cooling, 1 medium cooling, 2 low cooling, 3 no temp requirement init, 4 low heating , 5 medium heating, 6 high heating, 7 circulation
uint16_t performance_index_discharge_peak_temperature_percentage = 0;
uint16_t performance_index_charge_peak_temperature_percentage = 0;
uint8_t temperature_status_discharge =
0; //0 init, 1 temp under optimal, 2 temp optimal, 3 temp over optimal, 7 fault
uint8_t temperature_status_charge = 0; //0 init, 1 temp under optimal, 2 temp optimal, 3 temp over optimal, 7 fault
uint8_t isolation_fault =
0; //0 init, 1 no iso fault, 2 iso fault threshold1, 3 iso fault threshold2, 4 IWU defective
uint8_t isolation_status =
0; // 0 init, 1 = larger threshold1, 2 = smaller threshold1 total, 3 = smaller threshold1 intern, 4 = smaller threshold2 total, 5 = smaller threshold2 intern, 6 = no measurement, 7 = measurement active
uint8_t actual_temperature_highest_C = 0; //*0,5 -40
uint8_t actual_temperature_lowest_C = 0; //*0,5 -40
uint16_t actual_cellvoltage_highest_mV = 0; //bias 1000
uint16_t actual_cellvoltage_lowest_mV = 0; //bias 1000
uint16_t predicted_power_dyn_standard_watt = 0;
uint8_t predicted_time_dyn_standard_minutes = 0;
uint8_t mux = 0;
uint16_t cellvoltages[160] = {0};
uint16_t duration_discharge_power_watt = 0;
uint16_t duration_charge_power_watt = 0;
uint16_t maximum_voltage = 0;
uint16_t minimum_voltage = 0;
uint8_t battery_serialnumber[26];
uint8_t realtime_overcurrent_monitor = 0;
uint8_t realtime_CAN_communication_fault = 0;
uint8_t realtime_overcharge_warning = 0;
uint8_t realtime_SOC_too_high = 0;
uint8_t realtime_SOC_too_low = 0;
uint8_t realtime_SOC_jumping_warning = 0;
uint8_t realtime_temperature_difference_warning = 0;
uint8_t realtime_cell_overtemperature_warning = 0;
uint8_t realtime_cell_undertemperature_warning = 0;
uint8_t realtime_battery_overvoltage_warning = 0;
uint8_t realtime_battery_undervoltage_warning = 0;
uint8_t realtime_cell_overvoltage_warning = 0;
uint8_t realtime_cell_undervoltage_warning = 0;
uint8_t realtime_cell_imbalance_warning = 0;
uint8_t realtime_warning_battery_unathorized = 0;
bool component_protection_active = false;
bool shutdown_active = false;
bool transportation_mode_active = false;
uint8_t KL15_mode = 0;
uint8_t bus_knockout_timer = 0;
uint8_t hybrid_wakeup_reason = 0;
uint8_t wakeup_type = 0;
bool instrumentation_cluster_request = false;
uint8_t seconds = 0;
uint32_t first_can_msg = 0;
uint32_t last_can_msg_timestamp = 0;
bool hv_requested = false;
int32_t kwh_charge = 0;
int32_t kwh_discharge = 0;
#define TIME_YEAR 2024
#define TIME_MONTH 8
#define TIME_DAY 20
#define TIME_HOUR 10
#define TIME_MINUTE 5
#define TIME_SECOND 0
#define BMS_TARGET_HV_OFF 0
#define BMS_TARGET_HV_ON 1
#define BMS_TARGET_AC_CHARGING_EXT 3 //(HS + AC_2 contactors closed)
#define BMS_TARGET_AC_CHARGING 4 //(HS + AC contactors closed)
#define BMS_TARGET_DC_CHARGING 6 //(HS + DC contactors closed)
#define BMS_TARGET_INIT 7
#define DC_FASTCHARGE_NO_START_REQUEST 0x00
#define DC_FASTCHARGE_VEHICLE 0x40
#define DC_FASTCHARGE_LS1 0x80
#define DC_FASTCHARGE_LS2 0xC0
CAN_frame MEB_POLLING_FRAME = {.FD = true,
.ext_ID = true,
.DLC = 8,
.ID = 0x1C40007B, // SOC 02 8C
.data = {0x03, 0x22, 0x02, 0x8C, 0x55, 0x55, 0x55, 0x55}};
CAN_frame MEB_ACK_FRAME = {.FD = true,
.ext_ID = true,
.DLC = 8,
.ID = 0x1C40007B, // Ack
.data = {0x30, 0x00, 0x00, 0x55, 0x55, 0x55, 0x55, 0x55}};
//Messages needed for contactor closing
CAN_frame MEB_040 = {.FD = true, // Airbag
.ext_ID = false,
.DLC = 8,
.ID = 0x040, //Frame5 has HV deactivate request. Needs to be 0x00
.data = {0x7E, 0x83, 0x00, 0x01, 0x00, 0x00, 0x15, 0x00}};
CAN_frame MEB_0C0 = {
.FD = true, // EM1 message
.ext_ID = false,
.DLC = 32,
.ID = 0x0C0, //Frame 5-6 and maybe 7-8 important (external voltage at inverter)
.data = {0x77, 0x0A, 0xFE, 0xE7, 0x7F, 0x10, 0x27, 0x00, 0xE0, 0x7F, 0xFF, 0xF3, 0x3F, 0xFF, 0xF3, 0x3F,
0xFC, 0x0F, 0x00, 0x00, 0xC0, 0xFF, 0xFE, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame MEB_0FC = {
.FD = true, //
.ext_ID = false,
.DLC = 48,
.ID = 0x0FC, //This message contains emergency regen request?(byte19), battery needs to see this message
.data = {0x07, 0x08, 0x00, 0x00, 0x7E, 0x00, 0x40, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFE, 0xFE, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xF4, 0x01, 0x40, 0xFF, 0xEB, 0x7F, 0x0A, 0x88, 0xE3, 0x81, 0xAF, 0x42}};
CAN_frame MEB_6B2 = {.FD = true, // Diagnostics
.ext_ID = false,
.DLC = 8,
.ID = 0x6B2,
.data = {0x6A, 0xA7, 0x37, 0x80, 0xC9, 0xBD, 0xF6, 0xC2}};
CAN_frame MEB_17FC007B_poll = {.FD = true, // Non period request
.ext_ID = true,
.DLC = 8,
.ID = 0x17FC007B,
.data = {0x03, 0x22, 0x1E, 0x3D, 0x55, 0x55, 0x55, 0x55}};
CAN_frame MEB_1A5555A6 = {.FD = true,
.ext_ID = true,
.DLC = 8,
.ID = 0x1A5555A6,
.data = {0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame MEB_585 = {
.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x585,
.data = {0xCF, 0x38, 0xAF, 0x5B, 0x25, 0x00, 0x00, 0x00}}; // CF 38 AF 5B 25 00 00 00 in start4.log
// .data = {0xCF, 0x38, 0x20, 0x02, 0x25, 0xF7, 0x30, 0x00}}; // CF 38 AF 5B 25 00 00 00 in start4.log
CAN_frame MEB_5F5 = {.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x5F5,
.data = {0x23, 0x02, 0x39, 0xC0, 0x1B, 0x8B, 0xC8, 0x1B}};
CAN_frame MEB_641 = {.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x641,
.data = {0x37, 0x18, 0x00, 0x00, 0xF0, 0x00, 0xAA, 0x70}};
CAN_frame MEB_3C0 = {.FD = true,
.ext_ID = false,
.DLC = 4,
.ID = 0x3C0,
.data = {0x66, 0x00, 0x00, 0x00}}; // Klemmen_status_01
CAN_frame MEB_0FD = {.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x0FD, //CRC and counter, otherwise static
.data = {0x5F, 0xD0, 0x1F, 0x81, 0x00, 0x00, 0x00, 0x00}};
CAN_frame MEB_16A954FB = {.FD = true,
.ext_ID = true,
.DLC = 8,
.ID = 0x16A954FB,
.data = {0x00, 0xC0, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame MEB_1A555548 = {.FD = true,
.ext_ID = true,
.DLC = 8,
.ID = 0x1A555548,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame MEB_1A55552B = {.FD = true,
.ext_ID = true,
.DLC = 8,
.ID = 0x1A55552B,
.data = {0x00, 0x00, 0x00, 0xA0, 0x02, 0x04, 0x00, 0x30}};
CAN_frame MEB_569 = {.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x569, //HVEM
.data = {0x00, 0x00, 0x01, 0x3A, 0x00, 0x00, 0x00, 0x00}};
CAN_frame MEB_16A954B4 = {.FD = true,
.ext_ID = true,
.DLC = 8,
.ID = 0x16A954B4, //eTM
.data = {0xFE, 0xB6, 0x0D, 0x00, 0x00, 0xD5, 0x48, 0xFD}};
CAN_frame MEB_1B000046 = {.FD = false, // Not FD
.ext_ID = true,
.DLC = 8,
.ID = 0x1B000046, // Klima
.data = {0x00, 0x40, 0x08, 0x01, 0x00, 0x00, 0x00, 0x00}};
CAN_frame MEB_1B000010 = {.FD = false, // Not FD
.ext_ID = true,
.DLC = 8,
.ID = 0x1B000010, // Gateway
.data = {0x00, 0x50, 0x08, 0x50, 0x01, 0xFF, 0x30, 0x00}};
CAN_frame MEB_1B0000B9 = {.FD = false, // Not FD
.ext_ID = true,
.DLC = 8,
.ID = 0x1B0000B9, //DC/DC converter
.data = {0x00, 0x40, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00}};
CAN_frame MEB_153 = {.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x153, // content
.data = {0x00, 0x00, 0x00, 0xFF, 0xEF, 0xFE, 0xFF, 0xFF}};
CAN_frame MEB_5E1 = {.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x5E1, // content
.data = {0x7F, 0x2A, 0x00, 0x60, 0xFE, 0x00, 0x00, 0x00}};
CAN_frame MEB_3BE = {.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x3BE, // CRC, otherwise content
.data = {0x57, 0x0D, 0x00, 0x00, 0x00, 0x02, 0x04, 0x40}};
CAN_frame MEB_272 = {.FD = true, //HVLM_14
.ext_ID = false,
.DLC = 8,
.ID = 0x272, // content
.data = {0x00, 0x00, 0x00, 0x00, 0x48, 0x08, 0x00, 0x94}};
CAN_frame MEB_503 = {.FD = true, //HVK_01
.ext_ID = false,
.DLC = 8,
.ID = 0x503, // Content varies. Frame1 & 3 has HV req
.data = {0x5D, 0x61, 0x00, 0xFF, 0x7F, 0x80, 0xE3, 0x03}};
CAN_frame MEB_14C = {
.FD = true, //Motor message
.ext_ID = false,
.DLC = 32,
.ID = 0x14C, //CRC needed, content otherwise
.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}};
uint32_t can_msg_received = 0;
};
#endif #endif

View file

@ -1,5 +1,6 @@
#include "../include.h" #include "../include.h"
#ifdef MG_5_BATTERY_H #ifdef MG_5_BATTERY_H
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "../devboard/utils/events.h" #include "../devboard/utils/events.h"
#include "MG-5-BATTERY.h" #include "MG-5-BATTERY.h"
@ -11,19 +12,8 @@
- Most important ones - Most important ones
*/ */
/* Do not change code below unless you are sure what you are doing */ void Mg5Battery::
static unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was send update_values() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
static int BMS_SOC = 0;
CAN_frame MG_5_100 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x100,
.data = {0x00, 0x00, 0x00, 0x00, 0x80, 0x10, 0x00, 0x00}};
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
datalayer.battery.status.real_soc; datalayer.battery.status.real_soc;
@ -44,7 +34,7 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.temperature_max_dC; datalayer.battery.status.temperature_max_dC;
} }
void handle_incoming_can_frame_battery(CAN_frame rx_frame) { void Mg5Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
switch (rx_frame.ID) { switch (rx_frame.ID) {
case 0x171: //Following messages were detected on a MG5 battery BMS case 0x171: //Following messages were detected on a MG5 battery BMS
@ -108,7 +98,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
break; break;
} }
} }
void transmit_can_battery(unsigned long currentMillis) { void Mg5Battery::transmit_can(unsigned long currentMillis) {
//Send 10ms message //Send 10ms message
if (currentMillis - previousMillis10 >= INTERVAL_10_MS) { if (currentMillis - previousMillis10 >= INTERVAL_10_MS) {
previousMillis10 = currentMillis; previousMillis10 = currentMillis;
@ -123,7 +113,7 @@ void transmit_can_battery(unsigned long currentMillis) {
} }
} }
void setup_battery(void) { // Performs one time setup at startup void Mg5Battery::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "MG 5 battery", 63); strncpy(datalayer.system.info.battery_protocol, "MG 5 battery", 63);
datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.info.battery_protocol[63] = '\0';
datalayer.system.status.battery_allows_contactor_closing = true; datalayer.system.status.battery_allows_contactor_closing = true;

View file

@ -3,14 +3,35 @@
#include <Arduino.h> #include <Arduino.h>
#include "../include.h" #include "../include.h"
#define BATTERY_SELECTED #include "CanBattery.h"
#define MAX_PACK_VOLTAGE_DV 4040 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_DV 3100
#define MAX_CELL_DEVIATION_MV 150
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
void setup_battery(void); #define BATTERY_SELECTED
void transmit_can_frame(CAN_frame* tx_frame, int interface); #define SELECTED_BATTERY_CLASS Mg5Battery
class Mg5Battery : public CanBattery {
public:
virtual void setup(void);
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
virtual void update_values();
virtual void transmit_can(unsigned long currentMillis);
private:
static const int MAX_PACK_VOLTAGE_DV = 4040; //5000 = 500.0V
static const int MIN_PACK_VOLTAGE_DV = 3100;
static const int MAX_CELL_DEVIATION_MV = 150;
static const int MAX_CELL_VOLTAGE_MV = 4250; //Battery is put into emergency stop if one cell goes over this value
static const int MIN_CELL_VOLTAGE_MV = 2700; //Battery is put into emergency stop if one cell goes below this value
unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was send
unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
int BMS_SOC = 0;
CAN_frame MG_5_100 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x100,
.data = {0x00, 0x00, 0x00, 0x00, 0x80, 0x10, 0x00, 0x00}};
};
#endif #endif

View file

@ -4,223 +4,55 @@
#ifdef MQTT #ifdef MQTT
#include "../devboard/mqtt/mqtt.h" #include "../devboard/mqtt/mqtt.h"
#endif #endif
#include "../communication/can/comm_can.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"
/* Do not change code below unless you are sure what you are doing */ #include "../charger/CanCharger.h"
static unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was send
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
static unsigned long previousMillis10s = 0; // will store last time a 1s CAN Message was send
static uint8_t mprun10r = 0; //counter 0-20 for 0x1F2 message
static uint8_t mprun10 = 0; //counter 0-3
static uint8_t mprun100 = 0; //counter 0-3
// These CAN messages need to be sent towards the battery to keep it alive uint16_t Temp_fromRAW_to_F(uint16_t temperature);
CAN_frame LEAF_1F2 = {.FD = false, //Cryptographic functions
.ext_ID = false, void decodeChallengeData(unsigned int SeedInput, unsigned char* Crypt_Output_Buffer);
.DLC = 8, unsigned int CyclicXorHash16Bit(unsigned int param_1, unsigned int param_2);
.ID = 0x1F2, unsigned int ComputeMaskedXorProduct(unsigned int param_1, unsigned int param_2, unsigned int param_3);
.data = {0x10, 0x64, 0x00, 0xB0, 0x00, 0x1E, 0x00, 0x8F}}; short ShortMaskedSumAndProduct(short param_1, short param_2);
CAN_frame LEAF_50B = {.FD = false, unsigned int MaskedBitwiseRotateMultiply(unsigned int param_1, unsigned int param_2);
.ext_ID = false, unsigned int CryptAlgo(unsigned int param_1, unsigned int param_2, unsigned int param_3);
.DLC = 7,
.ID = 0x50B,
.data = {0x00, 0x00, 0x06, 0xC0, 0x00, 0x00, 0x00}};
CAN_frame LEAF_50C = {.FD = false,
.ext_ID = false,
.DLC = 6,
.ID = 0x50C,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame LEAF_1D4 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x1D4,
.data = {0x6E, 0x6E, 0x00, 0x04, 0x07, 0x46, 0xE0, 0x44}};
// Active polling messages
uint8_t PIDgroups[] = {0x01, 0x02, 0x04, 0x83, 0x84, 0x90};
uint8_t PIDindex = 0;
CAN_frame LEAF_GROUP_REQUEST = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x79B,
.data = {2, 0x21, 1, 0, 0, 0, 0, 0}};
CAN_frame LEAF_NEXT_LINE_REQUEST = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x79B,
.data = {0x30, 1, 0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}};
// The Li-ion battery controller only accepts a multi-message query. In fact, the LBC transmits many void NissanLeafBattery::
// groups: the first one contains lots of High Voltage battery data as SOC, currents, and voltage; the second update_values() { /* This function maps all the values fetched via CAN to the correct parameters used for modbus */
// replies with all the batterys cells voltages in millivolt, the third and the fifth one are still unknown, the
// fourth contains the four battery packs temperatures, and the last one tells which cell has the shunt active.
// There are also two more groups: group 61, which replies with lots of CAN messages (up to 48); here we
// found the SOH value, and group 84 that replies with the HV battery production serial.
static uint8_t crctable[256] = {
0, 133, 143, 10, 155, 30, 20, 145, 179, 54, 60, 185, 40, 173, 167, 34, 227, 102, 108, 233, 120, 253,
247, 114, 80, 213, 223, 90, 203, 78, 68, 193, 67, 198, 204, 73, 216, 93, 87, 210, 240, 117, 127, 250,
107, 238, 228, 97, 160, 37, 47, 170, 59, 190, 180, 49, 19, 150, 156, 25, 136, 13, 7, 130, 134, 3,
9, 140, 29, 152, 146, 23, 53, 176, 186, 63, 174, 43, 33, 164, 101, 224, 234, 111, 254, 123, 113, 244,
214, 83, 89, 220, 77, 200, 194, 71, 197, 64, 74, 207, 94, 219, 209, 84, 118, 243, 249, 124, 237, 104,
98, 231, 38, 163, 169, 44, 189, 56, 50, 183, 149, 16, 26, 159, 14, 139, 129, 4, 137, 12, 6, 131,
18, 151, 157, 24, 58, 191, 181, 48, 161, 36, 46, 171, 106, 239, 229, 96, 241, 116, 126, 251, 217, 92,
86, 211, 66, 199, 205, 72, 202, 79, 69, 192, 81, 212, 222, 91, 121, 252, 246, 115, 226, 103, 109, 232,
41, 172, 166, 35, 178, 55, 61, 184, 154, 31, 21, 144, 1, 132, 142, 11, 15, 138, 128, 5, 148, 17,
27, 158, 188, 57, 51, 182, 39, 162, 168, 45, 236, 105, 99, 230, 119, 242, 248, 125, 95, 218, 208, 85,
196, 65, 75, 206, 76, 201, 195, 70, 215, 82, 88, 221, 255, 122, 112, 245, 100, 225, 235, 110, 175, 42,
32, 165, 52, 177, 187, 62, 28, 153, 147, 22, 135, 2, 8, 141};
//Nissan LEAF battery parameters from constantly sent CAN
#define ZE0_BATTERY 0
#define AZE0_BATTERY 1
#define ZE1_BATTERY 2
static uint8_t LEAF_battery_Type = ZE0_BATTERY;
static bool battery_can_alive = false;
#define WH_PER_GID 77 //One GID is this amount of Watt hours
static uint16_t battery_Discharge_Power_Limit = 0; //Limit in kW
static uint16_t battery_Charge_Power_Limit = 0; //Limit in kW
static int16_t battery_MAX_POWER_FOR_CHARGER = 0; //Limit in kW
static int16_t battery_SOC = 500; //0 - 100.0 % (0-1000) The real SOC% in the battery
static uint16_t battery_TEMP = 0; //Temporary value used in status checks
static uint16_t battery_Wh_Remaining = 0; //Amount of energy in battery, in Wh
static uint16_t battery_GIDS = 273; //Startup in 24kWh mode
static uint16_t battery_MAX = 0;
static uint16_t battery_Max_GIDS = 273; //Startup in 24kWh mode
static uint16_t battery_StateOfHealth = 99; //State of health %
static uint16_t battery_Total_Voltage2 = 740; //Battery voltage (0-450V) [0.5V/bit, so actual range 0-800]
static int16_t battery_Current2 = 0; //Battery current (-400-200A) [0.5A/bit, so actual range -800-400]
static int16_t battery_HistData_Temperature_MAX = 6; //-40 to 86*C
static int16_t battery_HistData_Temperature_MIN = 5; //-40 to 86*C
static int16_t battery_AverageTemperature = 6; //Only available on ZE0, in celcius, -40 to +55
static uint8_t battery_Relay_Cut_Request = 0; //battery_FAIL
static uint8_t battery_Failsafe_Status = 0; //battery_STATUS
static bool battery_Interlock =
true; //Contains info on if HV leads are seated (Note, to use this both HV connectors need to be inserted)
static bool battery_Full_CHARGE_flag = false; //battery_FCHGEND , Goes to 1 if battery is fully charged
static bool battery_MainRelayOn_flag = false; //No-Permission=0, Main Relay On Permission=1
static bool battery_Capacity_Empty = false; //battery_EMPTY, , Goes to 1 if battery is empty
static bool battery_HeatExist = false; //battery_HEATEXIST, Specifies if battery pack is equipped with heating elements
static bool battery_Heating_Stop = false; //When transitioning from 0->1, signals a STOP heat request
static bool battery_Heating_Start = false; //When transitioning from 1->0, signals a START heat request
static bool battery_Batt_Heater_Mail_Send_Request = false; //Stores info when a heat request is happening
// Nissan LEAF battery data from polled CAN messages
static uint8_t battery_request_idx = 0;
static uint8_t group_7bb = 0;
static bool stop_battery_query = true;
static uint8_t hold_off_with_polling_10seconds = 2; //Paused for 20 seconds on startup
static uint16_t battery_cell_voltages[97]; //array with all the cellvoltages
static uint8_t battery_cellcounter = 0;
static uint16_t battery_min_max_voltage[2]; //contains cell min[0] and max[1] values in mV
static uint16_t battery_HX = 0; //Internal resistance
static uint16_t battery_insulation = 0; //Insulation resistance
static uint16_t battery_temp_raw_1 = 0;
static uint8_t battery_temp_raw_2_highnibble = 0;
static uint16_t battery_temp_raw_2 = 0;
static uint16_t battery_temp_raw_3 = 0;
static uint16_t battery_temp_raw_4 = 0;
static uint16_t battery_temp_raw_max = 0;
static uint16_t battery_temp_raw_min = 0;
static int16_t battery_temp_polled_max = 0;
static int16_t battery_temp_polled_min = 0;
static uint8_t BatterySerialNumber[15] = {0}; // Stores raw HEX values for ASCII chars
static uint8_t BatteryPartNumber[7] = {0}; // Stores raw HEX values for ASCII chars
static uint8_t BMSIDcode[8] = {0};
#ifdef DOUBLE_BATTERY
static uint8_t LEAF_battery2_Type = ZE0_BATTERY;
static bool battery2_can_alive = false;
static uint16_t battery2_Discharge_Power_Limit = 0; //Limit in kW
static uint16_t battery2_Charge_Power_Limit = 0; //Limit in kW
static int16_t battery2_MAX_POWER_FOR_CHARGER = 0; //Limit in kW
static int16_t battery2_SOC = 500; //0 - 100.0 % (0-1000) The real SOC% in the battery
static uint16_t battery2_TEMP = 0; //Temporary value used in status checks
static uint16_t battery2_Wh_Remaining = 0; //Amount of energy in battery, in Wh
static uint16_t battery2_GIDS = 273; //Startup in 24kWh mode
static uint16_t battery2_MAX = 0;
static uint16_t battery2_Max_GIDS = 273; //Startup in 24kWh mode
static uint16_t battery2_StateOfHealth = 99; //State of health %
static uint16_t battery2_Total_Voltage2 = 0; //Battery voltage (0-450V) [0.5V/bit, so actual range 0-800]
static int16_t battery2_Current2 = 0; //Battery current (-400-200A) [0.5A/bit, so actual range -800-400]
static int16_t battery2_HistData_Temperature_MAX = 6; //-40 to 86*C
static int16_t battery2_HistData_Temperature_MIN = 5; //-40 to 86*C
static int16_t battery2_AverageTemperature = 6; //Only available on ZE0, in celcius, -40 to +55
static uint8_t battery2_Relay_Cut_Request = 0; //battery2_FAIL
static uint8_t battery2_Failsafe_Status = 0; //battery2_STATUS
static bool battery2_Interlock =
true; //Contains info on if HV leads are seated (Note, to use this both HV connectors need to be inserted)
static bool battery2_Full_CHARGE_flag = false; //battery2_FCHGEND , Goes to 1 if battery is fully charged
static bool battery2_MainRelayOn_flag = false; //No-Permission=0, Main Relay On Permission=1
static bool battery2_Capacity_Empty = false; //battery2_EMPTY, , Goes to 1 if battery is empty
static bool battery2_HeatExist =
false; //battery2_HEATEXIST, Specifies if battery pack is equipped with heating elements
static bool battery2_Heating_Stop = false; //When transitioning from 0->1, signals a STOP heat request
static bool battery2_Heating_Start = false; //When transitioning from 1->0, signals a START heat request
static bool battery2_Batt_Heater_Mail_Send_Request = false; //Stores info when a heat request is happening
// Polled values
static uint8_t battery2_group_7bb = 0;
static uint8_t battery2_request_idx = 0;
static uint16_t battery2_cell_voltages[97]; //array with all the cellvoltages
static uint8_t battery2_cellcounter = 0;
static uint16_t battery2_min_max_voltage[2]; //contains cell min[0] and max[1] values in mV
static uint16_t battery2_HX = 0; //Internal resistance
static uint16_t battery2_insulation = 0; //Insulation resistance
static uint16_t battery2_temp_raw_1 = 0;
static uint8_t battery2_temp_raw_2_highnibble = 0;
static uint16_t battery2_temp_raw_2 = 0;
static uint16_t battery2_temp_raw_3 = 0;
static uint16_t battery2_temp_raw_4 = 0;
static uint16_t battery2_temp_raw_max = 0;
static uint16_t battery2_temp_raw_min = 0;
static int16_t battery2_temp_polled_max = 0;
static int16_t battery2_temp_polled_min = 0;
#endif // DOUBLE_BATTERY
// Clear SOH values
static uint8_t stateMachineClearSOH = 0xFF;
static uint32_t incomingChallenge = 0xFFFFFFFF;
static uint8_t solvedChallenge[8];
static bool challengeFailed = false;
CAN_frame LEAF_CLEAR_SOH = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x79B,
.data = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}};
void update_values_battery() { /* This function maps all the values fetched via CAN to the correct parameters used for modbus */
/* Start with mapping all values */ /* Start with mapping all values */
datalayer.battery.status.soh_pptt = (battery_StateOfHealth * 100); //Increase range from 99% -> 99.00% datalayer_battery->status.soh_pptt = (battery_StateOfHealth * 100); //Increase range from 99% -> 99.00%
datalayer.battery.status.real_soc = (battery_SOC * 10); datalayer_battery->status.real_soc = (battery_SOC * 10);
datalayer.battery.status.voltage_dV = datalayer_battery->status.voltage_dV =
(battery_Total_Voltage2 * 5); //0.5V/bit, multiply by 5 to get Voltage+1decimal (350.5V = 701) (battery_Total_Voltage2 * 5); //0.5V/bit, multiply by 5 to get Voltage+1decimal (350.5V = 701)
datalayer.battery.status.current_dA = datalayer_battery->status.current_dA =
(battery_Current2 * 5); //0.5A/bit, multiply by 5 to get Amp+1decimal (5,5A = 11) (battery_Current2 * 5); //0.5A/bit, multiply by 5 to get Amp+1decimal (5,5A = 11)
if (battery_Max_GIDS == 273) { //battery_Max_GIDS is stuck at 273 on ZE0 if (battery_Max_GIDS == 273) { //battery_Max_GIDS is stuck at 273 on ZE0
datalayer.battery.info.total_capacity_Wh = ((battery_Max_GIDS * WH_PER_GID * battery_StateOfHealth) / 100); datalayer_battery->info.total_capacity_Wh = ((battery_Max_GIDS * WH_PER_GID * battery_StateOfHealth) / 100);
} else { //battery_Max_GIDS updates on newer generations, making for a total_capacity_Wh value that makes sense } else { //battery_Max_GIDS updates on newer generations, making for a total_capacity_Wh value that makes sense
datalayer.battery.info.total_capacity_Wh = (battery_Max_GIDS * WH_PER_GID); datalayer_battery->info.total_capacity_Wh = (battery_Max_GIDS * WH_PER_GID);
} }
datalayer.battery.status.remaining_capacity_Wh = battery_Wh_Remaining; datalayer_battery->status.remaining_capacity_Wh = battery_Wh_Remaining;
//Update temperature readings. Method depends on which generation LEAF battery is used //Update temperature readings. Method depends on which generation LEAF battery is used
if (LEAF_battery_Type == ZE0_BATTERY) { if (LEAF_battery_Type == ZE0_BATTERY) {
//Since we only have average value, send the minimum as -1.0 degrees below average //Since we only have average value, send the minimum as -1.0 degrees below average
datalayer.battery.status.temperature_min_dC = datalayer_battery->status.temperature_min_dC =
((battery_AverageTemperature * 10) - 10); //Increase range from C to C+1, remove 1.0C ((battery_AverageTemperature * 10) - 10); //Increase range from C to C+1, remove 1.0C
datalayer.battery.status.temperature_max_dC = (battery_AverageTemperature * 10); //Increase range from C to C+1 datalayer_battery->status.temperature_max_dC = (battery_AverageTemperature * 10); //Increase range from C to C+1
} else if (LEAF_battery_Type == AZE0_BATTERY) { } else if (LEAF_battery_Type == AZE0_BATTERY) {
//Use the value sent constantly via CAN in 5C0 (only available on AZE0) //Use the value sent constantly via CAN in 5C0 (only available on AZE0)
datalayer.battery.status.temperature_min_dC = datalayer_battery->status.temperature_min_dC =
(battery_HistData_Temperature_MIN * 10); //Increase range from C to C+1 (battery_HistData_Temperature_MIN * 10); //Increase range from C to C+1
datalayer.battery.status.temperature_max_dC = datalayer_battery->status.temperature_max_dC =
(battery_HistData_Temperature_MAX * 10); //Increase range from C to C+1 (battery_HistData_Temperature_MAX * 10); //Increase range from C to C+1
} else { // ZE1 (TODO: Once the muxed value in 5C0 becomes known, switch to using that instead of this complicated polled value) } else { // ZE1 (TODO: Once the muxed value in 5C0 becomes known, switch to using that instead of this complicated polled value)
if (battery_temp_raw_min != 0) //We have a polled value available if (battery_temp_raw_min != 0) //We have a polled value available
@ -228,42 +60,42 @@ void update_values_battery() { /* This function maps all the values fetched via
battery_temp_polled_min = ((Temp_fromRAW_to_F(battery_temp_raw_min) - 320) * 5) / 9; //Convert from F to C battery_temp_polled_min = ((Temp_fromRAW_to_F(battery_temp_raw_min) - 320) * 5) / 9; //Convert from F to C
battery_temp_polled_max = ((Temp_fromRAW_to_F(battery_temp_raw_max) - 320) * 5) / 9; //Convert from F to C battery_temp_polled_max = ((Temp_fromRAW_to_F(battery_temp_raw_max) - 320) * 5) / 9; //Convert from F to C
if (battery_temp_polled_min < battery_temp_polled_max) { //Catch any edge cases from Temp_fromRAW_to_F function if (battery_temp_polled_min < battery_temp_polled_max) { //Catch any edge cases from Temp_fromRAW_to_F function
datalayer.battery.status.temperature_min_dC = battery_temp_polled_min; datalayer_battery->status.temperature_min_dC = battery_temp_polled_min;
datalayer.battery.status.temperature_max_dC = battery_temp_polled_max; datalayer_battery->status.temperature_max_dC = battery_temp_polled_max;
} else { } else {
datalayer.battery.status.temperature_min_dC = battery_temp_polled_max; datalayer_battery->status.temperature_min_dC = battery_temp_polled_max;
datalayer.battery.status.temperature_max_dC = battery_temp_polled_min; datalayer_battery->status.temperature_max_dC = battery_temp_polled_min;
} }
} }
} }
datalayer.battery.status.max_discharge_power_W = (battery_Discharge_Power_Limit * 1000); //kW to W datalayer_battery->status.max_discharge_power_W = (battery_Discharge_Power_Limit * 1000); //kW to W
datalayer.battery.status.max_charge_power_W = (battery_Charge_Power_Limit * 1000); //kW to W datalayer_battery->status.max_charge_power_W = (battery_Charge_Power_Limit * 1000); //kW to W
//Allow contactors to close //Allow contactors to close
if (battery_can_alive) { if (battery_can_alive && allows_contactor_closing) {
datalayer.system.status.battery_allows_contactor_closing = true; *allows_contactor_closing = true;
} }
/*Extra safety functions below*/ /*Extra safety functions below*/
if (battery_GIDS < 10) //700Wh left in battery! if (battery_GIDS < 10) //700Wh left in battery!
{ //Battery is running abnormally low, some discharge logic might have failed. Zero it all out. { //Battery is running abnormally low, some discharge logic might have failed. Zero it all out.
set_event(EVENT_BATTERY_EMPTY, 0); set_event(EVENT_BATTERY_EMPTY, 0);
datalayer.battery.status.real_soc = 0; datalayer_battery->status.real_soc = 0;
datalayer.battery.status.max_discharge_power_W = 0; datalayer_battery->status.max_discharge_power_W = 0;
} }
if (battery_Full_CHARGE_flag) { //Battery reports that it is fully charged stop all further charging incase it hasn't already if (battery_Full_CHARGE_flag) { //Battery reports that it is fully charged stop all further charging incase it hasn't already
set_event(EVENT_BATTERY_FULL, 0); set_event(EVENT_BATTERY_FULL, 0);
datalayer.battery.status.max_charge_power_W = 0; datalayer_battery->status.max_charge_power_W = 0;
} else { } else {
clear_event(EVENT_BATTERY_FULL); clear_event(EVENT_BATTERY_FULL);
} }
if (battery_Capacity_Empty) { //Battery reports that it is fully discharged. Stop all further discharging incase it hasn't already if (battery_Capacity_Empty) { //Battery reports that it is fully discharged. Stop all further discharging incase it hasn't already
set_event(EVENT_BATTERY_EMPTY, 0); set_event(EVENT_BATTERY_EMPTY, 0);
datalayer.battery.status.max_discharge_power_W = 0; datalayer_battery->status.max_discharge_power_W = 0;
} else { } else {
clear_event(EVENT_BATTERY_EMPTY); clear_event(EVENT_BATTERY_EMPTY);
} }
@ -276,8 +108,8 @@ void update_values_battery() { /* This function maps all the values fetched via
if (battery_Relay_Cut_Request) { //battery_FAIL, BMS requesting shutdown and contactors to be opened if (battery_Relay_Cut_Request) { //battery_FAIL, BMS requesting shutdown and contactors to be opened
//Note, this is sometimes triggered during the night while idle, and the BMS recovers after a while. Removed latching from this scenario //Note, this is sometimes triggered during the night while idle, and the BMS recovers after a while. Removed latching from this scenario
datalayer.battery.status.max_discharge_power_W = 0; datalayer_battery->status.max_discharge_power_W = 0;
datalayer.battery.status.max_charge_power_W = 0; datalayer_battery->status.max_charge_power_W = 0;
} }
if (battery_Failsafe_Status > 0) // 0 is normal, start charging/discharging if (battery_Failsafe_Status > 0) // 0 is normal, start charging/discharging
@ -338,430 +170,45 @@ void update_values_battery() { /* This function maps all the values fetched via
} }
// Update webserver datalayer // Update webserver datalayer
memcpy(datalayer_extended.nissanleaf.BatterySerialNumber, BatterySerialNumber, sizeof(BatterySerialNumber)); if (datalayer_nissan) {
memcpy(datalayer_extended.nissanleaf.BatteryPartNumber, BatteryPartNumber, sizeof(BatteryPartNumber)); memcpy(datalayer_nissan->BatterySerialNumber, BatterySerialNumber, sizeof(BatterySerialNumber));
memcpy(datalayer_extended.nissanleaf.BMSIDcode, BMSIDcode, sizeof(BMSIDcode)); memcpy(datalayer_nissan->BatteryPartNumber, BatteryPartNumber, sizeof(BatteryPartNumber));
datalayer_extended.nissanleaf.LEAF_gen = LEAF_battery_Type; memcpy(datalayer_nissan->BMSIDcode, BMSIDcode, sizeof(BMSIDcode));
datalayer_extended.nissanleaf.GIDS = battery_GIDS; datalayer_nissan->LEAF_gen = LEAF_battery_Type;
datalayer_extended.nissanleaf.ChargePowerLimit = battery_Charge_Power_Limit; datalayer_nissan->GIDS = battery_GIDS;
datalayer_extended.nissanleaf.MaxPowerForCharger = battery_MAX_POWER_FOR_CHARGER; datalayer_nissan->ChargePowerLimit = battery_Charge_Power_Limit;
datalayer_extended.nissanleaf.Interlock = battery_Interlock; datalayer_nissan->MaxPowerForCharger = battery_MAX_POWER_FOR_CHARGER;
datalayer_extended.nissanleaf.Insulation = battery_insulation; datalayer_nissan->Interlock = battery_Interlock;
datalayer_extended.nissanleaf.RelayCutRequest = battery_Relay_Cut_Request; datalayer_nissan->Insulation = battery_insulation;
datalayer_extended.nissanleaf.FailsafeStatus = battery_Failsafe_Status; datalayer_nissan->RelayCutRequest = battery_Relay_Cut_Request;
datalayer_extended.nissanleaf.Full = battery_Full_CHARGE_flag; datalayer_nissan->FailsafeStatus = battery_Failsafe_Status;
datalayer_extended.nissanleaf.Empty = battery_Capacity_Empty; datalayer_nissan->Full = battery_Full_CHARGE_flag;
datalayer_extended.nissanleaf.MainRelayOn = battery_MainRelayOn_flag; datalayer_nissan->Empty = battery_Capacity_Empty;
datalayer_extended.nissanleaf.HeatExist = battery_HeatExist; datalayer_nissan->MainRelayOn = battery_MainRelayOn_flag;
datalayer_extended.nissanleaf.HeatingStop = battery_Heating_Stop; datalayer_nissan->HeatExist = battery_HeatExist;
datalayer_extended.nissanleaf.HeatingStart = battery_Heating_Start; datalayer_nissan->HeatingStop = battery_Heating_Stop;
datalayer_extended.nissanleaf.HeaterSendRequest = battery_Batt_Heater_Mail_Send_Request; datalayer_nissan->HeatingStart = battery_Heating_Start;
datalayer_extended.nissanleaf.CryptoChallenge = incomingChallenge; datalayer_nissan->HeaterSendRequest = battery_Batt_Heater_Mail_Send_Request;
datalayer_extended.nissanleaf.SolvedChallengeMSB = datalayer_nissan->CryptoChallenge = incomingChallenge;
datalayer_nissan->SolvedChallengeMSB =
((solvedChallenge[7] << 24) | (solvedChallenge[6] << 16) | (solvedChallenge[5] << 8) | solvedChallenge[4]); ((solvedChallenge[7] << 24) | (solvedChallenge[6] << 16) | (solvedChallenge[5] << 8) | solvedChallenge[4]);
datalayer_extended.nissanleaf.SolvedChallengeLSB = datalayer_nissan->SolvedChallengeLSB =
((solvedChallenge[3] << 24) | (solvedChallenge[2] << 16) | (solvedChallenge[1] << 8) | solvedChallenge[0]); ((solvedChallenge[3] << 24) | (solvedChallenge[2] << 16) | (solvedChallenge[1] << 8) | solvedChallenge[0]);
datalayer_extended.nissanleaf.challengeFailed = challengeFailed; datalayer_nissan->challengeFailed = challengeFailed;
// Update requests from webserver datalayer // Update requests from webserver datalayer
if (datalayer_extended.nissanleaf.UserRequestSOHreset) { if (datalayer_nissan->UserRequestSOHreset) {
stateMachineClearSOH = 0; //Start the statemachine stateMachineClearSOH = 0; //Start the statemachine
datalayer_extended.nissanleaf.UserRequestSOHreset = false; datalayer_nissan->UserRequestSOHreset = false;
}
}
#ifdef DOUBLE_BATTERY
void update_values_battery2() { // Handle the values coming in from battery #2
/* Start with mapping all values */
datalayer.battery2.status.soh_pptt = (battery2_StateOfHealth * 100); //Increase range from 99% -> 99.00%
datalayer.battery2.status.real_soc = (battery2_SOC * 10);
datalayer.battery2.status.voltage_dV =
(battery2_Total_Voltage2 * 5); //0.5V/bit, multiply by 5 to get Voltage+1decimal (350.5V = 701)
datalayer.battery2.status.current_dA =
(battery2_Current2 * 5); //0.5A/bit, multiply by 5 to get Amp+1decimal (5,5A = 11)
if (battery2_Max_GIDS == 273) { //battery2_Max_GIDS is stuck at 273 on 24kWh packs
datalayer.battery2.info.total_capacity_Wh = ((battery2_Max_GIDS * WH_PER_GID * battery2_StateOfHealth) / 100);
} else { //battery_Max_GIDS updates on newer generations, making for a total_capacity_Wh value that makes sense
datalayer.battery2.info.total_capacity_Wh = (battery2_Max_GIDS * WH_PER_GID);
}
datalayer.battery2.status.remaining_capacity_Wh = battery2_Wh_Remaining;
//Update temperature readings. Method depends on which generation LEAF battery is used
if (LEAF_battery2_Type == ZE0_BATTERY) {
//Since we only have average value, send the minimum as -1.0 degrees below average
datalayer.battery2.status.temperature_min_dC =
((battery2_AverageTemperature * 10) - 10); //Increase range from C to C+1, remove 1.0C
datalayer.battery2.status.temperature_max_dC = (battery2_AverageTemperature * 10); //Increase range from C to C+1
} else if (LEAF_battery2_Type == AZE0_BATTERY) {
//Use the value sent constantly via CAN in 5C0 (only available on AZE0)
datalayer.battery2.status.temperature_min_dC =
(battery2_HistData_Temperature_MIN * 10); //Increase range from C to C+1
datalayer.battery2.status.temperature_max_dC =
(battery2_HistData_Temperature_MAX * 10); //Increase range from C to C+1
} else { // ZE1 (TODO: Once the muxed value in 5C0 becomes known, switch to using that instead of this complicated polled value)
if (battery2_temp_raw_min != 0) //We have a polled value available
{
battery2_temp_polled_min = ((Temp_fromRAW_to_F(battery2_temp_raw_min) - 320) * 5) / 9; //Convert from F to C
battery2_temp_polled_max = ((Temp_fromRAW_to_F(battery2_temp_raw_max) - 320) * 5) / 9; //Convert from F to C
if (battery2_temp_polled_min < battery2_temp_polled_max) { //Catch any edge cases from Temp_fromRAW_to_F function
datalayer.battery2.status.temperature_min_dC = battery2_temp_polled_min;
datalayer.battery2.status.temperature_max_dC = battery2_temp_polled_max;
} else {
datalayer.battery2.status.temperature_min_dC = battery2_temp_polled_max;
datalayer.battery2.status.temperature_max_dC = battery2_temp_polled_min;
}
}
}
datalayer.battery2.status.max_discharge_power_W = (battery2_Discharge_Power_Limit * 1000); //kW to W
datalayer.battery2.status.max_charge_power_W = (battery2_Charge_Power_Limit * 1000); //kW to W
/*Extra safety functions below*/
if (battery2_GIDS < 10) //700Wh left in battery!
{ //Battery is running abnormally low, some discharge logic might have failed. Zero it all out.
set_event(EVENT_BATTERY_EMPTY, 0);
datalayer.battery2.status.real_soc = 0;
datalayer.battery2.status.max_discharge_power_W = 0;
}
if (battery2_Full_CHARGE_flag) { //Battery reports that it is fully charged stop all further charging incase it hasn't already
set_event(EVENT_BATTERY_FULL, 0);
datalayer.battery2.status.max_charge_power_W = 0;
} else {
clear_event(EVENT_BATTERY_FULL);
}
if (battery2_Capacity_Empty) { //Battery reports that it is fully discharged. Stop all further discharging incase it hasn't already
set_event(EVENT_BATTERY_EMPTY, 0);
datalayer.battery2.status.max_discharge_power_W = 0;
} else {
clear_event(EVENT_BATTERY_EMPTY);
}
if (battery2_Total_Voltage2 == 0x3FF) { //Battery reports critical measurement unavailable
set_event(EVENT_BATTERY_VALUE_UNAVAILABLE, 0);
} else {
clear_event(EVENT_BATTERY_VALUE_UNAVAILABLE);
}
if (battery2_Relay_Cut_Request) { //battery2_FAIL, BMS requesting shutdown and contactors to be opened
//Note, this is sometimes triggered during the night while idle, and the BMS recovers after a while. Removed latching from this scenario
datalayer.battery2.status.max_discharge_power_W = 0;
datalayer.battery2.status.max_charge_power_W = 0;
}
if (battery2_Failsafe_Status > 0) // 0 is normal, start charging/discharging
{
switch (battery2_Failsafe_Status) {
case (1):
//Normal Stop Request
//This means that battery is fully discharged and it's OK to stop the session. For stationary storage we don't disconnect contactors, so we do nothing here.
break;
case (2):
//Charging Mode Stop Request
//This means that battery is fully charged and it's OK to stop the session. For stationary storage we don't disconnect contactors, so we do nothing here.
break;
case (3):
//Charging Mode Stop Request & Normal Stop Request
//Normal stop request. For stationary storage we don't disconnect contactors, so we ignore this.
break;
case (4):
//Caution Lamp Request
set_event(EVENT_BATTERY_CAUTION, 2);
break;
case (5):
//Caution Lamp Request & Normal Stop Request
set_event(EVENT_BATTERY_DISCHG_STOP_REQ, 2);
break;
case (6):
//Caution Lamp Request & Charging Mode Stop Request
set_event(EVENT_BATTERY_CHG_STOP_REQ, 2);
break;
case (7):
//Caution Lamp Request & Charging Mode Stop Request & Normal Stop Request
set_event(EVENT_BATTERY_CHG_DISCHG_STOP_REQ, 2);
break;
default:
break;
}
} else { //battery2_Failsafe_Status == 0
clear_event(EVENT_BATTERY_DISCHG_STOP_REQ);
clear_event(EVENT_BATTERY_CHG_STOP_REQ);
clear_event(EVENT_BATTERY_CHG_DISCHG_STOP_REQ);
}
#ifdef INTERLOCK_REQUIRED
if (!battery2_Interlock) {
set_event(EVENT_HVIL_FAILURE, 2);
} else {
clear_event(EVENT_HVIL_FAILURE);
}
#endif
if (battery2_HeatExist) {
if (battery2_Heating_Stop) {
set_event(EVENT_BATTERY_WARMED_UP, 2);
}
if (battery2_Heating_Start) {
set_event(EVENT_BATTERY_REQUESTS_HEAT, 2);
} }
} }
} }
void handle_incoming_can_frame_battery2(CAN_frame rx_frame) {
void NissanLeafBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
switch (rx_frame.ID) { switch (rx_frame.ID) {
case 0x1DB: case 0x1DB:
if (is_message_corrupt(rx_frame)) { if (is_message_corrupt(rx_frame)) {
datalayer.battery2.status.CAN_error_counter++; datalayer_battery->status.CAN_error_counter++;
break; //Message content malformed, abort reading data from it
}
battery2_Current2 = (rx_frame.data.u8[0] << 3) | (rx_frame.data.u8[1] & 0xe0) >> 5;
if (battery2_Current2 & 0x0400) {
// negative so extend the sign bit
battery2_Current2 |= 0xf800;
} //BatteryCurrentSignal , 2s comp, 1lSB = 0.5A/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
battery2_Relay_Cut_Request = ((rx_frame.data.u8[1] & 0x18) >> 3);
battery2_Failsafe_Status = (rx_frame.data.u8[1] & 0x07);
battery2_MainRelayOn_flag = (bool)((rx_frame.data.u8[3] & 0x20) >> 5);
//battery2_allows_contactor_closing written by check_interconnect_available();
battery2_Full_CHARGE_flag = (bool)((rx_frame.data.u8[3] & 0x10) >> 4);
battery2_Interlock = (bool)((rx_frame.data.u8[3] & 0x08) >> 3);
break;
case 0x1DC:
if (is_message_corrupt(rx_frame)) {
datalayer.battery2.status.CAN_error_counter++;
break; //Message content malformed, abort reading data from it
}
battery2_Discharge_Power_Limit = ((rx_frame.data.u8[0] << 2 | rx_frame.data.u8[1] >> 6) / 4.0);
battery2_Charge_Power_Limit = (((rx_frame.data.u8[1] & 0x3F) << 4 | rx_frame.data.u8[2] >> 4) / 4.0);
battery2_MAX_POWER_FOR_CHARGER = ((((rx_frame.data.u8[2] & 0x0F) << 6 | rx_frame.data.u8[3] >> 2) / 10.0) - 10);
break;
case 0x55B:
if (is_message_corrupt(rx_frame)) {
datalayer.battery2.status.CAN_error_counter++;
break; //Message content malformed, abort reading data from it
}
battery2_TEMP = (rx_frame.data.u8[0] << 2 | rx_frame.data.u8[1] >> 6);
if (battery2_TEMP != 0x3ff) { //3FF is unavailable value
battery2_SOC = battery2_TEMP;
}
battery2_Capacity_Empty = (bool)((rx_frame.data.u8[6] & 0x80) >> 7);
break;
case 0x5BC:
battery2_can_alive = true;
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE; // Let system know battery is sending CAN
battery2_MAX = ((rx_frame.data.u8[5] & 0x10) >> 4);
if (battery2_MAX) {
battery2_Max_GIDS = (rx_frame.data.u8[0] << 2) | ((rx_frame.data.u8[1] & 0xC0) >> 6);
//Max gids active, do nothing
//Only the 30/40/62kWh packs have this mux
} else { //Normal current GIDS value is transmitted
battery2_GIDS = (rx_frame.data.u8[0] << 2) | ((rx_frame.data.u8[1] & 0xC0) >> 6);
battery2_Wh_Remaining = (battery2_GIDS * WH_PER_GID);
}
if (LEAF_battery2_Type == ZE0_BATTERY) {
battery2_AverageTemperature = (rx_frame.data.u8[3] - 40); //In celcius, -40 to +55
}
battery2_TEMP = (rx_frame.data.u8[4] >> 1);
if (battery2_TEMP != 0) {
battery2_StateOfHealth = battery2_TEMP; //Collect state of health from battery
}
break;
case 0x5C0:
//This temperature only works for 2013-2017 AZE0 LEAF packs, the mux is different on other generations
if (LEAF_battery2_Type == AZE0_BATTERY) {
if ((rx_frame.data.u8[0] >> 6) ==
1) { // Battery MAX temperature. Effectively has only 7-bit precision, as the bottom bit is always 0.
battery2_HistData_Temperature_MAX = ((rx_frame.data.u8[2] / 2) - 40);
}
if ((rx_frame.data.u8[0] >> 6) ==
3) { // Battery MIN temperature. Effectively has only 7-bit precision, as the bottom bit is always 0.
battery2_HistData_Temperature_MIN = ((rx_frame.data.u8[2] / 2) - 40);
}
}
battery2_HeatExist = (rx_frame.data.u8[4] & 0x01);
battery2_Heating_Stop = ((rx_frame.data.u8[0] & 0x10) >> 4);
battery2_Heating_Start = ((rx_frame.data.u8[0] & 0x20) >> 5);
battery2_Batt_Heater_Mail_Send_Request = (rx_frame.data.u8[1] & 0x01);
break;
case 0x59E:
//AZE0 2013-2017 or ZE1 2018-2023 battery detected
//Only detect as AZE0 if not already set as ZE1
if (LEAF_battery2_Type != ZE1_BATTERY) {
LEAF_battery2_Type = AZE0_BATTERY;
}
break;
case 0x1ED:
case 0x1C2:
//ZE1 2018-2023 battery detected!
LEAF_battery2_Type = ZE1_BATTERY;
break;
case 0x79B:
stop_battery_query = true; //Someone is trying to read data with Leafspy, stop our own polling!
hold_off_with_polling_10seconds = 10; //Polling is paused for 100s
break;
case 0x7BB:
if (stop_battery_query) { //Leafspy/Service request is active, stop our own polling
break;
}
//First check which group data we are getting
if (rx_frame.data.u8[0] == 0x10) { //First message of a group
battery2_group_7bb = rx_frame.data.u8[3];
}
transmit_can_frame(&LEAF_NEXT_LINE_REQUEST, can_config.battery_double);
if (battery2_group_7bb == 0x01) //High precision SOC, Current, voltages etc.
{
if (rx_frame.data.u8[0] == 0x10) { //First frame
//High precision battery2_current_1 resides here, but has been deemed unusable by 62kWh owners
}
if (rx_frame.data.u8[0] == 0x21) { //Second frame
//High precision battery2_current_2 resides here, but has been deemed unusable by 62kWh owners
}
if (rx_frame.data.u8[0] == 0x23) { // Fourth frame
battery2_insulation = (uint16_t)((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6]);
}
if (rx_frame.data.u8[0] == 0x24) { // Fifth frame
battery2_HX = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) / 102.4;
}
}
if (battery2_group_7bb == 0x02) //Cell Voltages
{
if (rx_frame.data.u8[0] == 0x10) { //first frame is anomalous
battery2_request_idx = 0;
battery2_cell_voltages[battery2_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
battery2_cell_voltages[battery2_request_idx++] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
break;
}
if (rx_frame.data.u8[6] == 0xFF && rx_frame.data.u8[0] == 0x2C) { //Last frame
//Last frame does not contain any cell data, calculate the result
//Map all cell voltages to the global array
memcpy(datalayer.battery2.status.cell_voltages_mV, battery2_cell_voltages, 96 * sizeof(uint16_t));
//calculate min/max voltages
battery2_min_max_voltage[0] = 9999;
battery2_min_max_voltage[1] = 0;
for (battery2_cellcounter = 0; battery2_cellcounter < 96; battery2_cellcounter++) {
if (battery2_min_max_voltage[0] > battery2_cell_voltages[battery2_cellcounter])
battery2_min_max_voltage[0] = battery2_cell_voltages[battery2_cellcounter];
if (battery2_min_max_voltage[1] < battery2_cell_voltages[battery2_cellcounter])
battery2_min_max_voltage[1] = battery2_cell_voltages[battery2_cellcounter];
}
datalayer.battery2.status.cell_max_voltage_mV = battery2_min_max_voltage[1];
datalayer.battery2.status.cell_min_voltage_mV = battery2_min_max_voltage[0];
break;
}
if ((rx_frame.data.u8[0] % 2) == 0) { //even frames
battery2_cell_voltages[battery2_request_idx++] |= rx_frame.data.u8[1];
battery2_cell_voltages[battery2_request_idx++] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
battery2_cell_voltages[battery2_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
battery2_cell_voltages[battery2_request_idx++] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
} else { //odd frames
battery2_cell_voltages[battery2_request_idx++] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
battery2_cell_voltages[battery2_request_idx++] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
battery2_cell_voltages[battery2_request_idx++] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
battery2_cell_voltages[battery2_request_idx] = (rx_frame.data.u8[7] << 8);
}
}
if (battery2_group_7bb == 0x04) { //Temperatures
if (rx_frame.data.u8[0] == 0x10) { //First message
battery2_temp_raw_1 = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
battery2_temp_raw_2_highnibble = rx_frame.data.u8[7];
}
if (rx_frame.data.u8[0] == 0x21) { //Second message
battery2_temp_raw_2 = (battery2_temp_raw_2_highnibble << 8) | rx_frame.data.u8[1];
battery2_temp_raw_3 = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
battery2_temp_raw_4 = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
}
if (rx_frame.data.u8[0] == 0x22) { //Third message
//All values read, let's figure out the min/max!
if (battery2_temp_raw_3 == 65535) { //We are on a 2013+ pack that only has three temp sensors.
//Start with finding max value
battery2_temp_raw_max = battery2_temp_raw_1;
if (battery2_temp_raw_2 > battery2_temp_raw_max) {
battery2_temp_raw_max = battery2_temp_raw_2;
}
if (battery2_temp_raw_4 > battery2_temp_raw_max) {
battery2_temp_raw_max = battery2_temp_raw_4;
}
//Then find min
battery2_temp_raw_min = battery2_temp_raw_1;
if (battery2_temp_raw_2 < battery2_temp_raw_min) {
battery2_temp_raw_min = battery2_temp_raw_2;
}
if (battery2_temp_raw_4 < battery2_temp_raw_min) {
battery2_temp_raw_min = battery2_temp_raw_4;
}
} else { //All 4 temp sensors available on 2011-2012
//Start with finding max value
battery2_temp_raw_max = battery2_temp_raw_1;
if (battery2_temp_raw_2 > battery2_temp_raw_max) {
battery2_temp_raw_max = battery2_temp_raw_2;
}
if (battery2_temp_raw_3 > battery2_temp_raw_max) {
battery2_temp_raw_max = battery2_temp_raw_3;
}
if (battery2_temp_raw_4 > battery2_temp_raw_max) {
battery2_temp_raw_max = battery2_temp_raw_4;
}
//Then find min
battery2_temp_raw_min = battery2_temp_raw_1;
if (battery2_temp_raw_2 < battery2_temp_raw_min) {
battery2_temp_raw_min = battery2_temp_raw_2;
}
if (battery2_temp_raw_3 < battery2_temp_raw_min) {
battery2_temp_raw_min = battery2_temp_raw_2;
}
if (battery2_temp_raw_4 < battery2_temp_raw_min) {
battery2_temp_raw_min = battery2_temp_raw_4;
}
}
}
}
break;
default:
break;
}
}
#endif // DOUBLE_BATTERY
void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
switch (rx_frame.ID) {
case 0x1DB:
if (is_message_corrupt(rx_frame)) {
datalayer.battery.status.CAN_error_counter++;
break; //Message content malformed, abort reading data from it break; //Message content malformed, abort reading data from it
} }
battery_Current2 = (rx_frame.data.u8[0] << 3) | (rx_frame.data.u8[1] & 0xe0) >> 5; battery_Current2 = (rx_frame.data.u8[0] << 3) | (rx_frame.data.u8[1] & 0xe0) >> 5;
@ -784,7 +231,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
break; break;
case 0x1DC: case 0x1DC:
if (is_message_corrupt(rx_frame)) { if (is_message_corrupt(rx_frame)) {
datalayer.battery.status.CAN_error_counter++; datalayer_battery->status.CAN_error_counter++;
break; //Message content malformed, abort reading data from it break; //Message content malformed, abort reading data from it
} }
battery_Discharge_Power_Limit = ((rx_frame.data.u8[0] << 2 | rx_frame.data.u8[1] >> 6) / 4.0); battery_Discharge_Power_Limit = ((rx_frame.data.u8[0] << 2 | rx_frame.data.u8[1] >> 6) / 4.0);
@ -793,7 +240,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
break; break;
case 0x55B: case 0x55B:
if (is_message_corrupt(rx_frame)) { if (is_message_corrupt(rx_frame)) {
datalayer.battery.status.CAN_error_counter++; datalayer_battery->status.CAN_error_counter++;
break; //Message content malformed, abort reading data from it break; //Message content malformed, abort reading data from it
} }
battery_TEMP = (rx_frame.data.u8[0] << 2 | rx_frame.data.u8[1] >> 6); battery_TEMP = (rx_frame.data.u8[0] << 2 | rx_frame.data.u8[1] >> 6);
@ -804,7 +251,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
break; break;
case 0x5BC: case 0x5BC:
battery_can_alive = true; battery_can_alive = true;
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; // Let system know battery is sending CAN datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE; // Let system know battery is sending CAN
battery_MAX = ((rx_frame.data.u8[5] & 0x10) >> 4); battery_MAX = ((rx_frame.data.u8[5] & 0x10) >> 4);
if (battery_MAX) { if (battery_MAX) {
@ -886,7 +333,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
group_7bb = rx_frame.data.u8[3]; group_7bb = rx_frame.data.u8[3];
} }
transmit_can_frame(&LEAF_NEXT_LINE_REQUEST, can_config.battery); //Request the next frame for the group transmit_can_frame(&LEAF_NEXT_LINE_REQUEST, can_interface); //Request the next frame for the group
if (group_7bb == 0x01) //High precision SOC, Current, voltages etc. if (group_7bb == 0x01) //High precision SOC, Current, voltages etc.
{ {
@ -918,7 +365,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
//Last frame does not contain any cell data, calculate the result //Last frame does not contain any cell data, calculate the result
//Map all cell voltages to the global array //Map all cell voltages to the global array
memcpy(datalayer.battery.status.cell_voltages_mV, battery_cell_voltages, 96 * sizeof(uint16_t)); memcpy(datalayer_battery->status.cell_voltages_mV, battery_cell_voltages, 96 * sizeof(uint16_t));
//calculate min/max voltages //calculate min/max voltages
battery_min_max_voltage[0] = 9999; battery_min_max_voltage[0] = 9999;
@ -930,8 +377,8 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
battery_min_max_voltage[1] = battery_cell_voltages[battery_cellcounter]; battery_min_max_voltage[1] = battery_cell_voltages[battery_cellcounter];
} }
datalayer.battery.status.cell_max_voltage_mV = battery_min_max_voltage[1]; datalayer_battery->status.cell_max_voltage_mV = battery_min_max_voltage[1];
datalayer.battery.status.cell_min_voltage_mV = battery_min_max_voltage[0]; datalayer_battery->status.cell_min_voltage_mV = battery_min_max_voltage[0];
break; break;
} }
@ -1071,7 +518,8 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
break; break;
} }
} }
void transmit_can_battery(unsigned long currentMillis) {
void NissanLeafBattery::transmit_can(unsigned long currentMillis) {
if (datalayer.system.status.BMS_reset_in_progress || datalayer.system.status.BMS_startup_in_progress) { if (datalayer.system.status.BMS_reset_in_progress || datalayer.system.status.BMS_startup_in_progress) {
// Transmitting towards battery is halted while BMS is being reset // Transmitting towards battery is halted while BMS is being reset
@ -1105,10 +553,7 @@ void transmit_can_battery(unsigned long currentMillis) {
LEAF_1D4.data.u8[7] = 0xDE; LEAF_1D4.data.u8[7] = 0xDE;
break; break;
} }
transmit_can_frame(&LEAF_1D4, can_config.battery); transmit_can_frame(&LEAF_1D4, can_interface);
#ifdef DOUBLE_BATTERY
transmit_can_frame(&LEAF_1D4, can_config.battery_double);
#endif // DOUBLE_BATTERY
switch (mprun10r) { switch (mprun10r) {
case (0): case (0):
@ -1199,13 +644,10 @@ void transmit_can_battery(unsigned long currentMillis) {
break; break;
} }
//Only send this message when NISSANLEAF_CHARGER is not defined (otherwise it will collide!) //Only send this message when NISSANLEAF_CHARGER is not defined (otherwise it will collide!)
#ifndef NISSANLEAF_CHARGER if (!charger || charger->type() != ChargerType::NissanLeaf) {
transmit_can_frame(&LEAF_1F2, can_config.battery); transmit_can_frame(&LEAF_1F2, can_interface);
#ifdef DOUBLE_BATTERY }
transmit_can_frame(&LEAF_1F2, can_config.battery_double);
#endif // DOUBLE_BATTERY
#endif
mprun10r = (mprun10r + 1) % 20; // 0x1F2 patter repeats after 20 messages. 0-1..19-0 mprun10r = (mprun10r + 1) % 20; // 0x1F2 patter repeats after 20 messages. 0-1..19-0
@ -1228,10 +670,7 @@ void transmit_can_battery(unsigned long currentMillis) {
} }
// VCM message, containing info if battery should sleep or stay awake // VCM message, containing info if battery should sleep or stay awake
transmit_can_frame(&LEAF_50B, can_config.battery); // HCM_WakeUpSleepCommand == 11b == WakeUp, and CANMASK = 1 transmit_can_frame(&LEAF_50B, can_interface); // HCM_WakeUpSleepCommand == 11b == WakeUp, and CANMASK = 1
#ifdef DOUBLE_BATTERY
transmit_can_frame(&LEAF_50B, can_config.battery_double);
#endif // DOUBLE_BATTERY
LEAF_50C.data.u8[3] = mprun100; LEAF_50C.data.u8[3] = mprun100;
switch (mprun100) { switch (mprun100) {
@ -1252,10 +691,7 @@ void transmit_can_battery(unsigned long currentMillis) {
LEAF_50C.data.u8[5] = 0x9A; LEAF_50C.data.u8[5] = 0x9A;
break; break;
} }
transmit_can_frame(&LEAF_50C, can_config.battery); transmit_can_frame(&LEAF_50C, can_interface);
#ifdef DOUBLE_BATTERY
transmit_can_frame(&LEAF_50C, can_config.battery_double);
#endif // DOUBLE_BATTERY
mprun100 = (mprun100 + 1) % 4; // mprun100 cycles between 0-1-2-3-0-1... mprun100 = (mprun100 + 1) % 4; // mprun100 cycles between 0-1-2-3-0-1...
} }
@ -1271,10 +707,7 @@ void transmit_can_battery(unsigned long currentMillis) {
PIDindex = (PIDindex + 1) % 6; // 6 = amount of elements in the PIDgroups[] PIDindex = (PIDindex + 1) % 6; // 6 = amount of elements in the PIDgroups[]
LEAF_GROUP_REQUEST.data.u8[2] = PIDgroups[PIDindex]; LEAF_GROUP_REQUEST.data.u8[2] = PIDgroups[PIDindex];
transmit_can_frame(&LEAF_GROUP_REQUEST, can_config.battery); transmit_can_frame(&LEAF_GROUP_REQUEST, can_interface);
#ifdef DOUBLE_BATTERY
transmit_can_frame(&LEAF_GROUP_REQUEST, can_config.battery_double);
#endif // DOUBLE_BATTERY
} }
if (hold_off_with_polling_10seconds > 0) { if (hold_off_with_polling_10seconds > 0) {
@ -1286,7 +719,7 @@ void transmit_can_battery(unsigned long currentMillis) {
} }
} }
bool is_message_corrupt(CAN_frame rx_frame) { bool NissanLeafBattery::is_message_corrupt(CAN_frame rx_frame) {
uint8_t crc = 0; uint8_t crc = 0;
for (uint8_t j = 0; j < 7; j++) { for (uint8_t j = 0; j < 7; j++) {
crc = crctable[(crc ^ static_cast<uint8_t>(rx_frame.data.u8[j])) % 256]; crc = crctable[(crc ^ static_cast<uint8_t>(rx_frame.data.u8[j])) % 256];
@ -1325,7 +758,7 @@ uint16_t Temp_fromRAW_to_F(uint16_t temperature) { //This function feels horrib
return static_cast<uint16_t>(1094 + (309 - temperature) * 2.5714285714285715); return static_cast<uint16_t>(1094 + (309 - temperature) * 2.5714285714285715);
} }
void clearSOH(void) { void NissanLeafBattery::clearSOH(void) {
stop_battery_query = true; stop_battery_query = true;
hold_off_with_polling_10seconds = 10; // Active battery polling is paused for 100 seconds hold_off_with_polling_10seconds = 10; // Active battery polling is paused for 100 seconds
@ -1336,19 +769,19 @@ void clearSOH(void) {
break; break;
case 1: // Set CAN_PROCESS_FLAG to 0xC0 case 1: // Set CAN_PROCESS_FLAG to 0xC0
LEAF_CLEAR_SOH.data = {0x02, 0x10, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00}; LEAF_CLEAR_SOH.data = {0x02, 0x10, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00};
transmit_can_frame(&LEAF_CLEAR_SOH, can_config.battery); transmit_can_frame(&LEAF_CLEAR_SOH, can_interface);
// BMS should reply 02 50 C0 FF FF FF FF FF // BMS should reply 02 50 C0 FF FF FF FF FF
stateMachineClearSOH = 2; stateMachineClearSOH = 2;
break; break;
case 2: // Set something ? case 2: // Set something ?
LEAF_CLEAR_SOH.data = {0x02, 0x3E, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}; LEAF_CLEAR_SOH.data = {0x02, 0x3E, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00};
transmit_can_frame(&LEAF_CLEAR_SOH, can_config.battery); transmit_can_frame(&LEAF_CLEAR_SOH, can_interface);
// BMS should reply 7E FF FF FF FF FF FF // BMS should reply 7E FF FF FF FF FF FF
stateMachineClearSOH = 3; stateMachineClearSOH = 3;
break; break;
case 3: // Request challenge to solve case 3: // Request challenge to solve
LEAF_CLEAR_SOH.data = {0x02, 0x27, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00}; LEAF_CLEAR_SOH.data = {0x02, 0x27, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00};
transmit_can_frame(&LEAF_CLEAR_SOH, can_config.battery); transmit_can_frame(&LEAF_CLEAR_SOH, can_interface);
// BMS should reply with (challenge) 06 67 65 (02 DD 86 43) FF // BMS should reply with (challenge) 06 67 65 (02 DD 86 43) FF
stateMachineClearSOH = 4; stateMachineClearSOH = 4;
break; break;
@ -1356,34 +789,34 @@ void clearSOH(void) {
decodeChallengeData(incomingChallenge, solvedChallenge); decodeChallengeData(incomingChallenge, solvedChallenge);
LEAF_CLEAR_SOH.data = { LEAF_CLEAR_SOH.data = {
0x10, 0x0A, 0x27, 0x66, solvedChallenge[0], solvedChallenge[1], solvedChallenge[2], solvedChallenge[3]}; 0x10, 0x0A, 0x27, 0x66, solvedChallenge[0], solvedChallenge[1], solvedChallenge[2], solvedChallenge[3]};
transmit_can_frame(&LEAF_CLEAR_SOH, can_config.battery); transmit_can_frame(&LEAF_CLEAR_SOH, can_interface);
// BMS should reply 7BB 8 30 01 00 FF FF FF FF FF // Proceed with more data (PID ACK) // BMS should reply 7BB 8 30 01 00 FF FF FF FF FF // Proceed with more data (PID ACK)
stateMachineClearSOH = 5; stateMachineClearSOH = 5;
break; break;
case 5: // Reply with even more decoded challenge data case 5: // Reply with even more decoded challenge data
LEAF_CLEAR_SOH.data = { LEAF_CLEAR_SOH.data = {
0x21, solvedChallenge[4], solvedChallenge[5], solvedChallenge[6], solvedChallenge[7], 0x00, 0x00, 0x00}; 0x21, solvedChallenge[4], solvedChallenge[5], solvedChallenge[6], solvedChallenge[7], 0x00, 0x00, 0x00};
transmit_can_frame(&LEAF_CLEAR_SOH, can_config.battery); transmit_can_frame(&LEAF_CLEAR_SOH, can_interface);
// BMS should reply 02 67 66 FF FF FF FF FF // Thank you for the data // BMS should reply 02 67 66 FF FF FF FF FF // Thank you for the data
stateMachineClearSOH = 6; stateMachineClearSOH = 6;
break; break;
case 6: // Check if solved data was OK case 6: // Check if solved data was OK
LEAF_CLEAR_SOH.data = {0x03, 0x31, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}; LEAF_CLEAR_SOH.data = {0x03, 0x31, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00};
transmit_can_frame(&LEAF_CLEAR_SOH, can_config.battery); transmit_can_frame(&LEAF_CLEAR_SOH, can_interface);
//7BB 8 03 71 03 01 FF FF FF FF // If all is well, BMS replies with 03 71 03 01. //7BB 8 03 71 03 01 FF FF FF FF // If all is well, BMS replies with 03 71 03 01.
//Incase you sent wrong challenge, you get 03 7f 31 12 //Incase you sent wrong challenge, you get 03 7f 31 12
stateMachineClearSOH = 7; stateMachineClearSOH = 7;
break; break;
case 7: // Reset SOH% request case 7: // Reset SOH% request
LEAF_CLEAR_SOH.data = {0x03, 0x31, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00}; LEAF_CLEAR_SOH.data = {0x03, 0x31, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00};
transmit_can_frame(&LEAF_CLEAR_SOH, can_config.battery); transmit_can_frame(&LEAF_CLEAR_SOH, can_interface);
//7BB 8 03 71 03 02 FF FF FF FF // 03 71 03 02 means that BMS accepted command. //7BB 8 03 71 03 02 FF FF FF FF // 03 71 03 02 means that BMS accepted command.
//7BB 03 7f 31 12 means your challenge was wrong, so command ignored //7BB 03 7f 31 12 means your challenge was wrong, so command ignored
stateMachineClearSOH = 8; stateMachineClearSOH = 8;
break; break;
case 8: // Please proceed with resetting SOH case 8: // Please proceed with resetting SOH
LEAF_CLEAR_SOH.data = {0x02, 0x10, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00}; LEAF_CLEAR_SOH.data = {0x02, 0x10, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00};
transmit_can_frame(&LEAF_CLEAR_SOH, can_config.battery); transmit_can_frame(&LEAF_CLEAR_SOH, can_interface);
// 7BB 8 02 50 81 FF FF FF FF FF // SOH reset OK // 7BB 8 02 50 81 FF FF FF FF FF // SOH reset OK
stateMachineClearSOH = 255; stateMachineClearSOH = 255;
break; break;
@ -1505,24 +938,15 @@ void decodeChallengeData(unsigned int incomingChallenge, unsigned char* solvedCh
return; return;
} }
void setup_battery(void) { // Performs one time setup at startup void NissanLeafBattery::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "Nissan LEAF battery", 63); strncpy(datalayer.system.info.battery_protocol, "Nissan LEAF battery", 63);
datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.number_of_cells = 96; datalayer_battery->info.number_of_cells = 96;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer_battery->info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; datalayer_battery->info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; datalayer_battery->info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV; datalayer_battery->info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV; datalayer_battery->info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
#ifdef DOUBLE_BATTERY
datalayer.battery2.info.number_of_cells = datalayer.battery.info.number_of_cells;
datalayer.battery2.info.max_design_voltage_dV = datalayer.battery.info.max_design_voltage_dV;
datalayer.battery2.info.min_design_voltage_dV = datalayer.battery.info.min_design_voltage_dV;
datalayer.battery2.info.max_cell_voltage_mV = datalayer.battery.info.max_cell_voltage_mV;
datalayer.battery2.info.min_cell_voltage_mV = datalayer.battery.info.min_cell_voltage_mV;
datalayer.battery2.info.max_cell_voltage_deviation_mV = datalayer.battery.info.max_cell_voltage_deviation_mV;
#endif //DOUBLE_BATTERY
} }
#endif //NISSAN_LEAF_BATTERY #endif //NISSAN_LEAF_BATTERY

View file

@ -1,26 +1,189 @@
#ifndef NISSAN_LEAF_BATTERY_H #ifndef NISSAN_LEAF_BATTERY_H
#define NISSAN_LEAF_BATTERY_H #define NISSAN_LEAF_BATTERY_H
#include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h"
#include "../include.h" #include "../include.h"
#include "CanBattery.h"
#define BATTERY_SELECTED #define BATTERY_SELECTED
#define SELECTED_BATTERY_CLASS NissanLeafBattery
#define EXTENDED_DATA_PTR (&datalayer_extended.nissanleaf)
#define MAX_PACK_VOLTAGE_DV 4040 //5000 = 500.0V #define MAX_PACK_VOLTAGE_DV 4040 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_DV 2600 #define MIN_PACK_VOLTAGE_DV 2600
#define MAX_CELL_DEVIATION_MV 150 #define MAX_CELL_DEVIATION_MV 150
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value #define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value #define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
uint16_t Temp_fromRAW_to_F(uint16_t temperature); class NissanLeafBattery : public CanBattery {
bool is_message_corrupt(CAN_frame rx_frame); public:
void setup_battery(void); // Use this constructor for the second battery.
void transmit_can_frame(CAN_frame* tx_frame, int interface); NissanLeafBattery(DATALAYER_BATTERY_TYPE* datalayer_ptr, DATALAYER_INFO_NISSAN_LEAF* extended, int targetCan) {
void clearSOH(void); datalayer_battery = datalayer_ptr;
//Cryptographic functions allows_contactor_closing = nullptr;
void decodeChallengeData(unsigned int SeedInput, unsigned char* Crypt_Output_Buffer); datalayer_nissan = extended;
unsigned int CyclicXorHash16Bit(unsigned int param_1, unsigned int param_2); can_interface = targetCan;
unsigned int ComputeMaskedXorProduct(unsigned int param_1, unsigned int param_2, unsigned int param_3);
short ShortMaskedSumAndProduct(short param_1, short param_2); battery_Total_Voltage2 = 0;
unsigned int MaskedBitwiseRotateMultiply(unsigned int param_1, unsigned int param_2); }
unsigned int CryptAlgo(unsigned int param_1, unsigned int param_2, unsigned int param_3);
// Use the default constructor to create the first or single battery.
NissanLeafBattery() {
datalayer_battery = &datalayer.battery;
allows_contactor_closing = &datalayer.system.status.battery_allows_contactor_closing;
datalayer_nissan = &datalayer_extended.nissanleaf;
can_interface = can_config.battery;
}
virtual void setup(void);
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
virtual void update_values();
virtual void transmit_can(unsigned long currentMillis);
private:
bool is_message_corrupt(CAN_frame rx_frame);
void clearSOH(void);
DATALAYER_BATTERY_TYPE* datalayer_battery;
DATALAYER_INFO_NISSAN_LEAF* datalayer_nissan;
// If not null, this battery decides when the contactor can be closed and writes the value here.
bool* allows_contactor_closing;
int can_interface;
unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was send
unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
unsigned long previousMillis10s = 0; // will store last time a 1s CAN Message was send
uint8_t mprun10r = 0; //counter 0-20 for 0x1F2 message
uint8_t mprun10 = 0; //counter 0-3
uint8_t mprun100 = 0; //counter 0-3
// These CAN messages need to be sent towards the battery to keep it alive
CAN_frame LEAF_1F2 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x1F2,
.data = {0x10, 0x64, 0x00, 0xB0, 0x00, 0x1E, 0x00, 0x8F}};
CAN_frame LEAF_50B = {.FD = false,
.ext_ID = false,
.DLC = 7,
.ID = 0x50B,
.data = {0x00, 0x00, 0x06, 0xC0, 0x00, 0x00, 0x00}};
CAN_frame LEAF_50C = {.FD = false,
.ext_ID = false,
.DLC = 6,
.ID = 0x50C,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame LEAF_1D4 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x1D4,
.data = {0x6E, 0x6E, 0x00, 0x04, 0x07, 0x46, 0xE0, 0x44}};
// Active polling messages
uint8_t PIDgroups[6] = {0x01, 0x02, 0x04, 0x83, 0x84, 0x90};
uint8_t PIDindex = 0;
CAN_frame LEAF_GROUP_REQUEST = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x79B,
.data = {2, 0x21, 1, 0, 0, 0, 0, 0}};
CAN_frame LEAF_NEXT_LINE_REQUEST = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x79B,
.data = {0x30, 1, 0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}};
// The Li-ion battery controller only accepts a multi-message query. In fact, the LBC transmits many
// groups: the first one contains lots of High Voltage battery data as SOC, currents, and voltage; the second
// replies with all the batterys cells voltages in millivolt, the third and the fifth one are still unknown, the
// fourth contains the four battery packs temperatures, and the last one tells which cell has the shunt active.
// There are also two more groups: group 61, which replies with lots of CAN messages (up to 48); here we
// found the SOH value, and group 84 that replies with the HV battery production serial.
uint8_t crctable[256] = {
0, 133, 143, 10, 155, 30, 20, 145, 179, 54, 60, 185, 40, 173, 167, 34, 227, 102, 108, 233, 120, 253,
247, 114, 80, 213, 223, 90, 203, 78, 68, 193, 67, 198, 204, 73, 216, 93, 87, 210, 240, 117, 127, 250,
107, 238, 228, 97, 160, 37, 47, 170, 59, 190, 180, 49, 19, 150, 156, 25, 136, 13, 7, 130, 134, 3,
9, 140, 29, 152, 146, 23, 53, 176, 186, 63, 174, 43, 33, 164, 101, 224, 234, 111, 254, 123, 113, 244,
214, 83, 89, 220, 77, 200, 194, 71, 197, 64, 74, 207, 94, 219, 209, 84, 118, 243, 249, 124, 237, 104,
98, 231, 38, 163, 169, 44, 189, 56, 50, 183, 149, 16, 26, 159, 14, 139, 129, 4, 137, 12, 6, 131,
18, 151, 157, 24, 58, 191, 181, 48, 161, 36, 46, 171, 106, 239, 229, 96, 241, 116, 126, 251, 217, 92,
86, 211, 66, 199, 205, 72, 202, 79, 69, 192, 81, 212, 222, 91, 121, 252, 246, 115, 226, 103, 109, 232,
41, 172, 166, 35, 178, 55, 61, 184, 154, 31, 21, 144, 1, 132, 142, 11, 15, 138, 128, 5, 148, 17,
27, 158, 188, 57, 51, 182, 39, 162, 168, 45, 236, 105, 99, 230, 119, 242, 248, 125, 95, 218, 208, 85,
196, 65, 75, 206, 76, 201, 195, 70, 215, 82, 88, 221, 255, 122, 112, 245, 100, 225, 235, 110, 175, 42,
32, 165, 52, 177, 187, 62, 28, 153, 147, 22, 135, 2, 8, 141};
//Nissan LEAF battery parameters from constantly sent CAN
#define ZE0_BATTERY 0
#define AZE0_BATTERY 1
#define ZE1_BATTERY 2
uint8_t LEAF_battery_Type = ZE0_BATTERY;
bool battery_can_alive = false;
#define WH_PER_GID 77 //One GID is this amount of Watt hours
uint16_t battery_Discharge_Power_Limit = 0; //Limit in kW
uint16_t battery_Charge_Power_Limit = 0; //Limit in kW
int16_t battery_MAX_POWER_FOR_CHARGER = 0; //Limit in kW
int16_t battery_SOC = 500; //0 - 100.0 % (0-1000) The real SOC% in the battery
uint16_t battery_TEMP = 0; //Temporary value used in status checks
uint16_t battery_Wh_Remaining = 0; //Amount of energy in battery, in Wh
uint16_t battery_GIDS = 273; //Startup in 24kWh mode
uint16_t battery_MAX = 0;
uint16_t battery_Max_GIDS = 273; //Startup in 24kWh mode
uint16_t battery_StateOfHealth = 99; //State of health %
uint16_t battery_Total_Voltage2 = 740; //Battery voltage (0-450V) [0.5V/bit, so actual range 0-800]
int16_t battery_Current2 = 0; //Battery current (-400-200A) [0.5A/bit, so actual range -800-400]
int16_t battery_HistData_Temperature_MAX = 6; //-40 to 86*C
int16_t battery_HistData_Temperature_MIN = 5; //-40 to 86*C
int16_t battery_AverageTemperature = 6; //Only available on ZE0, in celcius, -40 to +55
uint8_t battery_Relay_Cut_Request = 0; //battery_FAIL
uint8_t battery_Failsafe_Status = 0; //battery_STATUS
bool battery_Interlock =
true; //Contains info on if HV leads are seated (Note, to use this both HV connectors need to be inserted)
bool battery_Full_CHARGE_flag = false; //battery_FCHGEND , Goes to 1 if battery is fully charged
bool battery_MainRelayOn_flag = false; //No-Permission=0, Main Relay On Permission=1
bool battery_Capacity_Empty = false; //battery_EMPTY, , Goes to 1 if battery is empty
bool battery_HeatExist = false; //battery_HEATEXIST, Specifies if battery pack is equipped with heating elements
bool battery_Heating_Stop = false; //When transitioning from 0->1, signals a STOP heat request
bool battery_Heating_Start = false; //When transitioning from 1->0, signals a START heat request
bool battery_Batt_Heater_Mail_Send_Request = false; //Stores info when a heat request is happening
// Nissan LEAF battery data from polled CAN messages
uint8_t battery_request_idx = 0;
uint8_t group_7bb = 0;
bool stop_battery_query = true;
uint8_t hold_off_with_polling_10seconds = 2; //Paused for 20 seconds on startup
uint16_t battery_cell_voltages[97]; //array with all the cellvoltages
uint8_t battery_cellcounter = 0;
uint16_t battery_min_max_voltage[2]; //contains cell min[0] and max[1] values in mV
uint16_t battery_HX = 0; //Internal resistance
uint16_t battery_insulation = 0; //Insulation resistance
uint16_t battery_temp_raw_1 = 0;
uint8_t battery_temp_raw_2_highnibble = 0;
uint16_t battery_temp_raw_2 = 0;
uint16_t battery_temp_raw_3 = 0;
uint16_t battery_temp_raw_4 = 0;
uint16_t battery_temp_raw_max = 0;
uint16_t battery_temp_raw_min = 0;
int16_t battery_temp_polled_max = 0;
int16_t battery_temp_polled_min = 0;
uint8_t BatterySerialNumber[15] = {0}; // Stores raw HEX values for ASCII chars
uint8_t BatteryPartNumber[7] = {0}; // Stores raw HEX values for ASCII chars
uint8_t BMSIDcode[8] = {0};
// Clear SOH values
uint8_t stateMachineClearSOH = 0xFF;
uint32_t incomingChallenge = 0xFFFFFFFF;
uint8_t solvedChallenge[8];
bool challengeFailed = false;
CAN_frame LEAF_CLEAR_SOH = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x79B,
.data = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}};
};
#endif #endif

View file

@ -1,32 +1,10 @@
#include "../include.h" #include "../include.h"
#ifdef ORION_BMS #ifdef ORION_BMS
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "../devboard/utils/events.h" #include "../devboard/utils/events.h"
#include "ORION-BMS.h" #include "ORION-BMS.h"
/* Do not change code below unless you are sure what you are doing */
static uint16_t cellvoltages[MAX_AMOUNT_CELLS]; //array with all the cellvoltages
static uint16_t Maximum_Cell_Voltage = 3700;
static uint16_t Minimum_Cell_Voltage = 3700;
static uint16_t Pack_Health = 99;
static int16_t Pack_Current = 0;
static int16_t Average_Temperature = 0;
static uint16_t Pack_Summed_Voltage = 0;
static int16_t Average_Current = 0;
static uint16_t High_Temperature = 0;
static uint16_t Pack_SOC_ppt = 0;
static uint16_t Pack_CCL = 0; //Charge current limit (A)
static uint16_t Pack_DCL = 0; //Discharge current limit (A)
static uint16_t Maximum_Pack_Voltage = 0;
static uint16_t Minimum_Pack_Voltage = 0;
static uint16_t CellID = 0;
static uint16_t CellVoltage = 0;
static uint16_t CellResistance = 0;
static uint16_t CellOpenVoltage = 0;
static uint16_t Checksum = 0;
static uint16_t CellBalancing = 0;
static uint8_t amount_of_detected_cells = 0;
void findMinMaxCellvoltages(const uint16_t arr[], size_t size, uint16_t& Minimum_Cell_Voltage, void findMinMaxCellvoltages(const uint16_t arr[], size_t size, uint16_t& Minimum_Cell_Voltage,
uint16_t& Maximum_Cell_Voltage) { uint16_t& Maximum_Cell_Voltage) {
Minimum_Cell_Voltage = std::numeric_limits<uint16_t>::max(); Minimum_Cell_Voltage = std::numeric_limits<uint16_t>::max();
@ -50,7 +28,7 @@ void findMinMaxCellvoltages(const uint16_t arr[], size_t size, uint16_t& Minimum
} }
} }
void update_values_battery() { void OrionBms::update_values() {
datalayer.battery.status.real_soc = Pack_SOC_ppt * 10; datalayer.battery.status.real_soc = Pack_SOC_ppt * 10;
@ -87,7 +65,7 @@ void update_values_battery() {
} }
} }
void handle_incoming_can_frame_battery(CAN_frame rx_frame) { void OrionBms::handle_incoming_can_frame(CAN_frame rx_frame) {
switch (rx_frame.ID) { switch (rx_frame.ID) {
case 0x356: case 0x356:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
@ -132,11 +110,11 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
} }
} }
void transmit_can_battery(unsigned long currentMillis) { void OrionBms::transmit_can(unsigned long currentMillis) {
// No transmission needed for this integration // No transmission needed for this integration
} }
void setup_battery(void) { // Performs one time setup at startup void OrionBms::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "DIY battery with Orion BMS (Victron setting)", 63); strncpy(datalayer.system.info.battery_protocol, "DIY battery with Orion BMS (Victron setting)", 63);
datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.number_of_cells = NUMBER_OF_CELLS; datalayer.battery.info.number_of_cells = NUMBER_OF_CELLS;

View file

@ -3,17 +3,48 @@
#include <Arduino.h> #include <Arduino.h>
#include "../include.h" #include "../include.h"
#include "CanBattery.h"
#define BATTERY_SELECTED #define BATTERY_SELECTED
#define SELECTED_BATTERY_CLASS OrionBms
/* Change the following to suit your battery */ class OrionBms : public CanBattery {
#define NUMBER_OF_CELLS 96 public:
#define MAX_PACK_VOLTAGE_DV 5000 //5000 = 500.0V virtual void setup(void);
#define MIN_PACK_VOLTAGE_DV 1500 virtual void handle_incoming_can_frame(CAN_frame rx_frame);
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value virtual void update_values();
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value virtual void transmit_can(unsigned long currentMillis);
#define MAX_CELL_DEVIATION_MV 150
void setup_battery(void); private:
void transmit_can_frame(CAN_frame* tx_frame, int interface); /* Change the following to suit your battery */
static const int NUMBER_OF_CELLS = 96;
static const int MAX_PACK_VOLTAGE_DV = 5000; //5000 = 500.0V
static const int MIN_PACK_VOLTAGE_DV = 1500;
static const int MAX_CELL_VOLTAGE_MV = 4250; //Battery is put into emergency stop if one cell goes over this value
static const int MIN_CELL_VOLTAGE_MV = 2700; //Battery is put into emergency stop if one cell goes below this value
static const int MAX_CELL_DEVIATION_MV = 150;
uint16_t cellvoltages[MAX_AMOUNT_CELLS]; //array with all the cellvoltages
uint16_t Maximum_Cell_Voltage = 3700;
uint16_t Minimum_Cell_Voltage = 3700;
uint16_t Pack_Health = 99;
int16_t Pack_Current = 0;
int16_t Average_Temperature = 0;
uint16_t Pack_Summed_Voltage = 0;
int16_t Average_Current = 0;
uint16_t High_Temperature = 0;
uint16_t Pack_SOC_ppt = 0;
uint16_t Pack_CCL = 0; //Charge current limit (A)
uint16_t Pack_DCL = 0; //Discharge current limit (A)
uint16_t Maximum_Pack_Voltage = 0;
uint16_t Minimum_Pack_Voltage = 0;
uint16_t CellID = 0;
uint16_t CellVoltage = 0;
uint16_t CellResistance = 0;
uint16_t CellOpenVoltage = 0;
uint16_t Checksum = 0;
uint16_t CellBalancing = 0;
uint8_t amount_of_detected_cells = 0;
};
#endif #endif

View file

@ -1,89 +1,44 @@
#include "../include.h" #include "../include.h"
#ifdef PYLON_BATTERY #ifdef PYLON_BATTERY
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "../devboard/utils/events.h" #include "../devboard/utils/events.h"
#include "PYLON-BATTERY.h" #include "PYLON-BATTERY.h"
/* Do not change code below unless you are sure what you are doing */ void PylonBattery::update_values() {
static unsigned long previousMillis1000 = 0; // will store last time a 1s CAN Message was sent
//Actual content messages datalayer_battery->status.real_soc = (SOC * 100); //increase SOC range from 0-100 -> 100.00
CAN_frame PYLON_3010 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x3010,
.data = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame PYLON_8200 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x8200,
.data = {0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame PYLON_8210 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x8210,
.data = {0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame PYLON_4200 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4200,
.data = {0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static int16_t celltemperature_max_dC = 0; datalayer_battery->status.soh_pptt = (SOH * 100); //Increase decimals from 100% -> 100.00%
static int16_t celltemperature_min_dC = 0;
static int16_t current_dA = 0;
static uint16_t voltage_dV = 0;
static uint16_t cellvoltage_max_mV = 3700;
static uint16_t cellvoltage_min_mV = 3700;
static uint16_t charge_cutoff_voltage = 0;
static uint16_t discharge_cutoff_voltage = 0;
static int16_t max_charge_current = 0;
static int16_t max_discharge_current = 0;
static uint8_t ensemble_info_ack = 0;
static uint8_t battery_module_quantity = 0;
static uint8_t battery_modules_in_series = 0;
static uint8_t cell_quantity_in_module = 0;
static uint8_t voltage_level = 0;
static uint8_t ah_number = 0;
static uint8_t SOC = 0;
static uint8_t SOH = 0;
static uint8_t charge_forbidden = 0;
static uint8_t discharge_forbidden = 0;
void update_values_battery() { datalayer_battery->status.voltage_dV = voltage_dV; //value is *10 (3700 = 370.0)
datalayer.battery.status.real_soc = (SOC * 100); //increase SOC range from 0-100 -> 100.00 datalayer_battery->status.current_dA = current_dA; //value is *10 (150 = 15.0) , invert the sign
datalayer.battery.status.soh_pptt = (SOH * 100); //Increase decimals from 100% -> 100.00% datalayer_battery->status.max_charge_power_W = (max_charge_current * (voltage_dV / 10));
datalayer.battery.status.voltage_dV = voltage_dV; //value is *10 (3700 = 370.0) datalayer_battery->status.max_discharge_power_W = (-max_discharge_current * (voltage_dV / 10));
datalayer.battery.status.current_dA = current_dA; //value is *10 (150 = 15.0) , invert the sign datalayer_battery->status.remaining_capacity_Wh = static_cast<uint32_t>(
(static_cast<double>(datalayer_battery->status.real_soc) / 10000) * datalayer_battery->info.total_capacity_Wh);
datalayer.battery.status.max_charge_power_W = (max_charge_current * (voltage_dV / 10)); datalayer_battery->status.cell_max_voltage_mV = cellvoltage_max_mV;
datalayer_battery->status.cell_voltages_mV[0] = cellvoltage_max_mV;
datalayer.battery.status.max_discharge_power_W = (-max_discharge_current * (voltage_dV / 10)); datalayer_battery->status.cell_min_voltage_mV = cellvoltage_min_mV;
datalayer_battery->status.cell_voltages_mV[1] = cellvoltage_min_mV;
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>( datalayer_battery->status.temperature_min_dC = celltemperature_min_dC;
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
datalayer.battery.status.cell_max_voltage_mV = cellvoltage_max_mV; datalayer_battery->status.temperature_max_dC = celltemperature_max_dC;
datalayer.battery.status.cell_voltages_mV[0] = cellvoltage_max_mV;
datalayer.battery.status.cell_min_voltage_mV = cellvoltage_min_mV; datalayer_battery->info.max_design_voltage_dV = charge_cutoff_voltage;
datalayer.battery.status.cell_voltages_mV[1] = cellvoltage_min_mV;
datalayer.battery.status.temperature_min_dC = celltemperature_min_dC; datalayer_battery->info.min_design_voltage_dV = discharge_cutoff_voltage;
datalayer.battery.status.temperature_max_dC = celltemperature_max_dC;
datalayer.battery.info.max_design_voltage_dV = charge_cutoff_voltage;
datalayer.battery.info.min_design_voltage_dV = discharge_cutoff_voltage;
} }
void handle_incoming_can_frame_battery(CAN_frame rx_frame) { void PylonBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
switch (rx_frame.ID) { switch (rx_frame.ID) {
case 0x7310: case 0x7310:
case 0x7311: case 0x7311:
@ -158,7 +113,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
} }
} }
void transmit_can_battery(unsigned long currentMillis) { void PylonBattery::transmit_can(unsigned long currentMillis) {
// Send 1s CAN Message // Send 1s CAN Message
if (currentMillis - previousMillis1000 >= INTERVAL_1_S) { if (currentMillis - previousMillis1000 >= INTERVAL_1_S) {
previousMillis1000 = currentMillis; previousMillis1000 = currentMillis;
@ -168,170 +123,26 @@ void transmit_can_battery(unsigned long currentMillis) {
transmit_can_frame(&PYLON_8200, can_config.battery); // Control device quit sleep status transmit_can_frame(&PYLON_8200, can_config.battery); // Control device quit sleep status
transmit_can_frame(&PYLON_8210, can_config.battery); // Charge command transmit_can_frame(&PYLON_8210, can_config.battery); // Charge command
#ifdef DOUBLE_BATTERY
transmit_can_frame(&PYLON_3010, can_config.battery_double); // Heartbeat
transmit_can_frame(&PYLON_4200, can_config.battery_double); // Ensemble OR System equipment info, depends on frame0
transmit_can_frame(&PYLON_8200, can_config.battery_double); // Control device quit sleep status
transmit_can_frame(&PYLON_8210, can_config.battery_double); // Charge command
#endif //DOUBLE_BATTERY
if (ensemble_info_ack) { if (ensemble_info_ack) {
PYLON_4200.data.u8[0] = 0x00; //Request system equipment info PYLON_4200.data.u8[0] = 0x00; //Request system equipment info
} }
} }
} }
#ifdef DOUBLE_BATTERY void PylonBattery::setup(void) { // Performs one time setup at startup
static int16_t battery2_celltemperature_max_dC = 0;
static int16_t battery2_celltemperature_min_dC = 0;
static int16_t battery2_current_dA = 0;
static uint16_t battery2_voltage_dV = 0;
static uint16_t battery2_cellvoltage_max_mV = 3700;
static uint16_t battery2_cellvoltage_min_mV = 3700;
static uint16_t battery2_charge_cutoff_voltage = 0;
static uint16_t battery2_discharge_cutoff_voltage = 0;
static int16_t battery2_max_charge_current = 0;
static int16_t battery2_max_discharge_current = 0;
static uint8_t battery2_ensemble_info_ack = 0;
static uint8_t battery2_module_quantity = 0;
static uint8_t battery2_modules_in_series = 0;
static uint8_t battery2_cell_quantity_in_module = 0;
static uint8_t battery2_voltage_level = 0;
static uint8_t battery2_ah_number = 0;
static uint8_t battery2_SOC = 0;
static uint8_t battery2_SOH = 0;
static uint8_t battery2_charge_forbidden = 0;
static uint8_t battery2_discharge_forbidden = 0;
void update_values_battery2() {
datalayer.battery2.status.real_soc = (battery2_SOC * 100); //increase SOC range from 0-100 -> 100.00
datalayer.battery2.status.soh_pptt = (battery2_SOH * 100); //Increase decimals from 100% -> 100.00%
datalayer.battery2.status.voltage_dV = battery2_voltage_dV; //value is *10 (3700 = 370.0)
datalayer.battery2.status.current_dA = battery2_current_dA; //value is *10 (150 = 15.0) , invert the sign
datalayer.battery2.status.max_charge_power_W = (battery2_max_charge_current * (battery2_voltage_dV / 10));
datalayer.battery2.status.max_discharge_power_W = (-battery2_max_discharge_current * (battery2_voltage_dV / 10));
datalayer.battery2.status.remaining_capacity_Wh = static_cast<uint32_t>(
(static_cast<double>(datalayer.battery2.status.real_soc) / 10000) * datalayer.battery2.info.total_capacity_Wh);
datalayer.battery2.status.cell_max_voltage_mV = battery2_cellvoltage_max_mV;
datalayer.battery2.status.cell_voltages_mV[0] = battery2_cellvoltage_max_mV;
datalayer.battery2.status.cell_min_voltage_mV = battery2_cellvoltage_min_mV;
datalayer.battery2.status.cell_voltages_mV[1] = battery2_cellvoltage_min_mV;
datalayer.battery2.status.temperature_min_dC = battery2_celltemperature_min_dC;
datalayer.battery2.status.temperature_max_dC = battery2_celltemperature_max_dC;
datalayer.battery2.info.max_design_voltage_dV = battery2_charge_cutoff_voltage;
datalayer.battery2.info.min_design_voltage_dV = battery2_discharge_cutoff_voltage;
datalayer.battery2.info.number_of_cells = datalayer.battery.info.number_of_cells;
}
void handle_incoming_can_frame_battery2(CAN_frame rx_frame) {
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
switch (rx_frame.ID) {
case 0x7310:
case 0x7311:
battery2_ensemble_info_ack = true;
// This message contains software/hardware version info. No interest to us
break;
case 0x7320:
case 0x7321:
battery2_ensemble_info_ack = true;
battery2_module_quantity = rx_frame.data.u8[0];
battery2_modules_in_series = rx_frame.data.u8[2];
battery2_cell_quantity_in_module = rx_frame.data.u8[3];
battery2_voltage_level = rx_frame.data.u8[4];
battery2_ah_number = rx_frame.data.u8[6];
break;
case 0x4210:
case 0x4211:
battery2_voltage_dV = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]);
battery2_current_dA = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) - 30000;
battery2_SOC = rx_frame.data.u8[6];
battery2_SOH = rx_frame.data.u8[7];
break;
case 0x4220:
case 0x4221:
battery2_charge_cutoff_voltage = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]);
battery2_discharge_cutoff_voltage = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]);
battery2_max_charge_current = (((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]) * 0.1) - 3000;
battery2_max_discharge_current = (((rx_frame.data.u8[7] << 8) | rx_frame.data.u8[6]) * 0.1) - 3000;
break;
case 0x4230:
case 0x4231:
battery2_cellvoltage_max_mV = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]);
battery2_cellvoltage_min_mV = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]);
break;
case 0x4240:
case 0x4241:
battery2_celltemperature_max_dC = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) - 1000;
battery2_celltemperature_min_dC = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) - 1000;
break;
case 0x4250:
case 0x4251:
//Byte0 Basic Status
//Byte1-2 Cycle Period
//Byte3 Error
//Byte4-5 Alarm
//Byte6-7 Protection
break;
case 0x4260:
case 0x4261:
//Byte0-1 Module Max Voltage
//Byte2-3 Module Min Voltage
//Byte4-5 Module Max. Voltage Number
//Byte6-7 Module Min. Voltage Number
break;
case 0x4270:
case 0x4271:
//Byte0-1 Module Max. Temperature
//Byte2-3 Module Min. Temperature
//Byte4-5 Module Max. Temperature Number
//Byte6-7 Module Min. Temperature Number
break;
case 0x4280:
case 0x4281:
battery2_charge_forbidden = rx_frame.data.u8[0];
battery2_discharge_forbidden = rx_frame.data.u8[1];
break;
case 0x4290:
case 0x4291:
break;
default:
break;
}
}
#endif //DOUBLE_BATTERY
void setup_battery(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "Pylon compatible battery", 63); strncpy(datalayer.system.info.battery_protocol, "Pylon compatible battery", 63);
datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.number_of_cells = 2; datalayer_battery->info.number_of_cells = 2;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer_battery->info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; datalayer_battery->info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; datalayer_battery->info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV; datalayer_battery->info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.system.status.battery_allows_contactor_closing = true;
#ifdef DOUBLE_BATTERY datalayer.battery2.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
datalayer.battery2.info.number_of_cells = datalayer.battery.info.number_of_cells;
datalayer.battery2.info.max_design_voltage_dV = datalayer.battery.info.max_design_voltage_dV; if (allows_contactor_closing) {
datalayer.battery2.info.min_design_voltage_dV = datalayer.battery.info.min_design_voltage_dV; *allows_contactor_closing = true;
datalayer.battery2.info.max_cell_voltage_mV = datalayer.battery.info.max_cell_voltage_mV; }
datalayer.battery2.info.min_cell_voltage_mV = datalayer.battery.info.min_cell_voltage_mV;
datalayer.battery2.info.max_cell_voltage_deviation_mV = datalayer.battery.info.max_cell_voltage_deviation_mV;
#endif //DOUBLE_BATTERY
} }
#endif #endif

View file

@ -1,18 +1,99 @@
#ifndef PYLON_BATTERY_H #ifndef PYLON_BATTERY_H
#define PYLON_BATTERY_H #define PYLON_BATTERY_H
#include <Arduino.h> #include <Arduino.h>
#include "../datalayer/datalayer.h"
#include "../include.h" #include "../include.h"
#include "CanBattery.h"
#define BATTERY_SELECTED #define BATTERY_SELECTED
#define SELECTED_BATTERY_CLASS PylonBattery
/* Change the following to suit your battery */ class PylonBattery : public CanBattery {
#define MAX_PACK_VOLTAGE_DV 5000 //5000 = 500.0V public:
#define MIN_PACK_VOLTAGE_DV 1500 // Use this constructor for the second battery.
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value PylonBattery(DATALAYER_BATTERY_TYPE* datalayer_ptr, bool* contactor_closing_allowed_ptr, int targetCan) {
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value datalayer_battery = datalayer_ptr;
#define MAX_CELL_DEVIATION_MV 150 contactor_closing_allowed = contactor_closing_allowed_ptr;
allows_contactor_closing = nullptr;
can_interface = targetCan;
}
void setup_battery(void); // Use the default constructor to create the first or single battery.
void transmit_can_frame(CAN_frame* tx_frame, int interface); PylonBattery() {
datalayer_battery = &datalayer.battery;
allows_contactor_closing = &datalayer.system.status.battery_allows_contactor_closing;
contactor_closing_allowed = nullptr;
can_interface = can_config.battery;
}
virtual void setup(void);
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
virtual void update_values();
virtual void transmit_can(unsigned long currentMillis);
private:
/* Change the following to suit your battery */
static const int MAX_PACK_VOLTAGE_DV = 5000; //5000 = 500.0V
static const int MIN_PACK_VOLTAGE_DV = 1500;
static const int MAX_CELL_VOLTAGE_MV = 4250; //Battery is put into emergency stop if one cell goes over this value
static const int MIN_CELL_VOLTAGE_MV = 2700; //Battery is put into emergency stop if one cell goes below this value
static const int MAX_CELL_DEVIATION_MV = 150;
DATALAYER_BATTERY_TYPE* datalayer_battery;
// If not null, this battery decides when the contactor can be closed and writes the value here.
bool* allows_contactor_closing;
// If not null, this battery listens to this boolean to determine whether contactor closing is allowed
bool* contactor_closing_allowed;
int can_interface;
unsigned long previousMillis1000 = 0; // will store last time a 1s CAN Message was sent
//Actual content messages
CAN_frame PYLON_3010 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x3010,
.data = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame PYLON_8200 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x8200,
.data = {0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame PYLON_8210 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x8210,
.data = {0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame PYLON_4200 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4200,
.data = {0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
int16_t celltemperature_max_dC = 0;
int16_t celltemperature_min_dC = 0;
int16_t current_dA = 0;
uint16_t voltage_dV = 0;
uint16_t cellvoltage_max_mV = 3700;
uint16_t cellvoltage_min_mV = 3700;
uint16_t charge_cutoff_voltage = 0;
uint16_t discharge_cutoff_voltage = 0;
int16_t max_charge_current = 0;
int16_t max_discharge_current = 0;
uint8_t ensemble_info_ack = 0;
uint8_t battery_module_quantity = 0;
uint8_t battery_modules_in_series = 0;
uint8_t cell_quantity_in_module = 0;
uint8_t voltage_level = 0;
uint8_t ah_number = 0;
uint8_t SOC = 50;
uint8_t SOH = 100;
uint8_t charge_forbidden = 0;
uint8_t discharge_forbidden = 0;
};
#endif #endif

View file

@ -1,5 +1,6 @@
#include "../include.h" #include "../include.h"
#ifdef RANGE_ROVER_PHEV_BATTERY #ifdef RANGE_ROVER_PHEV_BATTERY
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "../devboard/utils/events.h" #include "../devboard/utils/events.h"
#include "RANGE-ROVER-PHEV-BATTERY.h" #include "RANGE-ROVER-PHEV-BATTERY.h"
@ -46,111 +47,7 @@
- Figure out contactor closing requirements - Figure out contactor closing requirements
*/ */
/* Do not change code below unless you are sure what you are doing */ void RangeRoverPhevBattery::update_values() {
static unsigned long previousMillis50ms = 0; // will store last time a 50ms CAN Message was sent
//CAN content from battery
static bool StatusCAT5BPOChg = false;
static bool StatusCAT4Derate = false;
static uint8_t OCMonitorStatus = 0;
static bool StatusCAT3 = false;
static bool IsolationStatus = false;
static bool HVILStatus = false;
static bool ContactorStatus = false;
static uint8_t StatusGpCounter = 0;
static bool WeldCheckStatus = false;
static bool StatusCAT7NowBPO = false;
static bool StatusCAT6DlyBPO = false;
static uint8_t StatusGpCS = 0;
static uint8_t CAT6Count = 0;
static bool EndOfCharge = false;
static bool DerateWarning = false;
static bool PrechargeAllowed = false;
static uint8_t DischargeExtGpCounter = 0; // Counter 0-15
static uint8_t DischargeExtGpCS = 0; // CRC
static uint16_t DischargeVoltageLimit = 0; //Min voltage battery allows discharging to
static uint16_t DischargePowerLimitExt = 0; //Momentary Discharge power limit kW*0.01 (0-655)
static uint16_t DischargeContPwrLmt = 0; //Longterm Discharge power limit kW*0.01 (0-655)
static uint8_t PwrGpCS = 0; // CRC
static uint8_t PwrGpCounter = 0; // Counter 0-15
static uint16_t VoltageExt = 370; // Voltage of the HV Battery
static uint16_t VoltageBus = 0; // Voltage on the high-voltage DC bus
static int32_t CurrentExt =
209715; //Positive - discharge, Negative Charge (0 - 16777215) Scaling: 0.025 Offset: -209715.175 Units: Amps
static bool HVIsolationTestRunning = false;
static uint16_t VoltageOC =
0; //The instantaneous equivalent open-circuit voltage of the high voltage battery. This is used by the high-voltage inverter in power prediction and derating calculations.
static uint16_t DchCurrentLimit =
0; // A, 'Maximum current that can be delivered by the HV Battery during motoring mode i.e during discharging.
static uint16_t ChgCurrentLimit =
0; // - 1023 A, Maximum current that can be transferred into the HV Battery during generating mode i.e during charging. Charging is neagtive and discharging is positive.
static uint16_t ChargeContPwrLmt = 0; //Longterm charge power limit kW*0.01 (0-655)
static uint16_t ChargePowerLimitExt = 0; //Momentary Charge power limit kW*0.01 (0-655)
static uint8_t ChgExtGpCS = 0; // CRC
static uint8_t ChgExtGpCounter = 0; //counter 0-15
static uint16_t ChargeVoltageLimit = 500; //Max voltage limit during charging of the HV Battery.
static uint8_t CurrentWarning = 0; // 0 normal, 1 cell overcurrent, 2 cell undercurrent
static uint8_t TempWarning = 0; // 0 normal, 1 cell overtemp, 2 cell undertemp
static int8_t TempUpLimit = 0; //Upper temperature limit.
static uint8_t CellVoltWarning = 0; // 0 normal, 1 cell overvoltage, 2 cell undervoltage
static bool CCCVChargeMode = false; //0 CC, 1 = CV
static uint16_t CellVoltUpLimit = 0; //mV, Upper cell voltage limit
static uint16_t SOCHighestCell = 0; //0.01, %
static uint16_t SOCLowestCell = 0; //0.01, %
static uint16_t SOCAverage = 0; //0.01, %
static bool WakeUpTopUpReq =
false; //The HV Battery can trigger a vehicle wake-up to request its State of Charge to be increased.
static bool WakeUpThermalReq =
false; //The HV Battery can trigger a vehicle wake-up in order to be thermally managed (ie. cooled down OR warmed up).
static bool WakeUpDchReq =
false; //The HV Battery can trigger a vehicle wake-up to request its State of Charge to be reduced.
static uint16_t StateofHealth = 0;
static uint16_t EstimatedLossChg =
0; //fact0.001, kWh Expected energy which will be lost during charging (at the rate given by VSCEstChargePower) due to resistance within the HV Battery.
static bool CoolingRequest =
false; //HV Battery cooling request to be cooled by the eAC/chiller as its cooling needs exceed the LTR cooling loop capability.
static uint16_t EstimatedLossDch =
0; //fact0.001, kWh Expected energy which will be lost during discharging (at the rate given by VSCEstDischargePower) due to resistance within the HV Battery.
static uint8_t FanDutyRequest =
0; //Request from the HV Battery cooling system to demand a change of duty for the electrical engine cooling fan speed (whilst using its LTR cooling loop).
static bool ValveCtrlStat = false; //0 Chiller/Heater cooling loop requested , 1 LTR cooling loop requested
static uint16_t EstLossDchTgtSoC =
0; //fact0.001, kWh Expected energy which will be lost during discharging (at the rate given by VSCEstimatedDchPower) from the target charging SoC (PHEV: HVBattEnergyUsableMax, BEV: HVBattEnergyUsableBulk) down to HVBattEnergyUsableMin, due to resistance within the Traction Battery.
static uint8_t HeatPowerGenChg =
0; //fact0.1, kW, Estimated average heat generated by battery if charged at the rate given by VSCEstimatedChgPower.
static uint8_t HeatPowerGenDch =
0; //fact0.1, kW, Estimated average heat generated by battery if discharged at the rate given by VSCEstimatedDchPower.
static uint8_t WarmupRateChg =
0; //fact0.1, C/min , Expected average rate at which the battery will self-heat if charged at the rate given by VSCEstimatedChgPower.
static uint8_t WarmupRateDch =
0; //fact0.1, C/min , Expected average rate at which the battery will self-heat if discharged at the rate given by VSCEstimatedDchPower.
static uint16_t CellVoltageMax = 3700;
static uint16_t CellVoltageMin = 3700;
static int8_t CellTempAverage = 0; //factor0.5, -40 offset
static int8_t CellTempColdest = 0; //factor0.5, -40 offset
static int8_t CellTempHottest = 0; //factor0.5, -40 offset
static uint8_t HeaterCtrlStat = 0; //factor1, 0 offset
static bool ThermalOvercheck = false; // 0 OK, 1 NOT OK
static int8_t InletCoolantTemp = 0; //factor0.5, -40 offset
static bool ClntPumpDiagStat_UB = false;
static bool InletCoolantTemp_UB = false;
static bool CoolantLevel = false; // Coolant level OK , 1 NOT OK
static bool ClntPumpDiagStat = false; // 0 Pump OK, 1 NOT OK
static uint8_t MILRequest = 0; //No req, 1 ON, 2 FLASHING, 3 unused
static uint16_t EnergyAvailable = 0; //fac0.05 , The total energy available from the HV Battery
static uint16_t EnergyUsableMax = 0; //fac0.05 , The total energy available from the HV Battery at its maximum SOC
static uint16_t EnergyUsableMin = 0; //fac0.05 , The total energy available from the HV Battery at its minimum SOC
static uint16_t TotalCapacity =
0; //fac0.1 , Total Battery capacity in Kwh. This will reduce over the lifetime of the HV Battery.
//CAN messages needed by battery (LOG needed!)
CAN_frame RANGE_ROVER_18B = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x18B, //CONTENT??? TODO
.data = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
void update_values_battery() {
datalayer.battery.status.real_soc = SOCAverage; datalayer.battery.status.real_soc = SOCAverage;
@ -180,7 +77,7 @@ void update_values_battery() {
datalayer.battery.info.min_design_voltage_dV = DischargeVoltageLimit * 10; datalayer.battery.info.min_design_voltage_dV = DischargeVoltageLimit * 10;
} }
void handle_incoming_can_frame_battery(CAN_frame rx_frame) { void RangeRoverPhevBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
switch (rx_frame.ID) { switch (rx_frame.ID) {
case 0x080: // 15ms case 0x080: // 15ms
@ -301,7 +198,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
} }
} }
void transmit_can_battery(unsigned long currentMillis) { void RangeRoverPhevBattery::transmit_can(unsigned long currentMillis) {
// Send 50ms CAN Message // Send 50ms CAN Message
if (currentMillis - previousMillis50ms >= INTERVAL_50_MS) { if (currentMillis - previousMillis50ms >= INTERVAL_50_MS) {
previousMillis50ms = currentMillis; previousMillis50ms = currentMillis;
@ -310,7 +207,7 @@ void transmit_can_battery(unsigned long currentMillis) {
} }
} }
void setup_battery(void) { // Performs one time setup at startup void RangeRoverPhevBattery::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "Range Rover 13kWh PHEV battery (L494/L405)", 63); strncpy(datalayer.system.info.battery_protocol, "Range Rover 13kWh PHEV battery (L494/L405)", 63);
datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.info.battery_protocol[63] = '\0';
datalayer.system.status.battery_allows_contactor_closing = true; datalayer.system.status.battery_allows_contactor_closing = true;

View file

@ -3,16 +3,128 @@
#include <Arduino.h> #include <Arduino.h>
#include "../include.h" #include "../include.h"
#include "CanBattery.h"
#define BATTERY_SELECTED #define BATTERY_SELECTED
#define SELECTED_BATTERY_CLASS RangeRoverPhevBattery
/* Change the following to suit your battery */ class RangeRoverPhevBattery : public CanBattery {
#define MAX_PACK_VOLTAGE_DV 5000 //TODO: Configure public:
#define MIN_PACK_VOLTAGE_DV 0 //TODO: Configure virtual void setup(void);
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value virtual void handle_incoming_can_frame(CAN_frame rx_frame);
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value virtual void update_values();
#define MAX_CELL_DEVIATION_MV 150 virtual void transmit_can(unsigned long currentMillis);
void setup_battery(void); private:
void transmit_can_frame(CAN_frame* tx_frame, int interface); /* Change the following to suit your battery */
static const int MAX_PACK_VOLTAGE_DV = 5000; //TODO: Configure
static const int MIN_PACK_VOLTAGE_DV = 0; //TODO: Configure
static const int MAX_CELL_VOLTAGE_MV = 4250; //Battery is put into emergency stop if one cell goes over this value
static const int MIN_CELL_VOLTAGE_MV = 2700; //Battery is put into emergency stop if one cell goes below this value
static const int MAX_CELL_DEVIATION_MV = 150;
;
unsigned long previousMillis50ms = 0; // will store last time a 50ms CAN Message was sent
//CAN content from battery
bool StatusCAT5BPOChg = false;
bool StatusCAT4Derate = false;
uint8_t OCMonitorStatus = 0;
bool StatusCAT3 = false;
bool IsolationStatus = false;
bool HVILStatus = false;
bool ContactorStatus = false;
uint8_t StatusGpCounter = 0;
bool WeldCheckStatus = false;
bool StatusCAT7NowBPO = false;
bool StatusCAT6DlyBPO = false;
uint8_t StatusGpCS = 0;
uint8_t CAT6Count = 0;
bool EndOfCharge = false;
bool DerateWarning = false;
bool PrechargeAllowed = false;
uint8_t DischargeExtGpCounter = 0; // Counter 0-15
uint8_t DischargeExtGpCS = 0; // CRC
uint16_t DischargeVoltageLimit = 0; //Min voltage battery allows discharging to
uint16_t DischargePowerLimitExt = 0; //Momentary Discharge power limit kW*0.01 (0-655)
uint16_t DischargeContPwrLmt = 0; //Longterm Discharge power limit kW*0.01 (0-655)
uint8_t PwrGpCS = 0; // CRC
uint8_t PwrGpCounter = 0; // Counter 0-15
uint16_t VoltageExt = 370; // Voltage of the HV Battery
uint16_t VoltageBus = 0; // Voltage on the high-voltage DC bus
int32_t CurrentExt =
209715; //Positive - discharge, Negative Charge (0 - 16777215) Scaling: 0.025 Offset: -209715.175 Units: Amps
bool HVIsolationTestRunning = false;
uint16_t VoltageOC =
0; //The instantaneous equivalent open-circuit voltage of the high voltage battery. This is used by the high-voltage inverter in power prediction and derating calculations.
uint16_t DchCurrentLimit =
0; // A, 'Maximum current that can be delivered by the HV Battery during motoring mode i.e during discharging.
uint16_t ChgCurrentLimit =
0; // - 1023 A, Maximum current that can be transferred into the HV Battery during generating mode i.e during charging. Charging is neagtive and discharging is positive.
uint16_t ChargeContPwrLmt = 0; //Longterm charge power limit kW*0.01 (0-655)
uint16_t ChargePowerLimitExt = 0; //Momentary Charge power limit kW*0.01 (0-655)
uint8_t ChgExtGpCS = 0; // CRC
uint8_t ChgExtGpCounter = 0; //counter 0-15
uint16_t ChargeVoltageLimit = 500; //Max voltage limit during charging of the HV Battery.
uint8_t CurrentWarning = 0; // 0 normal, 1 cell overcurrent, 2 cell undercurrent
uint8_t TempWarning = 0; // 0 normal, 1 cell overtemp, 2 cell undertemp
int8_t TempUpLimit = 0; //Upper temperature limit.
uint8_t CellVoltWarning = 0; // 0 normal, 1 cell overvoltage, 2 cell undervoltage
bool CCCVChargeMode = false; //0 CC, 1 = CV
uint16_t CellVoltUpLimit = 0; //mV, Upper cell voltage limit
uint16_t SOCHighestCell = 0; //0.01, %
uint16_t SOCLowestCell = 0; //0.01, %
uint16_t SOCAverage = 0; //0.01, %
bool WakeUpTopUpReq =
false; //The HV Battery can trigger a vehicle wake-up to request its State of Charge to be increased.
bool WakeUpThermalReq =
false; //The HV Battery can trigger a vehicle wake-up in order to be thermally managed (ie. cooled down OR warmed up).
bool WakeUpDchReq =
false; //The HV Battery can trigger a vehicle wake-up to request its State of Charge to be reduced.
uint16_t StateofHealth = 0;
uint16_t EstimatedLossChg =
0; //fact0.001, kWh Expected energy which will be lost during charging (at the rate given by VSCEstChargePower) due to resistance within the HV Battery.
bool CoolingRequest =
false; //HV Battery cooling request to be cooled by the eAC/chiller as its cooling needs exceed the LTR cooling loop capability.
uint16_t EstimatedLossDch =
0; //fact0.001, kWh Expected energy which will be lost during discharging (at the rate given by VSCEstDischargePower) due to resistance within the HV Battery.
uint8_t FanDutyRequest =
0; //Request from the HV Battery cooling system to demand a change of duty for the electrical engine cooling fan speed (whilst using its LTR cooling loop).
bool ValveCtrlStat = false; //0 Chiller/Heater cooling loop requested , 1 LTR cooling loop requested
uint16_t EstLossDchTgtSoC =
0; //fact0.001, kWh Expected energy which will be lost during discharging (at the rate given by VSCEstimatedDchPower) from the target charging SoC (PHEV: HVBattEnergyUsableMax, BEV: HVBattEnergyUsableBulk) down to HVBattEnergyUsableMin, due to resistance within the Traction Battery.
uint8_t HeatPowerGenChg =
0; //fact0.1, kW, Estimated average heat generated by battery if charged at the rate given by VSCEstimatedChgPower.
uint8_t HeatPowerGenDch =
0; //fact0.1, kW, Estimated average heat generated by battery if discharged at the rate given by VSCEstimatedDchPower.
uint8_t WarmupRateChg =
0; //fact0.1, C/min , Expected average rate at which the battery will self-heat if charged at the rate given by VSCEstimatedChgPower.
uint8_t WarmupRateDch =
0; //fact0.1, C/min , Expected average rate at which the battery will self-heat if discharged at the rate given by VSCEstimatedDchPower.
uint16_t CellVoltageMax = 3700;
uint16_t CellVoltageMin = 3700;
int8_t CellTempAverage = 0; //factor0.5, -40 offset
int8_t CellTempColdest = 0; //factor0.5, -40 offset
int8_t CellTempHottest = 0; //factor0.5, -40 offset
uint8_t HeaterCtrlStat = 0; //factor1, 0 offset
bool ThermalOvercheck = false; // 0 OK, 1 NOT OK
int8_t InletCoolantTemp = 0; //factor0.5, -40 offset
bool ClntPumpDiagStat_UB = false;
bool InletCoolantTemp_UB = false;
bool CoolantLevel = false; // Coolant level OK , 1 NOT OK
bool ClntPumpDiagStat = false; // 0 Pump OK, 1 NOT OK
uint8_t MILRequest = 0; //No req, 1 ON, 2 FLASHING, 3 unused
uint16_t EnergyAvailable = 0; //fac0.05 , The total energy available from the HV Battery
uint16_t EnergyUsableMax = 0; //fac0.05 , The total energy available from the HV Battery at its maximum SOC
uint16_t EnergyUsableMin = 0; //fac0.05 , The total energy available from the HV Battery at its minimum SOC
uint16_t TotalCapacity =
0; //fac0.1 , Total Battery capacity in Kwh. This will reduce over the lifetime of the HV Battery.
//CAN messages needed by battery (LOG needed!)
CAN_frame RANGE_ROVER_18B = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x18B, //CONTENT??? TODO
.data = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
};
#endif #endif

View file

@ -1,5 +1,6 @@
#include "../include.h" #include "../include.h"
#ifdef RENAULT_KANGOO_BATTERY #ifdef RENAULT_KANGOO_BATTERY
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "../devboard/utils/events.h" #include "../devboard/utils/events.h"
#include "RENAULT-KANGOO-BATTERY.h" #include "RENAULT-KANGOO-BATTERY.h"
@ -18,60 +19,8 @@ There seems to be some values on the Kangoo that differ between the 22/33 kWh ve
This page has info on the larger 33kWh pack: https://openinverter.org/wiki/Renault_Kangoo_36 This page has info on the larger 33kWh pack: https://openinverter.org/wiki/Renault_Kangoo_36
*/ */
/* Do not change code below unless you are sure what you are doing */ void RenaultKangooBattery::
static uint32_t LB_Battery_Voltage = 3700; update_values() { //This function maps all the values fetched via CAN to the correct parameters
static uint32_t LB_Charge_Power_Limit_Watts = 0;
static int32_t LB_Current = 0;
static int16_t LB_MAX_TEMPERATURE = 0;
static int16_t LB_MIN_TEMPERATURE = 0;
static uint16_t LB_SOC = 0;
static uint16_t LB_SOH = 0;
static uint16_t LB_Discharge_Power_Limit = 0;
static uint16_t LB_Charge_Power_Limit = 0;
static uint16_t LB_kWh_Remaining = 0;
static uint16_t LB_Cell_Max_Voltage = 3700;
static uint16_t LB_Cell_Min_Voltage = 3700;
static uint16_t LB_MaxChargeAllowed_W = 0;
static uint8_t LB_Discharge_Power_Limit_Byte1 = 0;
static uint8_t GVI_Pollcounter = 0;
static uint8_t LB_EOCR = 0;
static uint8_t LB_HVBUV = 0;
static uint8_t LB_HVBIR = 0;
static uint8_t LB_CUV = 0;
static uint8_t LB_COV = 0;
static uint8_t LB_HVBOV = 0;
static uint8_t LB_HVBOT = 0;
static uint8_t LB_HVBOC = 0;
static uint8_t LB_MaxInput_kW = 0;
static uint8_t LB_MaxOutput_kW = 0;
static bool GVB_79B_Continue = false;
CAN_frame KANGOO_423 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x423,
.data = {0x0B, 0x1D, 0x00, 0x02, 0xB2, 0x20, 0xB2, 0xD9}}; // Charging
// Driving: 0x07 0x1D 0x00 0x02 0x5D 0x80 0x5D 0xD8
// Charging: 0x0B 0x1D 0x00 0x02 0xB2 0x20 0xB2 0xD9
// Fastcharging: 0x07 0x1E 0x00 0x01 0x5D 0x20 0xB2 0xC7
// Old hardcoded message: .data = {0x33, 0x00, 0xFF, 0xFF, 0x00, 0xE0, 0x00, 0x00}};
CAN_frame KANGOO_79B = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x79B,
.data = {0x02, 0x21, 0x01, 0x00, 0x00, 0xE0, 0x00, 0x00}};
CAN_frame KANGOO_79B_Continue = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x79B,
.data = {0x30, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was sent
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was sent
static unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was sent
static unsigned long GVL_pause = 0;
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters
datalayer.battery.status.real_soc = (LB_SOC * 100); //increase LB_SOC range from 0-100 -> 100.00 datalayer.battery.status.real_soc = (LB_SOC * 100); //increase LB_SOC range from 0-100 -> 100.00
@ -137,7 +86,7 @@ void update_values_battery() { //This function maps all the values fetched via
#endif #endif
} }
void handle_incoming_can_frame_battery(CAN_frame rx_frame) { void RenaultKangooBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
switch (rx_frame.ID) { switch (rx_frame.ID) {
case 0x155: //BMS1 case 0x155: //BMS1
@ -210,7 +159,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
} }
} }
void transmit_can_battery(unsigned long currentMillis) { void RenaultKangooBattery::transmit_can(unsigned long currentMillis) {
// Send 100ms CAN Message (for 2.4s, then pause 10s) // Send 100ms CAN Message (for 2.4s, then pause 10s)
if ((currentMillis - previousMillis100) >= (INTERVAL_100_MS + GVL_pause)) { if ((currentMillis - previousMillis100) >= (INTERVAL_100_MS + GVL_pause)) {
previousMillis100 = currentMillis; previousMillis100 = currentMillis;
@ -232,8 +181,7 @@ void transmit_can_battery(unsigned long currentMillis) {
} }
} }
void setup_battery(void) { // Performs one time setup at startup void RenaultKangooBattery::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "Renault Kangoo", 63); strncpy(datalayer.system.info.battery_protocol, "Renault Kangoo", 63);
datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.info.battery_protocol[63] = '\0';
datalayer.system.status.battery_allows_contactor_closing = true; datalayer.system.status.battery_allows_contactor_closing = true;

View file

@ -3,15 +3,77 @@
#include <Arduino.h> #include <Arduino.h>
#include "../include.h" #include "../include.h"
#define BATTERY_SELECTED #include "CanBattery.h"
#define MAX_PACK_VOLTAGE_DV 4150 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_DV 2500
#define MAX_CELL_DEVIATION_MV 150
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
#define MAX_CHARGE_POWER_W 5000 // Battery can be charged with this amount of power
void setup_battery(void); #define BATTERY_SELECTED
void transmit_can_frame(CAN_frame* tx_frame, int interface); #define SELECTED_BATTERY_CLASS RenaultKangooBattery
class RenaultKangooBattery : public CanBattery {
public:
virtual void setup(void);
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
virtual void update_values();
virtual void transmit_can(unsigned long currentMillis);
private:
static const int MAX_PACK_VOLTAGE_DV = 4150; //5000 = 500.0V
static const int MIN_PACK_VOLTAGE_DV = 2500;
static const int MAX_CELL_DEVIATION_MV = 150;
static const int MAX_CELL_VOLTAGE_MV = 4250; //Battery is put into emergency stop if one cell goes over this value
static const int MIN_CELL_VOLTAGE_MV = 2700; //Battery is put into emergency stop if one cell goes below this value
static const int MAX_CHARGE_POWER_W = 5000; // Battery can be charged with this amount of power
uint32_t LB_Battery_Voltage = 3700;
uint32_t LB_Charge_Power_Limit_Watts = 0;
int32_t LB_Current = 0;
int16_t LB_MAX_TEMPERATURE = 0;
int16_t LB_MIN_TEMPERATURE = 0;
uint16_t LB_SOC = 0;
uint16_t LB_SOH = 0;
uint16_t LB_Discharge_Power_Limit = 0;
uint16_t LB_Charge_Power_Limit = 0;
uint16_t LB_kWh_Remaining = 0;
uint16_t LB_Cell_Max_Voltage = 3700;
uint16_t LB_Cell_Min_Voltage = 3700;
uint16_t LB_MaxChargeAllowed_W = 0;
uint8_t LB_Discharge_Power_Limit_Byte1 = 0;
uint8_t GVI_Pollcounter = 0;
uint8_t LB_EOCR = 0;
uint8_t LB_HVBUV = 0;
uint8_t LB_HVBIR = 0;
uint8_t LB_CUV = 0;
uint8_t LB_COV = 0;
uint8_t LB_HVBOV = 0;
uint8_t LB_HVBOT = 0;
uint8_t LB_HVBOC = 0;
uint8_t LB_MaxInput_kW = 0;
uint8_t LB_MaxOutput_kW = 0;
bool GVB_79B_Continue = false;
CAN_frame KANGOO_423 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x423,
.data = {0x0B, 0x1D, 0x00, 0x02, 0xB2, 0x20, 0xB2, 0xD9}}; // Charging
// Driving: 0x07 0x1D 0x00 0x02 0x5D 0x80 0x5D 0xD8
// Charging: 0x0B 0x1D 0x00 0x02 0xB2 0x20 0xB2 0xD9
// Fastcharging: 0x07 0x1E 0x00 0x01 0x5D 0x20 0xB2 0xC7
// Old hardcoded message: .data = {0x33, 0x00, 0xFF, 0xFF, 0x00, 0xE0, 0x00, 0x00}};
CAN_frame KANGOO_79B = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x79B,
.data = {0x02, 0x21, 0x01, 0x00, 0x00, 0xE0, 0x00, 0x00}};
CAN_frame KANGOO_79B_Continue = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x79B,
.data = {0x30, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was sent
unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was sent
unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was sent
unsigned long GVL_pause = 0;
};
#endif #endif

View file

@ -5,19 +5,6 @@
#include "../devboard/utils/events.h" #include "../devboard/utils/events.h"
#include "RENAULT-TWIZY.h" #include "RENAULT-TWIZY.h"
/* Do not change code below unless you are sure what you are doing */
static int16_t cell_temperatures_dC[7] = {0};
static int16_t current_dA = 0;
static uint16_t voltage_dV = 0;
static int16_t cellvoltages_mV[14] = {0};
static int16_t max_discharge_power = 0;
static int16_t max_recup_power = 0;
static int16_t max_charge_power = 0;
static uint16_t SOC = 0;
static uint16_t SOH = 0;
static uint16_t remaining_capacity_Wh = 0;
int16_t max_value(int16_t* entries, size_t len) { int16_t max_value(int16_t* entries, size_t len) {
int result = INT16_MIN; int result = INT16_MIN;
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
@ -38,7 +25,7 @@ int16_t min_value(int16_t* entries, size_t len) {
return result; return result;
} }
void update_values_battery() { void RenaultTwizyBattery::update_values() {
datalayer.battery.status.real_soc = SOC; datalayer.battery.status.real_soc = SOC;
datalayer.battery.status.soh_pptt = SOH; datalayer.battery.status.soh_pptt = SOH;
@ -65,7 +52,7 @@ void update_values_battery() {
max_value(cell_temperatures_dC, sizeof(cell_temperatures_dC) / sizeof(*cell_temperatures_dC)); max_value(cell_temperatures_dC, sizeof(cell_temperatures_dC) / sizeof(*cell_temperatures_dC));
} }
void handle_incoming_can_frame_battery(CAN_frame rx_frame) { void RenaultTwizyBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
switch (rx_frame.ID) { switch (rx_frame.ID) {
case 0x155: case 0x155:
@ -127,11 +114,11 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
} }
} }
void transmit_can_battery(unsigned long currentMillis) { void RenaultTwizyBattery::transmit_can(unsigned long currentMillis) {
// we do not need to send anything to the battery for now // we do not need to send anything to the battery for now
} }
void setup_battery(void) { // Performs one time setup at startup void RenaultTwizyBattery::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "Renault Twizy", 63); strncpy(datalayer.system.info.battery_protocol, "Renault Twizy", 63);
datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.number_of_cells = 14; datalayer.battery.info.number_of_cells = 14;

View file

@ -1,15 +1,35 @@
#ifndef RENAULT_TWIZY_BATTERY_H #ifndef RENAULT_TWIZY_BATTERY_H
#define RENAULT_TWIZY_BATTERY_H #define RENAULT_TWIZY_BATTERY_H
#include "../include.h" #include "../include.h"
#include "CanBattery.h"
#define BATTERY_SELECTED #define BATTERY_SELECTED
#define MAX_PACK_VOLTAGE_DV 579 // 57.9V at 100% SOC (with 70% SOH, new one might be higher) #define SELECTED_BATTERY_CLASS RenaultTwizyBattery
#define MIN_PACK_VOLTAGE_DV 480 // 48.4V at 13.76% SOC
#define MAX_CELL_DEVIATION_MV 150
#define MAX_CELL_VOLTAGE_MV 4200 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 3400 //Battery is put into emergency stop if one cell goes below this value
void setup_battery(void); class RenaultTwizyBattery : public CanBattery {
void transmit_can_frame(CAN_frame* tx_frame, int interface); public:
virtual void setup(void);
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
virtual void update_values();
virtual void transmit_can(unsigned long currentMillis);
private:
static const int MAX_PACK_VOLTAGE_DV = 579; // 57.9V at 100% SOC (with 70% SOH, new one might be higher)
static const int MIN_PACK_VOLTAGE_DV = 480; // 48.4V at 13.76% SOC
static const int MAX_CELL_DEVIATION_MV = 150;
static const int MAX_CELL_VOLTAGE_MV = 4200; //Battery is put into emergency stop if one cell goes over this value
static const int MIN_CELL_VOLTAGE_MV = 3400; //Battery is put into emergency stop if one cell goes below this value
int16_t cell_temperatures_dC[7] = {0};
int16_t current_dA = 0;
uint16_t voltage_dV = 0;
int16_t cellvoltages_mV[14] = {0};
int16_t max_discharge_power = 0;
int16_t max_recup_power = 0;
int16_t max_charge_power = 0;
uint16_t SOC = 0;
uint16_t SOH = 0;
uint16_t remaining_capacity_Wh = 0;
};
#endif #endif

View file

@ -1,5 +1,6 @@
#include "../include.h" #include "../include.h"
#ifdef RENAULT_ZOE_GEN2_BATTERY #ifdef RENAULT_ZOE_GEN2_BATTERY
#include "../communication/can/comm_can.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"
@ -20,154 +21,10 @@ every time the power is reset which can be dangerous. In this state, the voltage
https://github.com/openvehicles/Open-Vehicle-Monitoring-System-3/blob/master/vehicle/OVMS.V3/components/vehicle_renaultzoe_ph2_obd/src/vehicle_renaultzoe_ph2_obd.cpp https://github.com/openvehicles/Open-Vehicle-Monitoring-System-3/blob/master/vehicle/OVMS.V3/components/vehicle_renaultzoe_ph2_obd/src/vehicle_renaultzoe_ph2_obd.cpp
https://github.com/ljames28/Renault-Zoe-PH2-ZE50-Canbus-LBC-Information?tab=readme-ov-file https://github.com/ljames28/Renault-Zoe-PH2-ZE50-Canbus-LBC-Information?tab=readme-ov-file
https://github.com/fesch/CanZE/tree/master/app/src/main/assets/ZOE_Ph2 https://github.com/fesch/CanZE/tree/master/app/src/main/assets/ZOE_Ph2
/* */
/* Do not change code below unless you are sure what you are doing */ void RenaultZoeGen2Battery::update_values() {
static uint16_t battery_soc = 0;
static uint16_t battery_usable_soc = 5000;
static uint16_t battery_soh = 10000;
static uint16_t battery_pack_voltage = 370;
static uint16_t battery_max_cell_voltage = 3700;
static uint16_t battery_min_cell_voltage = 3700;
static uint16_t battery_12v = 12000;
static uint16_t battery_avg_temp = 920;
static uint16_t battery_min_temp = 920;
static uint16_t battery_max_temp = 920;
static uint16_t battery_max_power = 0;
static uint16_t battery_interlock = 0;
static uint16_t battery_kwh = 0;
static int32_t battery_current = 32640;
static uint16_t battery_current_offset = 0;
static uint16_t battery_max_generated = 0;
static uint16_t battery_max_available = 0;
static uint16_t battery_current_voltage = 0;
static uint16_t battery_charging_status = 0;
static uint16_t battery_remaining_charge = 0;
static uint16_t battery_balance_capacity_total = 0;
static uint16_t battery_balance_time_total = 0;
static uint16_t battery_balance_capacity_sleep = 0;
static uint16_t battery_balance_time_sleep = 0;
static uint16_t battery_balance_capacity_wake = 0;
static uint16_t battery_balance_time_wake = 0;
static uint16_t battery_bms_state = 0;
static uint16_t battery_balance_switches = 0;
static uint16_t battery_energy_complete = 0;
static uint16_t battery_energy_partial = 0;
static uint16_t battery_slave_failures = 0;
static uint16_t battery_mileage = 0;
static uint16_t battery_fan_speed = 0;
static uint16_t battery_fan_period = 0;
static uint16_t battery_fan_control = 0;
static uint16_t battery_fan_duty = 0;
static uint16_t battery_temporisation = 0;
static uint16_t battery_time = 0;
static uint16_t battery_pack_time = 0;
static uint16_t battery_soc_min = 0;
static uint16_t battery_soc_max = 0;
static uint32_t ZOE_376_time_now_s = 1745452800; // Initialized to make the battery think it is April 24, 2025
unsigned long kProductionTimestamp_s =
1614454107; // Production timestamp in seconds since January 1, 1970. Production timestamp used: February 25, 2021 at 8:08:27 AM GMT
CAN_frame ZOE_373 = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x373,
.data = {0xC1, 0x40, 0x5D, 0xB2, 0x00, 0x01, 0xff,
0xe3}}; // FIXME: remove if not needed: {0xC1, 0x80, 0x5D, 0x5D, 0x00, 0x00, 0xff, 0xcb}};
CAN_frame ZOE_376 = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x373,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A,
0x00}}; // fill first 6 bytes with 0's. The first 6 bytes are calculated based on the current time.
CAN_frame ZOE_POLL_18DADBF1 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x18DADBF1,
.data = {0x03, 0x22, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00}};
//NVROL Reset
CAN_frame ZOE_NVROL_1_18DADBF1 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x18DADBF1,
.data = {0x02, 0x10, 0x03, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}};
CAN_frame ZOE_NVROL_2_18DADBF1 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x18DADBF1,
.data = {0x04, 0x31, 0x01, 0xB0, 0x09, 0x00, 0xAA, 0xAA}};
//Enable temporisation before sleep
CAN_frame ZOE_SLEEP_1_18DADBF1 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x18DADBF1,
.data = {0x02, 0x10, 0x03, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}};
CAN_frame ZOE_SLEEP_2_18DADBF1 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x18DADBF1,
.data = {0x04, 0x2E, 0x92, 0x81, 0x01, 0xAA, 0xAA, 0xAA}};
const uint16_t poll_commands[48] = {POLL_SOC,
POLL_USABLE_SOC,
POLL_SOH,
POLL_PACK_VOLTAGE,
POLL_CURRENT, //Repeated to speed up update rate on this critical measurement
POLL_MAX_CELL_VOLTAGE,
POLL_MIN_CELL_VOLTAGE,
POLL_12V,
POLL_AVG_TEMP,
POLL_MIN_TEMP,
POLL_CURRENT, //Repeated to speed up update rate on this critical measurement
POLL_MAX_TEMP,
POLL_MAX_POWER,
POLL_INTERLOCK,
POLL_KWH,
POLL_CURRENT, //Repeated to speed up update rate on this critical measurement
POLL_CURRENT_OFFSET,
POLL_MAX_GENERATED,
POLL_MAX_AVAILABLE,
POLL_CURRENT_VOLTAGE,
POLL_CHARGING_STATUS,
POLL_CURRENT, //Repeated to speed up update rate on this critical measurement
POLL_REMAINING_CHARGE,
POLL_BALANCE_CAPACITY_TOTAL,
POLL_BALANCE_TIME_TOTAL,
POLL_BALANCE_CAPACITY_SLEEP,
POLL_BALANCE_TIME_SLEEP,
POLL_CURRENT, //Repeated to speed up update rate on this critical measurement
POLL_BALANCE_CAPACITY_WAKE,
POLL_BALANCE_TIME_WAKE,
POLL_BMS_STATE,
POLL_BALANCE_SWITCHES,
POLL_ENERGY_COMPLETE,
POLL_CURRENT, //Repeated to speed up update rate on this critical measurement
POLL_ENERGY_PARTIAL,
POLL_SLAVE_FAILURES,
POLL_MILEAGE,
POLL_FAN_SPEED,
POLL_FAN_PERIOD,
POLL_CURRENT, //Repeated to speed up update rate on this critical measurement
POLL_FAN_CONTROL,
POLL_FAN_DUTY,
POLL_TEMPORISATION,
POLL_TIME,
POLL_PACK_TIME,
POLL_CURRENT, //Repeated to speed up update rate on this critical measurement
POLL_SOC_MIN,
POLL_SOC_MAX};
static uint8_t counter_373 = 0;
static uint8_t poll_index = 0;
static uint16_t currentpoll = POLL_SOC;
static uint16_t reply_poll = 0;
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was sent
static unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was sent
static unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was sent
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
datalayer.battery.status.soh_pptt = battery_soh; datalayer.battery.status.soh_pptt = battery_soh;
if (battery_soc >= 300) { if (battery_soc >= 300) {
@ -188,13 +45,16 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.max_charge_power_W = battery_max_generated * 10; datalayer.battery.status.max_charge_power_W = battery_max_generated * 10;
//Temperatures and voltages update at slow rate. Only publish new values once both have been sampled to avoid events
if ((battery_min_temp != 920) && (battery_max_temp != 920)) {
datalayer.battery.status.temperature_min_dC = ((battery_min_temp - 640) * 0.625); datalayer.battery.status.temperature_min_dC = ((battery_min_temp - 640) * 0.625);
datalayer.battery.status.temperature_max_dC = ((battery_max_temp - 640) * 0.625); datalayer.battery.status.temperature_max_dC = ((battery_max_temp - 640) * 0.625);
}
if ((battery_min_cell_voltage != 3700) && (battery_max_cell_voltage != 3700)) {
datalayer.battery.status.cell_min_voltage_mV = (battery_min_cell_voltage * 0.976563); datalayer.battery.status.cell_min_voltage_mV = (battery_min_cell_voltage * 0.976563);
datalayer.battery.status.cell_max_voltage_mV = (battery_max_cell_voltage * 0.976563); datalayer.battery.status.cell_max_voltage_mV = (battery_max_cell_voltage * 0.976563);
}
if (battery_12v < 11000) { //11.000V if (battery_12v < 11000) { //11.000V
set_event(EVENT_12V_LOW, battery_12v); set_event(EVENT_12V_LOW, battery_12v);
@ -244,7 +104,7 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer_extended.zoePH2.battery_soc_max = battery_soc_max; datalayer_extended.zoePH2.battery_soc_max = battery_soc_max;
} }
void handle_incoming_can_frame_battery(CAN_frame rx_frame) { void RenaultZoeGen2Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
switch (rx_frame.ID) { switch (rx_frame.ID) {
case 0x18DAF1DB: // LBC Reply from active polling case 0x18DAF1DB: // LBC Reply from active polling
@ -265,10 +125,16 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
battery_pack_voltage = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; battery_pack_voltage = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break; break;
case POLL_MAX_CELL_VOLTAGE: case POLL_MAX_CELL_VOLTAGE:
temporary_variable = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
if (temporary_variable > 500) { //Disregard messages with value unavailable
battery_max_cell_voltage = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; battery_max_cell_voltage = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
}
break; break;
case POLL_MIN_CELL_VOLTAGE: case POLL_MIN_CELL_VOLTAGE:
temporary_variable = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
if (temporary_variable > 500) { //Disregard messages with value unavailable
battery_min_cell_voltage = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; battery_min_cell_voltage = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
}
break; break;
case POLL_12V: case POLL_12V:
battery_12v = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; battery_12v = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
@ -375,6 +241,294 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
case POLL_SOC_MAX: case POLL_SOC_MAX:
battery_soc_max = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; battery_soc_max = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break; break;
case POLL_CELL_0:
datalayer.battery.status.cell_voltages_mV[0] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_1:
datalayer.battery.status.cell_voltages_mV[1] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_2:
datalayer.battery.status.cell_voltages_mV[2] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_3:
datalayer.battery.status.cell_voltages_mV[3] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_4:
datalayer.battery.status.cell_voltages_mV[4] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_5:
datalayer.battery.status.cell_voltages_mV[5] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_6:
datalayer.battery.status.cell_voltages_mV[6] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_7:
datalayer.battery.status.cell_voltages_mV[7] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_8:
datalayer.battery.status.cell_voltages_mV[8] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_9:
datalayer.battery.status.cell_voltages_mV[9] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_10:
datalayer.battery.status.cell_voltages_mV[10] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_11:
datalayer.battery.status.cell_voltages_mV[11] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_12:
datalayer.battery.status.cell_voltages_mV[12] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_13:
datalayer.battery.status.cell_voltages_mV[13] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_14:
datalayer.battery.status.cell_voltages_mV[14] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_15:
datalayer.battery.status.cell_voltages_mV[15] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_16:
datalayer.battery.status.cell_voltages_mV[16] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_17:
datalayer.battery.status.cell_voltages_mV[17] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_18:
datalayer.battery.status.cell_voltages_mV[18] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_19:
datalayer.battery.status.cell_voltages_mV[19] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_20:
datalayer.battery.status.cell_voltages_mV[20] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_21:
datalayer.battery.status.cell_voltages_mV[21] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_22:
datalayer.battery.status.cell_voltages_mV[22] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_23:
datalayer.battery.status.cell_voltages_mV[23] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_24:
datalayer.battery.status.cell_voltages_mV[24] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_25:
datalayer.battery.status.cell_voltages_mV[25] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_26:
datalayer.battery.status.cell_voltages_mV[26] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_27:
datalayer.battery.status.cell_voltages_mV[27] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_28:
datalayer.battery.status.cell_voltages_mV[28] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_29:
datalayer.battery.status.cell_voltages_mV[29] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_30:
datalayer.battery.status.cell_voltages_mV[30] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_31:
datalayer.battery.status.cell_voltages_mV[31] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_32:
datalayer.battery.status.cell_voltages_mV[32] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_33:
datalayer.battery.status.cell_voltages_mV[33] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_34:
datalayer.battery.status.cell_voltages_mV[34] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_35:
datalayer.battery.status.cell_voltages_mV[35] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_36:
datalayer.battery.status.cell_voltages_mV[36] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_37:
datalayer.battery.status.cell_voltages_mV[37] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_38:
datalayer.battery.status.cell_voltages_mV[38] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_39:
datalayer.battery.status.cell_voltages_mV[39] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_40:
datalayer.battery.status.cell_voltages_mV[40] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_41:
datalayer.battery.status.cell_voltages_mV[41] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_42:
datalayer.battery.status.cell_voltages_mV[42] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_43:
datalayer.battery.status.cell_voltages_mV[43] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_44:
datalayer.battery.status.cell_voltages_mV[44] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_45:
datalayer.battery.status.cell_voltages_mV[45] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_46:
datalayer.battery.status.cell_voltages_mV[46] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_47:
datalayer.battery.status.cell_voltages_mV[47] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_48:
datalayer.battery.status.cell_voltages_mV[48] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_49:
datalayer.battery.status.cell_voltages_mV[49] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_50:
datalayer.battery.status.cell_voltages_mV[50] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_51:
datalayer.battery.status.cell_voltages_mV[51] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_52:
datalayer.battery.status.cell_voltages_mV[52] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_53:
datalayer.battery.status.cell_voltages_mV[53] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_54:
datalayer.battery.status.cell_voltages_mV[54] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_55:
datalayer.battery.status.cell_voltages_mV[55] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_56:
datalayer.battery.status.cell_voltages_mV[56] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_57:
datalayer.battery.status.cell_voltages_mV[57] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_58:
datalayer.battery.status.cell_voltages_mV[58] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_59:
datalayer.battery.status.cell_voltages_mV[59] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_60:
datalayer.battery.status.cell_voltages_mV[60] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_61:
datalayer.battery.status.cell_voltages_mV[61] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_62:
datalayer.battery.status.cell_voltages_mV[62] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_63:
datalayer.battery.status.cell_voltages_mV[63] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_64:
datalayer.battery.status.cell_voltages_mV[64] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_65:
datalayer.battery.status.cell_voltages_mV[65] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_66:
datalayer.battery.status.cell_voltages_mV[66] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_67:
datalayer.battery.status.cell_voltages_mV[67] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_68:
datalayer.battery.status.cell_voltages_mV[68] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_69:
datalayer.battery.status.cell_voltages_mV[69] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_70:
datalayer.battery.status.cell_voltages_mV[70] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_71:
datalayer.battery.status.cell_voltages_mV[71] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_72:
datalayer.battery.status.cell_voltages_mV[72] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_73:
datalayer.battery.status.cell_voltages_mV[73] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_74:
datalayer.battery.status.cell_voltages_mV[74] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_75:
datalayer.battery.status.cell_voltages_mV[75] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_76:
datalayer.battery.status.cell_voltages_mV[76] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_77:
datalayer.battery.status.cell_voltages_mV[77] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_78:
datalayer.battery.status.cell_voltages_mV[78] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_79:
datalayer.battery.status.cell_voltages_mV[79] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_80:
datalayer.battery.status.cell_voltages_mV[80] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_81:
datalayer.battery.status.cell_voltages_mV[81] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_82:
datalayer.battery.status.cell_voltages_mV[82] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_83:
datalayer.battery.status.cell_voltages_mV[83] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_84:
datalayer.battery.status.cell_voltages_mV[84] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_85:
datalayer.battery.status.cell_voltages_mV[85] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_86:
datalayer.battery.status.cell_voltages_mV[86] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_87:
datalayer.battery.status.cell_voltages_mV[87] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_88:
datalayer.battery.status.cell_voltages_mV[88] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_89:
datalayer.battery.status.cell_voltages_mV[89] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_90:
datalayer.battery.status.cell_voltages_mV[90] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_91:
datalayer.battery.status.cell_voltages_mV[91] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_92:
datalayer.battery.status.cell_voltages_mV[92] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_93:
datalayer.battery.status.cell_voltages_mV[93] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_94:
datalayer.battery.status.cell_voltages_mV[94] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_95:
datalayer.battery.status.cell_voltages_mV[95] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
default: // Unknown reply default: // Unknown reply
break; break;
} }
@ -384,7 +538,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
} }
} }
void transmit_can_battery(unsigned long currentMillis) { void RenaultZoeGen2Battery::transmit_can(unsigned long currentMillis) {
if (datalayer_extended.zoePH2.UserRequestNVROLReset) { if (datalayer_extended.zoePH2.UserRequestNVROLReset) {
// Send NVROL reset frames // Send NVROL reset frames
transmit_reset_nvrol_frames(); transmit_reset_nvrol_frames();
@ -416,7 +570,7 @@ void transmit_can_battery(unsigned long currentMillis) {
// Update current poll from the array // Update current poll from the array
currentpoll = poll_commands[poll_index]; currentpoll = poll_commands[poll_index];
poll_index = (poll_index + 1) % 48; poll_index = (poll_index + 1) % 163;
ZOE_POLL_18DADBF1.data.u8[2] = (uint8_t)((currentpoll & 0xFF00) >> 8); ZOE_POLL_18DADBF1.data.u8[2] = (uint8_t)((currentpoll & 0xFF00) >> 8);
ZOE_POLL_18DADBF1.data.u8[3] = (uint8_t)(currentpoll & 0x00FF); ZOE_POLL_18DADBF1.data.u8[3] = (uint8_t)(currentpoll & 0x00FF);
@ -434,7 +588,7 @@ void transmit_can_battery(unsigned long currentMillis) {
} }
} }
void setup_battery(void) { // Performs one time setup at startup void RenaultZoeGen2Battery::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "Renault Zoe Gen2 50kWh", 63); strncpy(datalayer.system.info.battery_protocol, "Renault Zoe Gen2 50kWh", 63);
datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.info.battery_protocol[63] = '\0';
datalayer.system.status.battery_allows_contactor_closing = true; datalayer.system.status.battery_allows_contactor_closing = true;
@ -446,7 +600,7 @@ void setup_battery(void) { // Performs one time setup at startup
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV; datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
} }
void transmit_can_frame_376(void) { void RenaultZoeGen2Battery::transmit_can_frame_376(void) {
unsigned int secondsSinceProduction = ZOE_376_time_now_s - kProductionTimestamp_s; unsigned int secondsSinceProduction = ZOE_376_time_now_s - kProductionTimestamp_s;
float minutesSinceProduction = (float)secondsSinceProduction / 60.0; float minutesSinceProduction = (float)secondsSinceProduction / 60.0;
float yearUnfloored = minutesSinceProduction / 255.0 / 255.0; float yearUnfloored = minutesSinceProduction / 255.0 / 255.0;
@ -467,7 +621,7 @@ void transmit_can_frame_376(void) {
transmit_can_frame(&ZOE_376, can_config.battery); transmit_can_frame(&ZOE_376, can_config.battery);
} }
void transmit_reset_nvrol_frames(void) { void RenaultZoeGen2Battery::transmit_reset_nvrol_frames(void) {
// NVROL reset, part 1: send 0x021003AAAAAAAAAA // NVROL reset, part 1: send 0x021003AAAAAAAAAA
ZOE_POLL_18DADBF1.data = {0x02, 0x10, 0x03, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}; ZOE_POLL_18DADBF1.data = {0x02, 0x10, 0x03, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA};
transmit_can_frame(&ZOE_POLL_18DADBF1, can_config.battery); transmit_can_frame(&ZOE_POLL_18DADBF1, can_config.battery);
@ -497,7 +651,7 @@ void transmit_reset_nvrol_frames(void) {
wait_ms(30000); wait_ms(30000);
} }
void wait_ms(int duration_ms) { void RenaultZoeGen2Battery::wait_ms(int duration_ms) {
unsigned long freezeMillis = millis(); unsigned long freezeMillis = millis();
while (millis() - freezeMillis < duration_ms) { while (millis() - freezeMillis < duration_ms) {
// Do nothing - just wait // Do nothing - just wait

View file

@ -2,17 +2,424 @@
#define RENAULT_ZOE_GEN2_BATTERY_H #define RENAULT_ZOE_GEN2_BATTERY_H
#include "../include.h" #include "../include.h"
#include "CanBattery.h"
#define BATTERY_SELECTED #define BATTERY_SELECTED
#define MAX_PACK_VOLTAGE_DV 4100 //5000 = 500.0V #define SELECTED_BATTERY_CLASS RenaultZoeGen2Battery
#define MIN_PACK_VOLTAGE_DV 3000
#define MAX_CELL_DEVIATION_MV 150
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
void setup_battery(void); class RenaultZoeGen2Battery : public CanBattery {
void transmit_can_frame(CAN_frame* tx_frame, int interface); public:
virtual void setup(void);
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
virtual void update_values();
virtual void transmit_can(unsigned long currentMillis);
/** private:
static const int MAX_PACK_VOLTAGE_DV = 4100; //5000 = 500.0V
static const int MIN_PACK_VOLTAGE_DV = 3000;
static const int MAX_CELL_DEVIATION_MV = 150;
static const int MAX_CELL_VOLTAGE_MV = 4250; //Battery is put into emergency stop if one cell goes over this value
static const int MIN_CELL_VOLTAGE_MV = 2700; //Battery is put into emergency stop if one cell goes below this value
static const int POLL_SOC = 0x9001;
static const int POLL_USABLE_SOC = 0x9002;
static const int POLL_SOH = 0x9003;
static const int POLL_PACK_VOLTAGE = 0x9005;
static const int POLL_MAX_CELL_VOLTAGE = 0x9007;
static const int POLL_MIN_CELL_VOLTAGE = 0x9009;
static const int POLL_12V = 0x9011;
static const int POLL_AVG_TEMP = 0x9012;
static const int POLL_MIN_TEMP = 0x9013;
static const int POLL_MAX_TEMP = 0x9014;
static const int POLL_MAX_POWER = 0x9018;
static const int POLL_INTERLOCK = 0x901A;
static const int POLL_KWH = 0x91C8;
static const int POLL_CURRENT = 0x925D;
static const int POLL_CURRENT_OFFSET = 0x900C;
static const int POLL_MAX_GENERATED = 0x900E;
static const int POLL_MAX_AVAILABLE = 0x900F;
static const int POLL_CURRENT_VOLTAGE = 0x9130;
static const int POLL_CHARGING_STATUS = 0x9019;
static const int POLL_REMAINING_CHARGE = 0xF45B;
static const int POLL_BALANCE_CAPACITY_TOTAL = 0x924F;
static const int POLL_BALANCE_TIME_TOTAL = 0x9250;
static const int POLL_BALANCE_CAPACITY_SLEEP = 0x9251;
static const int POLL_BALANCE_TIME_SLEEP = 0x9252;
static const int POLL_BALANCE_CAPACITY_WAKE = 0x9262;
static const int POLL_BALANCE_TIME_WAKE = 0x9263;
static const int POLL_BMS_STATE = 0x9259;
static const int POLL_BALANCE_SWITCHES = 0x912B;
static const int POLL_ENERGY_COMPLETE = 0x9210;
static const int POLL_ENERGY_PARTIAL = 0x9215;
static const int POLL_SLAVE_FAILURES = 0x9129;
static const int POLL_MILEAGE = 0x91CF;
static const int POLL_FAN_SPEED = 0x912E;
static const int POLL_FAN_PERIOD = 0x91F4;
static const int POLL_FAN_CONTROL = 0x91C9;
static const int POLL_FAN_DUTY = 0x91F5;
static const int POLL_TEMPORISATION = 0x9281;
static const int POLL_TIME = 0x9261;
static const int POLL_PACK_TIME = 0x91C1;
static const int POLL_SOC_MIN = 0x91B9;
static const int POLL_SOC_MAX = 0x91BA;
static const int POLL_CELL_0 = 0x9021;
static const int POLL_CELL_1 = 0x9022;
static const int POLL_CELL_2 = 0x9023;
static const int POLL_CELL_3 = 0x9024;
static const int POLL_CELL_4 = 0x9025;
static const int POLL_CELL_5 = 0x9026;
static const int POLL_CELL_6 = 0x9027;
static const int POLL_CELL_7 = 0x9028;
static const int POLL_CELL_8 = 0x9029;
static const int POLL_CELL_9 = 0x902A;
static const int POLL_CELL_10 = 0x902B;
static const int POLL_CELL_11 = 0x902C;
static const int POLL_CELL_12 = 0x902D;
static const int POLL_CELL_13 = 0x902E;
static const int POLL_CELL_14 = 0x902F;
static const int POLL_CELL_15 = 0x9030;
static const int POLL_CELL_16 = 0x9031;
static const int POLL_CELL_17 = 0x9032;
static const int POLL_CELL_18 = 0x9033;
static const int POLL_CELL_19 = 0x9034;
static const int POLL_CELL_20 = 0x9035;
static const int POLL_CELL_21 = 0x9036;
static const int POLL_CELL_22 = 0x9037;
static const int POLL_CELL_23 = 0x9038;
static const int POLL_CELL_24 = 0x9039;
static const int POLL_CELL_25 = 0x903A;
static const int POLL_CELL_26 = 0x903B;
static const int POLL_CELL_27 = 0x903C;
static const int POLL_CELL_28 = 0x903D;
static const int POLL_CELL_29 = 0x903E;
static const int POLL_CELL_30 = 0x903F;
static const int POLL_CELL_31 = 0x9041;
static const int POLL_CELL_32 = 0x9042;
static const int POLL_CELL_33 = 0x9043;
static const int POLL_CELL_34 = 0x9044;
static const int POLL_CELL_35 = 0x9045;
static const int POLL_CELL_36 = 0x9046;
static const int POLL_CELL_37 = 0x9047;
static const int POLL_CELL_38 = 0x9048;
static const int POLL_CELL_39 = 0x9049;
static const int POLL_CELL_40 = 0x904A;
static const int POLL_CELL_41 = 0x904B;
static const int POLL_CELL_42 = 0x904C;
static const int POLL_CELL_43 = 0x904D;
static const int POLL_CELL_44 = 0x904E;
static const int POLL_CELL_45 = 0x904F;
static const int POLL_CELL_46 = 0x9050;
static const int POLL_CELL_47 = 0x9051;
static const int POLL_CELL_48 = 0x9052;
static const int POLL_CELL_49 = 0x9053;
static const int POLL_CELL_50 = 0x9054;
static const int POLL_CELL_51 = 0x9055;
static const int POLL_CELL_52 = 0x9056;
static const int POLL_CELL_53 = 0x9057;
static const int POLL_CELL_54 = 0x9058;
static const int POLL_CELL_55 = 0x9059;
static const int POLL_CELL_56 = 0x905A;
static const int POLL_CELL_57 = 0x905B;
static const int POLL_CELL_58 = 0x905C;
static const int POLL_CELL_59 = 0x905D;
static const int POLL_CELL_60 = 0x905E;
static const int POLL_CELL_61 = 0x905F;
static const int POLL_CELL_62 = 0x9061;
static const int POLL_CELL_63 = 0x9062;
static const int POLL_CELL_64 = 0x9063;
static const int POLL_CELL_65 = 0x9064;
static const int POLL_CELL_66 = 0x9065;
static const int POLL_CELL_67 = 0x9066;
static const int POLL_CELL_68 = 0x9067;
static const int POLL_CELL_69 = 0x9068;
static const int POLL_CELL_70 = 0x9069;
static const int POLL_CELL_71 = 0x906A;
static const int POLL_CELL_72 = 0x906B;
static const int POLL_CELL_73 = 0x906C;
static const int POLL_CELL_74 = 0x906D;
static const int POLL_CELL_75 = 0x906E;
static const int POLL_CELL_76 = 0x906F;
static const int POLL_CELL_77 = 0x9070;
static const int POLL_CELL_78 = 0x9071;
static const int POLL_CELL_79 = 0x9072;
static const int POLL_CELL_80 = 0x9073;
static const int POLL_CELL_81 = 0x9074;
static const int POLL_CELL_82 = 0x9075;
static const int POLL_CELL_83 = 0x9076;
static const int POLL_CELL_84 = 0x9077;
static const int POLL_CELL_85 = 0x9078;
static const int POLL_CELL_86 = 0x9079;
static const int POLL_CELL_87 = 0x907A;
static const int POLL_CELL_88 = 0x907B;
static const int POLL_CELL_89 = 0x907C;
static const int POLL_CELL_90 = 0x907D;
static const int POLL_CELL_91 = 0x907E;
static const int POLL_CELL_92 = 0x907F;
static const int POLL_CELL_93 = 0x9081;
static const int POLL_CELL_94 = 0x9082;
static const int POLL_CELL_95 = 0x9083;
uint16_t battery_soc = 0;
uint16_t battery_usable_soc = 5000;
uint16_t battery_soh = 10000;
uint16_t battery_pack_voltage = 370;
uint16_t battery_max_cell_voltage = 3700;
uint16_t battery_min_cell_voltage = 3700;
uint16_t battery_12v = 12000;
uint16_t battery_avg_temp = 920;
uint16_t battery_min_temp = 920;
uint16_t battery_max_temp = 920;
uint16_t battery_max_power = 0;
uint16_t battery_interlock = 0;
uint16_t battery_kwh = 0;
int32_t battery_current = 32640;
uint16_t battery_current_offset = 0;
uint16_t battery_max_generated = 0;
uint16_t battery_max_available = 0;
uint16_t battery_current_voltage = 0;
uint16_t battery_charging_status = 0;
uint16_t battery_remaining_charge = 0;
uint16_t battery_balance_capacity_total = 0;
uint16_t battery_balance_time_total = 0;
uint16_t battery_balance_capacity_sleep = 0;
uint16_t battery_balance_time_sleep = 0;
uint16_t battery_balance_capacity_wake = 0;
uint16_t battery_balance_time_wake = 0;
uint16_t battery_bms_state = 0;
uint16_t battery_balance_switches = 0;
uint16_t battery_energy_complete = 0;
uint16_t battery_energy_partial = 0;
uint16_t battery_slave_failures = 0;
uint16_t battery_mileage = 0;
uint16_t battery_fan_speed = 0;
uint16_t battery_fan_period = 0;
uint16_t battery_fan_control = 0;
uint16_t battery_fan_duty = 0;
uint16_t battery_temporisation = 0;
uint16_t battery_time = 0;
uint16_t battery_pack_time = 0;
uint16_t battery_soc_min = 0;
uint16_t battery_soc_max = 0;
uint16_t temporary_variable = 0;
uint32_t ZOE_376_time_now_s = 1745452800; // Initialized to make the battery think it is April 24, 2025
unsigned long kProductionTimestamp_s =
1614454107; // Production timestamp in seconds since January 1, 1970. Production timestamp used: February 25, 2021 at 8:08:27 AM GMT
CAN_frame ZOE_373 = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x373,
.data = {0xC1, 0x40, 0x5D, 0xB2, 0x00, 0x01, 0xff,
0xe3}}; // FIXME: remove if not needed: {0xC1, 0x80, 0x5D, 0x5D, 0x00, 0x00, 0xff, 0xcb}};
CAN_frame ZOE_376 = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x376,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A,
0x00}}; // fill first 6 bytes with 0's. The first 6 bytes are calculated based on the current time.
CAN_frame ZOE_POLL_18DADBF1 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x18DADBF1,
.data = {0x03, 0x22, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00}};
//NVROL Reset
CAN_frame ZOE_NVROL_1_18DADBF1 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x18DADBF1,
.data = {0x02, 0x10, 0x03, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}};
CAN_frame ZOE_NVROL_2_18DADBF1 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x18DADBF1,
.data = {0x04, 0x31, 0x01, 0xB0, 0x09, 0x00, 0xAA, 0xAA}};
//Enable temporisation before sleep
CAN_frame ZOE_SLEEP_1_18DADBF1 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x18DADBF1,
.data = {0x02, 0x10, 0x03, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}};
CAN_frame ZOE_SLEEP_2_18DADBF1 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x18DADBF1,
.data = {0x04, 0x2E, 0x92, 0x81, 0x01, 0xAA, 0xAA, 0xAA}};
const uint16_t poll_commands[163] = {POLL_SOC,
POLL_USABLE_SOC,
POLL_SOH,
POLL_PACK_VOLTAGE,
POLL_CURRENT, //Repeated to speed up update rate on this critical measurement
POLL_MAX_CELL_VOLTAGE,
POLL_MIN_CELL_VOLTAGE,
POLL_12V,
POLL_AVG_TEMP,
POLL_MIN_TEMP,
POLL_CURRENT, //Repeated to speed up update rate on this critical measurement
POLL_MAX_TEMP,
POLL_MAX_POWER,
POLL_INTERLOCK,
POLL_KWH,
POLL_CURRENT, //Repeated to speed up update rate on this critical measurement
POLL_CURRENT_OFFSET,
POLL_MAX_GENERATED,
POLL_MAX_AVAILABLE,
POLL_CURRENT_VOLTAGE,
POLL_CHARGING_STATUS,
POLL_CURRENT, //Repeated to speed up update rate on this critical measurement
POLL_REMAINING_CHARGE,
POLL_BALANCE_CAPACITY_TOTAL,
POLL_BALANCE_TIME_TOTAL,
POLL_BALANCE_CAPACITY_SLEEP,
POLL_BALANCE_TIME_SLEEP,
POLL_CURRENT, //Repeated to speed up update rate on this critical measurement
POLL_BALANCE_CAPACITY_WAKE,
POLL_BALANCE_TIME_WAKE,
POLL_BMS_STATE,
POLL_BALANCE_SWITCHES,
POLL_ENERGY_COMPLETE,
POLL_CURRENT, //Repeated to speed up update rate on this critical measurement
POLL_ENERGY_PARTIAL,
POLL_SLAVE_FAILURES,
POLL_MILEAGE,
POLL_FAN_SPEED,
POLL_FAN_PERIOD,
POLL_CURRENT, //Repeated to speed up update rate on this critical measurement
POLL_FAN_CONTROL,
POLL_FAN_DUTY,
POLL_TEMPORISATION,
POLL_TIME,
POLL_PACK_TIME,
POLL_CURRENT, //Repeated to speed up update rate on this critical measurement
POLL_SOC_MIN,
POLL_SOC_MAX,
POLL_CELL_0,
POLL_CELL_1,
POLL_CELL_2,
POLL_CURRENT, //Repeated to speed up update rate on this critical measurement
POLL_CELL_3,
POLL_CELL_4,
POLL_CELL_5,
POLL_CELL_6,
POLL_CELL_7,
POLL_CURRENT, //Repeated to speed up update rate on this critical measurement
POLL_CELL_8,
POLL_CELL_9,
POLL_CELL_10,
POLL_CELL_11,
POLL_CELL_12,
POLL_CURRENT, //Repeated to speed up update rate on this critical measurement
POLL_CELL_13,
POLL_CELL_14,
POLL_CELL_15,
POLL_CELL_16,
POLL_CELL_17,
POLL_CURRENT, //Repeated to speed up update rate on this critical measurement
POLL_CELL_18,
POLL_CELL_19,
POLL_CELL_20,
POLL_CELL_21,
POLL_CELL_22,
POLL_CURRENT, //Repeated to speed up update rate on this critical measurement
POLL_CELL_23,
POLL_CELL_24,
POLL_CELL_25,
POLL_CELL_26,
POLL_CELL_27,
POLL_CURRENT, //Repeated to speed up update rate on this critical measurement
POLL_CELL_28,
POLL_CELL_29,
POLL_CELL_30,
POLL_CELL_31,
POLL_CELL_32,
POLL_CURRENT, //Repeated to speed up update rate on this critical measurement
POLL_CELL_33,
POLL_CELL_34,
POLL_CELL_35,
POLL_CELL_36,
POLL_CELL_37,
POLL_CURRENT, //Repeated to speed up update rate on this critical measurement
POLL_CELL_38,
POLL_CELL_39,
POLL_CELL_40,
POLL_CELL_41,
POLL_CELL_42,
POLL_CURRENT, //Repeated to speed up update rate on this critical measurement
POLL_CELL_43,
POLL_CELL_44,
POLL_CELL_45,
POLL_CELL_46,
POLL_CELL_47,
POLL_CURRENT, //Repeated to speed up update rate on this critical measurement
POLL_CELL_48,
POLL_CELL_49,
POLL_CELL_50,
POLL_CELL_51,
POLL_CELL_52,
POLL_CURRENT, //Repeated to speed up update rate on this critical measurement
POLL_CELL_53,
POLL_CELL_54,
POLL_CELL_55,
POLL_CELL_56,
POLL_CELL_57,
POLL_CURRENT, //Repeated to speed up update rate on this critical measurement
POLL_CELL_58,
POLL_CELL_59,
POLL_CELL_60,
POLL_CELL_61,
POLL_CELL_62,
POLL_CURRENT, //Repeated to speed up update rate on this critical measurement
POLL_CELL_63,
POLL_CELL_64,
POLL_CELL_65,
POLL_CELL_66,
POLL_CELL_67,
POLL_CURRENT, //Repeated to speed up update rate on this critical measurement
POLL_CELL_68,
POLL_CELL_69,
POLL_CELL_70,
POLL_CELL_71,
POLL_CELL_72,
POLL_CURRENT, //Repeated to speed up update rate on this critical measurement
POLL_CELL_73,
POLL_CELL_74,
POLL_CELL_75,
POLL_CELL_76,
POLL_CELL_77,
POLL_CURRENT, //Repeated to speed up update rate on this critical measurement
POLL_CELL_78,
POLL_CELL_79,
POLL_CELL_80,
POLL_CELL_81,
POLL_CELL_82,
POLL_CURRENT, //Repeated to speed up update rate on this critical measurement
POLL_CELL_83,
POLL_CELL_84,
POLL_CELL_85,
POLL_CELL_86,
POLL_CELL_87,
POLL_CURRENT, //Repeated to speed up update rate on this critical measurement
POLL_CELL_88,
POLL_CELL_89,
POLL_CELL_90,
POLL_CELL_91,
POLL_CELL_92,
POLL_CURRENT, //Repeated to speed up update rate on this critical measurement
POLL_CELL_93,
POLL_CELL_94,
POLL_CELL_95};
uint8_t counter_373 = 0;
uint8_t poll_index = 0;
uint16_t currentpoll = POLL_SOC;
uint16_t reply_poll = 0;
unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was sent
unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was sent
unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was sent
/**
* @brief Transmit CAN frame 0x376 * @brief Transmit CAN frame 0x376
* *
* @param[in] void * @param[in] void
@ -20,66 +427,25 @@ void transmit_can_frame(CAN_frame* tx_frame, int interface);
* @return void * @return void
* *
*/ */
void transmit_can_frame_376(void); void transmit_can_frame_376(void);
/** /**
* @brief Reset NVROL, by sending specific frames * @brief Reset NVROL, by sending specific frames
* *
* @param[in] void * @param[in] void
* *
* @return void * @return void
*/ */
void transmit_reset_nvrol_frames(void); void transmit_reset_nvrol_frames(void);
/** /**
* @brief Wait function * @brief Wait function
* *
* @param[in] duration_ms wait duration in ms * @param[in] duration_ms wait duration in ms
* *
* @return void * @return void
*/ */
void wait_ms(int duration_ms); void wait_ms(int duration_ms);
};
#define POLL_SOC 0x9001
#define POLL_USABLE_SOC 0x9002
#define POLL_SOH 0x9003
#define POLL_PACK_VOLTAGE 0x9005
#define POLL_MAX_CELL_VOLTAGE 0x9007
#define POLL_MIN_CELL_VOLTAGE 0x9009
#define POLL_12V 0x9011
#define POLL_AVG_TEMP 0x9012
#define POLL_MIN_TEMP 0x9013
#define POLL_MAX_TEMP 0x9014
#define POLL_MAX_POWER 0x9018
#define POLL_INTERLOCK 0x901A
#define POLL_KWH 0x91C8
#define POLL_CURRENT 0x925D
#define POLL_CURRENT_OFFSET 0x900C
#define POLL_MAX_GENERATED 0x900E
#define POLL_MAX_AVAILABLE 0x900F
#define POLL_CURRENT_VOLTAGE 0x9130
#define POLL_CHARGING_STATUS 0x9019
#define POLL_REMAINING_CHARGE 0xF45B
#define POLL_BALANCE_CAPACITY_TOTAL 0x924F
#define POLL_BALANCE_TIME_TOTAL 0x9250
#define POLL_BALANCE_CAPACITY_SLEEP 0x9251
#define POLL_BALANCE_TIME_SLEEP 0x9252
#define POLL_BALANCE_CAPACITY_WAKE 0x9262
#define POLL_BALANCE_TIME_WAKE 0x9263
#define POLL_BMS_STATE 0x9259
#define POLL_BALANCE_SWITCHES 0x912B
#define POLL_ENERGY_COMPLETE 0x9210
#define POLL_ENERGY_PARTIAL 0x9215
#define POLL_SLAVE_FAILURES 0x9129
#define POLL_MILEAGE 0x91CF
#define POLL_FAN_SPEED 0x912E
#define POLL_FAN_PERIOD 0x91F4
#define POLL_FAN_CONTROL 0x91C9
#define POLL_FAN_DUTY 0x91F5
#define POLL_TEMPORISATION 0x9281
#define POLL_TIME 0x9261
#define POLL_PACK_TIME 0x91C1
#define POLL_SOC_MIN 0x91B9
#define POLL_SOC_MAX 0x91BA
#endif #endif

View file

@ -1,83 +1,11 @@
#include "../include.h" #include "../include.h"
#ifdef RJXZS_BMS #ifdef RJXZS_BMS
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "../devboard/utils/events.h" #include "../devboard/utils/events.h"
#include "RJXZS-BMS.h" #include "RJXZS-BMS.h"
/* Do not change code below unless you are sure what you are doing */ void RjxzsBms::update_values() {
static unsigned long previousMillis10s = 0; // will store last time a 10s CAN Message was sent
//Actual content messages
CAN_frame RJXZS_1C = {.FD = false, .ext_ID = true, .DLC = 3, .ID = 0xF4, .data = {0x1C, 0x00, 0x02}};
CAN_frame RJXZS_10 = {.FD = false, .ext_ID = true, .DLC = 3, .ID = 0xF4, .data = {0x10, 0x00, 0x02}};
#define FIVE_MINUTES 60
static uint8_t mux = 0;
static bool setup_completed = false;
static uint16_t total_voltage = 0;
static int16_t total_current = 0;
static uint16_t total_power = 0;
static uint16_t battery_usage_capacity = 0;
static uint16_t battery_capacity_percentage = 0;
static uint16_t charging_capacity = 0;
static uint16_t charging_recovery_voltage = 0;
static uint16_t discharging_recovery_voltage = 0;
static uint16_t remaining_capacity = 0;
static int16_t host_temperature = 0;
static uint16_t status_accounting = 0;
static uint16_t equalization_starting_voltage = 0;
static uint16_t discharge_protection_voltage = 0;
static uint16_t protective_current = 0;
static uint16_t battery_pack_capacity = 0;
static uint16_t number_of_battery_strings = 0;
static uint16_t charging_protection_voltage = 0;
static int16_t protection_temperature = 0;
static bool temperature_below_zero_mod1_4 = false;
static bool temperature_below_zero_mod5_8 = false;
static bool temperature_below_zero_mod9_12 = false;
static bool temperature_below_zero_mod13_16 = false;
static uint16_t module_1_temperature = 0;
static uint16_t module_2_temperature = 0;
static uint16_t module_3_temperature = 0;
static uint16_t module_4_temperature = 0;
static uint16_t module_5_temperature = 0;
static uint16_t module_6_temperature = 0;
static uint16_t module_7_temperature = 0;
static uint16_t module_8_temperature = 0;
static uint16_t module_9_temperature = 0;
static uint16_t module_10_temperature = 0;
static uint16_t module_11_temperature = 0;
static uint16_t module_12_temperature = 0;
static uint16_t module_13_temperature = 0;
static uint16_t module_14_temperature = 0;
static uint16_t module_15_temperature = 0;
static uint16_t module_16_temperature = 0;
static uint16_t low_voltage_power_outage_protection = 0;
static uint16_t low_voltage_power_outage_delayed = 0;
static uint16_t num_of_triggering_protection_cells = 0;
static uint16_t balanced_reference_voltage = 0;
static uint16_t minimum_cell_voltage = 0;
static uint16_t maximum_cell_voltage = 0;
static uint16_t cellvoltages[MAX_AMOUNT_CELLS];
static uint8_t populated_cellvoltages = 0;
static uint16_t accumulated_total_capacity_high = 0;
static uint16_t accumulated_total_capacity_low = 0;
static uint16_t pre_charge_delay_time = 0;
static uint16_t LCD_status = 0;
static uint16_t differential_pressure_setting_value = 0;
static uint16_t use_capacity_to_automatically_reset = 0;
static uint16_t low_temperature_protection_setting_value = 0;
static uint16_t protecting_historical_logs = 0;
static uint16_t hall_sensor_type = 0;
static uint16_t fan_start_setting_value = 0;
static uint16_t ptc_heating_start_setting_value = 0;
static uint16_t default_channel_state = 0;
static uint8_t timespent_without_soc = 0;
static bool charging_active = false;
static bool discharging_active = false;
void update_values_battery() {
datalayer.battery.status.real_soc = battery_capacity_percentage * 100; datalayer.battery.status.real_soc = battery_capacity_percentage * 100;
if (battery_capacity_percentage == 0) { if (battery_capacity_percentage == 0) {
@ -166,7 +94,7 @@ void update_values_battery() {
datalayer.battery.status.cell_min_voltage_mV = minimum_cell_voltage; datalayer.battery.status.cell_min_voltage_mV = minimum_cell_voltage;
} }
void handle_incoming_can_frame_battery(CAN_frame rx_frame) { void RjxzsBms::handle_incoming_can_frame(CAN_frame rx_frame) {
/* /*
// All CAN messages recieved will be logged via serial // All CAN messages recieved will be logged via serial
@ -571,7 +499,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
} }
} }
void transmit_can_battery(unsigned long currentMillis) { void RjxzsBms::transmit_can(unsigned long currentMillis) {
// Send 10s CAN Message // Send 10s CAN Message
if (currentMillis - previousMillis10s >= INTERVAL_10_S) { if (currentMillis - previousMillis10s >= INTERVAL_10_S) {
previousMillis10s = currentMillis; previousMillis10s = currentMillis;
@ -588,7 +516,7 @@ void transmit_can_battery(unsigned long currentMillis) {
} }
} }
void setup_battery(void) { // Performs one time setup at startup void RjxzsBms::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "RJXZS BMS, DIY battery", 63); strncpy(datalayer.system.info.battery_protocol, "RJXZS BMS, DIY battery", 63);
datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;

View file

@ -3,22 +3,105 @@
#include <Arduino.h> #include <Arduino.h>
#include "../include.h" #include "../include.h"
/* Tweak these according to your battery build */ #include "CanBattery.h"
#define MAX_PACK_VOLTAGE_DV 5000 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_DV 1500 #define BATTERY_SELECTED
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value #define SELECTED_BATTERY_CLASS RjxzsBms
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
#define MAX_CELL_DEVIATION_MV 250 class RjxzsBms : public CanBattery {
#define MAX_DISCHARGE_POWER_ALLOWED_W 5000 public:
#define MAX_CHARGE_POWER_ALLOWED_W 5000 virtual void setup(void);
#define MAX_CHARGE_POWER_WHEN_TOPBALANCING_W 500 virtual void handle_incoming_can_frame(CAN_frame rx_frame);
#define RAMPDOWN_SOC 9000 // (90.00) SOC% to start ramping down from max charge power towards 0 at 100.00% virtual void update_values();
virtual void transmit_can(unsigned long currentMillis);
private:
/* Tweak these according to your battery build */
static const int MAX_PACK_VOLTAGE_DV = 5000; //5000 = 500.0V
static const int MIN_PACK_VOLTAGE_DV = 1500;
static const int MAX_CELL_VOLTAGE_MV = 4250; //Battery is put into emergency stop if one cell goes over this value
static const int MIN_CELL_VOLTAGE_MV = 2700; //Battery is put into emergency stop if one cell goes below this value
static const int MAX_CELL_DEVIATION_MV = 250;
static const int MAX_DISCHARGE_POWER_ALLOWED_W = 5000;
static const int MAX_CHARGE_POWER_ALLOWED_W = 5000;
static const int MAX_CHARGE_POWER_WHEN_TOPBALANCING_W = 500;
static const int RAMPDOWN_SOC =
9000; // (90.00) SOC% to start ramping down from max charge power towards 0 at 100.00%
unsigned long previousMillis10s = 0; // will store last time a 10s CAN Message was sent
//Actual content messages
CAN_frame RJXZS_1C = {.FD = false, .ext_ID = true, .DLC = 3, .ID = 0xF4, .data = {0x1C, 0x00, 0x02}};
CAN_frame RJXZS_10 = {.FD = false, .ext_ID = true, .DLC = 3, .ID = 0xF4, .data = {0x10, 0x00, 0x02}};
static const int FIVE_MINUTES = 60;
uint8_t mux = 0;
bool setup_completed = false;
uint16_t total_voltage = 0;
int16_t total_current = 0;
uint16_t total_power = 0;
uint16_t battery_usage_capacity = 0;
uint16_t battery_capacity_percentage = 0;
uint16_t charging_capacity = 0;
uint16_t charging_recovery_voltage = 0;
uint16_t discharging_recovery_voltage = 0;
uint16_t remaining_capacity = 0;
int16_t host_temperature = 0;
uint16_t status_accounting = 0;
uint16_t equalization_starting_voltage = 0;
uint16_t discharge_protection_voltage = 0;
uint16_t protective_current = 0;
uint16_t battery_pack_capacity = 0;
uint16_t number_of_battery_strings = 0;
uint16_t charging_protection_voltage = 0;
int16_t protection_temperature = 0;
bool temperature_below_zero_mod1_4 = false;
bool temperature_below_zero_mod5_8 = false;
bool temperature_below_zero_mod9_12 = false;
bool temperature_below_zero_mod13_16 = false;
uint16_t module_1_temperature = 0;
uint16_t module_2_temperature = 0;
uint16_t module_3_temperature = 0;
uint16_t module_4_temperature = 0;
uint16_t module_5_temperature = 0;
uint16_t module_6_temperature = 0;
uint16_t module_7_temperature = 0;
uint16_t module_8_temperature = 0;
uint16_t module_9_temperature = 0;
uint16_t module_10_temperature = 0;
uint16_t module_11_temperature = 0;
uint16_t module_12_temperature = 0;
uint16_t module_13_temperature = 0;
uint16_t module_14_temperature = 0;
uint16_t module_15_temperature = 0;
uint16_t module_16_temperature = 0;
uint16_t low_voltage_power_outage_protection = 0;
uint16_t low_voltage_power_outage_delayed = 0;
uint16_t num_of_triggering_protection_cells = 0;
uint16_t balanced_reference_voltage = 0;
uint16_t minimum_cell_voltage = 0;
uint16_t maximum_cell_voltage = 0;
uint16_t cellvoltages[MAX_AMOUNT_CELLS];
uint8_t populated_cellvoltages = 0;
uint16_t accumulated_total_capacity_high = 0;
uint16_t accumulated_total_capacity_low = 0;
uint16_t pre_charge_delay_time = 0;
uint16_t LCD_status = 0;
uint16_t differential_pressure_setting_value = 0;
uint16_t use_capacity_to_automatically_reset = 0;
uint16_t low_temperature_protection_setting_value = 0;
uint16_t protecting_historical_logs = 0;
uint16_t hall_sensor_type = 0;
uint16_t fan_start_setting_value = 0;
uint16_t ptc_heating_start_setting_value = 0;
uint16_t default_channel_state = 0;
uint8_t timespent_without_soc = 0;
bool charging_active = false;
bool discharging_active = false;
};
/* Do not modify any rows below*/ /* Do not modify any rows below*/
#define BATTERY_SELECTED
#define NATIVECAN_250KBPS #define NATIVECAN_250KBPS
void setup_battery(void);
void transmit_can_frame(CAN_frame* tx_frame, int interface);
#endif #endif

View file

@ -0,0 +1,15 @@
#ifndef RS485_BATTERY_H
#define RS485_BATTERY_H
#include "Battery.h"
#include "src/devboard/utils/types.h"
// Abstract base class for batteries using the RS485 interface
class RS485Battery : public Battery {
public:
virtual void receive_RS485() = 0;
virtual void transmit_rs485() = 0;
};
#endif

View file

@ -1,5 +1,6 @@
#include "../include.h" #include "../include.h"
#ifdef SANTA_FE_PHEV_BATTERY #ifdef SANTA_FE_PHEV_BATTERY
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "../devboard/utils/events.h" #include "../devboard/utils/events.h"
#include "SANTA-FE-PHEV-BATTERY.h" #include "SANTA-FE-PHEV-BATTERY.h"
@ -13,113 +14,58 @@ TODO: Tweak temperature values once more data is known about them
TODO: Check if CRC function works like it should. This enables checking for corrupt messages TODO: Check if CRC function works like it should. This enables checking for corrupt messages
*/ */
/* Do not change code below unless you are sure what you are doing */ static uint8_t CalculateCRC8(CAN_frame rx_frame) {
static unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was send int crc = 0;
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
static unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was send
static uint8_t poll_data_pid = 0;
static uint8_t counter_200 = 0;
static uint8_t checksum_200 = 0;
static uint16_t SOC_Display = 0; for (uint8_t framepos = 0; framepos < 8; framepos++) {
static uint16_t batterySOH = 100; crc ^= rx_frame.data.u8[framepos];
static uint16_t CellVoltMax_mV = 3700;
static uint16_t CellVoltMin_mV = 3700;
static uint8_t CellVmaxNo = 0;
static uint8_t CellVminNo = 0;
static uint16_t allowedDischargePower = 0;
static uint16_t allowedChargePower = 0;
static uint16_t batteryVoltage = 0;
static int16_t leadAcidBatteryVoltage = 120;
static int8_t temperatureMax = 0;
static int8_t temperatureMin = 0;
static int16_t batteryAmps = 0;
static uint8_t StatusBattery = 0;
static uint16_t cellvoltages_mv[96];
#ifdef DOUBLE_BATTERY for (uint8_t j = 0; j < 8; j++) {
static uint16_t battery2_SOC_Display = 0; if ((crc & 0x80) != 0) {
static uint16_t battery2_SOH = 100; crc = (crc << 1) ^ 0x1;
static uint16_t battery2_CellVoltMax_mV = 3700; } else {
static uint16_t battery2_CellVoltMin_mV = 3700; crc <<= 1;
static uint8_t battery2_CellVmaxNo = 0; }
static uint8_t battery2_CellVminNo = 0; }
static uint16_t battery2_allowedDischargePower = 0; }
static uint16_t battery2_allowedChargePower = 0; return (uint8_t)crc;
static uint16_t battery2_batteryVoltage = 0; }
static int16_t battery2_leadAcidBatteryVoltage = 120;
static int8_t battery2_temperatureMax = 0;
static int8_t battery2_temperatureMin = 0;
static int16_t battery2_batteryAmps = 0;
static uint8_t battery2_StatusBattery = 0;
static uint16_t battery2_cellvoltages_mv[96];
#endif //DOUBLE_BATTERY
CAN_frame SANTAFE_200 = {.FD = false, void SantaFePhevBattery::
.ext_ID = false, update_values() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
.DLC = 8,
.ID = 0x200,
.data = {0x00, 0x00, 0x00, 0x00, 0x80, 0x10, 0x00, 0x00}};
CAN_frame SANTAFE_2A1 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x2A1,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x02}};
CAN_frame SANTAFE_2F0 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x2F0,
.data = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00}};
CAN_frame SANTAFE_523 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x523,
.data = {0x60, 0x00, 0x60, 0, 0, 0, 0, 0}};
CAN_frame SANTAFE_7E4_poll = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E4, //Polling frame, 0x22 01 0X
.data = {0x03, 0x22, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SANTAFE_7E4_ack = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E4, //Ack frame, correct PID is returned. Flow control message
.data = {0x30, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}};
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus datalayer_battery->status.real_soc = (SOC_Display * 10); //increase SOC range from 0-100.0 -> 100.00
datalayer.battery.status.real_soc = (SOC_Display * 10); //increase SOC range from 0-100.0 -> 100.00 datalayer_battery->status.soh_pptt = (batterySOH * 100); //Increase decimals from 100% -> 100.00%
datalayer.battery.status.soh_pptt = (batterySOH * 100); //Increase decimals from 100% -> 100.00% datalayer_battery->status.voltage_dV = batteryVoltage;
datalayer.battery.status.voltage_dV = batteryVoltage; datalayer_battery->status.current_dA = -batteryAmps;
datalayer.battery.status.current_dA = -batteryAmps; datalayer_battery->status.remaining_capacity_Wh = static_cast<uint32_t>(
(static_cast<double>(datalayer_battery->status.real_soc) / 10000) * datalayer_battery->info.total_capacity_Wh);
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>( datalayer_battery->status.max_discharge_power_W = allowedDischargePower * 10;
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
datalayer.battery.status.max_discharge_power_W = allowedDischargePower * 10; datalayer_battery->status.max_charge_power_W = allowedChargePower * 10;
datalayer.battery.status.max_charge_power_W = allowedChargePower * 10; datalayer_battery->status.cell_max_voltage_mV = CellVoltMax_mV;
datalayer.battery.status.cell_max_voltage_mV = CellVoltMax_mV; datalayer_battery->status.cell_min_voltage_mV = CellVoltMin_mV;
datalayer.battery.status.cell_min_voltage_mV = CellVoltMin_mV; datalayer_battery->status.temperature_min_dC = temperatureMin * 10; //Increase decimals, 17C -> 17.0C
datalayer.battery.status.temperature_min_dC = temperatureMin * 10; //Increase decimals, 17C -> 17.0C datalayer_battery->status.temperature_max_dC = temperatureMax * 10; //Increase decimals, 18C -> 18.0C
datalayer.battery.status.temperature_max_dC = temperatureMax * 10; //Increase decimals, 18C -> 18.0C
if (leadAcidBatteryVoltage < 110) { if (leadAcidBatteryVoltage < 110) {
set_event(EVENT_12V_LOW, leadAcidBatteryVoltage); set_event(EVENT_12V_LOW, leadAcidBatteryVoltage);
} }
} }
void handle_incoming_can_frame_battery(CAN_frame rx_frame) { void SantaFePhevBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
switch (rx_frame.ID) { switch (rx_frame.ID) {
case 0x1FF: case 0x1FF:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
StatusBattery = (rx_frame.data.u8[0] & 0x0F); StatusBattery = (rx_frame.data.u8[0] & 0x0F);
break; break;
case 0x4D5: case 0x4D5:
@ -127,16 +73,16 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
case 0x4DD: case 0x4DD:
break; break;
case 0x4DE: case 0x4DE:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break; break;
case 0x4E0: case 0x4E0:
break; break;
case 0x542: case 0x542:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
SOC_Display = ((rx_frame.data.u8[1] << 8) + rx_frame.data.u8[0]) / 2; SOC_Display = ((rx_frame.data.u8[1] << 8) + rx_frame.data.u8[0]) / 2;
break; break;
case 0x588: case 0x588:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
batteryVoltage = ((rx_frame.data.u8[1] << 8) + rx_frame.data.u8[0]); batteryVoltage = ((rx_frame.data.u8[1] << 8) + rx_frame.data.u8[0]);
break; break;
case 0x597: case 0x597:
@ -146,7 +92,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
case 0x5A7: case 0x5A7:
break; break;
case 0x5AD: case 0x5AD:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
batteryAmps = (rx_frame.data.u8[3] << 8) + rx_frame.data.u8[2]; batteryAmps = (rx_frame.data.u8[3] << 8) + rx_frame.data.u8[2];
break; break;
case 0x5AE: case 0x5AE:
@ -154,25 +100,25 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
case 0x5F1: case 0x5F1:
break; break;
case 0x620: case 0x620:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
leadAcidBatteryVoltage = rx_frame.data.u8[1]; leadAcidBatteryVoltage = rx_frame.data.u8[1];
temperatureMin = rx_frame.data.u8[6]; //Lowest temp in battery temperatureMin = rx_frame.data.u8[6]; //Lowest temp in battery
temperatureMax = rx_frame.data.u8[7]; //Highest temp in battery temperatureMax = rx_frame.data.u8[7]; //Highest temp in battery
break; break;
case 0x670: case 0x670:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
allowedChargePower = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]); allowedChargePower = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]);
allowedDischargePower = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]); allowedDischargePower = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]);
break; break;
case 0x671: case 0x671:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break; break;
case 0x7EC: //Data From polled PID group, BigEndian case 0x7EC: //Data From polled PID group, BigEndian
switch (rx_frame.data.u8[0]) { switch (rx_frame.data.u8[0]) {
case 0x10: //"PID Header" case 0x10: //"PID Header"
if (rx_frame.data.u8[4] == poll_data_pid) { if (rx_frame.data.u8[4] == poll_data_pid) {
transmit_can_frame(&SANTAFE_7E4_ack, transmit_can_frame(&SANTAFE_7E4_ack,
can_config.battery); //Send ack to BMS if the same frame is sent as polled can_interface); //Send ack to BMS if the same frame is sent as polled
} }
break; break;
case 0x21: //First frame in PID group case 0x21: //First frame in PID group
@ -317,7 +263,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
cellvoltages_mv[95] = (rx_frame.data.u8[5] * 20); cellvoltages_mv[95] = (rx_frame.data.u8[5] * 20);
//Map all cell voltages to the global array, we have sampled them all! //Map all cell voltages to the global array, we have sampled them all!
memcpy(datalayer.battery.status.cell_voltages_mV, cellvoltages_mv, 96 * sizeof(uint16_t)); memcpy(datalayer_battery->status.cell_voltages_mV, cellvoltages_mv, 96 * sizeof(uint16_t));
} else if (poll_data_pid == 5) { } else if (poll_data_pid == 5) {
} }
break; break;
@ -333,7 +279,8 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
break; break;
} }
} }
void transmit_can_battery(unsigned long currentMillis) {
void SantaFePhevBattery::transmit_can(unsigned long currentMillis) {
//Send 10ms message //Send 10ms message
if (currentMillis - previousMillis10 >= INTERVAL_10_MS) { if (currentMillis - previousMillis10 >= INTERVAL_10_MS) {
previousMillis10 = currentMillis; previousMillis10 = currentMillis;
@ -344,14 +291,9 @@ void transmit_can_battery(unsigned long currentMillis) {
SANTAFE_200.data.u8[7] = checksum_200; SANTAFE_200.data.u8[7] = checksum_200;
transmit_can_frame(&SANTAFE_200, can_config.battery); transmit_can_frame(&SANTAFE_200, can_interface);
transmit_can_frame(&SANTAFE_2A1, can_config.battery); transmit_can_frame(&SANTAFE_2A1, can_interface);
transmit_can_frame(&SANTAFE_2F0, can_config.battery); transmit_can_frame(&SANTAFE_2F0, can_interface);
#ifdef DOUBLE_BATTERY
transmit_can_frame(&SANTAFE_200, can_config.battery_double);
transmit_can_frame(&SANTAFE_2A1, can_config.battery_double);
transmit_can_frame(&SANTAFE_2F0, can_config.battery_double);
#endif //DOUBLE_BATTERY
counter_200++; counter_200++;
if (counter_200 > 0xF) { if (counter_200 > 0xF) {
@ -363,10 +305,7 @@ void transmit_can_battery(unsigned long currentMillis) {
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) { if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
previousMillis100 = currentMillis; previousMillis100 = currentMillis;
transmit_can_frame(&SANTAFE_523, can_config.battery); transmit_can_frame(&SANTAFE_523, can_interface);
#ifdef DOUBLE_BATTERY
transmit_can_frame(&SANTAFE_523, can_config.battery_double);
#endif //DOUBLE_BATTERY
} }
// Send 500ms CAN Message // Send 500ms CAN Message
@ -376,302 +315,23 @@ void transmit_can_battery(unsigned long currentMillis) {
// PID data is polled after last message sent from battery: // PID data is polled after last message sent from battery:
poll_data_pid = (poll_data_pid % 5) + 1; poll_data_pid = (poll_data_pid % 5) + 1;
SANTAFE_7E4_poll.data.u8[3] = (uint8_t)poll_data_pid; SANTAFE_7E4_poll.data.u8[3] = (uint8_t)poll_data_pid;
transmit_can_frame(&SANTAFE_7E4_poll, can_config.battery); transmit_can_frame(&SANTAFE_7E4_poll, can_interface);
#ifdef DOUBLE_BATTERY
transmit_can_frame(&SANTAFE_7E4_poll, can_config.battery_double);
#endif //DOUBLE_BATTERY
} }
} }
#ifdef DOUBLE_BATTERY void SantaFePhevBattery::setup(void) { // Performs one time setup at startup
void update_values_battery2() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
datalayer.battery2.status.real_soc = (battery2_SOC_Display * 10); //increase SOC range from 0-100.0 -> 100.00
datalayer.battery2.status.soh_pptt = (battery2_SOH * 100); //Increase decimals from 100% -> 100.00%
datalayer.battery2.status.voltage_dV = battery2_batteryVoltage;
datalayer.battery2.status.current_dA = -battery2_batteryAmps;
datalayer.battery2.status.remaining_capacity_Wh = static_cast<uint32_t>(
(static_cast<double>(datalayer.battery2.status.real_soc) / 10000) * datalayer.battery2.info.total_capacity_Wh);
datalayer.battery2.status.max_discharge_power_W = battery2_allowedDischargePower * 10;
datalayer.battery2.status.max_charge_power_W = battery2_allowedChargePower * 10;
//Power in watts, Negative = charging batt
datalayer.battery2.status.active_power_W =
((datalayer.battery2.status.voltage_dV * datalayer.battery2.status.current_dA) / 100);
datalayer.battery2.status.cell_max_voltage_mV = battery2_CellVoltMax_mV;
datalayer.battery2.status.cell_min_voltage_mV = battery2_CellVoltMin_mV;
datalayer.battery2.status.temperature_min_dC = battery2_temperatureMin * 10; //Increase decimals, 17C -> 17.0C
datalayer.battery2.status.temperature_max_dC = battery2_temperatureMax * 10; //Increase decimals, 18C -> 18.0C
if (battery2_leadAcidBatteryVoltage < 110) {
set_event(EVENT_12V_LOW, battery2_leadAcidBatteryVoltage);
}
}
void handle_incoming_can_frame_battery2(CAN_frame rx_frame) {
switch (rx_frame.ID) {
case 0x1FF:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery2_StatusBattery = (rx_frame.data.u8[0] & 0x0F);
break;
case 0x4D5:
break;
case 0x4DD:
break;
case 0x4DE:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x4E0:
break;
case 0x542:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery2_SOC_Display = ((rx_frame.data.u8[1] << 8) + rx_frame.data.u8[0]) / 2;
break;
case 0x588:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery2_batteryVoltage = ((rx_frame.data.u8[1] << 8) + rx_frame.data.u8[0]);
break;
case 0x597:
break;
case 0x5A6:
break;
case 0x5A7:
break;
case 0x5AD:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery2_batteryAmps = (rx_frame.data.u8[3] << 8) + rx_frame.data.u8[2];
break;
case 0x5AE:
break;
case 0x5F1:
break;
case 0x620:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery2_leadAcidBatteryVoltage = rx_frame.data.u8[1];
battery2_temperatureMin = rx_frame.data.u8[6]; //Lowest temp in battery
battery2_temperatureMax = rx_frame.data.u8[7]; //Highest temp in battery
break;
case 0x670:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery2_allowedChargePower = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]);
battery2_allowedDischargePower = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]);
break;
case 0x671:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x7EC: //Data From polled PID group, BigEndian
switch (rx_frame.data.u8[0]) {
case 0x10: //"PID Header"
if (rx_frame.data.u8[4] == poll_data_pid) {
transmit_can_frame(&SANTAFE_7E4_ack,
can_config.battery_double); //Send ack to BMS if the same frame is sent as polled
}
break;
case 0x21: //First frame in PID group
if (poll_data_pid == 1) {
} else if (poll_data_pid == 2) {
battery2_cellvoltages_mv[0] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[1] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[2] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[3] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[4] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[5] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 3) {
battery2_cellvoltages_mv[32] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[33] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[34] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[35] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[36] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[37] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 4) {
battery2_cellvoltages_mv[64] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[65] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[66] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[67] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[68] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[69] = (rx_frame.data.u8[7] * 20);
}
break;
case 0x22: //Second datarow in PID group
if (poll_data_pid == 2) {
battery2_cellvoltages_mv[6] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[7] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[8] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[9] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[10] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[11] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[12] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 3) {
battery2_cellvoltages_mv[38] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[39] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[40] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[41] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[42] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[43] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[44] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 4) {
battery2_cellvoltages_mv[70] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[71] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[72] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[73] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[74] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[75] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[76] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 6) {
}
break;
case 0x23: //Third datarow in PID group
if (poll_data_pid == 1) {
battery2_CellVoltMax_mV = (rx_frame.data.u8[7] * 20); //(volts *50) *20 =mV
} else if (poll_data_pid == 2) {
battery2_cellvoltages_mv[13] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[14] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[15] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[16] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[17] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[18] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[19] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 3) {
battery2_cellvoltages_mv[45] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[46] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[47] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[48] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[49] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[50] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[51] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 4) {
battery2_cellvoltages_mv[77] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[78] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[79] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[80] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[81] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[82] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[83] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 5) {
if (rx_frame.data.u8[6] > 0) {
battery2_SOH = rx_frame.data.u8[6];
}
if (battery2_SOH > 100) {
battery2_SOH = 100;
}
}
break;
case 0x24: //Fourth datarow in PID group
if (poll_data_pid == 1) {
battery2_CellVmaxNo = rx_frame.data.u8[1];
battery2_CellVminNo = rx_frame.data.u8[3];
CellVoltMin_mV = (rx_frame.data.u8[2] * 20); //(volts *50) *20 =mV
} else if (poll_data_pid == 2) {
battery2_cellvoltages_mv[20] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[21] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[22] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[23] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[24] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[25] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[26] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 3) {
battery2_cellvoltages_mv[52] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[53] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[54] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[55] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[56] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[57] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[58] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 4) {
battery2_cellvoltages_mv[84] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[85] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[86] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[87] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[88] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[89] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[90] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 5) {
}
break;
case 0x25: //Fifth datarow in PID group
if (poll_data_pid == 2) {
battery2_cellvoltages_mv[27] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[28] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[29] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[30] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[31] = (rx_frame.data.u8[5] * 20);
} else if (poll_data_pid == 3) {
battery2_cellvoltages_mv[59] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[60] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[61] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[62] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[63] = (rx_frame.data.u8[5] * 20);
} else if (poll_data_pid == 4) {
battery2_cellvoltages_mv[91] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[92] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[93] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[94] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[95] = (rx_frame.data.u8[5] * 20);
//Map all cell voltages to the global array, we have sampled them all!
memcpy(datalayer.battery2.status.cell_voltages_mV, battery2_cellvoltages_mv, 96 * sizeof(uint16_t));
} else if (poll_data_pid == 5) {
}
break;
case 0x26: //Sixth datarow in PID group
break;
case 0x27: //Seventh datarow in PID group
break;
case 0x28: //Eighth datarow in PID group
break;
}
break;
default:
break;
}
}
#endif //DOUBLE_BATTERY
uint8_t CalculateCRC8(CAN_frame rx_frame) {
int crc = 0;
for (uint8_t framepos = 0; framepos < 8; framepos++) {
crc ^= rx_frame.data.u8[framepos];
for (uint8_t j = 0; j < 8; j++) {
if ((crc & 0x80) != 0) {
crc = (crc << 1) ^ 0x1;
} else {
crc <<= 1;
}
}
}
return (uint8_t)crc;
}
void setup_battery(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "Santa Fe PHEV", 63); strncpy(datalayer.system.info.battery_protocol, "Santa Fe PHEV", 63);
datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.number_of_cells = 96; datalayer_battery->info.number_of_cells = 96;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer_battery->info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; datalayer_battery->info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; datalayer_battery->info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV; datalayer_battery->info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV; datalayer_battery->info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
datalayer.system.status.battery_allows_contactor_closing = true;
#ifdef DOUBLE_BATTERY if (allows_contactor_closing) {
datalayer.battery2.info.number_of_cells = datalayer.battery.info.number_of_cells; *allows_contactor_closing = true;
datalayer.battery2.info.max_design_voltage_dV = datalayer.battery.info.max_design_voltage_dV; }
datalayer.battery2.info.min_design_voltage_dV = datalayer.battery.info.min_design_voltage_dV;
datalayer.battery2.info.max_cell_voltage_mV = datalayer.battery.info.max_cell_voltage_mV;
datalayer.battery2.info.min_cell_voltage_mV = datalayer.battery.info.min_cell_voltage_mV;
datalayer.battery2.info.max_cell_voltage_deviation_mV = datalayer.battery.info.max_cell_voltage_deviation_mV;
#endif //DOUBLE_BATTERY
} }
#endif #endif

View file

@ -1,17 +1,101 @@
#ifndef SANTA_FE_PHEV_BATTERY_H #ifndef SANTA_FE_PHEV_BATTERY_H
#define SANTA_FE_PHEV_BATTERY_H #define SANTA_FE_PHEV_BATTERY_H
#include <Arduino.h> #include <Arduino.h>
#include "../datalayer/datalayer.h"
#include "../include.h" #include "../include.h"
#include "CanBattery.h"
#define BATTERY_SELECTED #define BATTERY_SELECTED
#define MAX_PACK_VOLTAGE_DV 4040 //5000 = 500.0V #define SELECTED_BATTERY_CLASS SantaFePhevBattery
#define MIN_PACK_VOLTAGE_DV 2880
#define MAX_CELL_DEVIATION_MV 250
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
uint8_t CalculateCRC8(CAN_frame rx_frame); class SantaFePhevBattery : public CanBattery {
void setup_battery(void); public:
void transmit_can_frame(CAN_frame* tx_frame, int interface); // Use this constructor for the second battery.
SantaFePhevBattery(DATALAYER_BATTERY_TYPE* datalayer_ptr, int targetCan) {
datalayer_battery = datalayer_ptr;
allows_contactor_closing = nullptr;
can_interface = targetCan;
}
// Use the default constructor to create the first or single battery.
SantaFePhevBattery() {
datalayer_battery = &datalayer.battery;
allows_contactor_closing = &datalayer.system.status.battery_allows_contactor_closing;
can_interface = can_config.battery;
}
virtual void setup(void);
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
virtual void update_values();
virtual void transmit_can(unsigned long currentMillis);
private:
DATALAYER_BATTERY_TYPE* datalayer_battery;
// If not null, this battery decides when the contactor can be closed and writes the value here.
bool* allows_contactor_closing;
int can_interface;
static const int MAX_PACK_VOLTAGE_DV = 4040; //5000 = 500.0V
static const int MIN_PACK_VOLTAGE_DV = 2880;
static const int MAX_CELL_DEVIATION_MV = 250;
static const int MAX_CELL_VOLTAGE_MV = 4250; //Battery is put into emergency stop if one cell goes over this value
static const int MIN_CELL_VOLTAGE_MV = 2700; //Battery is put into emergency stop if one cell goes below this value
unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was send
unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was send
uint8_t poll_data_pid = 0;
uint8_t counter_200 = 0;
uint8_t checksum_200 = 0;
uint16_t SOC_Display = 0;
uint16_t batterySOH = 100;
uint16_t CellVoltMax_mV = 3700;
uint16_t CellVoltMin_mV = 3700;
uint8_t CellVmaxNo = 0;
uint8_t CellVminNo = 0;
uint16_t allowedDischargePower = 0;
uint16_t allowedChargePower = 0;
uint16_t batteryVoltage = 0;
int16_t leadAcidBatteryVoltage = 120;
int8_t temperatureMax = 0;
int8_t temperatureMin = 0;
int16_t batteryAmps = 0;
uint8_t StatusBattery = 0;
uint16_t cellvoltages_mv[96];
CAN_frame SANTAFE_200 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x200,
.data = {0x00, 0x00, 0x00, 0x00, 0x80, 0x10, 0x00, 0x00}};
CAN_frame SANTAFE_2A1 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x2A1,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x02}};
CAN_frame SANTAFE_2F0 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x2F0,
.data = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00}};
CAN_frame SANTAFE_523 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x523,
.data = {0x60, 0x00, 0x60, 0, 0, 0, 0, 0}};
CAN_frame SANTAFE_7E4_poll = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E4, //Polling frame, 0x22 01 0X
.data = {0x03, 0x22, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SANTAFE_7E4_ack = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E4, //Ack frame, correct PID is returned. Flow control message
.data = {0x30, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}};
};
#endif #endif

View file

@ -4,34 +4,7 @@
#include "../devboard/utils/events.h" #include "../devboard/utils/events.h"
#include "SIMPBMS-BATTERY.h" #include "SIMPBMS-BATTERY.h"
#define SIMPBMS_MAX_CELLS 128 void SimpBmsBattery::update_values() {
/* Do not change code below unless you are sure what you are doing */
static unsigned long previousMillis1000 = 0; // will store last time a 1s CAN Message was sent
//Actual content messages
static int16_t celltemperature_max_dC = 0;
static int16_t celltemperature_min_dC = 0;
static int16_t current_dA = 0;
static uint16_t voltage_dV = 0;
static uint16_t cellvoltage_max_mV = 3700;
static uint16_t cellvoltage_min_mV = 3700;
static uint16_t charge_cutoff_voltage = 0;
static uint16_t discharge_cutoff_voltage = 0;
static int16_t max_charge_current = 0;
static int16_t max_discharge_current = 0;
static uint8_t ensemble_info_ack = 0;
static uint8_t cells_in_series = 0;
static uint8_t voltage_level = 0;
static uint8_t ah_total = 0;
static uint8_t SOC = 0;
static uint8_t SOH = 99;
static uint8_t charge_forbidden = 0;
static uint8_t discharge_forbidden = 0;
static uint16_t cellvoltages_mV[SIMPBMS_MAX_CELLS] = {0};
void update_values_battery() {
datalayer.battery.status.real_soc = (SOC * 100); //increase SOC range from 0-100 -> 100.00 datalayer.battery.status.real_soc = (SOC * 100); //increase SOC range from 0-100 -> 100.00
@ -67,7 +40,7 @@ void update_values_battery() {
datalayer.battery.info.number_of_cells = cells_in_series; datalayer.battery.info.number_of_cells = cells_in_series;
} }
void handle_incoming_can_frame_battery(CAN_frame rx_frame) { void SimpBmsBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
switch (rx_frame.ID) { switch (rx_frame.ID) {
case 0x355: case 0x355:
@ -115,11 +88,11 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
} }
} }
void transmit_can_battery(unsigned long currentMillis) { void SimpBmsBattery::transmit_can(unsigned long currentMillis) {
// No periodic transmitting for this battery type // No periodic transmitting for this battery type
} }
void setup_battery(void) { // Performs one time setup at startup void SimpBmsBattery::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "SIMPBMS battery", 63); strncpy(datalayer.system.info.battery_protocol, "SIMPBMS battery", 63);
datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.number_of_cells = CELL_COUNT; datalayer.battery.info.number_of_cells = CELL_COUNT;

View file

@ -3,17 +3,52 @@
#include <Arduino.h> #include <Arduino.h>
#include "../include.h" #include "../include.h"
#include "CanBattery.h"
#define BATTERY_SELECTED #define BATTERY_SELECTED
#define SELECTED_BATTERY_CLASS SimpBmsBattery
/* DEFAULT VALUES BMS will send configured */ class SimpBmsBattery : public CanBattery {
#define MAX_PACK_VOLTAGE_DV 5000 //5000 = 500.0V public:
#define MIN_PACK_VOLTAGE_DV 1500 virtual void setup(void);
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value virtual void handle_incoming_can_frame(CAN_frame rx_frame);
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value virtual void update_values();
#define MAX_CELL_DEVIATION_MV 500 virtual void transmit_can(unsigned long currentMillis);
#define CELL_COUNT 96
void setup_battery(void); private:
void transmit_can_frame(CAN_frame* tx_frame, int interface); /* DEFAULT VALUES BMS will send configured */
static const int MAX_PACK_VOLTAGE_DV = 5000; //5000 = 500.0V
static const int MIN_PACK_VOLTAGE_DV = 1500;
static const int MAX_CELL_VOLTAGE_MV = 4250; //Battery is put into emergency stop if one cell goes over this value
static const int MIN_CELL_VOLTAGE_MV = 2700; //Battery is put into emergency stop if one cell goes below this value
static const int MAX_CELL_DEVIATION_MV = 500;
static const int CELL_COUNT = 96;
static const int SIMPBMS_MAX_CELLS = 128;
unsigned long previousMillis1000 = 0; // will store last time a 1s CAN Message was sent
//Actual content messages
int16_t celltemperature_max_dC = 0;
int16_t celltemperature_min_dC = 0;
int16_t current_dA = 0;
uint16_t voltage_dV = 0;
uint16_t cellvoltage_max_mV = 3700;
uint16_t cellvoltage_min_mV = 3700;
uint16_t charge_cutoff_voltage = 0;
uint16_t discharge_cutoff_voltage = 0;
int16_t max_charge_current = 0;
int16_t max_discharge_current = 0;
uint8_t ensemble_info_ack = 0;
uint8_t cells_in_series = 0;
uint8_t voltage_level = 0;
uint8_t ah_total = 0;
uint8_t SOC = 0;
uint8_t SOH = 99;
uint8_t charge_forbidden = 0;
uint8_t discharge_forbidden = 0;
uint16_t cellvoltages_mV[SIMPBMS_MAX_CELLS] = {0};
};
#endif #endif

View file

@ -1,38 +1,12 @@
#include "../include.h" #include "../include.h"
#ifdef SONO_BATTERY #ifdef SONO_BATTERY
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "../devboard/utils/events.h" #include "../devboard/utils/events.h"
#include "SONO-BATTERY.h" #include "SONO-BATTERY.h"
/* Do not change code below unless you are sure what you are doing */ void SonoBattery::
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send update_values() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
static unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was send
static uint8_t seconds = 0;
static uint8_t functionalsafetybitmask = 0;
static uint16_t batteryVoltage = 3700;
static uint16_t allowedDischargePower = 0;
static uint16_t allowedChargePower = 0;
static uint16_t CellVoltMax_mV = 0;
static uint16_t CellVoltMin_mV = 0;
static int16_t batteryAmps = 0;
static int16_t temperatureMin = 0;
static int16_t temperatureMax = 0;
static uint8_t batterySOH = 99;
static uint8_t realSOC = 99;
CAN_frame SONO_400 = {.FD = false, //Message of Vehicle Command, 100ms
.ext_ID = false,
.DLC = 8,
.ID = 0x400,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SONO_401 = {.FD = false, //Message of Vehicle Date, 1000ms
.ext_ID = false,
.DLC = 8,
.ID = 0x400,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
datalayer.battery.status.real_soc = (realSOC * 100); //increase SOC range from 0-100 -> 100.00 datalayer.battery.status.real_soc = (realSOC * 100); //increase SOC range from 0-100 -> 100.00
@ -60,7 +34,7 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.temperature_max_dC = temperatureMax; datalayer.battery.status.temperature_max_dC = temperatureMax;
} }
void handle_incoming_can_frame_battery(CAN_frame rx_frame) { void SonoBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
switch (rx_frame.ID) { switch (rx_frame.ID) {
case 0x100: case 0x100:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
@ -137,7 +111,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
break; break;
} }
} }
void transmit_can_battery(unsigned long currentMillis) { void SonoBattery::transmit_can(unsigned long currentMillis) {
// Send 100ms CAN Message // Send 100ms CAN Message
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) { if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
previousMillis100 = currentMillis; previousMillis100 = currentMillis;
@ -166,7 +140,7 @@ void transmit_can_battery(unsigned long currentMillis) {
} }
} }
void setup_battery(void) { // Performs one time setup at startup void SonoBattery::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "Sono Motors Sion 64kWh LFP ", 63); strncpy(datalayer.system.info.battery_protocol, "Sono Motors Sion 64kWh LFP ", 63);
datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.number_of_cells = 96; datalayer.battery.info.number_of_cells = 96;

View file

@ -3,15 +3,51 @@
#include <Arduino.h> #include <Arduino.h>
#include "../include.h" #include "../include.h"
#define BATTERY_SELECTED #include "CanBattery.h"
#define MAX_PACK_VOLTAGE_DV 5000 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_DV 2500
#define MAX_CELL_DEVIATION_MV 250
#define MAX_CELL_VOLTAGE_MV 3800 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
uint8_t CalculateCRC8(CAN_frame rx_frame); #define BATTERY_SELECTED
void setup_battery(void); #define SELECTED_BATTERY_CLASS SonoBattery
void transmit_can_frame(CAN_frame* tx_frame, int interface);
class SonoBattery : public CanBattery {
public:
virtual void setup(void);
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
virtual void update_values();
virtual void transmit_can(unsigned long currentMillis);
private:
static const int MAX_PACK_VOLTAGE_DV = 5000; //5000 = 500.0V
static const int MIN_PACK_VOLTAGE_DV = 2500;
static const int MAX_CELL_DEVIATION_MV = 250;
static const int MAX_CELL_VOLTAGE_MV = 3800; //Battery is put into emergency stop if one cell goes over this value
static const int MIN_CELL_VOLTAGE_MV = 2700; //Battery is put into emergency stop if one cell goes below this value
unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was send
uint8_t seconds = 0;
uint8_t functionalsafetybitmask = 0;
uint16_t batteryVoltage = 3700;
uint16_t allowedDischargePower = 0;
uint16_t allowedChargePower = 0;
uint16_t CellVoltMax_mV = 0;
uint16_t CellVoltMin_mV = 0;
int16_t batteryAmps = 0;
int16_t temperatureMin = 0;
int16_t temperatureMax = 0;
uint8_t batterySOH = 99;
uint8_t realSOC = 99;
CAN_frame SONO_400 = {.FD = false, //Message of Vehicle Command, 100ms
.ext_ID = false,
.DLC = 8,
.ID = 0x400,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SONO_401 = {.FD = false, //Message of Vehicle Date, 1000ms
.ext_ID = false,
.DLC = 8,
.ID = 0x400,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
};
#endif #endif

View file

@ -1,453 +1,13 @@
#include "../include.h" #include "../include.h"
#ifdef TESLA_BATTERY #ifdef TESLA_BATTERY
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h" //For Advanced Battery Insights webpage #include "../datalayer/datalayer_extended.h" //For Advanced Battery Insights webpage
#include "../devboard/utils/events.h" #include "../devboard/utils/events.h"
#include "TESLA-BATTERY.h" #include "TESLA-BATTERY.h"
/* Do not change code below unless you are sure what you are doing */
/* Credits: Most of the code comes from Per Carlen's bms_comms_tesla_model3.py (https://gitlab.com/pelle8/batt2gen24/) */ /* Credits: Most of the code comes from Per Carlen's bms_comms_tesla_model3.py (https://gitlab.com/pelle8/batt2gen24/) */
static unsigned long previousMillis10 = 0; // will store last time a 50ms CAN Message was sent
static unsigned long previousMillis50 = 0; // will store last time a 50ms CAN Message was sent
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was sent
static unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was sent
static unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was sent
static bool alternate243 = false;
//0x221 545 VCFRONT_LVPowerState: "GenMsgCycleTime" 50ms
CAN_frame TESLA_221_1 = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x221,
.data = {0x41, 0x11, 0x01, 0x00, 0x00, 0x00, 0x20, 0x96}}; //Contactor frame 221 - close contactors
CAN_frame TESLA_221_2 = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x221,
.data = {0x61, 0x15, 0x01, 0x00, 0x00, 0x00, 0x20, 0xBA}}; //Contactor Frame 221 - hv_up_for_drive
//0x241 VCFRONT_coolant 100ms
CAN_frame TESLA_241 = {.FD = false,
.ext_ID = false,
.DLC = 7,
.ID = 0x241,
.data = {0x3C, 0x78, 0x2C, 0x0F, 0x1E, 0x5B, 0x00}};
//0x242 VCLEFT_LVPowerState 100ms
CAN_frame TESLA_242 = {.FD = false, .ext_ID = false, .DLC = 2, .ID = 0x242, .data = {0x10, 0x95}};
//0x243 VCRIGHT_hvacStatus 50ms
CAN_frame TESLA_243_1 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x243,
.data = {0xC9, 0x00, 0xEB, 0xD4, 0x31, 0x32, 0x02, 0x00}};
CAN_frame TESLA_243_2 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x243,
.data = {0x08, 0x81, 0x42, 0x60, 0x92, 0x2C, 0x0E, 0x09}};
//0x129 SteeringAngle 10ms
CAN_frame TESLA_129 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x129,
.data = {0x21, 0x24, 0x36, 0x5F, 0x00, 0x20, 0xFF, 0x3F}};
//0x612 UDS diagnostic requests - on demand
CAN_frame TESLA_602 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x602,
.data = {0x02, 0x27, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00}};
static uint8_t stateMachineClearIsolationFault = 0xFF;
static uint8_t stateMachineBMSReset = 0xFF;
static uint16_t sendContactorClosingMessagesStill = 300;
static uint16_t battery_cell_max_v = 3300;
static uint16_t battery_cell_min_v = 3300;
static uint16_t battery_cell_deviation_mV = 0; //contains the deviation between highest and lowest cell in mV
static bool cellvoltagesRead = false;
//0x3d2: 978 BMS_kwhCounter
static uint32_t battery_total_discharge = 0;
static uint32_t battery_total_charge = 0;
//0x352: 850 BMS_energyStatus
static bool BMS352_mux = false; // variable to store when 0x352 mux is present
static uint16_t battery_energy_buffer = 0; // kWh
static uint16_t battery_energy_buffer_m1 = 0; // kWh
static uint16_t battery_energy_to_charge_complete = 0; // kWh
static uint16_t battery_energy_to_charge_complete_m1 = 0; // kWh
static uint16_t battery_expected_energy_remaining = 0; // kWh
static uint16_t battery_expected_energy_remaining_m1 = 0; // kWh
static bool battery_full_charge_complete = false; // Changed to bool
static bool battery_fully_charged = false; // Changed to bool
static uint16_t battery_ideal_energy_remaining = 0; // kWh
static uint16_t battery_ideal_energy_remaining_m0 = 0; // kWh
static uint16_t battery_nominal_energy_remaining = 0; // kWh
static uint16_t battery_nominal_energy_remaining_m0 = 0; // kWh
static uint16_t battery_nominal_full_pack_energy = 0; // Kwh
static uint16_t battery_nominal_full_pack_energy_m0 = 0; // Kwh
//0x132 306 HVBattAmpVolt
static uint16_t battery_volts = 0; // V
static int16_t battery_amps = 0; // A
static int16_t battery_raw_amps = 0; // A
static uint16_t battery_charge_time_remaining = 0; // Minutes
//0x252 594 BMS_powerAvailable
static uint16_t BMS_maxRegenPower = 0; //rename from battery_regenerative_limit
static uint16_t BMS_maxDischargePower = 0; // rename from battery_discharge_limit
static uint16_t BMS_maxStationaryHeatPower = 0; //rename from battery_max_heat_park
static uint16_t BMS_hvacPowerBudget = 0; //rename from battery_hvac_max_power
static uint8_t BMS_notEnoughPowerForHeatPump = 0;
static uint8_t BMS_powerLimitState = 0;
static uint8_t BMS_inverterTQF = 0;
//0x2d2: 722 BMSVAlimits
static uint16_t battery_max_discharge_current = 0;
static uint16_t battery_max_charge_current = 0;
static uint16_t battery_bms_max_voltage = 0;
static uint16_t battery_bms_min_voltage = 0;
//0x2b4: 692 PCS_dcdcRailStatus
static uint16_t battery_dcdcHvBusVolt = 0; // Change name from battery_high_voltage to battery_dcdcHvBusVolt
static uint16_t battery_dcdcLvBusVolt = 0; // Change name from battery_low_voltage to battery_dcdcLvBusVolt
static uint16_t battery_dcdcLvOutputCurrent =
0; // Change name from battery_output_current to battery_dcdcLvOutputCurrent
//0x292: 658 BMS_socStatus
static uint16_t battery_beginning_of_life = 0; // kWh
static uint16_t battery_soc_min = 0;
static uint16_t battery_soc_max = 0;
static uint16_t battery_soc_ui = 0; //Change name from battery_soc_vi to reflect DBC battery_soc_ui
static uint16_t battery_soc_ave = 0;
static uint8_t battery_battTempPct = 0;
//0x392: BMS_packConfig
static uint32_t battery_packMass = 0;
static uint32_t battery_platformMaxBusVoltage = 0;
static uint32_t battery_packConfigMultiplexer = 0;
static uint32_t battery_moduleType = 0;
static uint32_t battery_reservedConfig = 0;
//0x332: 818 BattBrickMinMax:BMS_bmbMinMax
static int16_t battery_max_temp = 0; // C*
static int16_t battery_min_temp = 0; // C*
static uint16_t battery_BrickVoltageMax = 0;
static uint16_t battery_BrickVoltageMin = 0;
static uint8_t battery_BrickTempMaxNum = 0;
static uint8_t battery_BrickTempMinNum = 0;
static uint8_t battery_BrickModelTMax = 0;
static uint8_t battery_BrickModelTMin = 0;
static uint8_t battery_BrickVoltageMaxNum = 0; //rename from battery_max_vno
static uint8_t battery_BrickVoltageMinNum = 0; //rename from battery_min_vno
//0x20A: 522 HVP_contactorState
static uint8_t battery_contactor = 0; //State of contactor
static uint8_t battery_hvil_status = 0;
static uint8_t battery_packContNegativeState = 0;
static uint8_t battery_packContPositiveState = 0;
static uint8_t battery_packContactorSetState = 0;
static bool battery_packCtrsClosingAllowed = false; // Change to bool
static bool battery_pyroTestInProgress = false; // Change to bool
static bool battery_packCtrsOpenNowRequested = false; // Change to bool
static bool battery_packCtrsOpenRequested = false; // Change to bool
static uint8_t battery_packCtrsRequestStatus = 0;
static bool battery_packCtrsResetRequestRequired = false; // Change to bool
static bool battery_dcLinkAllowedToEnergize = false; // Change to bool
static bool battery_fcContNegativeAuxOpen = false; // Change to bool
static uint8_t battery_fcContNegativeState = 0;
static bool battery_fcContPositiveAuxOpen = false; // Change to bool
static uint8_t battery_fcContPositiveState = 0;
static uint8_t battery_fcContactorSetState = 0;
static bool battery_fcCtrsClosingAllowed = false; // Change to bool
static bool battery_fcCtrsOpenNowRequested = false; // Change to bool
static bool battery_fcCtrsOpenRequested = false; // Change to bool
static uint8_t battery_fcCtrsRequestStatus = 0;
static bool battery_fcCtrsResetRequestRequired = false; // Change to bool
static bool battery_fcLinkAllowedToEnergize = false; // Change to bool
//0x72A: BMS_serialNumber
static uint8_t BMS_SerialNumber[14] = {0}; // Stores raw HEX values for ASCII chars
//0x212: 530 BMS_status
static bool battery_BMS_hvacPowerRequest = false; //Change to bool
static bool battery_BMS_notEnoughPowerForDrive = false; //Change to bool
static bool battery_BMS_notEnoughPowerForSupport = false; //Change to bool
static bool battery_BMS_preconditionAllowed = false; //Change to bool
static bool battery_BMS_updateAllowed = false; //Change to bool
static bool battery_BMS_activeHeatingWorthwhile = false; //Change to bool
static bool battery_BMS_cpMiaOnHvs = false; //Change to bool
static uint8_t battery_BMS_contactorState = 0;
static uint8_t battery_BMS_state = 0;
static uint8_t battery_BMS_hvState = 0;
static uint16_t battery_BMS_isolationResistance = 0;
static bool battery_BMS_chargeRequest = false; //Change to bool
static bool battery_BMS_keepWarmRequest = false; //Change to bool
static uint8_t battery_BMS_uiChargeStatus = 0;
static bool battery_BMS_diLimpRequest = false; //Change to bool
static bool battery_BMS_okToShipByAir = false; //Change to bool
static bool battery_BMS_okToShipByLand = false; //Change to bool
static uint32_t battery_BMS_chgPowerAvailable = 0;
static uint8_t battery_BMS_chargeRetryCount = 0;
static bool battery_BMS_pcsPwmEnabled = false; //Change to bool
static bool battery_BMS_ecuLogUploadRequest = false; //Change to bool
static uint8_t battery_BMS_minPackTemperature = 0;
// 0x224:548 PCS_dcdcStatus
static uint8_t battery_PCS_dcdcPrechargeStatus = 0;
static uint8_t battery_PCS_dcdc12VSupportStatus = 0;
static uint8_t battery_PCS_dcdcHvBusDischargeStatus = 0;
static uint16_t battery_PCS_dcdcMainState = 0;
static uint8_t battery_PCS_dcdcSubState = 0;
static bool battery_PCS_dcdcFaulted = false; //Change to bool
static bool battery_PCS_dcdcOutputIsLimited = false; //Change to bool
static uint32_t battery_PCS_dcdcMaxOutputCurrentAllowed = 0;
static uint8_t battery_PCS_dcdcPrechargeRtyCnt = 0;
static uint8_t battery_PCS_dcdc12VSupportRtyCnt = 0;
static uint8_t battery_PCS_dcdcDischargeRtyCnt = 0;
static uint8_t battery_PCS_dcdcPwmEnableLine = 0;
static uint8_t battery_PCS_dcdcSupportingFixedLvTarget = 0;
static uint8_t battery_PCS_ecuLogUploadRequest = 0;
static uint8_t battery_PCS_dcdcPrechargeRestartCnt = 0;
static uint8_t battery_PCS_dcdcInitialPrechargeSubState = 0;
//0x312: 786 BMS_thermalStatus
static uint16_t BMS_powerDissipation = 0;
static uint16_t BMS_flowRequest = 0;
static uint16_t BMS_inletActiveCoolTargetT = 0;
static uint16_t BMS_inletPassiveTargetT = 0;
static uint16_t BMS_inletActiveHeatTargetT = 0;
static uint16_t BMS_packTMin = 0;
static uint16_t BMS_packTMax = 0;
static bool BMS_pcsNoFlowRequest = false;
static bool BMS_noFlowRequest = false;
//0x2A4; 676 PCS_thermalStatus
static int16_t PCS_chgPhATemp = 0;
static int16_t PCS_chgPhBTemp = 0;
static int16_t PCS_chgPhCTemp = 0;
static int16_t PCS_dcdcTemp = 0;
static int16_t PCS_ambientTemp = 0;
//0x2C4; 708 PCS_logging
static uint16_t PCS_logMessageSelect = 0;
static uint16_t PCS_dcdcMaxLvOutputCurrent = 0;
static uint16_t PCS_dcdcCurrentLimit = 0;
static uint16_t PCS_dcdcLvOutputCurrentTempLimit = 0;
static uint16_t PCS_dcdcUnifiedCommand = 0;
static uint16_t PCS_dcdcCLAControllerOutput = 0;
static int16_t PCS_dcdcTankVoltage = 0;
static uint16_t PCS_dcdcTankVoltageTarget = 0;
static uint16_t PCS_dcdcClaCurrentFreq = 0;
static int16_t PCS_dcdcTCommMeasured = 0;
static uint16_t PCS_dcdcShortTimeUs = 0;
static uint16_t PCS_dcdcHalfPeriodUs = 0;
static uint16_t PCS_dcdcIntervalMaxFrequency = 0;
static uint16_t PCS_dcdcIntervalMaxHvBusVolt = 0;
static uint16_t PCS_dcdcIntervalMaxLvBusVolt = 0;
static uint16_t PCS_dcdcIntervalMaxLvOutputCurr = 0;
static uint16_t PCS_dcdcIntervalMinFrequency = 0;
static uint16_t PCS_dcdcIntervalMinHvBusVolt = 0;
static uint16_t PCS_dcdcIntervalMinLvBusVolt = 0;
static uint16_t PCS_dcdcIntervalMinLvOutputCurr = 0;
static uint32_t PCS_dcdc12vSupportLifetimekWh = 0;
//0x7AA: //1962 HVP_debugMessage:
static uint8_t HVP_debugMessageMultiplexer = 0;
static bool HVP_gpioPassivePyroDepl = false; //Change to bool
static bool HVP_gpioPyroIsoEn = false; //Change to bool
static bool HVP_gpioCpFaultIn = false; //Change to bool
static bool HVP_gpioPackContPowerEn = false; //Change to bool
static bool HVP_gpioHvCablesOk = false; //Change to bool
static bool HVP_gpioHvpSelfEnable = false; //Change to bool
static bool HVP_gpioLed = false; //Change to bool
static bool HVP_gpioCrashSignal = false; //Change to bool
static bool HVP_gpioShuntDataReady = false; //Change to bool
static bool HVP_gpioFcContPosAux = false; //Change to bool
static bool HVP_gpioFcContNegAux = false; //Change to bool
static bool HVP_gpioBmsEout = false; //Change to bool
static bool HVP_gpioCpFaultOut = false; //Change to bool
static bool HVP_gpioPyroPor = false; //Change to bool
static bool HVP_gpioShuntEn = false; //Change to bool
static bool HVP_gpioHvpVerEn = false; //Change to bool
static bool HVP_gpioPackCoontPosFlywheel = false; //Change to bool
static bool HVP_gpioCpLatchEnable = false; //Change to bool
static bool HVP_gpioPcsEnable = false; //Change to bool
static bool HVP_gpioPcsDcdcPwmEnable = false; //Change to bool
static bool HVP_gpioPcsChargePwmEnable = false; //Change to bool
static bool HVP_gpioFcContPowerEnable = false; //Change to bool
static bool HVP_gpioHvilEnable = false; //Change to bool
static bool HVP_gpioSecDrdy = false; //Change to bool
static uint16_t HVP_hvp1v5Ref = 0;
static int16_t HVP_shuntCurrentDebug = 0;
static bool HVP_packCurrentMia = false; //Change to bool
static bool HVP_auxCurrentMia = false; //Change to bool
static bool HVP_currentSenseMia = false; //Change to bool
static bool HVP_shuntRefVoltageMismatch = false; //Change to bool
static bool HVP_shuntThermistorMia = false; //Change to bool
static bool HVP_shuntHwMia = false; //Change to bool
static int16_t HVP_dcLinkVoltage = 0;
static int16_t HVP_packVoltage = 0;
static int16_t HVP_fcLinkVoltage = 0;
static uint16_t HVP_packContVoltage = 0;
static int16_t HVP_packNegativeV = 0;
static int16_t HVP_packPositiveV = 0;
static uint16_t HVP_pyroAnalog = 0;
static int16_t HVP_dcLinkNegativeV = 0;
static int16_t HVP_dcLinkPositiveV = 0;
static int16_t HVP_fcLinkNegativeV = 0;
static uint16_t HVP_fcContCoilCurrent = 0;
static uint16_t HVP_fcContVoltage = 0;
static uint16_t HVP_hvilInVoltage = 0;
static uint16_t HVP_hvilOutVoltage = 0;
static int16_t HVP_fcLinkPositiveV = 0;
static uint16_t HVP_packContCoilCurrent = 0;
static uint16_t HVP_battery12V = 0;
static int16_t HVP_shuntRefVoltageDbg = 0;
static int16_t HVP_shuntAuxCurrentDbg = 0;
static int16_t HVP_shuntBarTempDbg = 0;
static int16_t HVP_shuntAsicTempDbg = 0;
static uint8_t HVP_shuntAuxCurrentStatus = 0;
static uint8_t HVP_shuntBarTempStatus = 0;
static uint8_t HVP_shuntAsicTempStatus = 0;
//0x3aa: HVP_alertMatrix1 Fault codes // Change to bool
static bool battery_WatchdogReset = false; //Warns if the processor has experienced a reset due to watchdog reset.
static bool battery_PowerLossReset = false; //Warns if the processor has experienced a reset due to power loss.
static bool battery_SwAssertion = false; //An internal software assertion has failed.
static bool battery_CrashEvent = false; //Warns if the crash signal is detected by HVP
static bool battery_OverDchgCurrentFault = false; //Warns if the pack discharge is above max discharge current limit
static bool battery_OverChargeCurrentFault =
false; //Warns if the pack discharge current is above max charge current limit
static bool battery_OverCurrentFault =
false; //Warns if the pack current (discharge or charge) is above max current limit.
static bool battery_OverTemperatureFault = false; //A pack module temperature is above maximum temperature limit
static bool battery_OverVoltageFault = false; //A brick voltage is above maximum voltage limit
static bool battery_UnderVoltageFault = false; //A brick voltage is below minimum voltage limit
static bool battery_PrimaryBmbMiaFault =
false; //Warns if the voltage and temperature readings from primary BMB chain are mia
static bool battery_SecondaryBmbMiaFault =
false; //Warns if the voltage and temperature readings from secondary BMB chain are mia
static bool battery_BmbMismatchFault =
false; //Warns if the primary and secondary BMB chain readings don't match with each other
static bool battery_BmsHviMiaFault = false; //Warns if the BMS node is mia on HVS or HVI CAN
static bool battery_CpMiaFault = false; //Warns if the CP node is mia on HVS CAN
static bool battery_PcsMiaFault = false; //The PCS node is mia on HVS CAN
static bool battery_BmsFault = false; //Warns if the BMS ECU has faulted
static bool battery_PcsFault = false; //Warns if the PCS ECU has faulted
static bool battery_CpFault = false; //Warns if the CP ECU has faulted
static bool battery_ShuntHwMiaFault = false; //Warns if the shunt current reading is not available
static bool battery_PyroMiaFault = false; //Warns if the pyro squib is not connected
static bool battery_hvsMiaFault = false; //Warns if the pack contactor hw fault
static bool battery_hviMiaFault = false; //Warns if the FC contactor hw fault
static bool battery_Supply12vFault = false; //Warns if the low voltage (12V) battery is below minimum voltage threshold
static bool battery_VerSupplyFault =
false; //Warns if the Energy reserve voltage supply is below minimum voltage threshold
static bool battery_HvilFault = false; //Warn if a High Voltage Inter Lock fault is detected
static bool battery_BmsHvsMiaFault = false; //Warns if the BMS node is mia on HVS or HVI CAN
static bool battery_PackVoltMismatchFault =
false; //Warns if the pack voltage doesn't match approximately with sum of brick voltages
static bool battery_EnsMiaFault = false; //Warns if the ENS line is not connected to HVC
static bool battery_PackPosCtrArcFault = false; //Warns if the HVP detectes series arc at pack contactor
static bool battery_packNegCtrArcFault = false; //Warns if the HVP detectes series arc at FC contactor
static bool battery_ShuntHwAndBmsMiaFault = false;
static bool battery_fcContHwFault = false;
static bool battery_robinOverVoltageFault = false;
static bool battery_packContHwFault = false;
static bool battery_pyroFuseBlown = false;
static bool battery_pyroFuseFailedToBlow = false;
static bool battery_CpilFault = false;
static bool battery_PackContactorFellOpen = false;
static bool battery_FcContactorFellOpen = false;
static bool battery_packCtrCloseBlocked = false;
static bool battery_fcCtrCloseBlocked = false;
static bool battery_packContactorForceOpen = false;
static bool battery_fcContactorForceOpen = false;
static bool battery_dcLinkOverVoltage = false;
static bool battery_shuntOverTemperature = false;
static bool battery_passivePyroDeploy = false;
static bool battery_logUploadRequest = false;
static bool battery_packCtrCloseFailed = false;
static bool battery_fcCtrCloseFailed = false;
static bool battery_shuntThermistorMia = false;
//0x320: 800 BMS_alertMatrix
static uint8_t battery_BMS_matrixIndex = 0; // Changed to bool
static bool battery_BMS_a061_robinBrickOverVoltage = false;
static bool battery_BMS_a062_SW_BrickV_Imbalance = false;
static bool battery_BMS_a063_SW_ChargePort_Fault = false;
static bool battery_BMS_a064_SW_SOC_Imbalance = false;
static bool battery_BMS_a127_SW_shunt_SNA = false;
static bool battery_BMS_a128_SW_shunt_MIA = false;
static bool battery_BMS_a069_SW_Low_Power = false;
static bool battery_BMS_a130_IO_CAN_Error = false;
static bool battery_BMS_a071_SW_SM_TransCon_Not_Met = false;
static bool battery_BMS_a132_HW_BMB_OTP_Uncorrctbl = false;
static bool battery_BMS_a134_SW_Delayed_Ctr_Off = false;
static bool battery_BMS_a075_SW_Chg_Disable_Failure = false;
static bool battery_BMS_a076_SW_Dch_While_Charging = false;
static bool battery_BMS_a017_SW_Brick_OV = false;
static bool battery_BMS_a018_SW_Brick_UV = false;
static bool battery_BMS_a019_SW_Module_OT = false;
static bool battery_BMS_a021_SW_Dr_Limits_Regulation = false;
static bool battery_BMS_a022_SW_Over_Current = false;
static bool battery_BMS_a023_SW_Stack_OV = false;
static bool battery_BMS_a024_SW_Islanded_Brick = false;
static bool battery_BMS_a025_SW_PwrBalance_Anomaly = false;
static bool battery_BMS_a026_SW_HFCurrent_Anomaly = false;
static bool battery_BMS_a087_SW_Feim_Test_Blocked = false;
static bool battery_BMS_a088_SW_VcFront_MIA_InDrive = false;
static bool battery_BMS_a089_SW_VcFront_MIA = false;
static bool battery_BMS_a090_SW_Gateway_MIA = false;
static bool battery_BMS_a091_SW_ChargePort_MIA = false;
static bool battery_BMS_a092_SW_ChargePort_Mia_On_Hv = false;
static bool battery_BMS_a034_SW_Passive_Isolation = false;
static bool battery_BMS_a035_SW_Isolation = false;
static bool battery_BMS_a036_SW_HvpHvilFault = false;
static bool battery_BMS_a037_SW_Flood_Port_Open = false;
static bool battery_BMS_a158_SW_HVP_HVI_Comms = false;
static bool battery_BMS_a039_SW_DC_Link_Over_Voltage = false;
static bool battery_BMS_a041_SW_Power_On_Reset = false;
static bool battery_BMS_a042_SW_MPU_Error = false;
static bool battery_BMS_a043_SW_Watch_Dog_Reset = false;
static bool battery_BMS_a044_SW_Assertion = false;
static bool battery_BMS_a045_SW_Exception = false;
static bool battery_BMS_a046_SW_Task_Stack_Usage = false;
static bool battery_BMS_a047_SW_Task_Stack_Overflow = false;
static bool battery_BMS_a048_SW_Log_Upload_Request = false;
static bool battery_BMS_a169_SW_FC_Pack_Weld = false;
static bool battery_BMS_a050_SW_Brick_Voltage_MIA = false;
static bool battery_BMS_a051_SW_HVC_Vref_Bad = false;
static bool battery_BMS_a052_SW_PCS_MIA = false;
static bool battery_BMS_a053_SW_ThermalModel_Sanity = false;
static bool battery_BMS_a054_SW_Ver_Supply_Fault = false;
static bool battery_BMS_a176_SW_GracefulPowerOff = false;
static bool battery_BMS_a059_SW_Pack_Voltage_Sensing = false;
static bool battery_BMS_a060_SW_Leakage_Test_Failure = false;
static bool battery_BMS_a077_SW_Charger_Regulation = false;
static bool battery_BMS_a081_SW_Ctr_Close_Blocked = false;
static bool battery_BMS_a082_SW_Ctr_Force_Open = false;
static bool battery_BMS_a083_SW_Ctr_Close_Failure = false;
static bool battery_BMS_a084_SW_Sleep_Wake_Aborted = false;
static bool battery_BMS_a094_SW_Drive_Inverter_MIA = false;
static bool battery_BMS_a099_SW_BMB_Communication = false;
static bool battery_BMS_a105_SW_One_Module_Tsense = false;
static bool battery_BMS_a106_SW_All_Module_Tsense = false;
static bool battery_BMS_a107_SW_Stack_Voltage_MIA = false;
static bool battery_BMS_a121_SW_NVRAM_Config_Error = false;
static bool battery_BMS_a122_SW_BMS_Therm_Irrational = false;
static bool battery_BMS_a123_SW_Internal_Isolation = false;
static bool battery_BMS_a129_SW_VSH_Failure = false;
static bool battery_BMS_a131_Bleed_FET_Failure = false;
static bool battery_BMS_a136_SW_Module_OT_Warning = false;
static bool battery_BMS_a137_SW_Brick_UV_Warning = false;
static bool battery_BMS_a138_SW_Brick_OV_Warning = false;
static bool battery_BMS_a139_SW_DC_Link_V_Irrational = false;
static bool battery_BMS_a141_SW_BMB_Status_Warning = false;
static bool battery_BMS_a144_Hvp_Config_Mismatch = false;
static bool battery_BMS_a145_SW_SOC_Change = false;
static bool battery_BMS_a146_SW_Brick_Overdischarged = false;
static bool battery_BMS_a149_SW_Missing_Config_Block = false;
static bool battery_BMS_a151_SW_external_isolation = false;
static bool battery_BMS_a156_SW_BMB_Vref_bad = false;
static bool battery_BMS_a157_SW_HVP_HVS_Comms = false;
static bool battery_BMS_a159_SW_HVP_ECU_Error = false;
static bool battery_BMS_a161_SW_DI_Open_Request = false;
static bool battery_BMS_a162_SW_No_Power_For_Support = false;
static bool battery_BMS_a163_SW_Contactor_Mismatch = false;
static bool battery_BMS_a164_SW_Uncontrolled_Regen = false;
static bool battery_BMS_a165_SW_Pack_Partial_Weld = false;
static bool battery_BMS_a166_SW_Pack_Full_Weld = false;
static bool battery_BMS_a167_SW_FC_Partial_Weld = false;
static bool battery_BMS_a168_SW_FC_Full_Weld = false;
static bool battery_BMS_a170_SW_Limp_Mode = false;
static bool battery_BMS_a171_SW_Stack_Voltage_Sense = false;
static bool battery_BMS_a174_SW_Charge_Failure = false;
static bool battery_BMS_a179_SW_Hvp_12V_Fault = false;
static bool battery_BMS_a180_SW_ECU_reset_blocked = false;
// Function definitions
inline const char* getContactorText(int index) { inline const char* getContactorText(int index) {
switch (index) { switch (index) {
case 0: case 0:
@ -767,7 +327,8 @@ inline const char* getFault(bool value) {
return value ? "ACTIVE" : "NOT_ACTIVE"; return value ? "ACTIVE" : "NOT_ACTIVE";
} }
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus void TeslaBattery::
update_values() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
//After values are mapped, we perform some safety checks, and do some serial printouts //After values are mapped, we perform some safety checks, and do some serial printouts
datalayer.battery.status.soh_pptt = 9900; //Tesla batteries do not send a SOH% value on bus. Hardcode to 99% datalayer.battery.status.soh_pptt = 9900; //Tesla batteries do not send a SOH% value on bus. Hardcode to 99%
@ -1138,7 +699,7 @@ void update_values_battery() { //This function maps all the values fetched via
#endif //DEBUG_LOG #endif //DEBUG_LOG
} }
void handle_incoming_can_frame_battery(CAN_frame rx_frame) { void TeslaBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
static uint8_t mux = 0; static uint8_t mux = 0;
static uint16_t temp = 0; static uint16_t temp = 0;
static bool mux0_read = false; static bool mux0_read = false;
@ -1828,7 +1389,6 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
} }
} }
#if defined(TESLA_MODEL_SX_BATTERY) || defined(EXP_TESLA_BMS_DIGITAL_HVIL)
CAN_frame can_msg_1CF[] = { CAN_frame can_msg_1CF[] = {
{.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x1CF, .data = {0x01, 0x00, 0x00, 0x1A, 0x1C, 0x02, 0x60, 0x69}}, {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x1CF, .data = {0x01, 0x00, 0x00, 0x1A, 0x1C, 0x02, 0x60, 0x69}},
{.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x1CF, .data = {0x01, 0x00, 0x00, 0x1A, 0x1C, 0x02, 0x80, 0x89}}, {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x1CF, .data = {0x01, 0x00, 0x00, 0x1A, 0x1C, 0x02, 0x80, 0x89}},
@ -1862,9 +1422,8 @@ unsigned long lastSend118 = 0;
int index_1CF = 0; int index_1CF = 0;
int index_118 = 0; int index_118 = 0;
#endif //defined(TESLA_MODEL_SX_BATTERY) || defined(EXP_TESLA_BMS_DIGITAL_HVIL)
void transmit_can_battery(unsigned long currentMillis) { void TeslaBattery::transmit_can(unsigned long currentMillis) {
/*From bielec: My fist 221 message, to close the contactors is 0x41, 0x11, 0x01, 0x00, 0x00, 0x00, 0x20, 0x96 and then, /*From bielec: My fist 221 message, to close the contactors is 0x41, 0x11, 0x01, 0x00, 0x00, 0x00, 0x20, 0x96 and then,
to cause "hv_up_for_drive" I send an additional 221 message 0x61, 0x15, 0x01, 0x00, 0x00, 0x00, 0x20, 0xBA so to cause "hv_up_for_drive" I send an additional 221 message 0x61, 0x15, 0x01, 0x00, 0x00, 0x00, 0x20, 0xBA so
two 221 messages are being continuously transmitted. When I want to shut down, I stop the second message and only send two 221 messages are being continuously transmitted. When I want to shut down, I stop the second message and only send
@ -1874,7 +1433,7 @@ the first, for a few cycles, then stop all messages which causes the contactor
return; //All cellvoltages not read yet, do not proceed with contactor closing return; //All cellvoltages not read yet, do not proceed with contactor closing
} }
#if defined(TESLA_MODEL_SX_BATTERY) || defined(EXP_TESLA_BMS_DIGITAL_HVIL) if (operate_contactors) {
if ((datalayer.system.status.inverter_allows_contactor_closing) && (datalayer.battery.status.bms_status != FAULT)) { if ((datalayer.system.status.inverter_allows_contactor_closing) && (datalayer.battery.status.bms_status != FAULT)) {
if (currentMillis - lastSend1CF >= 10) { if (currentMillis - lastSend1CF >= 10) {
transmit_can_frame(&can_msg_1CF[index_1CF], can_config.battery); transmit_can_frame(&can_msg_1CF[index_1CF], can_config.battery);
@ -1893,7 +1452,7 @@ the first, for a few cycles, then stop all messages which causes the contactor
index_1CF = 0; index_1CF = 0;
index_118 = 0; index_118 = 0;
} }
#endif //defined(TESLA_MODEL_SX_BATTERY) || defined(EXP_TESLA_BMS_DIGITAL_HVIL) }
//Send 10ms message //Send 10ms message
if (currentMillis - previousMillis10 >= INTERVAL_10_MS) { if (currentMillis - previousMillis10 >= INTERVAL_10_MS) {
@ -2032,6 +1591,12 @@ the first, for a few cycles, then stop all messages which causes the contactor
} }
} }
void printDebugIfActive(uint8_t symbol, const char* message) {
if (symbol == 1) {
logging.println(message);
}
}
void print_int_with_units(char* header, int value, char* units) { void print_int_with_units(char* header, int value, char* units) {
logging.print(header); logging.print(header);
logging.print(value); logging.print(value);
@ -2049,7 +1614,7 @@ void print_SOC(char* header, int SOC) {
logging.println("%"); logging.println("%");
} }
void printFaultCodesIfActive() { void TeslaBattery::printFaultCodesIfActive() {
if (battery_packCtrsClosingAllowed == 0) { if (battery_packCtrsClosingAllowed == 0) {
logging.println( logging.println(
"ERROR: Check high voltage connectors and interlock circuit! Closing contactor not allowed! Values: "); "ERROR: Check high voltage connectors and interlock circuit! Closing contactor not allowed! Values: ");
@ -2217,24 +1782,11 @@ void printFaultCodesIfActive() {
printDebugIfActive(battery_BMS_a180_SW_ECU_reset_blocked, "ERROR: BMS_a180_SW_ECU_reset_blocked"); printDebugIfActive(battery_BMS_a180_SW_ECU_reset_blocked, "ERROR: BMS_a180_SW_ECU_reset_blocked");
} }
void printDebugIfActive(uint8_t symbol, const char* message) { void TeslaModel3YBattery::setup(void) { // Performs one time setup at startup
if (symbol == 1) {
logging.println(message); if (allows_contactor_closing) {
*allows_contactor_closing = true;
} }
}
void setup_battery(void) { // Performs one time setup at startup
datalayer.system.status.battery_allows_contactor_closing = true;
#ifdef TESLA_MODEL_SX_BATTERY // Always use NCM/A mode on S/X packs
strncpy(datalayer.system.info.battery_protocol, "Tesla Model S/X", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_SX_NCMA;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_SX_NCMA;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_NCA_NCM;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_NCA_NCM;
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_NCA_NCM;
#endif // TESLA_MODEL_SX_BATTERY
#ifdef TESLA_MODEL_3Y_BATTERY // Model 3/Y can be either LFP or NCM/A #ifdef TESLA_MODEL_3Y_BATTERY // Model 3/Y can be either LFP or NCM/A
strncpy(datalayer.system.info.battery_protocol, "Tesla Model 3/Y", 63); strncpy(datalayer.system.info.battery_protocol, "Tesla Model 3/Y", 63);
@ -2256,4 +1808,18 @@ void setup_battery(void) { // Performs one time setup at startup
#endif // TESLA_MODEL_3Y_BATTERY #endif // TESLA_MODEL_3Y_BATTERY
} }
void TeslaModelSXBattery::setup(void) {
if (allows_contactor_closing) {
*allows_contactor_closing = true;
}
strncpy(datalayer.system.info.battery_protocol, "Tesla Model S/X", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_SX_NCMA;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_SX_NCMA;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_NCA_NCM;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_NCA_NCM;
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_NCA_NCM;
}
#endif // TESLA_BATTERY #endif // TESLA_BATTERY

View file

@ -1,43 +1,512 @@
#ifndef TESLA_BATTERY_H #ifndef TESLA_BATTERY_H
#define TESLA_BATTERY_H #define TESLA_BATTERY_H
#include "../datalayer/datalayer.h"
#include "../include.h" #include "../include.h"
#include "CanBattery.h"
#define BATTERY_SELECTED #define BATTERY_SELECTED
#ifdef TESLA_MODEL_3Y_BATTERY
/* Modify these if needed */ #define SELECTED_BATTERY_CLASS TeslaModel3YBattery
#define MAXCHARGEPOWERALLOWED 15000 // 15000W we use a define since the value supplied by Tesla is always 0 #endif
#define MAXDISCHARGEPOWERALLOWED 60000 // 60000W we use a define since the value supplied by Tesla is always 0 #ifdef TESLA_MODEL_SX_BATTERY
#define SELECTED_BATTERY_CLASS TeslaModelSXBattery
/* Do not change the defines below */ #endif
#define RAMPDOWN_SOC 900 // 90.0 SOC% to start ramping down from max charge power towards 0 at 100.00%
#define RAMPDOWNPOWERALLOWED 10000 // What power we ramp down from towards top balancing
#define FLOAT_MAX_POWER_W 200 // W, what power to allow for top balancing battery
#define FLOAT_START_MV 20 // mV, how many mV under overvoltage to start float charging
#define MAX_PACK_VOLTAGE_SX_NCMA 4600 // V+1, if pack voltage goes over this, charge stops
#define MIN_PACK_VOLTAGE_SX_NCMA 3100 // V+1, if pack voltage goes over this, charge stops
#define MAX_PACK_VOLTAGE_3Y_NCMA 4030 // V+1, if pack voltage goes over this, charge stops
#define MIN_PACK_VOLTAGE_3Y_NCMA 3100 // V+1, if pack voltage goes below this, discharge stops
#define MAX_PACK_VOLTAGE_3Y_LFP 3880 // V+1, if pack voltage goes over this, charge stops
#define MIN_PACK_VOLTAGE_3Y_LFP 2968 // V+1, if pack voltage goes below this, discharge stops
#define MAX_CELL_DEVIATION_NCA_NCM 500 //LED turns yellow on the board if mv delta exceeds this value
#define MAX_CELL_DEVIATION_LFP 400 //LED turns yellow on the board if mv delta exceeds this value
#define MAX_CELL_VOLTAGE_NCA_NCM 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_NCA_NCM 2950 //Battery is put into emergency stop if one cell goes below this value
#define MAX_CELL_VOLTAGE_LFP 3650 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_LFP 2800 //Battery is put into emergency stop if one cell goes below this value
//#define EXP_TESLA_BMS_DIGITAL_HVIL // Experimental parameter. Forces the transmission of additional CAN frames for experimental purposes, to test potential HVIL issues in 3/Y packs with newer firmware. //#define EXP_TESLA_BMS_DIGITAL_HVIL // Experimental parameter. Forces the transmission of additional CAN frames for experimental purposes, to test potential HVIL issues in 3/Y packs with newer firmware.
void printFaultCodesIfActive(); class TeslaBattery : public CanBattery {
void printDebugIfActive(uint8_t symbol, const char* message); public:
void print_int_with_units(char* header, int value, char* units); // Use the default constructor to create the first or single battery.
void print_SOC(char* header, int SOC); TeslaBattery() { allows_contactor_closing = &datalayer.system.status.battery_allows_contactor_closing; }
void setup_battery(void);
void transmit_can_frame(CAN_frame* tx_frame, int interface);
#ifdef DOUBLE_BATTERY virtual void handle_incoming_can_frame(CAN_frame rx_frame);
void printFaultCodesIfActive_battery2(); virtual void update_values();
#endif //DOUBLE_BATTERY virtual void transmit_can(unsigned long currentMillis);
protected:
/* Modify these if needed */
static const int MAXCHARGEPOWERALLOWED =
15000; // 15000W we use a define since the value supplied by Tesla is always 0
static const int MAXDISCHARGEPOWERALLOWED =
60000; // 60000W we use a define since the value supplied by Tesla is always 0
/* Do not change the defines below */
static const int RAMPDOWN_SOC = 900; // 90.0 SOC% to start ramping down from max charge power towards 0 at 100.00%
static const int RAMPDOWNPOWERALLOWED = 10000; // What power we ramp down from towards top balancing
static const int FLOAT_MAX_POWER_W = 200; // W, what power to allow for top balancing battery
static const int FLOAT_START_MV = 20; // mV, how many mV under overvoltage to start float charging
static const int MAX_PACK_VOLTAGE_SX_NCMA = 4600; // V+1, if pack voltage goes over this, charge stops
static const int MIN_PACK_VOLTAGE_SX_NCMA = 3100; // V+1, if pack voltage goes over this, charge stops
static const int MAX_PACK_VOLTAGE_3Y_NCMA = 4030; // V+1, if pack voltage goes over this, charge stops
static const int MIN_PACK_VOLTAGE_3Y_NCMA = 3100; // V+1, if pack voltage goes below this, discharge stops
static const int MAX_PACK_VOLTAGE_3Y_LFP = 3880; // V+1, if pack voltage goes over this, charge stops
static const int MIN_PACK_VOLTAGE_3Y_LFP = 2968; // V+1, if pack voltage goes below this, discharge stops
static const int MAX_CELL_DEVIATION_NCA_NCM = 500; //LED turns yellow on the board if mv delta exceeds this value
static const int MAX_CELL_DEVIATION_LFP = 400; //LED turns yellow on the board if mv delta exceeds this value
static const int MAX_CELL_VOLTAGE_NCA_NCM =
4250; //Battery is put into emergency stop if one cell goes over this value
static const int MIN_CELL_VOLTAGE_NCA_NCM =
2950; //Battery is put into emergency stop if one cell goes below this value
static const int MAX_CELL_VOLTAGE_LFP = 3650; //Battery is put into emergency stop if one cell goes over this value
static const int MIN_CELL_VOLTAGE_LFP = 2800; //Battery is put into emergency stop if one cell goes below this value
bool operate_contactors = false;
// If not null, this battery decides when the contactor can be closed and writes the value here.
bool* allows_contactor_closing;
void printFaultCodesIfActive();
unsigned long previousMillis10 = 0; // will store last time a 50ms CAN Message was sent
unsigned long previousMillis50 = 0; // will store last time a 50ms CAN Message was sent
unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was sent
unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was sent
unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was sent
bool alternate243 = false;
//0x221 545 VCFRONT_LVPowerState: "GenMsgCycleTime" 50ms
CAN_frame TESLA_221_1 = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x221,
.data = {0x41, 0x11, 0x01, 0x00, 0x00, 0x00, 0x20, 0x96}}; //Contactor frame 221 - close contactors
CAN_frame TESLA_221_2 = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x221,
.data = {0x61, 0x15, 0x01, 0x00, 0x00, 0x00, 0x20, 0xBA}}; //Contactor Frame 221 - hv_up_for_drive
//0x241 VCFRONT_coolant 100ms
CAN_frame TESLA_241 = {.FD = false,
.ext_ID = false,
.DLC = 7,
.ID = 0x241,
.data = {0x3C, 0x78, 0x2C, 0x0F, 0x1E, 0x5B, 0x00}};
//0x242 VCLEFT_LVPowerState 100ms
CAN_frame TESLA_242 = {.FD = false, .ext_ID = false, .DLC = 2, .ID = 0x242, .data = {0x10, 0x95}};
//0x243 VCRIGHT_hvacStatus 50ms
CAN_frame TESLA_243_1 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x243,
.data = {0xC9, 0x00, 0xEB, 0xD4, 0x31, 0x32, 0x02, 0x00}};
CAN_frame TESLA_243_2 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x243,
.data = {0x08, 0x81, 0x42, 0x60, 0x92, 0x2C, 0x0E, 0x09}};
//0x129 SteeringAngle 10ms
CAN_frame TESLA_129 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x129,
.data = {0x21, 0x24, 0x36, 0x5F, 0x00, 0x20, 0xFF, 0x3F}};
//0x612 UDS diagnostic requests - on demand
CAN_frame TESLA_602 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x602,
.data = {0x02, 0x27, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00}};
uint8_t stateMachineClearIsolationFault = 0xFF;
uint8_t stateMachineBMSReset = 0xFF;
uint16_t sendContactorClosingMessagesStill = 300;
uint16_t battery_cell_max_v = 3300;
uint16_t battery_cell_min_v = 3300;
uint16_t battery_cell_deviation_mV = 0; //contains the deviation between highest and lowest cell in mV
bool cellvoltagesRead = false;
//0x3d2: 978 BMS_kwhCounter
uint32_t battery_total_discharge = 0;
uint32_t battery_total_charge = 0;
//0x352: 850 BMS_energyStatus
bool BMS352_mux = false; // variable to store when 0x352 mux is present
uint16_t battery_energy_buffer = 0; // kWh
uint16_t battery_energy_buffer_m1 = 0; // kWh
uint16_t battery_energy_to_charge_complete = 0; // kWh
uint16_t battery_energy_to_charge_complete_m1 = 0; // kWh
uint16_t battery_expected_energy_remaining = 0; // kWh
uint16_t battery_expected_energy_remaining_m1 = 0; // kWh
bool battery_full_charge_complete = false; // Changed to bool
bool battery_fully_charged = false; // Changed to bool
uint16_t battery_ideal_energy_remaining = 0; // kWh
uint16_t battery_ideal_energy_remaining_m0 = 0; // kWh
uint16_t battery_nominal_energy_remaining = 0; // kWh
uint16_t battery_nominal_energy_remaining_m0 = 0; // kWh
uint16_t battery_nominal_full_pack_energy = 0; // Kwh
uint16_t battery_nominal_full_pack_energy_m0 = 0; // Kwh
//0x132 306 HVBattAmpVolt
uint16_t battery_volts = 0; // V
int16_t battery_amps = 0; // A
int16_t battery_raw_amps = 0; // A
uint16_t battery_charge_time_remaining = 0; // Minutes
//0x252 594 BMS_powerAvailable
uint16_t BMS_maxRegenPower = 0; //rename from battery_regenerative_limit
uint16_t BMS_maxDischargePower = 0; // rename from battery_discharge_limit
uint16_t BMS_maxStationaryHeatPower = 0; //rename from battery_max_heat_park
uint16_t BMS_hvacPowerBudget = 0; //rename from battery_hvac_max_power
uint8_t BMS_notEnoughPowerForHeatPump = 0;
uint8_t BMS_powerLimitState = 0;
uint8_t BMS_inverterTQF = 0;
//0x2d2: 722 BMSVAlimits
uint16_t battery_max_discharge_current = 0;
uint16_t battery_max_charge_current = 0;
uint16_t battery_bms_max_voltage = 0;
uint16_t battery_bms_min_voltage = 0;
//0x2b4: 692 PCS_dcdcRailStatus
uint16_t battery_dcdcHvBusVolt = 0; // Change name from battery_high_voltage to battery_dcdcHvBusVolt
uint16_t battery_dcdcLvBusVolt = 0; // Change name from battery_low_voltage to battery_dcdcLvBusVolt
uint16_t battery_dcdcLvOutputCurrent = 0; // Change name from battery_output_current to battery_dcdcLvOutputCurrent
//0x292: 658 BMS_socStatus
uint16_t battery_beginning_of_life = 0; // kWh
uint16_t battery_soc_min = 0;
uint16_t battery_soc_max = 0;
uint16_t battery_soc_ui = 0; //Change name from battery_soc_vi to reflect DBC battery_soc_ui
uint16_t battery_soc_ave = 0;
uint8_t battery_battTempPct = 0;
//0x392: BMS_packConfig
uint32_t battery_packMass = 0;
uint32_t battery_platformMaxBusVoltage = 0;
uint32_t battery_packConfigMultiplexer = 0;
uint32_t battery_moduleType = 0;
uint32_t battery_reservedConfig = 0;
//0x332: 818 BattBrickMinMax:BMS_bmbMinMax
int16_t battery_max_temp = 0; // C*
int16_t battery_min_temp = 0; // C*
uint16_t battery_BrickVoltageMax = 0;
uint16_t battery_BrickVoltageMin = 0;
uint8_t battery_BrickTempMaxNum = 0;
uint8_t battery_BrickTempMinNum = 0;
uint8_t battery_BrickModelTMax = 0;
uint8_t battery_BrickModelTMin = 0;
uint8_t battery_BrickVoltageMaxNum = 0; //rename from battery_max_vno
uint8_t battery_BrickVoltageMinNum = 0; //rename from battery_min_vno
//0x20A: 522 HVP_contactorState
uint8_t battery_contactor = 0; //State of contactor
uint8_t battery_hvil_status = 0;
uint8_t battery_packContNegativeState = 0;
uint8_t battery_packContPositiveState = 0;
uint8_t battery_packContactorSetState = 0;
bool battery_packCtrsClosingAllowed = false; // Change to bool
bool battery_pyroTestInProgress = false; // Change to bool
bool battery_packCtrsOpenNowRequested = false; // Change to bool
bool battery_packCtrsOpenRequested = false; // Change to bool
uint8_t battery_packCtrsRequestStatus = 0;
bool battery_packCtrsResetRequestRequired = false; // Change to bool
bool battery_dcLinkAllowedToEnergize = false; // Change to bool
bool battery_fcContNegativeAuxOpen = false; // Change to bool
uint8_t battery_fcContNegativeState = 0;
bool battery_fcContPositiveAuxOpen = false; // Change to bool
uint8_t battery_fcContPositiveState = 0;
uint8_t battery_fcContactorSetState = 0;
bool battery_fcCtrsClosingAllowed = false; // Change to bool
bool battery_fcCtrsOpenNowRequested = false; // Change to bool
bool battery_fcCtrsOpenRequested = false; // Change to bool
uint8_t battery_fcCtrsRequestStatus = 0;
bool battery_fcCtrsResetRequestRequired = false; // Change to bool
bool battery_fcLinkAllowedToEnergize = false; // Change to bool
//0x72A: BMS_serialNumber
uint8_t BMS_SerialNumber[14] = {0}; // Stores raw HEX values for ASCII chars
//0x212: 530 BMS_status
bool battery_BMS_hvacPowerRequest = false; //Change to bool
bool battery_BMS_notEnoughPowerForDrive = false; //Change to bool
bool battery_BMS_notEnoughPowerForSupport = false; //Change to bool
bool battery_BMS_preconditionAllowed = false; //Change to bool
bool battery_BMS_updateAllowed = false; //Change to bool
bool battery_BMS_activeHeatingWorthwhile = false; //Change to bool
bool battery_BMS_cpMiaOnHvs = false; //Change to bool
uint8_t battery_BMS_contactorState = 0;
uint8_t battery_BMS_state = 0;
uint8_t battery_BMS_hvState = 0;
uint16_t battery_BMS_isolationResistance = 0;
bool battery_BMS_chargeRequest = false; //Change to bool
bool battery_BMS_keepWarmRequest = false; //Change to bool
uint8_t battery_BMS_uiChargeStatus = 0;
bool battery_BMS_diLimpRequest = false; //Change to bool
bool battery_BMS_okToShipByAir = false; //Change to bool
bool battery_BMS_okToShipByLand = false; //Change to bool
uint32_t battery_BMS_chgPowerAvailable = 0;
uint8_t battery_BMS_chargeRetryCount = 0;
bool battery_BMS_pcsPwmEnabled = false; //Change to bool
bool battery_BMS_ecuLogUploadRequest = false; //Change to bool
uint8_t battery_BMS_minPackTemperature = 0;
// 0x224:548 PCS_dcdcStatus
uint8_t battery_PCS_dcdcPrechargeStatus = 0;
uint8_t battery_PCS_dcdc12VSupportStatus = 0;
uint8_t battery_PCS_dcdcHvBusDischargeStatus = 0;
uint16_t battery_PCS_dcdcMainState = 0;
uint8_t battery_PCS_dcdcSubState = 0;
bool battery_PCS_dcdcFaulted = false; //Change to bool
bool battery_PCS_dcdcOutputIsLimited = false; //Change to bool
uint32_t battery_PCS_dcdcMaxOutputCurrentAllowed = 0;
uint8_t battery_PCS_dcdcPrechargeRtyCnt = 0;
uint8_t battery_PCS_dcdc12VSupportRtyCnt = 0;
uint8_t battery_PCS_dcdcDischargeRtyCnt = 0;
uint8_t battery_PCS_dcdcPwmEnableLine = 0;
uint8_t battery_PCS_dcdcSupportingFixedLvTarget = 0;
uint8_t battery_PCS_ecuLogUploadRequest = 0;
uint8_t battery_PCS_dcdcPrechargeRestartCnt = 0;
uint8_t battery_PCS_dcdcInitialPrechargeSubState = 0;
//0x312: 786 BMS_thermalStatus
uint16_t BMS_powerDissipation = 0;
uint16_t BMS_flowRequest = 0;
uint16_t BMS_inletActiveCoolTargetT = 0;
uint16_t BMS_inletPassiveTargetT = 0;
uint16_t BMS_inletActiveHeatTargetT = 0;
uint16_t BMS_packTMin = 0;
uint16_t BMS_packTMax = 0;
bool BMS_pcsNoFlowRequest = false;
bool BMS_noFlowRequest = false;
//0x2A4; 676 PCS_thermalStatus
int16_t PCS_chgPhATemp = 0;
int16_t PCS_chgPhBTemp = 0;
int16_t PCS_chgPhCTemp = 0;
int16_t PCS_dcdcTemp = 0;
int16_t PCS_ambientTemp = 0;
//0x2C4; 708 PCS_logging
uint16_t PCS_logMessageSelect = 0;
uint16_t PCS_dcdcMaxLvOutputCurrent = 0;
uint16_t PCS_dcdcCurrentLimit = 0;
uint16_t PCS_dcdcLvOutputCurrentTempLimit = 0;
uint16_t PCS_dcdcUnifiedCommand = 0;
uint16_t PCS_dcdcCLAControllerOutput = 0;
int16_t PCS_dcdcTankVoltage = 0;
uint16_t PCS_dcdcTankVoltageTarget = 0;
uint16_t PCS_dcdcClaCurrentFreq = 0;
int16_t PCS_dcdcTCommMeasured = 0;
uint16_t PCS_dcdcShortTimeUs = 0;
uint16_t PCS_dcdcHalfPeriodUs = 0;
uint16_t PCS_dcdcIntervalMaxFrequency = 0;
uint16_t PCS_dcdcIntervalMaxHvBusVolt = 0;
uint16_t PCS_dcdcIntervalMaxLvBusVolt = 0;
uint16_t PCS_dcdcIntervalMaxLvOutputCurr = 0;
uint16_t PCS_dcdcIntervalMinFrequency = 0;
uint16_t PCS_dcdcIntervalMinHvBusVolt = 0;
uint16_t PCS_dcdcIntervalMinLvBusVolt = 0;
uint16_t PCS_dcdcIntervalMinLvOutputCurr = 0;
uint32_t PCS_dcdc12vSupportLifetimekWh = 0;
//0x7AA: //1962 HVP_debugMessage:
uint8_t HVP_debugMessageMultiplexer = 0;
bool HVP_gpioPassivePyroDepl = false; //Change to bool
bool HVP_gpioPyroIsoEn = false; //Change to bool
bool HVP_gpioCpFaultIn = false; //Change to bool
bool HVP_gpioPackContPowerEn = false; //Change to bool
bool HVP_gpioHvCablesOk = false; //Change to bool
bool HVP_gpioHvpSelfEnable = false; //Change to bool
bool HVP_gpioLed = false; //Change to bool
bool HVP_gpioCrashSignal = false; //Change to bool
bool HVP_gpioShuntDataReady = false; //Change to bool
bool HVP_gpioFcContPosAux = false; //Change to bool
bool HVP_gpioFcContNegAux = false; //Change to bool
bool HVP_gpioBmsEout = false; //Change to bool
bool HVP_gpioCpFaultOut = false; //Change to bool
bool HVP_gpioPyroPor = false; //Change to bool
bool HVP_gpioShuntEn = false; //Change to bool
bool HVP_gpioHvpVerEn = false; //Change to bool
bool HVP_gpioPackCoontPosFlywheel = false; //Change to bool
bool HVP_gpioCpLatchEnable = false; //Change to bool
bool HVP_gpioPcsEnable = false; //Change to bool
bool HVP_gpioPcsDcdcPwmEnable = false; //Change to bool
bool HVP_gpioPcsChargePwmEnable = false; //Change to bool
bool HVP_gpioFcContPowerEnable = false; //Change to bool
bool HVP_gpioHvilEnable = false; //Change to bool
bool HVP_gpioSecDrdy = false; //Change to bool
uint16_t HVP_hvp1v5Ref = 0;
int16_t HVP_shuntCurrentDebug = 0;
bool HVP_packCurrentMia = false; //Change to bool
bool HVP_auxCurrentMia = false; //Change to bool
bool HVP_currentSenseMia = false; //Change to bool
bool HVP_shuntRefVoltageMismatch = false; //Change to bool
bool HVP_shuntThermistorMia = false; //Change to bool
bool HVP_shuntHwMia = false; //Change to bool
int16_t HVP_dcLinkVoltage = 0;
int16_t HVP_packVoltage = 0;
int16_t HVP_fcLinkVoltage = 0;
uint16_t HVP_packContVoltage = 0;
int16_t HVP_packNegativeV = 0;
int16_t HVP_packPositiveV = 0;
uint16_t HVP_pyroAnalog = 0;
int16_t HVP_dcLinkNegativeV = 0;
int16_t HVP_dcLinkPositiveV = 0;
int16_t HVP_fcLinkNegativeV = 0;
uint16_t HVP_fcContCoilCurrent = 0;
uint16_t HVP_fcContVoltage = 0;
uint16_t HVP_hvilInVoltage = 0;
uint16_t HVP_hvilOutVoltage = 0;
int16_t HVP_fcLinkPositiveV = 0;
uint16_t HVP_packContCoilCurrent = 0;
uint16_t HVP_battery12V = 0;
int16_t HVP_shuntRefVoltageDbg = 0;
int16_t HVP_shuntAuxCurrentDbg = 0;
int16_t HVP_shuntBarTempDbg = 0;
int16_t HVP_shuntAsicTempDbg = 0;
uint8_t HVP_shuntAuxCurrentStatus = 0;
uint8_t HVP_shuntBarTempStatus = 0;
uint8_t HVP_shuntAsicTempStatus = 0;
//0x3aa: HVP_alertMatrix1 Fault codes // Change to bool
bool battery_WatchdogReset = false; //Warns if the processor has experienced a reset due to watchdog reset.
bool battery_PowerLossReset = false; //Warns if the processor has experienced a reset due to power loss.
bool battery_SwAssertion = false; //An internal software assertion has failed.
bool battery_CrashEvent = false; //Warns if the crash signal is detected by HVP
bool battery_OverDchgCurrentFault = false; //Warns if the pack discharge is above max discharge current limit
bool battery_OverChargeCurrentFault = false; //Warns if the pack discharge current is above max charge current limit
bool battery_OverCurrentFault = false; //Warns if the pack current (discharge or charge) is above max current limit.
bool battery_OverTemperatureFault = false; //A pack module temperature is above maximum temperature limit
bool battery_OverVoltageFault = false; //A brick voltage is above maximum voltage limit
bool battery_UnderVoltageFault = false; //A brick voltage is below minimum voltage limit
bool battery_PrimaryBmbMiaFault =
false; //Warns if the voltage and temperature readings from primary BMB chain are mia
bool battery_SecondaryBmbMiaFault =
false; //Warns if the voltage and temperature readings from secondary BMB chain are mia
bool battery_BmbMismatchFault =
false; //Warns if the primary and secondary BMB chain readings don't match with each other
bool battery_BmsHviMiaFault = false; //Warns if the BMS node is mia on HVS or HVI CAN
bool battery_CpMiaFault = false; //Warns if the CP node is mia on HVS CAN
bool battery_PcsMiaFault = false; //The PCS node is mia on HVS CAN
bool battery_BmsFault = false; //Warns if the BMS ECU has faulted
bool battery_PcsFault = false; //Warns if the PCS ECU has faulted
bool battery_CpFault = false; //Warns if the CP ECU has faulted
bool battery_ShuntHwMiaFault = false; //Warns if the shunt current reading is not available
bool battery_PyroMiaFault = false; //Warns if the pyro squib is not connected
bool battery_hvsMiaFault = false; //Warns if the pack contactor hw fault
bool battery_hviMiaFault = false; //Warns if the FC contactor hw fault
bool battery_Supply12vFault = false; //Warns if the low voltage (12V) battery is below minimum voltage threshold
bool battery_VerSupplyFault = false; //Warns if the Energy reserve voltage supply is below minimum voltage threshold
bool battery_HvilFault = false; //Warn if a High Voltage Inter Lock fault is detected
bool battery_BmsHvsMiaFault = false; //Warns if the BMS node is mia on HVS or HVI CAN
bool battery_PackVoltMismatchFault =
false; //Warns if the pack voltage doesn't match approximately with sum of brick voltages
bool battery_EnsMiaFault = false; //Warns if the ENS line is not connected to HVC
bool battery_PackPosCtrArcFault = false; //Warns if the HVP detectes series arc at pack contactor
bool battery_packNegCtrArcFault = false; //Warns if the HVP detectes series arc at FC contactor
bool battery_ShuntHwAndBmsMiaFault = false;
bool battery_fcContHwFault = false;
bool battery_robinOverVoltageFault = false;
bool battery_packContHwFault = false;
bool battery_pyroFuseBlown = false;
bool battery_pyroFuseFailedToBlow = false;
bool battery_CpilFault = false;
bool battery_PackContactorFellOpen = false;
bool battery_FcContactorFellOpen = false;
bool battery_packCtrCloseBlocked = false;
bool battery_fcCtrCloseBlocked = false;
bool battery_packContactorForceOpen = false;
bool battery_fcContactorForceOpen = false;
bool battery_dcLinkOverVoltage = false;
bool battery_shuntOverTemperature = false;
bool battery_passivePyroDeploy = false;
bool battery_logUploadRequest = false;
bool battery_packCtrCloseFailed = false;
bool battery_fcCtrCloseFailed = false;
bool battery_shuntThermistorMia = false;
//0x320: 800 BMS_alertMatrix
uint8_t battery_BMS_matrixIndex = 0; // Changed to bool
bool battery_BMS_a061_robinBrickOverVoltage = false;
bool battery_BMS_a062_SW_BrickV_Imbalance = false;
bool battery_BMS_a063_SW_ChargePort_Fault = false;
bool battery_BMS_a064_SW_SOC_Imbalance = false;
bool battery_BMS_a127_SW_shunt_SNA = false;
bool battery_BMS_a128_SW_shunt_MIA = false;
bool battery_BMS_a069_SW_Low_Power = false;
bool battery_BMS_a130_IO_CAN_Error = false;
bool battery_BMS_a071_SW_SM_TransCon_Not_Met = false;
bool battery_BMS_a132_HW_BMB_OTP_Uncorrctbl = false;
bool battery_BMS_a134_SW_Delayed_Ctr_Off = false;
bool battery_BMS_a075_SW_Chg_Disable_Failure = false;
bool battery_BMS_a076_SW_Dch_While_Charging = false;
bool battery_BMS_a017_SW_Brick_OV = false;
bool battery_BMS_a018_SW_Brick_UV = false;
bool battery_BMS_a019_SW_Module_OT = false;
bool battery_BMS_a021_SW_Dr_Limits_Regulation = false;
bool battery_BMS_a022_SW_Over_Current = false;
bool battery_BMS_a023_SW_Stack_OV = false;
bool battery_BMS_a024_SW_Islanded_Brick = false;
bool battery_BMS_a025_SW_PwrBalance_Anomaly = false;
bool battery_BMS_a026_SW_HFCurrent_Anomaly = false;
bool battery_BMS_a087_SW_Feim_Test_Blocked = false;
bool battery_BMS_a088_SW_VcFront_MIA_InDrive = false;
bool battery_BMS_a089_SW_VcFront_MIA = false;
bool battery_BMS_a090_SW_Gateway_MIA = false;
bool battery_BMS_a091_SW_ChargePort_MIA = false;
bool battery_BMS_a092_SW_ChargePort_Mia_On_Hv = false;
bool battery_BMS_a034_SW_Passive_Isolation = false;
bool battery_BMS_a035_SW_Isolation = false;
bool battery_BMS_a036_SW_HvpHvilFault = false;
bool battery_BMS_a037_SW_Flood_Port_Open = false;
bool battery_BMS_a158_SW_HVP_HVI_Comms = false;
bool battery_BMS_a039_SW_DC_Link_Over_Voltage = false;
bool battery_BMS_a041_SW_Power_On_Reset = false;
bool battery_BMS_a042_SW_MPU_Error = false;
bool battery_BMS_a043_SW_Watch_Dog_Reset = false;
bool battery_BMS_a044_SW_Assertion = false;
bool battery_BMS_a045_SW_Exception = false;
bool battery_BMS_a046_SW_Task_Stack_Usage = false;
bool battery_BMS_a047_SW_Task_Stack_Overflow = false;
bool battery_BMS_a048_SW_Log_Upload_Request = false;
bool battery_BMS_a169_SW_FC_Pack_Weld = false;
bool battery_BMS_a050_SW_Brick_Voltage_MIA = false;
bool battery_BMS_a051_SW_HVC_Vref_Bad = false;
bool battery_BMS_a052_SW_PCS_MIA = false;
bool battery_BMS_a053_SW_ThermalModel_Sanity = false;
bool battery_BMS_a054_SW_Ver_Supply_Fault = false;
bool battery_BMS_a176_SW_GracefulPowerOff = false;
bool battery_BMS_a059_SW_Pack_Voltage_Sensing = false;
bool battery_BMS_a060_SW_Leakage_Test_Failure = false;
bool battery_BMS_a077_SW_Charger_Regulation = false;
bool battery_BMS_a081_SW_Ctr_Close_Blocked = false;
bool battery_BMS_a082_SW_Ctr_Force_Open = false;
bool battery_BMS_a083_SW_Ctr_Close_Failure = false;
bool battery_BMS_a084_SW_Sleep_Wake_Aborted = false;
bool battery_BMS_a094_SW_Drive_Inverter_MIA = false;
bool battery_BMS_a099_SW_BMB_Communication = false;
bool battery_BMS_a105_SW_One_Module_Tsense = false;
bool battery_BMS_a106_SW_All_Module_Tsense = false;
bool battery_BMS_a107_SW_Stack_Voltage_MIA = false;
bool battery_BMS_a121_SW_NVRAM_Config_Error = false;
bool battery_BMS_a122_SW_BMS_Therm_Irrational = false;
bool battery_BMS_a123_SW_Internal_Isolation = false;
bool battery_BMS_a129_SW_VSH_Failure = false;
bool battery_BMS_a131_Bleed_FET_Failure = false;
bool battery_BMS_a136_SW_Module_OT_Warning = false;
bool battery_BMS_a137_SW_Brick_UV_Warning = false;
bool battery_BMS_a138_SW_Brick_OV_Warning = false;
bool battery_BMS_a139_SW_DC_Link_V_Irrational = false;
bool battery_BMS_a141_SW_BMB_Status_Warning = false;
bool battery_BMS_a144_Hvp_Config_Mismatch = false;
bool battery_BMS_a145_SW_SOC_Change = false;
bool battery_BMS_a146_SW_Brick_Overdischarged = false;
bool battery_BMS_a149_SW_Missing_Config_Block = false;
bool battery_BMS_a151_SW_external_isolation = false;
bool battery_BMS_a156_SW_BMB_Vref_bad = false;
bool battery_BMS_a157_SW_HVP_HVS_Comms = false;
bool battery_BMS_a159_SW_HVP_ECU_Error = false;
bool battery_BMS_a161_SW_DI_Open_Request = false;
bool battery_BMS_a162_SW_No_Power_For_Support = false;
bool battery_BMS_a163_SW_Contactor_Mismatch = false;
bool battery_BMS_a164_SW_Uncontrolled_Regen = false;
bool battery_BMS_a165_SW_Pack_Partial_Weld = false;
bool battery_BMS_a166_SW_Pack_Full_Weld = false;
bool battery_BMS_a167_SW_FC_Partial_Weld = false;
bool battery_BMS_a168_SW_FC_Full_Weld = false;
bool battery_BMS_a170_SW_Limp_Mode = false;
bool battery_BMS_a171_SW_Stack_Voltage_Sense = false;
bool battery_BMS_a174_SW_Charge_Failure = false;
bool battery_BMS_a179_SW_Hvp_12V_Fault = false;
bool battery_BMS_a180_SW_ECU_reset_blocked = false;
};
class TeslaModel3YBattery : public TeslaBattery {
public:
TeslaModel3YBattery() {
#ifdef EXP_TESLA_BMS_DIGITAL_HVIL
operate_contactors = true;
#endif
}
virtual void setup(void);
};
class TeslaModelSXBattery : public TeslaBattery {
public:
TeslaModelSXBattery() { operate_contactors = true; }
virtual void setup(void);
};
#endif #endif

View file

@ -3,153 +3,89 @@
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "TEST-FAKE-BATTERY.h" #include "TEST-FAKE-BATTERY.h"
/* Do not change code below unless you are sure what you are doing */
static unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was send
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
static unsigned long previousMillis10s = 0; // will store last time a 1s CAN Message was send
CAN_frame TEST = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x123,
.data = {0x10, 0x64, 0x00, 0xB0, 0x00, 0x1E, 0x00, 0x8F}};
void print_units(char* header, int value, char* units) { void print_units(char* header, int value, char* units) {
logging.print(header); logging.print(header);
logging.print(value); logging.print(value);
logging.print(units); logging.print(units);
} }
void update_values_battery() { /* This function puts fake values onto the parameters sent towards the inverter */ void TestFakeBattery::
update_values() { /* This function puts fake values onto the parameters sent towards the inverter */
datalayer.battery.status.real_soc = 5000; // 50.00% datalayer_battery->status.real_soc = 5000; // 50.00%
datalayer.battery.status.soh_pptt = 9900; // 99.00% datalayer_battery->status.soh_pptt = 9900; // 99.00%
//datalayer.battery.status.voltage_dV = 3700; // 370.0V , value set in startup in .ino file, editable via webUI //datalayer_battery->status.voltage_dV = 3700; // 370.0V , value set in startup in .ino file, editable via webUI
datalayer.battery.status.current_dA = 0; // 0 A datalayer_battery->status.current_dA = 0; // 0 A
datalayer.battery.info.total_capacity_Wh = 30000; // 30kWh datalayer_battery->info.total_capacity_Wh = 30000; // 30kWh
datalayer.battery.status.remaining_capacity_Wh = 15000; // 15kWh datalayer_battery->status.remaining_capacity_Wh = 15000; // 15kWh
datalayer.battery.status.cell_max_voltage_mV = 3596; datalayer_battery->status.cell_max_voltage_mV = 3596;
datalayer.battery.status.cell_min_voltage_mV = 3500; datalayer_battery->status.cell_min_voltage_mV = 3500;
datalayer.battery.status.temperature_min_dC = 50; // 5.0*C datalayer_battery->status.temperature_min_dC = 50; // 5.0*C
datalayer.battery.status.temperature_max_dC = 60; // 6.0*C datalayer_battery->status.temperature_max_dC = 60; // 6.0*C
datalayer.battery.status.max_discharge_power_W = 5000; // 5kW datalayer_battery->status.max_discharge_power_W = 5000; // 5kW
datalayer.battery.status.max_charge_power_W = 5000; // 5kW datalayer_battery->status.max_charge_power_W = 5000; // 5kW
for (int i = 0; i < 97; ++i) { for (int i = 0; i < 97; ++i) {
datalayer.battery.status.cell_voltages_mV[i] = 3700 + random(-20, 21); datalayer_battery->status.cell_voltages_mV[i] = 3700 + random(-20, 21);
} }
//Fake that we get CAN messages //Fake that we get CAN messages
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
/*Finally print out values to serial if configured to do so*/ /*Finally print out values to serial if configured to do so*/
#ifdef DEBUG_LOG #ifdef DEBUG_LOG
logging.println("FAKE Values going to inverter"); logging.println("FAKE Values going to inverter");
print_units("SOH%: ", (datalayer.battery.status.soh_pptt * 0.01), "% "); print_units("SOH%: ", (datalayer_battery->status.soh_pptt * 0.01), "% ");
print_units(", SOC%: ", (datalayer.battery.status.reported_soc * 0.01), "% "); print_units(", SOC%: ", (datalayer_battery->status.reported_soc * 0.01), "% ");
print_units(", Voltage: ", (datalayer.battery.status.voltage_dV * 0.1), "V "); print_units(", Voltage: ", (datalayer_battery->status.voltage_dV * 0.1), "V ");
print_units(", Max discharge power: ", datalayer.battery.status.max_discharge_power_W, "W "); print_units(", Max discharge power: ", datalayer_battery->status.max_discharge_power_W, "W ");
print_units(", Max charge power: ", datalayer.battery.status.max_charge_power_W, "W "); print_units(", Max charge power: ", datalayer_battery->status.max_charge_power_W, "W ");
print_units(", Max temp: ", (datalayer.battery.status.temperature_max_dC * 0.1), "°C "); print_units(", Max temp: ", (datalayer_battery->status.temperature_max_dC * 0.1), "°C ");
print_units(", Min temp: ", (datalayer.battery.status.temperature_min_dC * 0.1), "°C "); print_units(", Min temp: ", (datalayer_battery->status.temperature_min_dC * 0.1), "°C ");
print_units(", Max cell voltage: ", datalayer.battery.status.cell_max_voltage_mV, "mV "); print_units(", Max cell voltage: ", datalayer_battery->status.cell_max_voltage_mV, "mV ");
print_units(", Min cell voltage: ", datalayer.battery.status.cell_min_voltage_mV, "mV "); print_units(", Min cell voltage: ", datalayer_battery->status.cell_min_voltage_mV, "mV ");
logging.println(""); logging.println("");
#endif #endif
} }
#ifdef DOUBLE_BATTERY void TestFakeBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
void update_values_battery2() { // Handle the values coming in from battery #2
datalayer.battery2.info.number_of_cells = 96;
datalayer.battery2.status.real_soc = 5000; // 50.00%
datalayer.battery2.status.soh_pptt = 9900; // 99.00%
//datalayer.battery.status.voltage_dV = 3700; // 370.0V , value set in startup in .ino file, editable via webUI
datalayer.battery2.status.current_dA = 0; // 0 A
datalayer.battery2.info.total_capacity_Wh = 30000; // 30kWh
datalayer.battery2.status.remaining_capacity_Wh = 15000; // 15kWh
datalayer.battery2.status.cell_max_voltage_mV = 3596;
datalayer.battery2.status.cell_min_voltage_mV = 3500;
datalayer.battery2.status.temperature_min_dC = 50; // 5.0*C
datalayer.battery2.status.temperature_max_dC = 60; // 6.0*C
datalayer.battery2.status.max_discharge_power_W = 5000; // 5kW
datalayer.battery2.status.max_charge_power_W = 5000; // 5kW
for (int i = 0; i < 97; ++i) {
datalayer.battery2.status.cell_voltages_mV[i] = 3700 + random(-20, 21);
}
//Fake that we get CAN messages
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
/*Finally print out values to serial if configured to do so*/
#ifdef DEBUG_LOG
logging.println("FAKE Values battery 2 going to inverter");
print_units("SOH 2 %: ", (datalayer.battery2.status.soh_pptt * 0.01), "% ");
print_units(", SOC 2 %: ", (datalayer.battery2.status.reported_soc * 0.01), "% ");
print_units(", Voltage 2: ", (datalayer.battery2.status.voltage_dV * 0.1), "V ");
print_units(", Max discharge power 2: ", datalayer.battery2.status.max_discharge_power_W, "W ");
print_units(", Max charge power 2: ", datalayer.battery2.status.max_charge_power_W, "W ");
print_units(", Max temp 2: ", (datalayer.battery2.status.temperature_max_dC * 0.1), "°C ");
print_units(", Min temp 2: ", (datalayer.battery2.status.temperature_min_dC * 0.1), "°C ");
print_units(", Max cell voltage 2: ", datalayer.battery2.status.cell_max_voltage_mV, "mV ");
print_units(", Min cell voltage 2: ", datalayer.battery2.status.cell_min_voltage_mV, "mV ");
logging.println("");
#endif
} }
void handle_incoming_can_frame_battery2(CAN_frame rx_frame) { void TestFakeBattery::transmit_can(unsigned long currentMillis) {
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
}
#endif // DOUBLE_BATTERY
void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
}
void transmit_can_battery(unsigned long currentMillis) {
// Send 100ms CAN Message // Send 100ms CAN Message
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) { if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
previousMillis100 = currentMillis; previousMillis100 = currentMillis;
// Put fake messages here incase you want to test sending CAN // Put fake messages here incase you want to test sending CAN
//transmit_can_frame(&TEST, can_config.battery); //transmit_can_frame(&TEST, can_interface);
} }
} }
void setup_battery(void) { // Performs one time setup at startup void TestFakeBattery::setup(void) { // Performs one time setup at startup
randomSeed(analogRead(0)); randomSeed(analogRead(0));
strncpy(datalayer.system.info.battery_protocol, "Fake battery for testing purposes", 63); strncpy(datalayer.system.info.battery_protocol, "Fake battery for testing purposes", 63);
datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.max_design_voltage_dV = datalayer_battery->info.max_design_voltage_dV =
4040; // 404.4V, over this, charging is not possible (goes into forced discharge) 4040; // 404.4V, over this, charging is not possible (goes into forced discharge)
datalayer.battery.info.min_design_voltage_dV = 2450; // 245.0V under this, discharging further is disabled datalayer_battery->info.min_design_voltage_dV = 2450; // 245.0V under this, discharging further is disabled
datalayer.battery.info.number_of_cells = 96; datalayer_battery->info.number_of_cells = 96;
datalayer.system.status.battery_allows_contactor_closing = true;
if (allows_contactor_closing) {
*allows_contactor_closing = true;
}
} }
#endif #endif

View file

@ -1,11 +1,50 @@
#ifndef TEST_FAKE_BATTERY_H #ifndef TEST_FAKE_BATTERY_H
#define TEST_FAKE_BATTERY_H #define TEST_FAKE_BATTERY_H
#include "../datalayer/datalayer.h"
#include "../include.h" #include "../include.h"
#include "CanBattery.h"
#define BATTERY_SELECTED #define BATTERY_SELECTED
#define MAX_CELL_DEVIATION_MV 9999 #define SELECTED_BATTERY_CLASS TestFakeBattery
void setup_battery(void); class TestFakeBattery : public CanBattery {
void transmit_can_frame(CAN_frame* tx_frame, int interface); public:
// Use this constructor for the second battery.
TestFakeBattery(DATALAYER_BATTERY_TYPE* datalayer_ptr, int targetCan) {
datalayer_battery = datalayer_ptr;
can_interface = targetCan;
allows_contactor_closing = nullptr;
}
// Use the default constructor to create the first or single battery.
TestFakeBattery() {
datalayer_battery = &datalayer.battery;
can_interface = can_config.battery;
allows_contactor_closing = &datalayer.system.status.battery_allows_contactor_closing;
}
virtual void setup();
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
virtual void update_values();
virtual void transmit_can(unsigned long currentMillis);
private:
DATALAYER_BATTERY_TYPE* datalayer_battery;
int can_interface;
// If not null, this battery decides when the contactor can be closed and writes the value here.
bool* allows_contactor_closing;
static const int MAX_CELL_DEVIATION_MV = 9999;
unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was send
unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
unsigned long previousMillis10s = 0; // will store last time a 1s CAN Message was send
CAN_frame TEST = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x123,
.data = {0x10, 0x64, 0x00, 0xB0, 0x00, 0x1E, 0x00, 0x8F}};
};
#endif #endif

View file

@ -1,106 +1,13 @@
#include "../include.h" #include "../include.h"
#ifdef VOLVO_SPA_BATTERY #ifdef VOLVO_SPA_BATTERY
#include "../communication/can/comm_can.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"
#include "VOLVO-SPA-BATTERY.h" #include "VOLVO-SPA-BATTERY.h"
/* Do not change code below unless you are sure what you are doing */ void VolvoSpaBattery::
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send update_values() { //This function maps all the values fetched via CAN to the correct parameters used for the inverter
static unsigned long previousMillis1s = 0; // will store last time a 1s CAN Message was send
static unsigned long previousMillis60s = 0; // will store last time a 60s CAN Message was send
static float BATT_U = 0; //0x3A
static float MAX_U = 0; //0x3A
static float MIN_U = 0; //0x3A
static float BATT_I = 0; //0x3A
static int32_t CHARGE_ENERGY = 0; //0x1A1
static uint8_t BATT_ERR_INDICATION = 0; //0x413
static float BATT_T_MAX = 0; //0x413
static float BATT_T_MIN = 0; //0x413
static float BATT_T_AVG = 0; //0x413
static uint16_t SOC_BMS = 0; //0X37D
static uint16_t SOC_CALC = 0;
static uint16_t CELL_U_MAX = 3700; //0x37D
static uint16_t CELL_U_MIN = 3700; //0x37D
static uint8_t CELL_ID_U_MAX = 0; //0x37D
static uint16_t HvBattPwrLimDchaSoft = 0; //0x369
static uint16_t HvBattPwrLimDcha1 = 0; //0x175
static uint16_t HvBattPwrLimDchaSlowAgi = 0; //0x177
static uint16_t HvBattPwrLimChrgSlowAgi = 0; //0x177
static uint8_t batteryModuleNumber = 0x10; // First battery module
static uint8_t battery_request_idx = 0;
static uint8_t rxConsecutiveFrames = 0;
static uint16_t min_max_voltage[2]; //contains cell min[0] and max[1] values in mV
static uint8_t cellcounter = 0;
static uint16_t cell_voltages[108]; //array with all the cellvoltages
static bool startedUp = false;
static uint8_t DTC_reset_counter = 0;
CAN_frame VOLVO_536 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x536,
//.data = {0x00, 0x40, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00}}; //Network manage frame
.data = {0x00, 0x40, 0x40, 0x01, 0x00, 0x00, 0x00, 0x00}}; //Network manage frame
CAN_frame VOLVO_140_CLOSE = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x140,
.data = {0x00, 0x02, 0x00, 0xB7, 0xFF, 0x03, 0xFF, 0x82}}; //Close contactors message
CAN_frame VOLVO_140_OPEN = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x140,
.data = {0x00, 0x02, 0x00, 0x9E, 0xFF, 0x03, 0xFF, 0x82}}; //Open contactor message
CAN_frame VOLVO_372 = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x372,
.data = {0x00, 0xA6, 0x07, 0x14, 0x04, 0x00, 0x80, 0x00}}; //Ambient Temp -->>VERIFY this data content!!!<<--
CAN_frame VOLVO_CELL_U_Req = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x735,
.data = {0x03, 0x22, 0x4B, 0x00, 0x00, 0x00, 0x00, 0x00}}; //Cell voltage request frame
CAN_frame VOLVO_FlowControl = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x735,
.data = {0x30, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00}}; //Flowcontrol
CAN_frame VOLVO_SOH_Req = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x735,
.data = {0x03, 0x22, 0x49, 0x6D, 0x00, 0x00, 0x00, 0x00}}; //Battery SOH request frame
CAN_frame VOLVO_BECMsupplyVoltage_Req = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x735,
.data = {0x03, 0x22, 0xF4, 0x42, 0x00, 0x00, 0x00, 0x00}}; //BECM supply voltage request frame
CAN_frame VOLVO_DTC_Erase = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7FF,
.data = {0x04, 0x14, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00}}; //Global DTC erase
CAN_frame VOLVO_BECM_ECUreset = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x735,
.data = {0x02, 0x11, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00}}; //BECM ECU reset command (reboot/powercycle BECM)
CAN_frame VOLVO_DTCreadout = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7FF,
.data = {0x02, 0x19, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}}; //Global DTC readout
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for the inverter
uint8_t cnt = 0; uint8_t cnt = 0;
// Update webserver datalayer // Update webserver datalayer
@ -227,7 +134,7 @@ void update_values_battery() { //This function maps all the values fetched via
#endif #endif
} }
void handle_incoming_can_frame_battery(CAN_frame rx_frame) { void VolvoSpaBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
switch (rx_frame.ID) { switch (rx_frame.ID) {
case 0x3A: case 0x3A:
@ -427,7 +334,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
} }
} }
void readCellVoltages() { void VolvoSpaBattery::readCellVoltages() {
battery_request_idx = 0; battery_request_idx = 0;
batteryModuleNumber = 0x10; batteryModuleNumber = 0x10;
rxConsecutiveFrames = 0; rxConsecutiveFrames = 0;
@ -435,7 +342,7 @@ void readCellVoltages() {
transmit_can_frame(&VOLVO_CELL_U_Req, can_config.battery); //Send cell voltage read request for first module transmit_can_frame(&VOLVO_CELL_U_Req, can_config.battery); //Send cell voltage read request for first module
} }
void transmit_can_battery(unsigned long currentMillis) { void VolvoSpaBattery::transmit_can(unsigned long currentMillis) {
// Send 100ms CAN Message // Send 100ms CAN Message
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) { if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
previousMillis100 = currentMillis; previousMillis100 = currentMillis;
@ -470,7 +377,7 @@ void transmit_can_battery(unsigned long currentMillis) {
} }
} }
void setup_battery(void) { // Performs one time setup at startup void VolvoSpaBattery::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "Volvo / Polestar 69/78kWh SPA battery", 63); strncpy(datalayer.system.info.battery_protocol, "Volvo / Polestar 69/78kWh SPA battery", 63);
datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.number_of_cells = 0; // Initializes when all cells have been read datalayer.battery.info.number_of_cells = 0; // Initializes when all cells have been read

View file

@ -3,16 +3,122 @@
#include <Arduino.h> #include <Arduino.h>
#include "../include.h" #include "../include.h"
#define BATTERY_SELECTED #include "CanBattery.h"
#define MAX_PACK_VOLTAGE_108S_DV 4540
#define MIN_PACK_VOLTAGE_108S_DV 2938
#define MAX_PACK_VOLTAGE_96S_DV 4080
#define MIN_PACK_VOLTAGE_96S_DV 2620
#define MAX_CELL_DEVIATION_MV 250
#define MAX_CELL_VOLTAGE_MV 4260 // Charging is halted if one cell goes above this
#define MIN_CELL_VOLTAGE_MV 2700 // Charging is halted if one cell goes below this
void setup_battery(void); #define BATTERY_SELECTED
void transmit_can_frame(CAN_frame* tx_frame, int interface); #define SELECTED_BATTERY_CLASS VolvoSpaBattery
class VolvoSpaBattery : public CanBattery {
public:
virtual void setup(void);
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
virtual void update_values();
virtual void transmit_can(unsigned long currentMillis);
private:
void readCellVoltages();
static const int MAX_PACK_VOLTAGE_108S_DV = 4540;
static const int MIN_PACK_VOLTAGE_108S_DV = 2938;
static const int MAX_PACK_VOLTAGE_96S_DV = 4080;
static const int MIN_PACK_VOLTAGE_96S_DV = 2620;
static const int MAX_CELL_DEVIATION_MV = 250;
static const int MAX_CELL_VOLTAGE_MV = 4260; // Charging is halted if one cell goes above this
static const int MIN_CELL_VOLTAGE_MV = 2700; // Charging is halted if one cell goes below this
unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
unsigned long previousMillis1s = 0; // will store last time a 1s CAN Message was send
unsigned long previousMillis60s = 0; // will store last time a 60s CAN Message was send
float BATT_U = 0; //0x3A
float MAX_U = 0; //0x3A
float MIN_U = 0; //0x3A
float BATT_I = 0; //0x3A
int32_t CHARGE_ENERGY = 0; //0x1A1
uint8_t BATT_ERR_INDICATION = 0; //0x413
float BATT_T_MAX = 0; //0x413
float BATT_T_MIN = 0; //0x413
float BATT_T_AVG = 0; //0x413
uint16_t SOC_BMS = 0; //0X37D
uint16_t SOC_CALC = 0;
uint16_t CELL_U_MAX = 3700; //0x37D
uint16_t CELL_U_MIN = 3700; //0x37D
uint8_t CELL_ID_U_MAX = 0; //0x37D
uint16_t HvBattPwrLimDchaSoft = 0; //0x369
uint16_t HvBattPwrLimDcha1 = 0; //0x175
uint16_t HvBattPwrLimDchaSlowAgi = 0; //0x177
uint16_t HvBattPwrLimChrgSlowAgi = 0; //0x177
uint8_t batteryModuleNumber = 0x10; // First battery module
uint8_t battery_request_idx = 0;
uint8_t rxConsecutiveFrames = 0;
uint16_t min_max_voltage[2]; //contains cell min[0] and max[1] values in mV
uint8_t cellcounter = 0;
uint16_t cell_voltages[108]; //array with all the cellvoltages
bool startedUp = false;
uint8_t DTC_reset_counter = 0;
CAN_frame VOLVO_536 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x536,
//.data = {0x00, 0x40, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00}}; //Network manage frame
.data = {0x00, 0x40, 0x40, 0x01, 0x00, 0x00, 0x00, 0x00}}; //Network manage frame
CAN_frame VOLVO_140_CLOSE = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x140,
.data = {0x00, 0x02, 0x00, 0xB7, 0xFF, 0x03, 0xFF, 0x82}}; //Close contactors message
CAN_frame VOLVO_140_OPEN = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x140,
.data = {0x00, 0x02, 0x00, 0x9E, 0xFF, 0x03, 0xFF, 0x82}}; //Open contactor message
CAN_frame VOLVO_372 = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x372,
.data = {0x00, 0xA6, 0x07, 0x14, 0x04, 0x00, 0x80, 0x00}}; //Ambient Temp -->>VERIFY this data content!!!<<--
CAN_frame VOLVO_CELL_U_Req = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x735,
.data = {0x03, 0x22, 0x4B, 0x00, 0x00, 0x00, 0x00, 0x00}}; //Cell voltage request frame
CAN_frame VOLVO_FlowControl = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x735,
.data = {0x30, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00}}; //Flowcontrol
CAN_frame VOLVO_SOH_Req = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x735,
.data = {0x03, 0x22, 0x49, 0x6D, 0x00, 0x00, 0x00, 0x00}}; //Battery SOH request frame
CAN_frame VOLVO_BECMsupplyVoltage_Req = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x735,
.data = {0x03, 0x22, 0xF4, 0x42, 0x00, 0x00, 0x00, 0x00}}; //BECM supply voltage request frame
CAN_frame VOLVO_DTC_Erase = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7FF,
.data = {0x04, 0x14, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00}}; //Global DTC erase
CAN_frame VOLVO_BECM_ECUreset = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x735,
.data = {0x02, 0x11, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00}}; //BECM ECU reset command (reboot/powercycle BECM)
CAN_frame VOLVO_DTCreadout = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7FF,
.data = {0x02, 0x19, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}}; //Global DTC readout
};
#endif #endif

View file

@ -1,108 +1,13 @@
#include "../include.h" #include "../include.h"
#ifdef VOLVO_SPA_HYBRID_BATTERY #ifdef VOLVO_SPA_HYBRID_BATTERY
#include "../communication/can/comm_can.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"
#include "VOLVO-SPA-HYBRID-BATTERY.h" #include "VOLVO-SPA-HYBRID-BATTERY.h"
/* Do not change code below unless you are sure what you are doing */ void VolvoSpaHybridBattery::
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send update_values() { //This function maps all the values fetched via CAN to the correct parameters used for the inverter
static unsigned long previousMillis1s = 0; // will store last time a 1s CAN Message was send
static unsigned long previousMillis60s = 0; // will store last time a 60s CAN Message was send
static float BATT_U = 0; //0x3A
static float MAX_U = 0; //0x3A
static float MIN_U = 0; //0x3A
static float BATT_I = 0; //0x3A
static int32_t CHARGE_ENERGY = 0; //0x1A1
static uint8_t BATT_ERR_INDICATION = 0; //0x413
static float BATT_T_MAX = 0; //0x413
static float BATT_T_MIN = 0; //0x413
static float BATT_T_AVG = 0; //0x413
static uint16_t SOC_BMS = 0; //0X37D
static uint16_t SOC_CALC = 0;
static uint16_t CELL_U_MAX = 3700; //0x37D
static uint16_t CELL_U_MIN = 3700; //0x37D
static uint8_t CELL_ID_U_MAX = 0; //0x37D
static uint16_t HvBattPwrLimDchaSoft = 0; //0x369
static uint16_t HvBattPwrLimDcha1 = 0; //0x175
//static uint16_t HvBattPwrLimDchaSlowAgi = 0; //0x177
//static uint16_t HvBattPwrLimChrgSlowAgi = 0; //0x177
//static uint8_t batteryModuleNumber = 0x10; // First battery module
static uint8_t battery_request_idx = 0;
static uint8_t rxConsecutiveFrames = 0;
static uint16_t min_max_voltage[2]; //contains cell min[0] and max[1] values in mV
static uint8_t cellcounter = 0;
static uint32_t remaining_capacity = 0;
static uint16_t cell_voltages[102]; //array with all the cellvoltages
static bool startedUp = false;
static uint8_t DTC_reset_counter = 0;
CAN_frame VOLVO_536 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x536,
//.data = {0x00, 0x40, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00}}; //Network manage frame
.data = {0x00, 0x40, 0x40, 0x01, 0x00, 0x00, 0x00, 0x00}}; //Network manage frame
CAN_frame VOLVO_140_CLOSE = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x140,
.data = {0x00, 0x02, 0x00, 0xB7, 0xFF, 0x03, 0xFF, 0x82}}; //Close contactors message
CAN_frame VOLVO_140_OPEN = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x140,
.data = {0x00, 0x02, 0x00, 0x9E, 0xFF, 0x03, 0xFF, 0x82}}; //Open contactor message
CAN_frame VOLVO_372 = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x372,
.data = {0x00, 0xA6, 0x07, 0x14, 0x04, 0x00, 0x80, 0x00}}; //Ambient Temp -->>VERIFY this data content!!!<<--
CAN_frame VOLVO_CELL_U_Req = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x735,
.data = {0x03, 0x22, 0x48, 0x06, 0x00, 0x00, 0x00, 0x00}}; //Cell voltage request frame // changed
CAN_frame VOLVO_FlowControl = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x735,
.data = {0x30, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00}}; //Flowcontrol
CAN_frame VOLVO_SOH_Req = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x735,
.data = {0x03, 0x22, 0x49, 0x6D, 0x00, 0x00, 0x00, 0x00}}; //Battery SOH request frame
CAN_frame VOLVO_BECMsupplyVoltage_Req = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x735,
.data = {0x03, 0x22, 0xF4, 0x42, 0x00, 0x00, 0x00, 0x00}}; //BECM supply voltage request frame
CAN_frame VOLVO_DTC_Erase = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7FF,
.data = {0x04, 0x14, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00}}; //Global DTC erase
CAN_frame VOLVO_BECM_ECUreset = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x735,
.data = {0x02, 0x11, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00}}; //BECM ECU reset command (reboot/powercycle BECM)
CAN_frame VOLVO_DTCreadout = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7FF,
.data = {0x02, 0x19, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}}; //Global DTC readout
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for the inverter
uint8_t cnt = 0; uint8_t cnt = 0;
// Update webserver datalayer // Update webserver datalayer
@ -213,7 +118,7 @@ void update_values_battery() { //This function maps all the values fetched via
#endif #endif
} }
void handle_incoming_can_frame_battery(CAN_frame rx_frame) { void VolvoSpaHybridBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
switch (rx_frame.ID) { switch (rx_frame.ID) {
case 0x3A: case 0x3A:
@ -602,7 +507,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
} }
} }
void readCellVoltages() { void VolvoSpaHybridBattery::readCellVoltages() {
battery_request_idx = 0; battery_request_idx = 0;
//batteryModuleNumber = 0x10; //batteryModuleNumber = 0x10;
rxConsecutiveFrames = 0; rxConsecutiveFrames = 0;
@ -610,7 +515,7 @@ void readCellVoltages() {
transmit_can_frame(&VOLVO_CELL_U_Req, can_config.battery); //Send cell voltage read request for first module transmit_can_frame(&VOLVO_CELL_U_Req, can_config.battery); //Send cell voltage read request for first module
} }
void transmit_can_battery(unsigned long currentMillis) { void VolvoSpaHybridBattery::transmit_can(unsigned long currentMillis) {
// Send 100ms CAN Message // Send 100ms CAN Message
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) { if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
previousMillis100 = currentMillis; previousMillis100 = currentMillis;
@ -648,7 +553,7 @@ void transmit_can_battery(unsigned long currentMillis) {
} }
} }
void setup_battery(void) { // Performs one time setup at startup void VolvoSpaHybridBattery::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "Volvo PHEV battery", 63); //changed strncpy(datalayer.system.info.battery_protocol, "Volvo PHEV battery", 63); //changed
datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.number_of_cells = 102; //was 108, changed datalayer.battery.info.number_of_cells = 102; //was 108, changed

View file

@ -3,14 +3,122 @@
#include <Arduino.h> #include <Arduino.h>
#include "../include.h" #include "../include.h"
#define BATTERY_SELECTED #include "CanBattery.h"
#define MAX_PACK_VOLTAGE_DV 4294 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_DV 2754
#define MAX_CELL_DEVIATION_MV 250
#define MAX_CELL_VOLTAGE_MV 4210 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
void setup_battery(void); #define BATTERY_SELECTED
void transmit_can_frame(CAN_frame* tx_frame, int interface); #define SELECTED_BATTERY_CLASS VolvoSpaHybridBattery
class VolvoSpaHybridBattery : public CanBattery {
public:
virtual void setup(void);
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
virtual void update_values();
virtual void transmit_can(unsigned long currentMillis);
private:
void readCellVoltages();
static const int MAX_PACK_VOLTAGE_DV = 4294; //5000 = 500.0V
static const int MIN_PACK_VOLTAGE_DV = 2754;
static const int MAX_CELL_DEVIATION_MV = 250;
static const int MAX_CELL_VOLTAGE_MV = 4210; //Battery is put into emergency stop if one cell goes over this value
static const int MIN_CELL_VOLTAGE_MV = 2700; //Battery is put into emergency stop if one cell goes below this value
unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
unsigned long previousMillis1s = 0; // will store last time a 1s CAN Message was send
unsigned long previousMillis60s = 0; // will store last time a 60s CAN Message was send
float BATT_U = 0; //0x3A
float MAX_U = 0; //0x3A
float MIN_U = 0; //0x3A
float BATT_I = 0; //0x3A
int32_t CHARGE_ENERGY = 0; //0x1A1
uint8_t BATT_ERR_INDICATION = 0; //0x413
float BATT_T_MAX = 0; //0x413
float BATT_T_MIN = 0; //0x413
float BATT_T_AVG = 0; //0x413
uint16_t SOC_BMS = 0; //0X37D
uint16_t SOC_CALC = 0;
uint16_t CELL_U_MAX = 3700; //0x37D
uint16_t CELL_U_MIN = 3700; //0x37D
uint8_t CELL_ID_U_MAX = 0; //0x37D
uint16_t HvBattPwrLimDchaSoft = 0; //0x369
uint16_t HvBattPwrLimDcha1 = 0; //0x175
//uint16_t HvBattPwrLimDchaSlowAgi = 0; //0x177
//uint16_t HvBattPwrLimChrgSlowAgi = 0; //0x177
//uint8_t batteryModuleNumber = 0x10; // First battery module
uint8_t battery_request_idx = 0;
uint8_t rxConsecutiveFrames = 0;
uint16_t min_max_voltage[2]; //contains cell min[0] and max[1] values in mV
uint8_t cellcounter = 0;
uint32_t remaining_capacity = 0;
uint16_t cell_voltages[102]; //array with all the cellvoltages
bool startedUp = false;
uint8_t DTC_reset_counter = 0;
CAN_frame VOLVO_536 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x536,
//.data = {0x00, 0x40, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00}}; //Network manage frame
.data = {0x00, 0x40, 0x40, 0x01, 0x00, 0x00, 0x00, 0x00}}; //Network manage frame
CAN_frame VOLVO_140_CLOSE = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x140,
.data = {0x00, 0x02, 0x00, 0xB7, 0xFF, 0x03, 0xFF, 0x82}}; //Close contactors message
CAN_frame VOLVO_140_OPEN = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x140,
.data = {0x00, 0x02, 0x00, 0x9E, 0xFF, 0x03, 0xFF, 0x82}}; //Open contactor message
CAN_frame VOLVO_372 = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x372,
.data = {0x00, 0xA6, 0x07, 0x14, 0x04, 0x00, 0x80, 0x00}}; //Ambient Temp -->>VERIFY this data content!!!<<--
CAN_frame VOLVO_CELL_U_Req = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x735,
.data = {0x03, 0x22, 0x48, 0x06, 0x00, 0x00, 0x00, 0x00}}; //Cell voltage request frame // changed
CAN_frame VOLVO_FlowControl = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x735,
.data = {0x30, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00}}; //Flowcontrol
CAN_frame VOLVO_SOH_Req = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x735,
.data = {0x03, 0x22, 0x49, 0x6D, 0x00, 0x00, 0x00, 0x00}}; //Battery SOH request frame
CAN_frame VOLVO_BECMsupplyVoltage_Req = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x735,
.data = {0x03, 0x22, 0xF4, 0x42, 0x00, 0x00, 0x00, 0x00}}; //BECM supply voltage request frame
CAN_frame VOLVO_DTC_Erase = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7FF,
.data = {0x04, 0x14, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00}}; //Global DTC erase
CAN_frame VOLVO_BECM_ECUreset = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x735,
.data = {0x02, 0x11, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00}}; //BECM ECU reset command (reboot/powercycle BECM)
CAN_frame VOLVO_DTCreadout = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7FF,
.data = {0x02, 0x19, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}}; //Global DTC readout
};
#endif #endif

View file

@ -0,0 +1,9 @@
#include "../include.h"
CanCharger* charger = nullptr;
void setup_charger() {
#ifdef SELECTED_CHARGER_CLASS
charger = new SELECTED_CHARGER_CLASS();
#endif
}

View file

@ -2,15 +2,14 @@
#define CHARGERS_H #define CHARGERS_H
#include "../../USER_SETTINGS.h" #include "../../USER_SETTINGS.h"
#ifdef CHEVYVOLT_CHARGER
#include "CHEVY-VOLT-CHARGER.h" #include "CHEVY-VOLT-CHARGER.h"
#endif
#ifdef NISSANLEAF_CHARGER
#include "NISSAN-LEAF-CHARGER.h" #include "NISSAN-LEAF-CHARGER.h"
#endif
void map_can_frame_to_variable_charger(CAN_frame rx_frame); // Constructs the global charger object based on build-time selection of charger type.
void transmit_can_charger(unsigned long currentMillis); // Safe to call even though no charger is selected.
void setup_charger();
// The selected charger or null if no charger in use.
extern CanCharger* charger;
#endif #endif

View file

@ -1,7 +1,7 @@
#include "../include.h"
#ifdef CHEVYVOLT_CHARGER
#include "../datalayer/datalayer.h"
#include "CHEVY-VOLT-CHARGER.h" #include "CHEVY-VOLT-CHARGER.h"
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
#include "../include.h"
/* This implements Chevy Volt / Ampera charger support (2011-2015 model years). /* This implements Chevy Volt / Ampera charger support (2011-2015 model years).
* *
@ -19,29 +19,8 @@
* 2024 smaresca * 2024 smaresca
*/ */
/* CAN cycles and timers */
static unsigned long previousMillis30ms = 0; // 30ms cycle for keepalive frames
static unsigned long previousMillis200ms = 0; // 200ms cycle for commanding I/V targets
static unsigned long previousMillis5000ms = 0; // 5s status printout to serial
enum CHARGER_MODES : uint8_t { MODE_DISABLED = 0, MODE_LV, MODE_HV, MODE_HVLV };
//Actual content messages
static CAN_frame charger_keepalive_frame = {.FD = false,
.ext_ID = false,
.DLC = 1,
.ID = 0x30E, //one byte only, indicating enabled or disabled
.data = {MODE_DISABLED}};
static CAN_frame charger_set_targets = {
.FD = false,
.ext_ID = false,
.DLC = 4,
.ID = 0x304,
.data = {0x40, 0x00, 0x00, 0x00}}; // data[0] is a static value, meaning unknown
/* We are mostly sending out not receiving */ /* We are mostly sending out not receiving */
void map_can_frame_to_variable_charger(CAN_frame rx_frame) { void ChevyVoltCharger::map_can_frame_to_variable(CAN_frame rx_frame) {
uint16_t charger_stat_HVcur_temp = 0; uint16_t charger_stat_HVcur_temp = 0;
uint16_t charger_stat_HVvol_temp = 0; uint16_t charger_stat_HVvol_temp = 0;
uint16_t charger_stat_LVcur_temp = 0; uint16_t charger_stat_LVcur_temp = 0;
@ -92,13 +71,13 @@ void map_can_frame_to_variable_charger(CAN_frame rx_frame) {
break; break;
default: default:
#ifdef DEBUG_LOG #ifdef DEBUG_LOG
logging.printf("CAN Rcv unknown frame MsgID=%x\n", rx_frame.MsgID); logging.printf("CAN Rcv unknown frame MsgID=%x\n", rx_frame.ID);
#endif #endif
break; break;
} }
} }
void transmit_can_charger(unsigned long currentMillis) { void ChevyVoltCharger::transmit_can(unsigned long currentMillis) {
uint16_t Vol_temp = 0; uint16_t Vol_temp = 0;
uint16_t setpoint_HV_VDC = floor(datalayer.charger.charger_setpoint_HV_VDC); uint16_t setpoint_HV_VDC = floor(datalayer.charger.charger_setpoint_HV_VDC);
@ -170,12 +149,11 @@ void transmit_can_charger(unsigned long currentMillis) {
/* Serial echo every 5s of charger stats */ /* Serial echo every 5s of charger stats */
if (currentMillis - previousMillis5000ms >= INTERVAL_5_S) { if (currentMillis - previousMillis5000ms >= INTERVAL_5_S) {
previousMillis5000ms = currentMillis; previousMillis5000ms = currentMillis;
logging.printf("Charger AC in IAC=%fA VAC=%fV\n", charger_stat_ACcur, charger_stat_ACvol); logging.printf("Charger AC in IAC=%fA VAC=%fV\n", AC_input_current(), AC_input_voltage());
logging.printf("Charger HV out IDC=%fA VDC=%fV\n", charger_stat_HVcur, charger_stat_HVvol); logging.printf("Charger HV out IDC=%fA VDC=%fV\n", HVDC_output_current(), HVDC_output_voltage());
logging.printf("Charger LV out IDC=%fA VDC=%fV\n", charger_stat_LVcur, charger_stat_LVvol); logging.printf("Charger LV out IDC=%fA VDC=%fV\n", LVDC_output_current(), LVDC_output_voltage());
logging.printf("Charger mode=%s\n", (charger_mode > MODE_DISABLED) ? "Enabled" : "Disabled"); logging.printf("Charger mode=%s\n", (charger_mode > MODE_DISABLED) ? "Enabled" : "Disabled");
logging.printf("Charger HVset=%uV,%uA finishCurrent=%uA\n", setpoint_HV_VDC, setpoint_HV_IDC, setpoint_HV_IDC_END); logging.printf("Charger HVset=%uV,%uA finishCurrent=%uA\n", setpoint_HV_VDC, setpoint_HV_IDC, setpoint_HV_IDC_END);
} }
#endif #endif
} }
#endif

View file

@ -1,18 +1,62 @@
#ifndef CHEVYVOLT_CHARGER_H #ifndef CHEVYVOLT_CHARGER_H
#define CHEVYVOLT_CHARGER_H #define CHEVYVOLT_CHARGER_H
#include <Arduino.h> #include <Arduino.h>
#include "../datalayer/datalayer.h"
#include "../include.h" #include "../include.h"
#include "CanCharger.h"
#define CHARGER_SELECTED #ifdef CHEVYVOLT_CHARGER
#define SELECTED_CHARGER_CLASS ChevyVoltCharger
#endif
/* Charger hardware limits class ChevyVoltCharger : public CanCharger {
public:
ChevyVoltCharger() : CanCharger(ChargerType::ChevyVolt) {}
const char* name() { return "Chevy Volt Gen1 Charger"; }
void map_can_frame_to_variable(CAN_frame rx_frame);
void transmit_can(unsigned long currentMillis);
float outputPowerDC() {
return static_cast<float>(datalayer.charger.charger_stat_HVcur * datalayer.charger.charger_stat_HVvol);
}
bool efficiencySupported() { return true; }
float efficiency() {
float chgPwrAC = static_cast<float>(datalayer.charger.charger_stat_ACcur * datalayer.charger.charger_stat_ACvol);
return outputPowerDC() / chgPwrAC * 100;
}
private:
/* Charger hardware limits
* *
* Relative to runtime settings, expectations are: * Relative to runtime settings, expectations are:
* hw minimum <= setting minimum <= setting maximum <= hw max * hw minimum <= setting minimum <= setting maximum <= hw max
*/ */
#define CHEVYVOLT_MAX_HVDC 420.0 const float CHEVYVOLT_MAX_HVDC = 420.0;
#define CHEVYVOLT_MIN_HVDC 200.0 const float CHEVYVOLT_MIN_HVDC = 200.0;
#define CHEVYVOLT_MAX_AMP 11.5 const float CHEVYVOLT_MAX_AMP = 11.5;
#define CHEVYVOLT_MAX_POWER 3300 const float CHEVYVOLT_MAX_POWER = 3300;
/* CAN cycles and timers */
unsigned long previousMillis30ms = 0; // 30ms cycle for keepalive frames
unsigned long previousMillis200ms = 0; // 200ms cycle for commanding I/V targets
unsigned long previousMillis5000ms = 0; // 5s status printout to serial
enum CHARGER_MODES : uint8_t { MODE_DISABLED = 0, MODE_LV, MODE_HV, MODE_HVLV };
//Actual content messages
CAN_frame charger_keepalive_frame = {.FD = false,
.ext_ID = false,
.DLC = 1,
.ID = 0x30E, //one byte only, indicating enabled or disabled
.data = {MODE_DISABLED}};
CAN_frame charger_set_targets = {.FD = false,
.ext_ID = false,
.DLC = 4,
.ID = 0x304,
.data = {0x40, 0x00, 0x00, 0x00}}; // data[0] is a value, meaning unknown
};
#endif #endif

View file

@ -0,0 +1,48 @@
#ifndef CAN_CHARGER_H
#define CAN_CHARGER_H
#include "src/devboard/utils/types.h"
#include "../datalayer/datalayer.h"
enum class ChargerType { NissanLeaf, ChevyVolt };
// Generic base class for all chargers
class Charger {
public:
ChargerType type() { return m_type; }
virtual const char* name() = 0;
virtual float outputPowerDC() = 0;
virtual float HVDC_output_voltage() { return datalayer.charger.charger_stat_HVvol; }
virtual float HVDC_output_current() { return datalayer.charger.charger_stat_HVcur; }
virtual float LVDC_output_voltage() { return datalayer.charger.charger_stat_LVvol; }
virtual float LVDC_output_current() { return datalayer.charger.charger_stat_LVcur; }
virtual float AC_input_voltage() { return datalayer.charger.charger_stat_ACvol; }
virtual float AC_input_current() { return datalayer.charger.charger_stat_ACcur; }
virtual bool efficiencySupported() { return false; }
virtual float efficiency() { return 0; }
protected:
Charger(ChargerType type) : m_type(type) {}
private:
ChargerType m_type;
};
// Base class for chargers on a CAN bus
class CanCharger : public Charger {
public:
virtual void map_can_frame_to_variable(CAN_frame rx_frame) = 0;
virtual void transmit_can(unsigned long currentMillis) = 0;
protected:
CanCharger(ChargerType type) : Charger(type) {}
};
#endif

View file

@ -1,7 +1,7 @@
#include "../include.h"
#ifdef NISSANLEAF_CHARGER
#include "../datalayer/datalayer.h"
#include "NISSAN-LEAF-CHARGER.h" #include "NISSAN-LEAF-CHARGER.h"
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
#include "../include.h"
/* This implements Nissan LEAF PDM charger support. 2013-2024 Gen2/3 PDMs are supported /* This implements Nissan LEAF PDM charger support. 2013-2024 Gen2/3 PDMs are supported
* *
@ -20,68 +20,6 @@
* battery onto the CAN bus. * battery onto the CAN bus.
*/ */
/* CAN cycles and timers */
static unsigned long previousMillis10ms = 0;
static unsigned long previousMillis100ms = 0;
/* LEAF charger/battery parameters */
enum OBC_MODES : uint8_t {
IDLE_OR_QC = 1,
FINISHED = 2,
CHARGING_OR_INTERRUPTED = 4,
IDLE1 = 8,
IDLE2 = 9,
PLUGGED_IN_WAITING_ON_TIMER
};
enum OBC_VOLTAGES : uint8_t { NO_SIGNAL = 0, AC110 = 1, AC230 = 2, ABNORMAL_WAVE = 3 };
static uint16_t OBC_Charge_Power = 0; // Actual charger output
static uint8_t mprun100 = 0; // Counter 0-3
static uint8_t mprun10 = 0; // Counter 0-3
static uint8_t OBC_Charge_Status = IDLE_OR_QC;
static uint8_t OBC_Status_AC_Voltage = 0; //1=110V, 2=230V
static uint8_t OBCpowerSetpoint = 0;
static uint8_t OBCpower = 0;
static bool PPStatus = false;
static bool OBCwakeup = false;
//Actual content messages
static CAN_frame LEAF_1DB = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x1DB,
.data = {0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00}};
static CAN_frame LEAF_1DC = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x1DC,
.data = {0x6E, 0x0A, 0x05, 0xD5, 0x00, 0x00, 0x00, 0x00}};
static CAN_frame LEAF_1F2 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x1F2,
.data = {0x30, 0x00, 0x20, 0xAC, 0x00, 0x3C, 0x00, 0x8F}};
static CAN_frame LEAF_50B = {.FD = false,
.ext_ID = false,
.DLC = 7,
.ID = 0x50B,
.data = {0x00, 0x00, 0x06, 0xC0, 0x00, 0x00, 0x00}};
static CAN_frame LEAF_55B = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x55B,
.data = {0xA4, 0x40, 0xAA, 0x00, 0xDF, 0xC0, 0x10, 0x00}};
static CAN_frame LEAF_5BC = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x5BC,
.data = {0x3D, 0x80, 0xF0, 0x64, 0xB0, 0x01, 0x00, 0x32}};
static CAN_frame LEAF_59E = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x59E,
.data = {0x00, 0x00, 0x0C, 0x76, 0x18, 0x00, 0x00, 0x00}};
static uint8_t crctable[256] = { static uint8_t crctable[256] = {
0, 133, 143, 10, 155, 30, 20, 145, 179, 54, 60, 185, 40, 173, 167, 34, 227, 102, 108, 233, 120, 253, 0, 133, 143, 10, 155, 30, 20, 145, 179, 54, 60, 185, 40, 173, 167, 34, 227, 102, 108, 233, 120, 253,
247, 114, 80, 213, 223, 90, 203, 78, 68, 193, 67, 198, 204, 73, 216, 93, 87, 210, 240, 117, 127, 250, 247, 114, 80, 213, 223, 90, 203, 78, 68, 193, 67, 198, 204, 73, 216, 93, 87, 210, 240, 117, 127, 250,
@ -114,7 +52,7 @@ static uint8_t calculate_checksum_nibble(CAN_frame* frame) {
return sum; return sum;
} }
void map_can_frame_to_variable_charger(CAN_frame rx_frame) { void NissanLeafCharger::map_can_frame_to_variable(CAN_frame rx_frame) {
switch (rx_frame.ID) { switch (rx_frame.ID) {
case 0x679: // This message fires once when charging cable is plugged in case 0x679: // This message fires once when charging cable is plugged in
@ -156,7 +94,7 @@ void map_can_frame_to_variable_charger(CAN_frame rx_frame) {
} }
} }
void transmit_can_charger(unsigned long currentMillis) { void NissanLeafCharger::transmit_can(unsigned long currentMillis) {
/* Send keepalive with mode every 10ms */ /* Send keepalive with mode every 10ms */
if (currentMillis - previousMillis10ms >= INTERVAL_10_MS) { if (currentMillis - previousMillis10ms >= INTERVAL_10_MS) {
@ -246,4 +184,3 @@ void transmit_can_charger(unsigned long currentMillis) {
#endif #endif
} }
} }
#endif

View file

@ -3,6 +3,94 @@
#include <Arduino.h> #include <Arduino.h>
#include "../include.h" #include "../include.h"
#define CHARGER_SELECTED #include "CanCharger.h"
#ifdef NISSANLEAF_CHARGER
#define SELECTED_CHARGER_CLASS NissanLeafCharger
#endif
class NissanLeafCharger : public CanCharger {
public:
NissanLeafCharger() : CanCharger(ChargerType::NissanLeaf) {}
const char* name() { return "Nissan LEAF 2013-2024 PDM charger"; }
void map_can_frame_to_variable(CAN_frame rx_frame);
void transmit_can(unsigned long currentMillis);
float outputPowerDC() { return static_cast<float>(datalayer.charger.charger_stat_HVcur * 100); }
float HVDC_output_current() {
// P/U=I
if (datalayer.battery.status.voltage_dV > 0) {
return outputPowerDC() / (datalayer.battery.status.voltage_dV / 10);
}
return 0;
}
float HVDC_output_voltage() { return static_cast<float>(datalayer.battery.status.voltage_dV / 10); }
private:
/* CAN cycles and timers */
unsigned long previousMillis10ms = 0;
unsigned long previousMillis100ms = 0;
/* LEAF charger/battery parameters */
enum OBC_MODES : uint8_t {
IDLE_OR_QC = 1,
FINISHED = 2,
CHARGING_OR_INTERRUPTED = 4,
IDLE1 = 8,
IDLE2 = 9,
PLUGGED_IN_WAITING_ON_TIMER
};
enum OBC_VOLTAGES : uint8_t { NO_SIGNAL = 0, AC110 = 1, AC230 = 2, ABNORMAL_WAVE = 3 };
uint16_t OBC_Charge_Power = 0; // Actual charger output
uint8_t mprun100 = 0; // Counter 0-3
uint8_t mprun10 = 0; // Counter 0-3
uint8_t OBC_Charge_Status = IDLE_OR_QC;
uint8_t OBC_Status_AC_Voltage = 0; //1=110V, 2=230V
uint8_t OBCpowerSetpoint = 0;
uint8_t OBCpower = 0;
bool PPStatus = false;
bool OBCwakeup = false;
//Actual content messages
CAN_frame LEAF_1DB = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x1DB,
.data = {0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00}};
CAN_frame LEAF_1DC = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x1DC,
.data = {0x6E, 0x0A, 0x05, 0xD5, 0x00, 0x00, 0x00, 0x00}};
CAN_frame LEAF_1F2 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x1F2,
.data = {0x30, 0x00, 0x20, 0xAC, 0x00, 0x3C, 0x00, 0x8F}};
CAN_frame LEAF_50B = {.FD = false,
.ext_ID = false,
.DLC = 7,
.ID = 0x50B,
.data = {0x00, 0x00, 0x06, 0xC0, 0x00, 0x00, 0x00}};
CAN_frame LEAF_55B = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x55B,
.data = {0xA4, 0x40, 0xAA, 0x00, 0xDF, 0xC0, 0x10, 0x00}};
CAN_frame LEAF_5BC = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x5BC,
.data = {0x3D, 0x80, 0xF0, 0x64, 0xB0, 0x01, 0x00, 0x32}};
CAN_frame LEAF_59E = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x59E,
.data = {0x00, 0x00, 0x0C, 0x76, 0x18, 0x00, 0x00, 0x00}};
};
#endif #endif

View file

@ -119,9 +119,9 @@ void transmit_can(unsigned long currentMillis) {
transmit_can_inverter(currentMillis); transmit_can_inverter(currentMillis);
#endif // CAN_INVERTER_SELECTED #endif // CAN_INVERTER_SELECTED
#ifdef CHARGER_SELECTED if (charger) {
transmit_can_charger(currentMillis); charger->transmit_can(currentMillis);
#endif // CHARGER_SELECTED }
#ifdef CAN_SHUNT_SELECTED #ifdef CAN_SHUNT_SELECTED
transmit_can_shunt(currentMillis); transmit_can_shunt(currentMillis);
@ -330,10 +330,8 @@ void map_can_frame_to_variable(CAN_frame* rx_frame, int interface) {
handle_incoming_can_frame_battery2(*rx_frame); handle_incoming_can_frame_battery2(*rx_frame);
#endif #endif
} }
if (interface == can_config.charger) { if (interface == can_config.charger && charger) {
#ifdef CHARGER_SELECTED charger->map_can_frame_to_variable(*rx_frame);
map_can_frame_to_variable_charger(*rx_frame);
#endif
} }
if (interface == can_config.shunt) { if (interface == can_config.shunt) {
#ifdef CAN_SHUNT_SELECTED #ifdef CAN_SHUNT_SELECTED

View file

@ -225,7 +225,7 @@ void handle_contactors() {
#ifdef CONTACTOR_CONTROL_DOUBLE_BATTERY #ifdef CONTACTOR_CONTROL_DOUBLE_BATTERY
void handle_contactors_battery2() { void handle_contactors_battery2() {
if ((contactorStatus == COMPLETED) && datalayer.system.status.battery2_allows_contactor_closing) { if ((contactorStatus == COMPLETED) && datalayer.system.status.battery2_allowed_contactor_closing) {
set(SECOND_NEGATIVE_CONTACTOR_PIN, ON); set(SECOND_NEGATIVE_CONTACTOR_PIN, ON);
set(SECOND_POSITIVE_CONTACTOR_PIN, ON); set(SECOND_POSITIVE_CONTACTOR_PIN, ON);
datalayer.system.status.contactors_battery2_engaged = true; datalayer.system.status.contactors_battery2_engaged = true;

View file

@ -2,20 +2,22 @@
#include "../../datalayer/datalayer.h" #include "../../datalayer/datalayer.h"
#include "../../datalayer/datalayer_extended.h" #include "../../datalayer/datalayer_extended.h"
#include "../../include.h" #include "../../include.h"
// Parameters
#ifdef PRECHARGE_CONTROL #ifdef PRECHARGE_CONTROL
// Parameters
#define MAX_PRECHARGE_TIME_MS 15000 // Maximum time precharge may be enabled #define MAX_PRECHARGE_TIME_MS 15000 // Maximum time precharge may be enabled
#define Precharge_default_PWM_Freq 11000 #define Precharge_default_PWM_Freq 11000
#define Precharge_min_PWM_Freq 5000 #define Precharge_min_PWM_Freq 5000
#define Precharge_max_PWM_Freq 34000 #define Precharge_max_PWM_Freq 34000
#define PWM_Res 8 #define Precharge_PWM_Res 8
#define PWM_OFF_DUTY 0 #define PWM_Freq 20000 // 20 kHz frequency, beyond audible range
#define PWM_Precharge_Channel 0 #define PWM_Precharge_Channel 0
#ifndef INVERTER_DISCONNECT_CONTACTOR_IS_NORMALLY_OPEN
#define ON 0 //Normally closed contactors use inverted logic
#define OFF 1 //Normally closed contactors use inverted logic
#else
#define ON 1
#define OFF 0
#endif
unsigned long prechargeStartTime = 0; unsigned long prechargeStartTime = 0;
static uint32_t freq = Precharge_default_PWM_Freq; static uint32_t freq = Precharge_default_PWM_Freq;
uint16_t delta_freq = 1; uint16_t delta_freq = 1;
@ -28,48 +30,33 @@ void init_precharge_control() {
#ifdef DEBUG_LOG #ifdef DEBUG_LOG
logging.printf("Precharge control initialised\n"); logging.printf("Precharge control initialised\n");
#endif #endif
pinMode(PRECHARGE_PIN, OUTPUT); pinMode(HIA4V1_PIN, OUTPUT);
digitalWrite(PRECHARGE_PIN, LOW); digitalWrite(HIA4V1_PIN, LOW);
pinMode(POSITIVE_CONTACTOR_PIN, OUTPUT); pinMode(INVERTER_DISCONNECT_CONTACTOR_PIN, OUTPUT);
digitalWrite(POSITIVE_CONTACTOR_PIN, LOW); digitalWrite(INVERTER_DISCONNECT_CONTACTOR_PIN, LOW);
} }
// Main functions // Main functions
void handle_precharge_control() { void handle_precharge_control(unsigned long currentMillis) {
unsigned long currentTime = millis();
#ifdef MEB_BATTERY
int32_t target_voltage = datalayer.battery.status.voltage_dV; int32_t target_voltage = datalayer.battery.status.voltage_dV;
int32_t external_voltage = datalayer_extended.meb.BMS_voltage_intermediate_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 (datalayer.system.status.precharge_status) { switch (datalayer.system.status.precharge_status) {
case AUTO_PRECHARGE_IDLE: case AUTO_PRECHARGE_IDLE:
#if 0
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) {
datalayer.system.status.precharge_status = AUTO_PRECHARGE_START;
}
#else
if (datalayer.system.settings.start_precharging) { if (datalayer.system.settings.start_precharging) {
datalayer.system.status.precharge_status = AUTO_PRECHARGE_START; datalayer.system.status.precharge_status = AUTO_PRECHARGE_START;
} }
#endif
break; break;
case AUTO_PRECHARGE_START: case AUTO_PRECHARGE_START:
freq = Precharge_default_PWM_Freq; freq = Precharge_default_PWM_Freq;
ledcAttachChannel(PRECHARGE_PIN, freq, PWM_Res, PWM_Precharge_Channel); ledcAttachChannel(HIA4V1_PIN, freq, Precharge_PWM_Res, PWM_Precharge_Channel);
ledcWriteTone(PRECHARGE_PIN, freq); // Set frequency and set dutycycle to 50% ledcWriteTone(HIA4V1_PIN, freq); // Set frequency and set dutycycle to 50%
prechargeStartTime = currentTime; prechargeStartTime = currentMillis;
datalayer.system.status.precharge_status = AUTO_PRECHARGE_PRECHARGING; datalayer.system.status.precharge_status = AUTO_PRECHARGE_PRECHARGING;
#ifdef DEBUG_LOG #ifdef DEBUG_LOG
logging.printf("Precharge: Starting sequence\n"); logging.printf("Precharge: Starting sequence\n");
#endif #endif
digitalWrite(POSITIVE_CONTACTOR_PIN, HIGH); digitalWrite(INVERTER_DISCONNECT_CONTACTOR_PIN, OFF);
break; break;
case AUTO_PRECHARGE_PRECHARGING: case AUTO_PRECHARGE_PRECHARGING:
@ -97,24 +84,24 @@ void handle_precharge_control() {
logging.printf("Precharge: Target: %d V Extern: %d V Frequency: %u\n", target_voltage / 10, logging.printf("Precharge: Target: %d V Extern: %d V Frequency: %u\n", target_voltage / 10,
external_voltage / 10, freq); external_voltage / 10, freq);
#endif #endif
ledcWriteTone(PRECHARGE_PIN, freq); ledcWriteTone(HIA4V1_PIN, freq);
} }
if ((datalayer.battery.status.real_bms_status != BMS_STANDBY && if ((datalayer.battery.status.real_bms_status != BMS_STANDBY &&
datalayer.battery.status.real_bms_status != BMS_ACTIVE) || datalayer.battery.status.real_bms_status != BMS_ACTIVE) ||
datalayer.battery.status.bms_status != ACTIVE || datalayer.system.settings.equipment_stop_active) { datalayer.battery.status.bms_status != ACTIVE || datalayer.system.settings.equipment_stop_active) {
pinMode(PRECHARGE_PIN, OUTPUT); pinMode(HIA4V1_PIN, OUTPUT);
digitalWrite(PRECHARGE_PIN, LOW); digitalWrite(HIA4V1_PIN, LOW);
digitalWrite(POSITIVE_CONTACTOR_PIN, LOW); digitalWrite(INVERTER_DISCONNECT_CONTACTOR_PIN, ON);
datalayer.system.status.precharge_status = AUTO_PRECHARGE_IDLE; datalayer.system.status.precharge_status = AUTO_PRECHARGE_IDLE;
#ifdef DEBUG_LOG #ifdef DEBUG_LOG
logging.printf("Precharge: Disabling Precharge bms not standby/active or equipment stop\n"); logging.printf("Precharge: Disabling Precharge bms not standby/active or equipment stop\n");
#endif #endif
} else if (currentTime - prechargeStartTime >= MAX_PRECHARGE_TIME_MS || } else if (currentMillis - prechargeStartTime >= MAX_PRECHARGE_TIME_MS ||
datalayer.battery.status.real_bms_status == BMS_FAULT) { datalayer.battery.status.real_bms_status == BMS_FAULT) {
pinMode(PRECHARGE_PIN, OUTPUT); pinMode(HIA4V1_PIN, OUTPUT);
digitalWrite(PRECHARGE_PIN, LOW); digitalWrite(HIA4V1_PIN, LOW);
digitalWrite(POSITIVE_CONTACTOR_PIN, LOW); digitalWrite(INVERTER_DISCONNECT_CONTACTOR_PIN, ON);
datalayer.system.status.precharge_status = AUTO_PRECHARGE_OFF; datalayer.system.status.precharge_status = AUTO_PRECHARGE_OFF;
#ifdef DEBUG_LOG #ifdef DEBUG_LOG
logging.printf("Precharge: Disabled (timeout reached / BMS fault) -> AUTO_PRECHARGE_OFF\n"); logging.printf("Precharge: Disabled (timeout reached / BMS fault) -> AUTO_PRECHARGE_OFF\n");
@ -123,9 +110,9 @@ void handle_precharge_control() {
// Add event // Add event
} else if (datalayer.system.status.battery_allows_contactor_closing) { } else if (datalayer.system.status.battery_allows_contactor_closing) {
pinMode(PRECHARGE_PIN, OUTPUT); pinMode(HIA4V1_PIN, OUTPUT);
digitalWrite(PRECHARGE_PIN, LOW); digitalWrite(HIA4V1_PIN, LOW);
digitalWrite(POSITIVE_CONTACTOR_PIN, LOW); digitalWrite(INVERTER_DISCONNECT_CONTACTOR_PIN, ON);
datalayer.system.status.precharge_status = AUTO_PRECHARGE_COMPLETED; datalayer.system.status.precharge_status = AUTO_PRECHARGE_COMPLETED;
#ifdef DEBUG_LOG #ifdef DEBUG_LOG
logging.printf("Precharge: Disabled (contacts closed) -> COMPLETED\n"); logging.printf("Precharge: Disabled (contacts closed) -> COMPLETED\n");
@ -147,8 +134,8 @@ void handle_precharge_control() {
!datalayer.system.status.inverter_allows_contactor_closing || !datalayer.system.status.inverter_allows_contactor_closing ||
datalayer.system.settings.equipment_stop_active || datalayer.battery.status.bms_status != FAULT) { datalayer.system.settings.equipment_stop_active || datalayer.battery.status.bms_status != FAULT) {
datalayer.system.status.precharge_status = AUTO_PRECHARGE_IDLE; datalayer.system.status.precharge_status = AUTO_PRECHARGE_IDLE;
pinMode(PRECHARGE_PIN, OUTPUT); pinMode(HIA4V1_PIN, OUTPUT);
digitalWrite(PRECHARGE_PIN, LOW); digitalWrite(HIA4V1_PIN, LOW);
#ifdef DEBUG_LOG #ifdef DEBUG_LOG
logging.printf("Precharge: equipment stop activated -> IDLE\n"); logging.printf("Precharge: equipment stop activated -> IDLE\n");
#endif #endif
@ -159,4 +146,4 @@ void handle_precharge_control() {
break; break;
} }
} }
#endif // AUTO_PRECHARGE_CONTROL #endif // PRECHARGE_CONTROL

View file

@ -17,19 +17,10 @@ void init_precharge_control();
/** /**
* @brief Handle contactors * @brief Handle contactors
* *
* @param[in] void * @param[in] unsigned long currentMillis
* *
* @return void * @return void
*/ */
void handle_precharge_control(); void handle_precharge_control(unsigned long currentMillis);
/**
* @brief Handle contactors of battery 2
*
* @param[in] void
*
* @return void
*/
void handle_contactors_battery2();
#endif // _PRECHARGE_CONTROL_H_ #endif // _PRECHARGE_CONTROL_H_

View file

@ -1,19 +1,7 @@
#include "comm_rs485.h" #include "comm_rs485.h"
#include "../../include.h" #include "../../include.h"
// Parameters
#ifdef MODBUS_INVERTER_SELECTED
#define MB_RTU_NUM_VALUES 13100
uint16_t mbPV[MB_RTU_NUM_VALUES]; // Process variable memory
// Create a ModbusRTU server instance listening on Serial2 with 2000ms timeout
ModbusServerRTU MBserver(Serial2, 2000);
#endif
// Initialization functions
void init_rs485() { void init_rs485() {
// Set up Modbus RTU Server
#ifdef RS485_EN_PIN #ifdef RS485_EN_PIN
pinMode(RS485_EN_PIN, OUTPUT); pinMode(RS485_EN_PIN, OUTPUT);
digitalWrite(RS485_EN_PIN, HIGH); digitalWrite(RS485_EN_PIN, HIGH);
@ -26,21 +14,7 @@ void init_rs485() {
pinMode(PIN_5V_EN, OUTPUT); pinMode(PIN_5V_EN, OUTPUT);
digitalWrite(PIN_5V_EN, HIGH); digitalWrite(PIN_5V_EN, HIGH);
#endif // PIN_5V_EN #endif // PIN_5V_EN
#if defined(RS485_INVERTER_SELECTED) || defined(RS485_BATTERY_SELECTED)
Serial2.begin(RS485_BAUDRATE, SERIAL_8N1, RS485_RX_PIN, RS485_TX_PIN); // Inverters and batteries are expected to initialize their serial port in their setup-function
#endif // RS485_INVERTER_SELECTED || RS485_BATTERY_SELECTED // for RS485 or Modbus comms.
#ifdef MODBUS_INVERTER_SELECTED
// Init Static data to the RTU Modbus
handle_static_data_modbus();
// Init Serial2 connected to the RTU Modbus
RTUutils::prepareHardwareSerial(Serial2);
Serial2.begin(9600, SERIAL_8N1, RS485_RX_PIN, RS485_TX_PIN);
// Register served function code worker for server
MBserver.registerWorker(MBTCP_ID, READ_HOLD_REGISTER, &FC03);
MBserver.registerWorker(MBTCP_ID, WRITE_HOLD_REGISTER, &FC06);
MBserver.registerWorker(MBTCP_ID, WRITE_MULT_REGISTERS, &FC16);
MBserver.registerWorker(MBTCP_ID, R_W_MULT_REGISTERS, &FC23);
// Start ModbusRTU background task
MBserver.begin(Serial2, MODBUS_CORE);
#endif // MODBUS_INVERTER_SELECTED
} }

View file

@ -1,7 +1,9 @@
#ifndef _DATALAYER_H_ #ifndef _DATALAYER_H_
#define _DATALAYER_H_ #define _DATALAYER_H_
#include "../include.h" #include "../../USER_SETTINGS.h"
#include "../devboard/utils/types.h"
#include "../system_settings.h"
typedef struct { typedef struct {
/** uint32_t */ /** uint32_t */
@ -295,10 +297,12 @@ typedef struct {
* we report the inverter as missing entirely on the CAN bus. * we report the inverter as missing entirely on the CAN bus.
*/ */
uint8_t CAN_inverter_still_alive = CAN_STILL_ALIVE; uint8_t CAN_inverter_still_alive = CAN_STILL_ALIVE;
/** True if the battery allows for the contactors to close */ /** True if the primary battery allows for the contactors to close */
bool battery_allows_contactor_closing = false; bool battery_allows_contactor_closing = false;
/** True if the second battery allows for the contactors to close */
bool battery2_allows_contactor_closing = false; /** True if the second battery is allowed to close the contactors */
bool battery2_allowed_contactor_closing = false;
/** True if the inverter allows for the contactors to close */ /** True if the inverter allows for the contactors to close */
bool inverter_allows_contactor_closing = true; bool inverter_allows_contactor_closing = true;
#ifdef CONTACTOR_CONTROL #ifdef CONTACTOR_CONTROL

View file

@ -1,7 +1,8 @@
#ifndef _DATALAYER_EXTENDED_H_ #ifndef _DATALAYER_EXTENDED_H_
#define _DATALAYER_EXTENDED_H_ #define _DATALAYER_EXTENDED_H_
#include "../include.h" #include <stdint.h>
#include "../../USER_SETTINGS.h"
typedef struct { typedef struct {
/** uint16_t */ /** uint16_t */
@ -40,6 +41,9 @@ typedef struct {
} DATALAYER_INFO_BOLTAMPERA; } DATALAYER_INFO_BOLTAMPERA;
typedef struct { typedef struct {
/** User requesting contactor open or close via WebUI*/
bool UserRequestContactorClose = false;
bool UserRequestContactorOpen = false;
/** uint16_t */ /** uint16_t */
/** Terminal 30 - 12V SME Supply Voltage */ /** Terminal 30 - 12V SME Supply Voltage */
uint16_t T30_Voltage = 0; uint16_t T30_Voltage = 0;
@ -295,6 +299,33 @@ typedef struct {
uint16_t InsulationResistance = 0; uint16_t InsulationResistance = 0;
} DATALAYER_INFO_ECMP; } DATALAYER_INFO_ECMP;
typedef struct {
/** uint8_t */
/** Battery software/hardware/serial versions, stores raw HEX values for ASCII chars */
uint8_t BatterySoftwareVersion[16] = {0};
uint8_t BatteryHardwareVersion[16] = {0};
uint8_t BatterySerialNumber[28] = {0};
/** int16_t */
/** Module temperatures 1-6 */
int16_t ModuleTemperatures[6] = {0};
/** uint16_t */
/** Various values polled via OBD2 PIDs */
uint16_t soc = 0;
uint16_t CC2voltage = 0;
uint16_t cellMaxVoltageNumber = 0;
uint16_t cellMinVoltageNumber = 0;
uint16_t cellTotalAmount = 0;
uint16_t specificialVoltage = 0;
uint16_t unknown1 = 0;
uint16_t rawSOCmax = 0;
uint16_t rawSOCmin = 0;
uint16_t unknown4 = 0;
uint16_t capModMax = 0;
uint16_t capModMin = 0;
uint16_t unknown7 = 0;
uint16_t unknown8 = 0;
} DATALAYER_INFO_GEELY_GEOMETRY_C;
typedef struct { typedef struct {
uint8_t total_cell_count = 0; uint8_t total_cell_count = 0;
int16_t battery_12V = 0; int16_t battery_12V = 0;
@ -304,16 +335,6 @@ typedef struct {
uint8_t batteryManagementMode = 0; uint8_t batteryManagementMode = 0;
uint8_t BMS_ign = 0; uint8_t BMS_ign = 0;
uint8_t batteryRelay = 0; uint8_t batteryRelay = 0;
#ifdef DOUBLE_BATTERY
uint8_t battery2_total_cell_count = 0;
int16_t battery2_battery_12V = 0;
uint8_t battery2_waterleakageSensor = 0;
int8_t battery2_temperature_water_inlet = 0;
int8_t battery2_powerRelayTemperature = 0;
uint8_t battery2_batteryManagementMode = 0;
uint8_t battery2_BMS_ign = 0;
uint8_t battery2_batteryRelay = 0;
#endif //DOUBLE BATTERY
} DATALAYER_INFO_KIAHYUNDAI64; } DATALAYER_INFO_KIAHYUNDAI64;
typedef struct { typedef struct {
@ -781,7 +802,9 @@ class DataLayerExtended {
DATALAYER_INFO_CELLPOWER cellpower; DATALAYER_INFO_CELLPOWER cellpower;
DATALAYER_INFO_CMFAEV CMFAEV; DATALAYER_INFO_CMFAEV CMFAEV;
DATALAYER_INFO_ECMP stellantisECMP; DATALAYER_INFO_ECMP stellantisECMP;
DATALAYER_INFO_GEELY_GEOMETRY_C geometryC;
DATALAYER_INFO_KIAHYUNDAI64 KiaHyundai64; DATALAYER_INFO_KIAHYUNDAI64 KiaHyundai64;
DATALAYER_INFO_KIAHYUNDAI64 KiaHyundai64_2;
DATALAYER_INFO_TESLA tesla; DATALAYER_INFO_TESLA tesla;
DATALAYER_INFO_NISSAN_LEAF nissanleaf; DATALAYER_INFO_NISSAN_LEAF nissanleaf;
DATALAYER_INFO_MEB meb; DATALAYER_INFO_MEB meb;

View file

@ -61,6 +61,10 @@
// SMA CAN contactor pins // SMA CAN contactor pins
#define INVERTER_CONTACTOR_ENABLE_PIN 36 #define INVERTER_CONTACTOR_ENABLE_PIN 36
// Automatic precharging
#define HIA4V1_PIN 25
#define INVERTER_DISCONNECT_CONTACTOR_PIN 32
// SD card // SD card
//#define SD_MISO_PIN 2 //#define SD_MISO_PIN 2
//#define SD_MOSI_PIN 15 //#define SD_MOSI_PIN 15

View file

@ -62,6 +62,10 @@ The pin layout below supports the following:
// Equipment stop pin // Equipment stop pin
#define EQUIPMENT_STOP_PIN GPIO_NUM_12 #define EQUIPMENT_STOP_PIN GPIO_NUM_12
// Automatic precharging
#define HIA4V1_PIN GPIO_NUM_17
#define INVERTER_DISCONNECT_CONTACTOR_PIN GPIO_NUM_5
// BMW_I3_BATTERY wake up pin // BMW_I3_BATTERY wake up pin
#ifdef BMW_I3_BATTERY #ifdef BMW_I3_BATTERY
#define WUP_PIN1 GPIO_NUM_25 // Wake up pin for battery 1 #define WUP_PIN1 GPIO_NUM_25 // Wake up pin for battery 1

View file

@ -53,6 +53,10 @@
#define PRECHARGE_PIN 25 #define PRECHARGE_PIN 25
#define BMS_POWER 18 // Note, this pin collides with CAN add-ons and Chademo #define BMS_POWER 18 // Note, this pin collides with CAN add-ons and Chademo
// Automatic precharging
#define HIA4V1_PIN 25
#define INVERTER_DISCONNECT_CONTACTOR_PIN 32
// SMA CAN contactor pins // SMA CAN contactor pins
#define INVERTER_CONTACTOR_ENABLE_PIN 5 #define INVERTER_CONTACTOR_ENABLE_PIN 5

View file

@ -51,6 +51,10 @@ GPIOs on extra header
#define PRECHARGE_PIN 25 #define PRECHARGE_PIN 25
#define BMS_POWER 23 #define BMS_POWER 23
// Automatic precharging
#define HIA4V1_PIN 19 //Available as extra GPIO via pin header
#define INVERTER_DISCONNECT_CONTACTOR_PIN 25
// SMA CAN contactor pins // SMA CAN contactor pins
#define INVERTER_CONTACTOR_ENABLE_PIN 2 #define INVERTER_CONTACTOR_ENABLE_PIN 2

View file

@ -70,6 +70,7 @@ SensorConfig sensorConfigTemplate[] = {
{"state_of_health", "State Of Health", "", "%", "battery"}, {"state_of_health", "State Of Health", "", "%", "battery"},
{"temperature_min", "Temperature Min", "", "°C", "temperature"}, {"temperature_min", "Temperature Min", "", "°C", "temperature"},
{"temperature_max", "Temperature Max", "", "°C", "temperature"}, {"temperature_max", "Temperature Max", "", "°C", "temperature"},
{"cpu_temp", "CPU Temperature", "", "°C", "temperature"},
{"stat_batt_power", "Stat Batt Power", "", "W", "power"}, {"stat_batt_power", "Stat Batt Power", "", "W", "power"},
{"battery_current", "Battery Current", "", "A", "current"}, {"battery_current", "Battery Current", "", "A", "current"},
{"cell_max_voltage", "Cell Max Voltage", "", "V", "voltage"}, {"cell_max_voltage", "Cell Max Voltage", "", "V", "voltage"},
@ -169,6 +170,7 @@ void set_battery_attributes(JsonDocument& doc, const DATALAYER_BATTERY_TYPE& bat
doc["state_of_health" + suffix] = ((float)battery.status.soh_pptt) / 100.0; doc["state_of_health" + suffix] = ((float)battery.status.soh_pptt) / 100.0;
doc["temperature_min" + suffix] = ((float)((int16_t)battery.status.temperature_min_dC)) / 10.0; doc["temperature_min" + suffix] = ((float)((int16_t)battery.status.temperature_min_dC)) / 10.0;
doc["temperature_max" + suffix] = ((float)((int16_t)battery.status.temperature_max_dC)) / 10.0; doc["temperature_max" + suffix] = ((float)((int16_t)battery.status.temperature_max_dC)) / 10.0;
doc["cpu_temp" + suffix] = datalayer.system.info.CPU_temperature;
doc["stat_batt_power" + suffix] = ((float)((int32_t)battery.status.active_power_W)); doc["stat_batt_power" + suffix] = ((float)((int32_t)battery.status.active_power_W));
doc["battery_current" + suffix] = ((float)((int16_t)battery.status.current_dA)) / 10.0; doc["battery_current" + suffix] = ((float)((int16_t)battery.status.current_dA)) / 10.0;
doc["battery_voltage" + suffix] = ((float)battery.status.voltage_dV) / 10.0; doc["battery_voltage" + suffix] = ((float)battery.status.voltage_dV) / 10.0;
@ -484,6 +486,7 @@ void mqtt_message_received(char* topic_raw, int topic_len, char* data, int data_
if (strcmp(topic, generateButtonTopic("STOP").c_str()) == 0) { if (strcmp(topic, generateButtonTopic("STOP").c_str()) == 0) {
setBatteryPause(true, false, true); setBatteryPause(true, false, true);
} }
free(topic);
} }
static void mqtt_event_handler(void* handler_args, esp_event_base_t base, int32_t event_id, void* event_data) { static void mqtt_event_handler(void* handler_args, esp_event_base_t base, int32_t event_id, void* event_data) {

View file

@ -231,7 +231,7 @@ void update_machineryprotection() {
} }
#endif //CAN_INVERTER_SELECTED #endif //CAN_INVERTER_SELECTED
#ifdef CHARGER_SELECTED if (charger) {
// Check if the charger is still sending CAN messages. If we go 60s without messages we raise a warning // Check if the charger is still sending CAN messages. If we go 60s without messages we raise a warning
if (!datalayer.charger.CAN_charger_still_alive) { if (!datalayer.charger.CAN_charger_still_alive) {
set_event(EVENT_CAN_CHARGER_MISSING, can_config.charger); set_event(EVENT_CAN_CHARGER_MISSING, can_config.charger);
@ -239,7 +239,7 @@ void update_machineryprotection() {
datalayer.charger.CAN_charger_still_alive--; datalayer.charger.CAN_charger_still_alive--;
clear_event(EVENT_CAN_CHARGER_MISSING); clear_event(EVENT_CAN_CHARGER_MISSING);
} }
#endif //CHARGER_SELECTED }
#ifdef DOUBLE_BATTERY // Additional Double-Battery safeties are checked here #ifdef DOUBLE_BATTERY // Additional Double-Battery safeties are checked here
// Check if the Battery 2 BMS is still sending CAN messages. If we go 60s without messages we raise a warning // Check if the Battery 2 BMS is still sending CAN messages. If we go 60s without messages we raise a warning

View file

@ -58,6 +58,8 @@ String advanced_battery_processor(const String& var) {
#endif //BOLT_AMPERA_BATTERY #endif //BOLT_AMPERA_BATTERY
#ifdef BMW_IX_BATTERY #ifdef BMW_IX_BATTERY
content += "<button onclick='askContactorClose()'>Close Contactors</button>";
content += "<button onclick='askContactorOpen()'>Open Contactors</button>";
content += content +=
"<h4>Battery Voltage after Contactor: " + String(datalayer_extended.bmwix.battery_voltage_after_contactor) + "<h4>Battery Voltage after Contactor: " + String(datalayer_extended.bmwix.battery_voltage_after_contactor) +
" dV</h4>"; " dV</h4>";
@ -469,32 +471,67 @@ String advanced_battery_processor(const String& var) {
"<h4>Insulation Resistance: " + String(datalayer_extended.stellantisECMP.InsulationResistance) + "kOhm</h4>"; "<h4>Insulation Resistance: " + String(datalayer_extended.stellantisECMP.InsulationResistance) + "kOhm</h4>";
#endif //STELLANTIS_ECMP_BATTERY #endif //STELLANTIS_ECMP_BATTERY
#ifdef GEELY_GEOMETRY_C_BATTERY
char readableSerialNumber[29]; // One extra space for null terminator
memcpy(readableSerialNumber, datalayer_extended.geometryC.BatterySerialNumber,
sizeof(datalayer_extended.geometryC.BatterySerialNumber));
readableSerialNumber[28] = '\0'; // Null terminate the string
char readableSoftwareVersion[17]; // One extra space for null terminator
memcpy(readableSoftwareVersion, datalayer_extended.geometryC.BatterySoftwareVersion,
sizeof(datalayer_extended.geometryC.BatterySoftwareVersion));
readableSoftwareVersion[16] = '\0'; // Null terminate the string
char readableHardwareVersion[17]; // One extra space for null terminator
memcpy(readableHardwareVersion, datalayer_extended.geometryC.BatteryHardwareVersion,
sizeof(datalayer_extended.geometryC.BatteryHardwareVersion));
readableHardwareVersion[16] = '\0'; // Null terminate the string
content += "<h4>Serial number: " + String(readableSoftwareVersion) + "</h4>";
content += "<h4>Software version: " + String(readableSerialNumber) + "</h4>";
content += "<h4>Hardware version: " + String(readableHardwareVersion) + "</h4>";
content += "<h4>SOC display: " + String(datalayer_extended.geometryC.soc) + "ppt</h4>";
content += "<h4>CC2 voltage: " + String(datalayer_extended.geometryC.CC2voltage) + "mV</h4>";
content += "<h4>Cell max voltage number: " + String(datalayer_extended.geometryC.cellMaxVoltageNumber) + "</h4>";
content += "<h4>Cell min voltage number: " + String(datalayer_extended.geometryC.cellMinVoltageNumber) + "</h4>";
content += "<h4>Cell total amount: " + String(datalayer_extended.geometryC.cellTotalAmount) + "S</h4>";
content += "<h4>Specificial Voltage: " + String(datalayer_extended.geometryC.specificialVoltage) + "dV</h4>";
content += "<h4>Unknown1: " + String(datalayer_extended.geometryC.unknown1) + "</h4>";
content += "<h4>Raw SOC max: " + String(datalayer_extended.geometryC.rawSOCmax) + "</h4>";
content += "<h4>Raw SOC min: " + String(datalayer_extended.geometryC.rawSOCmin) + "</h4>";
content += "<h4>Unknown4: " + String(datalayer_extended.geometryC.unknown4) + "</h4>";
content += "<h4>Capacity module max: " + String((datalayer_extended.geometryC.capModMax / 10)) + "Ah</h4>";
content += "<h4>Capacity module min: " + String((datalayer_extended.geometryC.capModMin / 10)) + "Ah</h4>";
content += "<h4>Unknown7: " + String(datalayer_extended.geometryC.unknown7) + "</h4>";
content += "<h4>Unknown8: " + String(datalayer_extended.geometryC.unknown8) + "</h4>";
content +=
"<h4>Module 1 temperature: " + String(datalayer_extended.geometryC.ModuleTemperatures[0]) + " &deg;C</h4>";
content +=
"<h4>Module 2 temperature: " + String(datalayer_extended.geometryC.ModuleTemperatures[1]) + " &deg;C</h4>";
content +=
"<h4>Module 3 temperature: " + String(datalayer_extended.geometryC.ModuleTemperatures[2]) + " &deg;C</h4>";
content +=
"<h4>Module 4 temperature: " + String(datalayer_extended.geometryC.ModuleTemperatures[3]) + " &deg;C</h4>";
content +=
"<h4>Module 5 temperature: " + String(datalayer_extended.geometryC.ModuleTemperatures[4]) + " &deg;C</h4>";
content +=
"<h4>Module 6 temperature: " + String(datalayer_extended.geometryC.ModuleTemperatures[5]) + " &deg;C</h4>";
#endif //GEELY_GEOMETRY_C_BATTERY
#ifdef KIA_HYUNDAI_64_BATTERY #ifdef KIA_HYUNDAI_64_BATTERY
content += "<h4>Cells: " + String(datalayer_extended.KiaHyundai64.total_cell_count) + "S</h4>"; auto print_hyundai = [&content](DATALAYER_INFO_KIAHYUNDAI64& data) {
content += "<h4>12V voltage: " + String(datalayer_extended.KiaHyundai64.battery_12V / 10.0, 1) + "</h4>"; content += "<h4>Cells: " + String(data.total_cell_count) + "S</h4>";
content += "<h4>Waterleakage: " + String(datalayer_extended.KiaHyundai64.waterleakageSensor) + "</h4>"; content += "<h4>12V voltage: " + String(data.battery_12V / 10.0, 1) + "</h4>";
content += content += "<h4>Waterleakage: " + String(data.waterleakageSensor) + "</h4>";
"<h4>Temperature, water inlet: " + String(datalayer_extended.KiaHyundai64.temperature_water_inlet) + "</h4>"; content += "<h4>Temperature, water inlet: " + String(data.temperature_water_inlet) + "</h4>";
content += content += "<h4>Temperature, power relay: " + String(data.powerRelayTemperature) + "</h4>";
"<h4>Temperature, power relay: " + String(datalayer_extended.KiaHyundai64.powerRelayTemperature) + "</h4>"; content += "<h4>Batterymanagement mode: " + String(data.batteryManagementMode) + "</h4>";
content += "<h4>Batterymanagement mode: " + String(datalayer_extended.KiaHyundai64.batteryManagementMode) + "</h4>"; content += "<h4>BMS ignition: " + String(data.BMS_ign) + "</h4>";
content += "<h4>BMS ignition: " + String(datalayer_extended.KiaHyundai64.BMS_ign) + "</h4>"; content += "<h4>Battery relay: " + String(data.batteryRelay) + "</h4>";
content += "<h4>Battery relay: " + String(datalayer_extended.KiaHyundai64.batteryRelay) + "</h4>"; };
print_hyundai(datalayer_extended.KiaHyundai64);
#ifdef DOUBLE_BATTERY #ifdef DOUBLE_BATTERY
content += "<h4>Values from battery 2</h4>"; content += "<h4>Values from battery 2</h4>";
content += "<h4>Cells: " + String(datalayer_extended.KiaHyundai64.battery2_total_cell_count) + "S</h4>"; print_hyundai(datalayer_extended.KiaHyundai64_2);
content += "<h4>12V voltage: " + String(datalayer_extended.KiaHyundai64.battery2_battery_12V / 10.0, 1) + "</h4>";
content += "<h4>Waterleakage: " + String(datalayer_extended.KiaHyundai64.battery2_waterleakageSensor) + "</h4>";
content +=
"<h4>Temperature, water inlet: " + String(datalayer_extended.KiaHyundai64.battery2_temperature_water_inlet) +
"</h4>";
content +=
"<h4>Temperature, power relay: " + String(datalayer_extended.KiaHyundai64.battery2_powerRelayTemperature) +
"</h4>";
content += "<h4>Batterymanagement mode: " + String(datalayer_extended.KiaHyundai64.battery2_batteryManagementMode) +
"</h4>";
content += "<h4>BMS ignition: " + String(datalayer_extended.KiaHyundai64.battery2_BMS_ign) + "</h4>";
content += "<h4>Battery relay: " + String(datalayer_extended.KiaHyundai64.battery2_batteryRelay) + "</h4>";
#endif //DOUBLE_BATTERY #endif //DOUBLE_BATTERY
#endif //KIA_HYUNDAI_64_BATTERY #endif //KIA_HYUNDAI_64_BATTERY
@ -1500,12 +1537,14 @@ String advanced_battery_processor(const String& var) {
!defined(TESLA_BATTERY) && !defined(NISSAN_LEAF_BATTERY) && !defined(BMW_I3_BATTERY) && \ !defined(TESLA_BATTERY) && !defined(NISSAN_LEAF_BATTERY) && !defined(BMW_I3_BATTERY) && \
!defined(BYD_ATTO_3_BATTERY) && !defined(RENAULT_ZOE_GEN2_BATTERY) && !defined(CELLPOWER_BMS) && \ !defined(BYD_ATTO_3_BATTERY) && !defined(RENAULT_ZOE_GEN2_BATTERY) && !defined(CELLPOWER_BMS) && \
!defined(MEB_BATTERY) && !defined(VOLVO_SPA_BATTERY) && !defined(VOLVO_SPA_HYBRID_BATTERY) && \ !defined(MEB_BATTERY) && !defined(VOLVO_SPA_BATTERY) && !defined(VOLVO_SPA_HYBRID_BATTERY) && \
!defined(KIA_HYUNDAI_64_BATTERY) && !defined(CMFA_EV_BATTERY) && \ !defined(KIA_HYUNDAI_64_BATTERY) && !defined(CMFA_EV_BATTERY) && !defined(STELLANTIS_ECMP_BATTERY) && \
!defined(STELLANTIS_ECMP_BATTERY) //Only the listed types have extra info !defined(KIA_HYUNDAI_64_BATTERY) && !defined(GEELY_GEOMETRY_C_BATTERY) && \
!defined(CMFA_EV_BATTERY) //Only the listed types have extra info
content += "No extra information available for this battery type"; content += "No extra information available for this battery type";
#endif #endif
content += "</div>"; content += "</div>";
content += "<script>"; content += "<script>";
content += content +=
"function askTeslaClearIsolation() { if (window.confirm('Are you sure you want to clear any active isolation " "function askTeslaClearIsolation() { if (window.confirm('Are you sure you want to clear any active isolation "
@ -1518,6 +1557,7 @@ String advanced_battery_processor(const String& var) {
content += "}"; content += "}";
content += "function goToMainPage() { window.location.href = '/'; }"; content += "function goToMainPage() { window.location.href = '/'; }";
content += "</script>"; content += "</script>";
content += "<script>"; content += "<script>";
content += content +=
"function askTeslaResetBMS() { if (window.confirm('Are you sure you want to reset the " "function askTeslaResetBMS() { if (window.confirm('Are you sure you want to reset the "
@ -1530,6 +1570,7 @@ String advanced_battery_processor(const String& var) {
content += "}"; content += "}";
content += "function goToMainPage() { window.location.href = '/'; }"; content += "function goToMainPage() { window.location.href = '/'; }";
content += "</script>"; content += "</script>";
content += "<script>"; content += "<script>";
content += content +=
"function askResetCrash() { if (window.confirm('Are you sure you want to reset crash data? " "function askResetCrash() { if (window.confirm('Are you sure you want to reset crash data? "
@ -1554,6 +1595,33 @@ String advanced_battery_processor(const String& var) {
content += "}"; content += "}";
content += "function goToMainPage() { window.location.href = '/'; }"; content += "function goToMainPage() { window.location.href = '/'; }";
content += "</script>"; content += "</script>";
content += "<script>";
content +=
"function askContactorClose() { if (window.confirm('Are you sure you want to tirgger "
"a contactor close request?')) { "
"bmwIxCloseContactorRequest(); } }";
content += "function bmwIxCloseContactorRequest() {";
content += " var xhr = new XMLHttpRequest();";
content += " xhr.open('GET', '/bmwIxCloseContactorRequest', true);";
content += " xhr.send();";
content += "}";
content += "function goToMainPage() { window.location.href = '/'; }";
content += "</script>";
content += "<script>";
content +=
"function askContactorOpen() { if (window.confirm('Are you sure you want to tirgger "
"a contactor open request?')) { "
"bmwIxOpenContactorRequest(); } }";
content += "function bmwIxOpenContactorRequest() {";
content += " var xhr = new XMLHttpRequest();";
content += " xhr.open('GET', '/bmwIxOpenContactorRequest', true);";
content += " xhr.send();";
content += "}";
content += "function goToMainPage() { window.location.href = '/'; }";
content += "</script>";
content += "<script>"; content += "<script>";
content += content +=
"function askResetSOH() { if (window.confirm('Are you sure you want to reset degradation data? " "function askResetSOH() { if (window.confirm('Are you sure you want to reset degradation data? "
@ -1566,6 +1634,7 @@ String advanced_battery_processor(const String& var) {
content += "}"; content += "}";
content += "function goToMainPage() { window.location.href = '/'; }"; content += "function goToMainPage() { window.location.href = '/'; }";
content += "</script>"; content += "</script>";
content += "<script>"; content += "<script>";
content += content +=
"function Volvo_askEraseDTC() { if (window.confirm('Are you sure you want to erase DTCs?')) { " "function Volvo_askEraseDTC() { if (window.confirm('Are you sure you want to erase DTCs?')) { "
@ -1599,6 +1668,7 @@ String advanced_battery_processor(const String& var) {
content += "}"; content += "}";
content += "function goToMainPage() { window.location.href = '/'; }"; content += "function goToMainPage() { window.location.href = '/'; }";
content += "</script>"; content += "</script>";
// Additial functions added // Additial functions added
content += "<script>"; content += "<script>";
content += "function exportLog() { window.location.href = '/export_log'; }"; content += "function exportLog() { window.location.href = '/export_log'; }";

Some files were not shown because too many files have changed in this diff Show more