mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-03 17:59:27 +02:00
Merge pull request #537 from M4GNV5/feat/renault-twizy
Add new battery implementation "Renault Twizy" (LV)
This commit is contained in:
commit
31dc3c8e25
6 changed files with 171 additions and 1 deletions
|
@ -22,6 +22,7 @@
|
|||
//#define PYLON_BATTERY
|
||||
//#define RJXZS_BMS
|
||||
//#define RENAULT_KANGOO_BATTERY
|
||||
//#define RENAULT_TWIZY_BATTERY
|
||||
//#define RENAULT_ZOE_GEN1_BATTERY
|
||||
//#define RENAULT_ZOE_GEN2_BATTERY
|
||||
//#define SANTA_FE_PHEV_BATTERY
|
||||
|
|
|
@ -54,6 +54,10 @@
|
|||
#include "RENAULT-KANGOO-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#ifdef RENAULT_TWIZY_BATTERY
|
||||
#include "RENAULT-TWIZY.h"
|
||||
#endif
|
||||
|
||||
#ifdef RENAULT_ZOE_GEN1_BATTERY
|
||||
#include "RENAULT-ZOE-GEN1-BATTERY.h"
|
||||
#endif
|
||||
|
|
150
Software/src/battery/RENAULT-TWIZY.cpp
Normal file
150
Software/src/battery/RENAULT-TWIZY.cpp
Normal file
|
@ -0,0 +1,150 @@
|
|||
#include <cstdint>
|
||||
#include "../include.h"
|
||||
#ifdef RENAULT_TWIZY_BATTERY
|
||||
#include "../datalayer/datalayer.h"
|
||||
#include "../devboard/utils/events.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) {
|
||||
int result = INT16_MIN;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (entries[i] > result)
|
||||
result = entries[i];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int16_t min_value(int16_t* entries, size_t len) {
|
||||
int result = INT16_MAX;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (entries[i] < result)
|
||||
result = entries[i];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void update_values_battery() {
|
||||
|
||||
datalayer.battery.status.real_soc = SOC;
|
||||
datalayer.battery.status.soh_pptt = SOH;
|
||||
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.remaining_capacity_Wh = remaining_capacity_Wh;
|
||||
|
||||
datalayer.battery.status.active_power_W = //Power in watts, Negative = charging batt
|
||||
((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100);
|
||||
|
||||
// The twizy provides two values: one for the maximum charge provided by the on-board charger
|
||||
// and one for the maximum charge during recuperation.
|
||||
// For now we use the lower of the two (usually the charger one)
|
||||
datalayer.battery.status.max_charge_power_W = max_charge_power < max_recup_power ? max_charge_power : max_recup_power;
|
||||
|
||||
datalayer.battery.status.max_discharge_power_W = max_discharge_power;
|
||||
|
||||
memcpy(datalayer.battery.status.cell_voltages_mV, cellvoltages_mV, sizeof(cellvoltages_mV));
|
||||
datalayer.battery.status.cell_min_voltage_mV =
|
||||
min_value(cellvoltages_mV, sizeof(cellvoltages_mV) / sizeof(*cellvoltages_mV));
|
||||
datalayer.battery.status.cell_max_voltage_mV =
|
||||
max_value(cellvoltages_mV, sizeof(cellvoltages_mV) / sizeof(*cellvoltages_mV));
|
||||
|
||||
datalayer.battery.status.temperature_min_dC =
|
||||
min_value(cell_temperatures_dC, sizeof(cell_temperatures_dC) / sizeof(*cell_temperatures_dC));
|
||||
datalayer.battery.status.temperature_max_dC =
|
||||
max_value(cell_temperatures_dC, sizeof(cell_temperatures_dC) / sizeof(*cell_temperatures_dC));
|
||||
}
|
||||
|
||||
void receive_can_battery(CAN_frame rx_frame) {
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
switch (rx_frame.ID) {
|
||||
case 0x155:
|
||||
// max charge power is in steps of 300W from 0 to 7
|
||||
max_charge_power = (uint16_t)rx_frame.data.u8[0] * 300;
|
||||
|
||||
// current is encoded as a 12 bit integer with Amps = value / 4 - 500
|
||||
current_dA = (((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]) & 0xfff) * 10 / 4 - 5000;
|
||||
|
||||
// SOC is encoded as 16 bit integer with SOC% = value / 400
|
||||
SOC = (((uint16_t)rx_frame.data.u8[4] << 8) | (uint16_t)rx_frame.data.u8[5]) / 4;
|
||||
break;
|
||||
case 0x424:
|
||||
max_recup_power = rx_frame.data.u8[2] * 500;
|
||||
max_discharge_power = rx_frame.data.u8[3] * 500;
|
||||
SOH = (uint16_t)rx_frame.data.u8[5] * 100;
|
||||
break;
|
||||
case 0x425:
|
||||
remaining_capacity_Wh = (uint16_t)rx_frame.data.u8[1] * 100;
|
||||
break;
|
||||
case 0x554:
|
||||
for (int i = 0; i < 7; i++)
|
||||
cell_temperatures_dC[i] = (int16_t)rx_frame.data.u8[i] * 10 - 400;
|
||||
break;
|
||||
case 0x556:
|
||||
// cell voltages are 12 bit with V = value / 200
|
||||
cellvoltages_mV[0] = (((int16_t)rx_frame.data.u8[0] << 4) | ((int16_t)rx_frame.data.u8[1] >> 4)) * 10 / 2;
|
||||
cellvoltages_mV[1] = (((int16_t)(rx_frame.data.u8[1] & 0xf) << 8) | (int16_t)rx_frame.data.u8[2]) * 10 / 2;
|
||||
cellvoltages_mV[2] = (((int16_t)rx_frame.data.u8[3] << 4) | ((int16_t)rx_frame.data.u8[4] >> 4)) * 10 / 2;
|
||||
cellvoltages_mV[3] = (((int16_t)(rx_frame.data.u8[4] & 0xf) << 8) | (int16_t)rx_frame.data.u8[5]) * 10 / 2;
|
||||
cellvoltages_mV[4] = (((int16_t)rx_frame.data.u8[6] << 4) | ((int16_t)rx_frame.data.u8[7] >> 4)) * 10 / 2;
|
||||
break;
|
||||
case 0x557:
|
||||
// cell voltages are 12 bit with V = value / 200
|
||||
cellvoltages_mV[5] = (((int16_t)rx_frame.data.u8[0] << 4) | ((int16_t)rx_frame.data.u8[1] >> 4)) * 10 / 2;
|
||||
cellvoltages_mV[6] = (((int16_t)(rx_frame.data.u8[1] & 0xf) << 8) | (int16_t)rx_frame.data.u8[2]) * 10 / 2;
|
||||
cellvoltages_mV[7] = (((int16_t)rx_frame.data.u8[3] << 4) | ((int16_t)rx_frame.data.u8[4] >> 4)) * 10 / 2;
|
||||
cellvoltages_mV[8] = (((int16_t)(rx_frame.data.u8[4] & 0xf) << 8) | (int16_t)rx_frame.data.u8[5]) * 10 / 2;
|
||||
cellvoltages_mV[9] = (((int16_t)rx_frame.data.u8[6] << 4) | ((int16_t)rx_frame.data.u8[7] >> 4)) * 10 / 2;
|
||||
break;
|
||||
case 0x55E:
|
||||
// cell voltages are 12 bit with V = value / 200
|
||||
cellvoltages_mV[10] = (((int16_t)rx_frame.data.u8[0] << 4) | ((int16_t)rx_frame.data.u8[1] >> 4)) * 10 / 2;
|
||||
cellvoltages_mV[11] = (((int16_t)(rx_frame.data.u8[1] & 0xf) << 8) | (int16_t)rx_frame.data.u8[2]) * 10 / 2;
|
||||
cellvoltages_mV[12] = (((int16_t)rx_frame.data.u8[3] << 4) | ((int16_t)rx_frame.data.u8[4] >> 4)) * 10 / 2;
|
||||
cellvoltages_mV[13] = (((int16_t)(rx_frame.data.u8[4] & 0xf) << 8) | (int16_t)rx_frame.data.u8[5]) * 10 / 2;
|
||||
// battery odometer in bytes 6 and 7
|
||||
break;
|
||||
case 0x55F:
|
||||
// TODO: twizy has two pack voltages, assumingly the minimum and maximum measured.
|
||||
// They usually only differ by 0.1V. We use the lower one here
|
||||
// The other one is in the last 12 bit of the CAN packet
|
||||
|
||||
// pack voltage is encoded as 16 bit integer in dV
|
||||
voltage_dV = (((int16_t)rx_frame.data.u8[5] << 4) | ((int16_t)rx_frame.data.u8[6] >> 4));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void send_can_battery() {
|
||||
// we do not need to send anything to the battery for now
|
||||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Renault Twizy battery selected");
|
||||
#endif
|
||||
|
||||
datalayer.battery.info.number_of_cells = 14;
|
||||
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.battery.info.total_capacity_Wh = 6600;
|
||||
}
|
||||
|
||||
#endif
|
12
Software/src/battery/RENAULT-TWIZY.h
Normal file
12
Software/src/battery/RENAULT-TWIZY.h
Normal file
|
@ -0,0 +1,12 @@
|
|||
#ifndef RENAULT_TWIZY_BATTERY_H
|
||||
#define RENAULT_TWIZY_BATTERY_H
|
||||
#include "../include.h"
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
#define MAX_PACK_VOLTAGE_DV 579 // 57.9V at 100% SOC (with 70% SOH, new one might be higher)
|
||||
#define MIN_PACK_VOLTAGE_DV 480 // 48.4V at 13.76% SOC
|
||||
#define MAX_CELL_DEVIATION_MV 500
|
||||
#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
|
||||
|
||||
#endif
|
|
@ -97,7 +97,7 @@ void update_machineryprotection() {
|
|||
clear_event(EVENT_SOH_LOW);
|
||||
}
|
||||
|
||||
#ifndef PYLON_BATTERY
|
||||
#if !defined(PYLON_BATTERY) && !defined(RENAULT_TWIZY_BATTERY)
|
||||
// Check if SOC% is plausible
|
||||
if (datalayer.battery.status.voltage_dV >
|
||||
(datalayer.battery.info.max_design_voltage_dV -
|
||||
|
|
|
@ -549,6 +549,9 @@ String processor(const String& var) {
|
|||
#ifdef RENAULT_KANGOO_BATTERY
|
||||
content += "Renault Kangoo";
|
||||
#endif // RENAULT_KANGOO_BATTERY
|
||||
#ifdef RENAULT_TWIZY_BATTERY
|
||||
content += "Renault Twizy";
|
||||
#endif // RENAULT_TWIZY_BATTERY
|
||||
#ifdef RENAULT_ZOE_GEN1_BATTERY
|
||||
content += "Renault Zoe Gen1 22/40";
|
||||
#endif // RENAULT_ZOE_GEN1_BATTERY
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue