Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 21 additions & 7 deletions include/CONSTANTS.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,19 @@ namespace constants {
static_assert(kBoardThermistorIndex == kMonitoredThermistorsPerModule,
"Board thermistor should be the first thermistor excluded from aggregate status");

constexpr uint32_t kPollIntervalMs = 500;
// interval at which the BMS pulls data from the latest slave board readings
constexpr uint32_t kPollIntervalMs = 100;
// interval BMS sends data via Serial
constexpr uint32_t kLogIntervalMs = 2000;
constexpr uint32_t kModuleScanIntervalMs = 2000;
// interval BMS scans modules
constexpr uint32_t kModuleScanIntervalMs = 500;

constexpr uint32_t kCanBitRate = 250000;
constexpr uint32_t kCanStatusIntervalMs = 500;
constexpr uint32_t kCanStatusMessageId = 0x070;
constexpr uint32_t kCanStatusIntervalMs = 1000; // Rate at which data CAN status information is sent
constexpr uint32_t kCanChargerIntervalMs = 1000; // Rate to check if the charger sent a CAN message to toggle on charging mode
constexpr uint32_t kCanChargerTimeOutMs = 2000; // If the charger stops sending CAN messages then toggle off charging mode
constexpr uint32_t kCanChargerControlIntervalMs = 1000; // Rate at which the charger needs a CAN control message
constexpr uint32_t kChargerStatusUpdateIntervalMs = 200; // Rate at which charge states are updated based on BMS polled data
constexpr uint8_t kCanStatusPayloadLength = 8;

constexpr uint8_t kConnectDebounce = 2;
Expand All @@ -44,10 +50,18 @@ namespace constants {

constexpr float kTempWarningMinC = 5.0f;
constexpr float kTempGoodMaxC = 50.0f;
constexpr float kTempWarningMaxC = 60.0f;
constexpr float kTempExhaustedMaxC = 70.0f;
constexpr uint8_t kTempChargingFaultC = 60; // stop charging if max cell temp reaches this value
constexpr float kTempWarningMaxC = 60.0f; // if cells reach 60 C then the BMS should error
constexpr float kTempExhaustedMaxC = 70.0f; // the cells should not go any higher than this per the datasheet
constexpr float kBoardThermistorFaultMinC = 70.0f;

constexpr std::size_t kLedCount = 13;
constexpr uint8_t kLedBrightness = 32;
} // namespace constants

// constants for charging
constexpr uint16_t kChargerPinConsideredHigh = 620; // Charger analog read pin will read 1 V when high, thus 4096/3.3 V = 1241, and we add space for uncertainty and noise
constexpr uint16_t kVoltageChargerMaxPackV = 445; // Charger will shutoff if limit is over-reached and BMS or wiring fails; this parameter is sent to the charger via CAN in charger mode
constexpr float kMaxChargerPowerOutputW = 6550.0f; // ELCON charger specification
constexpr uint16_t kStartChargeA = 1; // 0.5C charging amperage

} // namespace constants
10 changes: 6 additions & 4 deletions include/PINS.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@
#define ADBMS_SPI_SCLK 10
#define ADBMS_SPI_MOSI 11
#define ADBMS_SPI_MISO 12
#define CHARGE_ENABLE_SENSE 13
#define DRIVE_ENABLE_SENSE 14
#define BMS_STATUS_OUTPUT 15
#define UART0_TX 16
#define UART0_RX 17
#define CAN0_INT 18
Expand All @@ -33,7 +31,11 @@
#define GPIO_25 25
#define GPIO_26 26
#define GPIO_27 27
#define GPIO_28 28
#define GPIO_29 29

// pin out for shutdown circut
#define BMS_STATUS_OUTPUT 28

// pin to enable or disable charging
#define CHARGE_ENABLE_SENSE 29

#endif //PINS_H
68 changes: 68 additions & 0 deletions include/SOCLookUpTable.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#pragma once

#include <cstddef>
#include <cstdint>

// SOC : State of charge
// Human readable state of the accumulator because the charging logic
// uses only the min cell voltage or the max cell voltage in the pack

namespace soclookuptable {
Comment thread
potter4500 marked this conversation as resolved.
constexpr uint8_t kNumLookUpPoints = 100;

constexpr float kVoltageTable[kNumLookUpPoints] = {
3150.0f, 3159.6f, 3169.2f, 3178.8f, 3188.4f, 3198.0f, 3207.6f, 3217.2f,
3226.8f, 3236.4f, 3246.0f, 3255.6f, 3265.2f, 3274.7f, 3284.3f, 3293.9f,
3303.5f, 3313.1f, 3322.7f, 3332.3f, 3341.9f, 3351.5f, 3361.1f, 3370.7f,
3380.3f, 3389.9f, 3399.5f, 3409.1f, 3418.7f, 3428.3f, 3437.9f, 3447.5f,
3457.1f, 3466.7f, 3476.3f, 3485.9f, 3495.5f, 3505.1f, 3514.6f, 3524.2f,
3533.8f, 3543.4f, 3553.0f, 3562.6f, 3572.2f, 3581.8f, 3591.4f, 3601.0f,
3610.6f, 3620.2f, 3629.8f, 3639.4f, 3649.0f, 3658.6f, 3668.2f, 3677.8f,
3687.4f, 3697.0f, 3706.6f, 3716.2f, 3725.8f, 3735.4f, 3744.9f, 3754.5f,
3764.1f, 3773.7f, 3783.3f, 3792.9f, 3802.5f, 3812.1f, 3821.7f, 3831.3f,
3840.9f, 3850.5f, 3860.1f, 3869.7f, 3879.3f, 3888.9f, 3898.5f, 3908.1f,
3917.7f, 3927.3f, 3936.9f, 3946.5f, 3956.1f, 3965.7f, 3975.3f, 3984.8f,
3994.4f, 4004.0f, 4013.6f, 4023.2f, 4032.8f, 4042.4f, 4052.0f, 4061.6f,
4071.2f, 4080.8f, 4090.4f, 4100.0f
};

constexpr float kSocTable[kNumLookUpPoints] = {
0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.8f, 0.9f, 1.1f,
1.3f, 1.5f, 1.7f, 1.9f, 2.1f, 2.4f, 2.7f, 2.9f, 3.3f, 3.6f,
4.0f, 4.4f, 4.8f, 5.3f, 5.8f, 6.3f, 6.8f, 7.5f, 8.1f, 8.8f,
9.5f, 10.3f, 11.2f, 12.0f, 13.0f, 14.0f, 15.0f, 16.2f, 17.3f, 18.6f,
19.9f, 21.3f, 22.7f, 24.2f, 25.8f, 27.4f, 29.1f, 30.8f, 32.6f, 34.5f,
36.4f, 38.3f, 40.3f, 42.3f, 44.4f, 46.4f, 48.5f, 50.6f, 52.7f, 54.8f,
56.9f, 58.9f, 61.0f, 63.0f, 64.9f, 66.9f, 68.7f, 70.6f, 72.4f, 74.1f,
75.8f, 77.4f, 78.9f, 80.4f, 81.8f, 83.1f, 84.4f, 85.6f, 86.8f, 87.9f,
88.9f, 89.9f, 90.8f, 91.7f, 92.5f, 93.3f, 94.0f, 94.7f, 95.3f, 95.9f,
96.5f, 97.0f, 97.5f, 97.9f, 98.3f, 98.7f, 99.1f, 99.4f, 99.7f, 100.0f,
};

} // State of charge lookup table

// Maps the first table (voltages) to the second table (percentage)
float ReadBMS::lookUpSOC(uint16_t cellMv) {
// check if value is out of range
if (cellMv <= soclookuptable::kVoltageTable[0]) {
return soclookuptable::kSocTable[0];
}
if (cellMv >= soclookuptable::kVoltageTable[soclookuptable::kNumLookUpPoints - 1]) {
return soclookuptable::kSocTable[soclookuptable::kNumLookUpPoints - 1];
}

// linear interpolation to map between the two tables
for (size_t i=0; i < soclookuptable::kNumLookUpPoints - 1; i++) {
if (cellMv >= soclookuptable::kVoltageTable[i] && cellMv <= soclookuptable::kVoltageTable[i+1]) {
float v1 = soclookuptable::kVoltageTable[i];
float v2 = soclookuptable::kVoltageTable[i + 1];
float soc1 = soclookuptable::kSocTable[i];
float soc2 = soclookuptable::kSocTable[i + 1];

return static_cast<float>((soc1 * (v1 - cellMv) + soc2 * (cellMv - v2)) / (v1 - v2));
}
}

// fallback
return 0.0;
}
8 changes: 0 additions & 8 deletions include/SystemStatus.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,15 +88,7 @@ inline StatusMode evaluateVoltageStatus(const ModuleReadings& module) {
if (cellMv == adbms6830::BMSInterface::kInvalidCellValue) {
return StatusMode::BAD_DATA;
}
#if defined(DANGEROUS_MODE)
if (cellMv >= constants::kBalanceMaxCellMv) {
continue;
}
if (cellMv < constants::kCellVoltageErrorMinMv ||
(cellMv > constants::kCellVoltageErrorMaxMv && cellMv < constants::kBalanceMaxCellMv)) {
#else
if (cellMv < constants::kCellVoltageErrorMinMv || cellMv > constants::kCellVoltageErrorMaxMv) {
#endif
return StatusMode::ERROR;
}
if (cellMv < constants::kCellVoltageExhaustedMinMv || cellMv > constants::kCellVoltageWarningMaxMv) {
Expand Down
13 changes: 7 additions & 6 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ framework = arduino
board_build.core = earlephilhower
board_build.filesystem_size = 0.5m
monitor_speed = 115200
monitor_filters = default, log2file
upload_protocol = picotool
build_flags =
-DARDUINO_USB_CDC_ON_BOOT=1
lib_deps =
fastled/FastLED@^3.10.3
pierremolinaro/acan2517
pierremolinaro/acan2517FD
build_flags =
-DARDUINO_USB_CDC_ON_BOOT=1
lib_deps =
fastled/FastLED@^3.10.3
pierremolinaro/acan2517
pierremolinaro/acan2517FD
85 changes: 81 additions & 4 deletions src/BMSControl.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "BMSControl.h"
#include "SOCLookUpTable.h"

const SPISettings ReadBMS::kBmsSpiSettings(1000000, MSBFIRST, SPI_MODE0);

Expand Down Expand Up @@ -77,9 +78,31 @@ void ReadBMS::updateBalancing(bool enabled) {
return;
}

// update global pack min cell voltage
uint16_t lowestPackCellMv = UINT16_MAX;
for (std::size_t moduleIndex = 0; moduleIndex < kModuleCount; ++moduleIndex) {
ModuleReadings& module = pollData_.modules[moduleIndex];
module.balanceMask = balanceMaskForModule(module, module.balanceMask);

// pass disconnected cells for now, but we need to look into this
if (!module.connected || !module.cellDataValid) {
continue;
}

for (uint16_t cellMv : module.cellVoltages) {
// pass invalid cells for now, but we need to look this later
if (cellMv == adbms6830::BMSInterface::kInvalidCellValue) {
continue;
}

if (cellMv < lowestPackCellMv) {
lowestPackCellMv = cellMv;
}
}
}

for (std::size_t moduleIndex = 0; moduleIndex < kModuleCount; ++moduleIndex) {
ModuleReadings& module = pollData_.modules[moduleIndex];
module.balanceMask = balanceMaskForModule(module, module.balanceMask, lowestPackCellMv);
}

for (std::size_t moduleIndex = 0; moduleIndex < kModuleCount; ++moduleIndex) {
Expand Down Expand Up @@ -311,15 +334,19 @@ void ReadBMS::logModuleSiliconIds(const LogSnapshot& snapshot, Stream& stream) {
stream.println();
}

uint16_t ReadBMS::balanceMaskForModule(const ModuleReadings& module, uint16_t currentMask) {
uint16_t ReadBMS::balanceMaskForModule(const ModuleReadings& module, uint16_t currentMask, uint16_t lowestPackCellMv) {
if (!module.connected || !module.cellDataValid) {
return 0;
}
if (boardThermistorFaulted(module)) {
return 0;
}

uint16_t minCellMv = UINT16_MAX;
// grab the min cell voltage for the module, this is the balance target
// thus if the pack min cell voltage is inserted here instead, then each
// module will balance globally to each other
#ifdef BALANCE_MODULES_INDEPENDENTLY
uint16_t targetCellMv = UINT16_MAX;
for (uint16_t cellMv : module.cellVoltages) {
if (cellMv == adbms6830::BMSInterface::kInvalidCellValue) {
return 0;
Expand All @@ -335,11 +362,19 @@ uint16_t ReadBMS::balanceMaskForModule(const ModuleReadings& module, uint16_t cu
if (minCellMv == UINT16_MAX) {
return 0;
}
#else
// Balance modules to the global min cell voltage
uint16_t targetCellMv = lowestPackCellMv;
if (targetCellMv == UINT16_MAX) {
return 0;
}
#endif

uint16_t desiredMask = 0;
for (std::size_t cellIndex = 0; cellIndex < module.cellVoltages.size(); ++cellIndex) {
const uint16_t cellMv = module.cellVoltages[cellIndex];
const uint16_t deltaMv = static_cast<uint16_t>(cellMv - minCellMv);
// calculate delta voltage from the target cell voltage pulled previously
const uint16_t deltaMv = static_cast<uint16_t>(cellMv - targetCellMv);
const uint16_t cellBit = static_cast<uint16_t>(1u << cellIndex);
const bool currentlyBalancing = (currentMask & cellBit) != 0u;
if (currentlyBalancing) {
Expand Down Expand Up @@ -691,3 +726,45 @@ void ReadBMS::copyModuleReadings(ModuleReadings& destination,
destination.cellVoltages = source.cellVoltages;
destination.thermistorTempsC = source.thermistorTempsC;
}

// Update State of charge struct data
ReadBMS::StateOfCharge ReadBMS::pollSOC() {
uint16_t lowestCellMv = UINT16_MAX;
uint16_t highestCellMv = 0;
float minStateOfCharge = 0.0f;
float maxStateOfCharge = 0.0f;

// get lastest module readings
updatePollData();

for (const ReadBMS::ModuleReadings &module : pollData_.modules)
{
if (!module.connected || !module.cellDataValid) {
continue;
}
Comment on lines +742 to +744

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this intentional to ignore bad cells? Might want to add a comment explaining why it's okay to ignore.


for (uint16_t cellMv : module.cellVoltages) {
if (cellMv == adbms6830::BMSInterface::kInvalidCellValue) {
continue;
}

if (cellMv < lowestCellMv) {
lowestCellMv = cellMv;
}

if (cellMv > highestCellMv) {
highestCellMv = cellMv;
}
}
}

minStateOfCharge = lookUpSOC(lowestCellMv);
maxStateOfCharge = lookUpSOC(highestCellMv);

StateOfCharge soc{};
soc.minSOC = minStateOfCharge;
soc.maxSOC = maxStateOfCharge;
soc.minCellMv = lowestCellMv;
soc.maxCellMv = highestCellMv;
return soc;
}
13 changes: 12 additions & 1 deletion src/BMSControl.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,21 @@ class ReadBMS {
std::array<adbms6830::BMSInterface::SiliconIdReadback, constants::kModuleCount> moduleSiliconIds{};
};

// To be used for charging logic and CAN msg data sent to the dashboard
struct StateOfCharge {
Comment thread
potter4500 marked this conversation as resolved.
float minSOC = 0.0f;
float maxSOC = 0.0f;
uint16_t minCellMv = 0;
uint16_t maxCellMv = 0;
};

ReadBMS();

void begin();
void pollBMS();
void updateBalancing(bool enabled);
const PollData& data() const;
StateOfCharge pollSOC(); // to pull SOC data for charging
LogSnapshot captureLogSnapshot() const;
static void logBalancingState(const LogSnapshot& snapshot, Stream& stream);
static void logConnectedModules(const LogSnapshot& snapshot, Stream& stream);
Expand Down Expand Up @@ -82,7 +91,7 @@ class ReadBMS {
static AggregateStats cellStatsForModule(const ModuleReadings& module);
static AggregateStats thermistorStatsForModule(const ModuleReadings& module);
static bool boardThermistorFaulted(const ModuleReadings& module);
static uint16_t balanceMaskForModule(const ModuleReadings& module, uint16_t currentMask);
static uint16_t balanceMaskForModule(const ModuleReadings &module, uint16_t currentMask, uint16_t lowestPackCellMv);
static void printSiliconId(Stream& stream, const adbms6830::BMSInterface::SiliconIdReadback& siliconId);
void applyBalanceMask(adbms6830::BMSInterface& bmsInterface,
std::array<uint16_t, kModuleCount>& appliedMasks,
Expand Down Expand Up @@ -115,6 +124,8 @@ class ReadBMS {
const adbms6830::BMSInterface::ModuleData& source,
bool connected) const;

float lookUpSOC(uint16_t cellMv);

static const SPISettings kBmsSpiSettings;

adbms6830::ADBMS6830Driver mainBmsDriver_;
Expand Down
8 changes: 0 additions & 8 deletions src/JboxIO.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,6 @@ void JboxIO::init() {
pinMode(CHARGE_ENABLE_SENSE, INPUT);
}

bool JboxIO::readDriveEnable() const {
return digitalRead(DRIVE_ENABLE_SENSE) == HIGH;
}

bool JboxIO::readChargeEnable() const {
return digitalRead(CHARGE_ENABLE_SENSE) == HIGH;
}

void JboxIO::setStatus(StatusMode mode) const {
digitalWrite(BMS_STATUS_OUTPUT, mode == StatusMode::GOOD ? HIGH : LOW);
}
20 changes: 20 additions & 0 deletions src/MCP2517Can.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,26 @@ class MCP2517Can {
Osc40MHzDiv2,
};

enum CanMsgId : uint32_t {
MotorControlCommand = 0x0C0, // Motor control command, BMS will switch to drive ready mode when this msg is recieved

BmsStatus = 0x070, // BMS status message
StateOfCharge = 0x072, // BMS state of charge

ChargerControl = 0x1806E5F4, // ELCON CAN 3865 charger control message id
ChargerStatus = 0x18FF50E5, // ELCON CAN 3865 charger status broadcast message
};

enum ChargerControl : bool {
ChargerStart = 0,
ChargerClose = 1,
};

enum ChargingMode : bool {
ChargingMode = 0,
HeatingMode = 1,
};

struct Message {
uint32_t id = 0;
bool extended = false;
Expand Down
Loading