Skip to content

Commit a21eb38

Browse files
authored
Merge pull request #91 from openppg/bms-cell-low-temps
Handle bad BMS cell temperature probes
2 parents 50ef221 + 7549747 commit a21eb38

8 files changed

Lines changed: 292 additions & 28 deletions

File tree

inc/sp140/bms.h

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,47 @@
33
#include <Arduino.h>
44
#include <SPI.h>
55
#include <BMS_CAN.h>
6+
#include <math.h>
67

78
#include "sp140/structs.h"
89

910
// BMS-related constants
1011
#define MCP_CS 5 // MCP2515 CS pin
1112
#define MCP_BAUDRATE 250000
1213

14+
// BMS cell probe disconnect policy.
15+
// The library returns NaN for disconnected probes. This app tolerates up to
16+
// BMS_MAX_IGNORED_DISCONNECTED_PROBES disconnected; beyond that the NaN is
17+
// replaced with the sentinel value so downstream temp monitors fire an alert.
18+
constexpr uint8_t BMS_CELL_PROBE_COUNT = 4;
19+
constexpr uint8_t BMS_MAX_IGNORED_DISCONNECTED_PROBES = 2;
20+
21+
inline void sanitizeCellProbeTemps(
22+
const float temps[BMS_CELL_PROBE_COUNT],
23+
float out[BMS_CELL_PROBE_COUNT]) {
24+
uint8_t disconnectedCount = 0;
25+
26+
// First pass: count disconnected probes (NaN from library)
27+
for (uint8_t i = 0; i < BMS_CELL_PROBE_COUNT; i++) {
28+
if (isnan(temps[i])) disconnectedCount++;
29+
}
30+
31+
// Second pass: if within tolerance, pass NaN through (silently ignored).
32+
// If too many are disconnected, replace excess NaN with the sentinel value
33+
// so temperature monitors fire a CRIT_LOW alert.
34+
uint8_t ignoredCount = 0;
35+
for (uint8_t i = 0; i < BMS_CELL_PROBE_COUNT; i++) {
36+
if (!isnan(temps[i])) {
37+
out[i] = temps[i];
38+
} else if (ignoredCount < BMS_MAX_IGNORED_DISCONNECTED_PROBES) {
39+
out[i] = NAN;
40+
ignoredCount++;
41+
} else {
42+
out[i] = BMS_CAN::TEMP_PROBE_DISCONNECTED;
43+
}
44+
}
45+
}
46+
1347
// External declarations
1448
extern STR_BMS_TELEMETRY_140 bmsTelemetryData;
1549
extern BMS_CAN* bms_can;

inc/sp140/structs.h

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ typedef struct {
7676
float power; // Power (kW)
7777
float highest_cell_voltage; // Highest individual cell voltage (V)
7878
float lowest_cell_voltage; // Lowest individual cell voltage (V)
79-
float highest_temperature; // Highest temperature reading (°C)
80-
float lowest_temperature; // Lowest temperature reading (°C)
79+
float highest_temperature; // Highest valid temperature reading (°C), NaN if unavailable
80+
float lowest_temperature; // Lowest valid temperature reading (°C), NaN if unavailable
8181
float energy_cycle; // Energy per cycle (kWh)
8282
uint32_t battery_cycle; // Battery cycle count
8383
uint8_t battery_fail_level; // Battery failure status
@@ -92,10 +92,10 @@ typedef struct {
9292
// Individual temperature sensors
9393
float mos_temperature; // BMS MOSFET temperature (°C) - index 0
9494
float balance_temperature; // BMS balance resistor temperature (°C) - index 1
95-
float t1_temperature; // T1 cell temperature sensor (°C) - index 2
96-
float t2_temperature; // T2 cell temperature sensor (°C) - index 3
97-
float t3_temperature; // T3 cell temperature sensor (°C) - index 4
98-
float t4_temperature; // T4 cell temperature sensor (°C) - index 5
95+
float t1_temperature; // T1 cell temperature sensor (°C), NaN if disconnected - index 2
96+
float t2_temperature; // T2 cell temperature sensor (°C), NaN if disconnected - index 3
97+
float t3_temperature; // T3 cell temperature sensor (°C), NaN if disconnected - index 4
98+
float t4_temperature; // T4 cell temperature sensor (°C), NaN if disconnected - index 5
9999
} STR_BMS_TELEMETRY_140;
100100
#pragma pack(pop)
101101

platformio.ini

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ lib_ignore =
2121

2222

2323
[env:OpenPPG-CESP32S3-CAN-SP140]
24-
platform = espressif32@6.12.0
24+
platform = espressif32@6.13.0
2525
board = m5stack-stamps3
2626
framework = arduino
2727
src_folder = sp140
@@ -43,19 +43,19 @@ debug_tool = esp-builtin
4343
lib_deps =
4444
Wire
4545
SPI
46-
ArduinoJson@7.3.1
46+
ArduinoJson@7.4.3
4747
Time@1.6.1
48-
adafruit/Adafruit BusIO@1.17.2
48+
adafruit/Adafruit BusIO@1.17.4
4949
adafruit/Adafruit BMP3XX Library@2.1.6
5050
adafruit/Adafruit ST7735 and ST7789 Library@1.11.0
51-
adafruit/Adafruit NeoPixel@1.15.2
52-
adafruit/Adafruit CAN@0.2.1
51+
adafruit/Adafruit NeoPixel@1.15.4
52+
adafruit/Adafruit CAN@0.2.3
5353
adafruit/Adafruit MCP2515@0.2.1
5454
https://github.com/rlogiacco/CircularBuffer@1.4.0
5555
https://github.com/openppg/SINE-ESC-CAN#8caa93996b5d000fe10ca5265bd1c472dfdf885b
56-
https://github.com/openppg/ANT-BMS-CAN#da685ce2a0e87e23df625f33ad751203ad6a4f8f
56+
https://github.com/openppg/ANT-BMS-CAN#fd54852bc6f1c9608e37af9ca7c13ea4135c095b
5757
lvgl/lvgl@^8.4.0
58-
h2zero/NimBLE-Arduino@^2.3.5
58+
h2zero/NimBLE-Arduino@^2.3.9
5959
lib_ignore =
6060
Adafruit SleepyDog Library
6161
${extra.lib_ignore}

src/sp140/bms.cpp

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,34 @@
33
#include "sp140/globals.h"
44
#include "sp140/lvgl/lvgl_core.h" // for spiBusMutex
55

6+
namespace {
7+
8+
void logBmsCellProbeConnectionTransitions(const float sanitizedCellTemps[BMS_CELL_PROBE_COUNT]) {
9+
static bool hasPreviousState = false;
10+
static bool wasConnected[BMS_CELL_PROBE_COUNT] = {false, false, false, false};
11+
12+
for (uint8_t i = 0; i < BMS_CELL_PROBE_COUNT; i++) {
13+
const bool connected = !isnan(sanitizedCellTemps[i]);
14+
15+
if (!hasPreviousState) {
16+
wasConnected[i] = connected;
17+
continue;
18+
}
19+
20+
if (connected != wasConnected[i]) {
21+
USBSerial.printf("[BMS] T%u sensor %s (sanitized=%.1fC)\n",
22+
i + 1,
23+
connected ? "reconnected" : "disconnected",
24+
sanitizedCellTemps[i]);
25+
wasConnected[i] = connected;
26+
}
27+
}
28+
29+
hasPreviousState = true;
30+
}
31+
32+
} // namespace
33+
634
STR_BMS_TELEMETRY_140 bmsTelemetryData = {
735
.bmsState = TelemetryState::NOT_CONNECTED
836
};
@@ -51,10 +79,6 @@ void updateBMSData() {
5179
// Calculated highest cell minus lowest cell voltage
5280
bmsTelemetryData.voltage_differential = bms_can->getHighestCellVoltage() - bms_can->getLowestCellVoltage();
5381

54-
// Temperature readings
55-
bmsTelemetryData.highest_temperature = bms_can->getHighestTemperature();
56-
bmsTelemetryData.lowest_temperature = bms_can->getLowestTemperature();
57-
5882
// Battery statistics
5983
bmsTelemetryData.battery_cycle = bms_can->getBatteryCycle();
6084
bmsTelemetryData.energy_cycle = bms_can->getEnergyCycle();
@@ -70,13 +94,31 @@ void updateBMSData() {
7094
bmsTelemetryData.cell_voltages[i] = bms_can->getCellVoltage(i);
7195
}
7296

73-
// Populate individual temperature sensors
97+
// Populate temperature sensors (library returns NaN for disconnected probes)
7498
bmsTelemetryData.mos_temperature = bms_can->getTemperature(0); // BMS MOSFET
7599
bmsTelemetryData.balance_temperature = bms_can->getTemperature(1); // BMS Balance resistors
76-
bmsTelemetryData.t1_temperature = bms_can->getTemperature(2); // Cell probe 1
77-
bmsTelemetryData.t2_temperature = bms_can->getTemperature(3); // Cell probe 2
78-
bmsTelemetryData.t3_temperature = bms_can->getTemperature(4); // Cell probe 3
79-
bmsTelemetryData.t4_temperature = bms_can->getTemperature(5); // Cell probe 4
100+
101+
// Cell probes: library already returns NaN for disconnected. Apply app-level
102+
// policy (tolerate up to N disconnected before alerting).
103+
const float cellTemps[BMS_CELL_PROBE_COUNT] = {
104+
bms_can->getTemperature(2),
105+
bms_can->getTemperature(3),
106+
bms_can->getTemperature(4),
107+
bms_can->getTemperature(5)
108+
};
109+
float sanitizedCellTemps[BMS_CELL_PROBE_COUNT];
110+
sanitizeCellProbeTemps(cellTemps, sanitizedCellTemps);
111+
bmsTelemetryData.t1_temperature = sanitizedCellTemps[0];
112+
bmsTelemetryData.t2_temperature = sanitizedCellTemps[1];
113+
bmsTelemetryData.t3_temperature = sanitizedCellTemps[2];
114+
bmsTelemetryData.t4_temperature = sanitizedCellTemps[3];
115+
116+
// Emit transition logs to help field-debug intermittent probe wiring issues.
117+
logBmsCellProbeConnectionTransitions(sanitizedCellTemps);
118+
119+
// Library already excludes disconnected probes from high/low temps
120+
bmsTelemetryData.highest_temperature = bms_can->getHighestTemperature();
121+
bmsTelemetryData.lowest_temperature = bms_can->getLowestTemperature();
80122

81123
bmsTelemetryData.lastUpdateMs = millis();
82124
unsigned long dur = bmsTelemetryData.lastUpdateMs - tStart;

src/sp140/lvgl/lvgl_updates.cpp

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -334,11 +334,25 @@ void updateLvglMainScreen(
334334
float batteryPercent = unifiedBatteryData.soc;
335335
float totalVolts = unifiedBatteryData.volts;
336336
float lowestCellV = bmsTelemetry.lowest_cell_voltage;
337-
// Calculate highest cell temperature from T1-T4 only (excluding MOSFET and balance temps)
338-
float batteryTemp = bmsTelemetry.t1_temperature;
339-
if (bmsTelemetry.t2_temperature > batteryTemp) batteryTemp = bmsTelemetry.t2_temperature;
340-
if (bmsTelemetry.t3_temperature > batteryTemp) batteryTemp = bmsTelemetry.t3_temperature;
341-
if (bmsTelemetry.t4_temperature > batteryTemp) batteryTemp = bmsTelemetry.t4_temperature;
337+
// Calculate battery temp from connected T1-T4 cell probes only.
338+
const float cellTemps[] = {
339+
bmsTelemetry.t1_temperature,
340+
bmsTelemetry.t2_temperature,
341+
bmsTelemetry.t3_temperature,
342+
bmsTelemetry.t4_temperature
343+
};
344+
float batteryTemp = NAN;
345+
bool hasValidBatteryTemp = false;
346+
for (float cellTemp : cellTemps) {
347+
if (isnan(cellTemp)) {
348+
continue;
349+
}
350+
351+
if (!hasValidBatteryTemp || cellTemp > batteryTemp) {
352+
batteryTemp = cellTemp;
353+
hasValidBatteryTemp = true;
354+
}
355+
}
342356
float escTemp = escTelemetry.cap_temp;
343357
float motorTemp = escTelemetry.motor_temp;
344358
// Check if BMS or ESC is connected
@@ -660,7 +674,7 @@ void updateLvglMainScreen(
660674
lv_obj_remove_style(batt_temp_bg, &style_warning, 0);
661675
lv_obj_remove_style(batt_temp_bg, &style_critical, 0);
662676

663-
if (bmsTelemetry.bmsState == TelemetryState::CONNECTED) {
677+
if (bmsTelemetry.bmsState == TelemetryState::CONNECTED && hasValidBatteryTemp) {
664678
lv_label_set_text_fmt(batt_temp_label, "%d", static_cast<int>(batteryTemp));
665679
if (batteryTemp >= bmsCellTempThresholds.critHigh) {
666680
lv_obj_add_style(batt_temp_bg, &style_critical, 0);
@@ -672,6 +686,7 @@ void updateLvglMainScreen(
672686
lv_obj_add_flag(batt_temp_bg, LV_OBJ_FLAG_HIDDEN);
673687
}
674688
} else {
689+
// No valid cell probe connected: show "-" instead of a fake low reading.
675690
lv_label_set_text(batt_temp_label, "-");
676691
lv_obj_add_flag(batt_temp_bg, LV_OBJ_FLAG_HIDDEN);
677692
}

test/native_stubs/BMS_CAN.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#pragma once
2+
3+
#include <cmath>
4+
#include <cstdint>
5+
6+
class BMS_CAN {
7+
public:
8+
static constexpr float TEMP_PROBE_DISCONNECTED = -40.0f;
9+
};

test/native_stubs/SPI.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#pragma once
2+
3+
class SPIClass {};

0 commit comments

Comments
 (0)