From fc109cd954545a86659605a2d2c68b3863e9be3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Tue, 9 Sep 2025 21:08:07 +0300 Subject: [PATCH] Add Rivian battery support --- Software/src/battery/BATTERIES.cpp | 8 +- Software/src/battery/BATTERIES.h | 1 + Software/src/battery/Battery.h | 1 + Software/src/battery/RIVIAN-BATTERY.cpp | 177 ++++++++++++++++++++++++ Software/src/battery/RIVIAN-BATTERY.h | 60 ++++++++ 5 files changed, 243 insertions(+), 4 deletions(-) create mode 100644 Software/src/battery/RIVIAN-BATTERY.cpp create mode 100644 Software/src/battery/RIVIAN-BATTERY.h diff --git a/Software/src/battery/BATTERIES.cpp b/Software/src/battery/BATTERIES.cpp index f4639e26..a58c1062 100644 --- a/Software/src/battery/BATTERIES.cpp +++ b/Software/src/battery/BATTERIES.cpp @@ -116,6 +116,8 @@ const char* name_for_battery_type(BatteryType type) { return RenaultZoeGen1Battery::Name; case BatteryType::RenaultZoe2: return RenaultZoeGen2Battery::Name; + case BatteryType::RivianBattery: + return RivianBattery::Name; case BatteryType::SamsungSdiLv: return SamsungSdiLVBattery::Name; case BatteryType::SantaFePhev: @@ -137,11 +139,7 @@ const char* name_for_battery_type(BatteryType type) { } } -#ifdef LFP_CHEMISTRY -const battery_chemistry_enum battery_chemistry_default = battery_chemistry_enum::LFP; -#else const battery_chemistry_enum battery_chemistry_default = battery_chemistry_enum::NMC; -#endif battery_chemistry_enum user_selected_battery_chemistry = battery_chemistry_default; @@ -216,6 +214,8 @@ Battery* create_battery(BatteryType type) { return new RenaultZoeGen1Battery(); case BatteryType::RenaultZoe2: return new RenaultZoeGen2Battery(); + case BatteryType::RivianBattery: + return new RivianBattery(); case BatteryType::SamsungSdiLv: return new SamsungSdiLVBattery(); case BatteryType::SantaFePhev: diff --git a/Software/src/battery/BATTERIES.h b/Software/src/battery/BATTERIES.h index 84ebbe87..0324c189 100644 --- a/Software/src/battery/BATTERIES.h +++ b/Software/src/battery/BATTERIES.h @@ -44,6 +44,7 @@ void setup_can_shunt(); #include "RENAULT-TWIZY.h" #include "RENAULT-ZOE-GEN1-BATTERY.h" #include "RENAULT-ZOE-GEN2-BATTERY.h" +#include "RIVIAN-BATTERY.h" #include "RJXZS-BMS.h" #include "SAMSUNG-SDI-LV-BATTERY.h" #include "SANTA-FE-PHEV-BATTERY.h" diff --git a/Software/src/battery/Battery.h b/Software/src/battery/Battery.h index 0b99f588..c929519f 100644 --- a/Software/src/battery/Battery.h +++ b/Software/src/battery/Battery.h @@ -47,6 +47,7 @@ enum class BatteryType { HyundaiIoniq28 = 39, Kia64FD = 40, RelionBattery = 41, + RivianBattery = 42, Highest }; diff --git a/Software/src/battery/RIVIAN-BATTERY.cpp b/Software/src/battery/RIVIAN-BATTERY.cpp new file mode 100644 index 00000000..4492710b --- /dev/null +++ b/Software/src/battery/RIVIAN-BATTERY.cpp @@ -0,0 +1,177 @@ +#include "RIVIAN-BATTERY.h" + +#include "../battery/BATTERIES.h" +#include "../communication/can/comm_can.h" +#include "../datalayer/datalayer.h" +#include "../devboard/utils/events.h" + +/* +Initial support for Rivian BIG battery (135kWh) +The battery has 3x CAN channels +- Failover CAN (CAN-FD) lots of content, not required for operation +- Platform CAN (500kbps) with all the control messages needed to control the battery <- This is the one we want +- Battery CAN (500kbps) lots of content, not required for operation +*/ + +void RivianBattery::update_values() { + + datalayer.battery.status.real_soc = battery_SOC; + + datalayer.battery.status.soh_pptt; + + datalayer.battery.status.voltage_dV = battery_voltage; + datalayer.battery.status.current_dA = ((int16_t)battery_current / 10.0 - 3200) * 10; + + datalayer.battery.info.total_capacity_Wh = kWh_available_total * 5; + datalayer.battery.status.remaining_capacity_Wh = kWh_available_max * 5; + + //static lower limits for testing + // datalayer.battery.info.total_capacity_Wh = 10000; + // datalayer.battery.status.remaining_capacity_Wh = 9800; + + datalayer.battery.status.max_charge_power_W = ((battery_voltage / 10) * ((battery_charge_limit_amp / 20))); + datalayer.battery.status.max_discharge_power_W = + (abs((battery_voltage / 10) * ((battery_discharge_limit_amp / 20) - 3276.75))); + + //static lower limits for testing + // datalayer.battery.status.max_charge_power_W = 2000; + // datalayer.battery.status.max_discharge_power_W = 3000; + + datalayer.battery.status.cell_min_voltage_mV = 3700 - BMS_state; + datalayer.battery.status.cell_max_voltage_mV = 3700; + + datalayer.battery.status.temperature_min_dC = battery_min_temperature * 10; + datalayer.battery.status.temperature_max_dC = battery_max_temperature * 10; +} + +void RivianBattery::handle_incoming_can_frame(CAN_frame rx_frame) { + switch (rx_frame.ID) { + case 0x160: //Current [Platform CAN]+ + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + battery_current = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]); + break; + case 0x151: //Celltemps (requires other CAN channel) + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x120: //Voltages [Platform CAN]+ + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + battery_voltage = (((rx_frame.data.u8[7] & 0x1F) << 8) | rx_frame.data.u8[6]); + break; + case 0x25A: //SOC and kWh [Platform CAN]+ + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + //battery_SOC = (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[0]); + kWh_available_max = ((uint32_t)((rx_frame.data.u8[3] << 24) | (rx_frame.data.u8[2] << 16) | + (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) & + 0b11111111111111110000000000) >> + 10; + kWh_available_total = ((uint32_t)((rx_frame.data.u8[6] << 24) | (rx_frame.data.u8[5] << 16) | + (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[3]) & + 0b111111111111111100) >> + 2; + ; + break; + case 0x405: //State [Platform CAN]+ + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + BMS_state = (rx_frame.data.u8[0] & 0x03); + break; + case 0x100: //Discharge/Charge speed (Not visible on Platform-CAN?) + battery_charge_limit_amp = ((uint32_t)((rx_frame.data.u8[3] << 24) | (rx_frame.data.u8[2] << 16) | + (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) & + 0b1111111111111111000000000000) >> + 12; + battery_discharge_limit_amp = + ((uint32_t)((rx_frame.data.u8[5] << 16) | (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[3]) & + 0b11111111111111110000) >> + 4; + break; + case 0x153: //Temperatures (Not visible on Platform-CAN?)+ + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + battery_max_temperature = (rx_frame.data.u8[5] / 2) - 40; + battery_min_temperature = (rx_frame.data.u8[6] / 2) - 40; + break; + case 0x55B: //Temperatures (Not visible on Platform-CAN?)+ + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + battery_SOC = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + default: + break; + } +} + +void RivianBattery::transmit_can(unsigned long currentMillis) { + // Send 500ms CAN Message, too fast and the pack can't change states (pre-charge is built in, seems we can't change state during pre-charge) + // 100ms seems to draw too much current for a 5A supply during contactor pull in + if (currentMillis - previousMillis10 >= (INTERVAL_200_MS)) { + previousMillis10 = currentMillis; + + //If we want to close contactors, and preconditions are met + if ((datalayer.system.status.inverter_allows_contactor_closing) && (datalayer.battery.status.bms_status != FAULT)) { + //Standby -> Ready Mode + if (BMS_state == STANDBY || BMS_state == SLEEP) { + RIVIAN_150.data.u8[0] = 0x03; + RIVIAN_150.data.u8[2] = 0x01; + RIVIAN_420.data.u8[0] = 0x02; + RIVIAN_200.data.u8[0] = 0x08; + + transmit_can_frame(&RIVIAN_150); + transmit_can_frame(&RIVIAN_420); + transmit_can_frame(&RIVIAN_41F); + transmit_can_frame(&RIVIAN_207); + transmit_can_frame(&RIVIAN_200); + } + //Ready mode -> Go Mode + + if (BMS_state == READY) { + RIVIAN_150.data.u8[0] = 0x3E; + RIVIAN_150.data.u8[2] = 0x03; + RIVIAN_420.data.u8[0] = 0x03; + + transmit_can_frame(&RIVIAN_150); + transmit_can_frame(&RIVIAN_420); + } + + } else { //If we want to open contactors, transition the other way + //Go mode -> Ready Mode + + if (BMS_state == GO) { + RIVIAN_150.data.u8[0] = 0x03; + RIVIAN_150.data.u8[2] = 0x01; + + transmit_can_frame(&RIVIAN_150); + } + + if (BMS_state == READY) { + RIVIAN_150.data.u8[0] = 0x03; + RIVIAN_150.data.u8[2] = 0x01; + RIVIAN_420.data.u8[0] = 0x01; + RIVIAN_200.data.u8[0] = 0x10; + + transmit_can_frame(&RIVIAN_245); + transmit_can_frame(&RIVIAN_150); + transmit_can_frame(&RIVIAN_420); + transmit_can_frame(&RIVIAN_41F); + transmit_can_frame(&RIVIAN_200); + } + } + //disabled this because the battery didn't like it so fast (slowed to 100ms) and because the battery couldn't change states fast enough + //as much as I don't like the "free-running" aspect of it, checking the BMS_state and acting on it should fix issues caused by that. + //transmit_can_frame(&RIVIAN_150); + //transmit_can_frame(&RIVIAN_420); + //transmit_can_frame(&RIVIAN_41F); + //transmit_can_frame(&RIVIAN_207); + //transmit_can_frame(&RIVIAN_200); + } +} + +void RivianBattery::setup(void) { // Performs one time setup at startup + strncpy(datalayer.system.info.battery_protocol, Name, 63); + datalayer.system.info.battery_protocol[63] = '\0'; + datalayer.battery.info.number_of_cells = 108; + datalayer.battery.info.total_capacity_Wh = 135000; + datalayer.battery.info.chemistry = NMC; + 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.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; + datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV; + datalayer.system.status.battery_allows_contactor_closing = true; +} diff --git a/Software/src/battery/RIVIAN-BATTERY.h b/Software/src/battery/RIVIAN-BATTERY.h new file mode 100644 index 00000000..d74acb65 --- /dev/null +++ b/Software/src/battery/RIVIAN-BATTERY.h @@ -0,0 +1,60 @@ +#ifndef RIVIAN_BATTERY_H +#define RIVIAN_BATTERY_H +#include "CanBattery.h" + +class RivianBattery : 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); + static constexpr const char* Name = "Rivian R1T large 135kWh battery"; + + private: + static const int MAX_PACK_VOLTAGE_DV = 4480; + static const int MIN_PACK_VOLTAGE_DV = 2920; + 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 = 3300; //Battery is put into emergency stop if one cell goes below this value + + uint8_t BMS_state = 0; + uint16_t battery_voltage = 3700; + uint16_t battery_SOC = 5000; + int32_t battery_current = 0; + uint16_t kWh_available_total = 135; + uint16_t kWh_available_max = 135; + int16_t battery_min_temperature = 0; + int16_t battery_max_temperature = 0; + uint16_t battery_discharge_limit_amp = 0; + uint16_t battery_charge_limit_amp = 0; + static const uint8_t SLEEP = 0; + static const uint8_t STANDBY = 1; + static const uint8_t READY = 2; + static const uint8_t GO = 3; + unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was sent + + CAN_frame RIVIAN_150 = {.FD = false, + .ext_ID = false, + .DLC = 6, + .ID = 0x150, + .data = {0x03, 0x00, 0x01, 0x00, 0x01, 0x00}}; + CAN_frame RIVIAN_420 = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x420, + .data = {0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; + CAN_frame RIVIAN_41F = {.FD = false, .ext_ID = false, .DLC = 3, .ID = 0x41F, .data = {0x62, 0x10, 0x00}}; + CAN_frame RIVIAN_245 = {.FD = false, + .ext_ID = false, + .DLC = 6, + .ID = 0x245, + .data = {0x10, 0x00, 0x00, 0x00, 0x00, 0x00}}; + CAN_frame RIVIAN_200 = {.FD = false, .ext_ID = false, .DLC = 1, .ID = 0x200, .data = {0x08}}; + CAN_frame RIVIAN_207 = {.FD = false, + .ext_ID = false, + .DLC = 1, + .ID = 0x207, + .data = {0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00}}; +}; + +#endif