From 4a993ebf7308a0bf1d970a866215934ef345adc7 Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Mon, 6 Jul 2026 16:29:48 -0400 Subject: [PATCH 01/58] Fix cell temp sanitizer --- Core/Inc/cell_temp_sanitizer.h | 22 ++-- Core/Inc/datastructs.h | 2 + Core/Src/analyzer.c | 1 + Core/Src/cell_temp_sanitizer.c | 234 +++++++++++++++++++-------------- 4 files changed, 152 insertions(+), 107 deletions(-) diff --git a/Core/Inc/cell_temp_sanitizer.h b/Core/Inc/cell_temp_sanitizer.h index d87bb290..1bf7f7fb 100644 --- a/Core/Inc/cell_temp_sanitizer.h +++ b/Core/Inc/cell_temp_sanitizer.h @@ -1,25 +1,25 @@ -#include -#include "timer.h" #include "datastructs.h" #ifndef CELL_TEMP_SANITIZER_H #define CELL_TEMP_SANITIZER_H -#define TOO_DIFF_THRESHOLD 0.20 - /** - * @brief Creates a grid of therm_t. - * @param sanitizer Struct containing sanitized therm data + * @brief Initializes the temperature sanitizer state. + * + * @param sanitizer Pointer to the sanitizer data structure. */ void temp_sanitizer_init(sanitizer_t *sanitizer); /** - * @brief Given a grid of cell temperatures, updates the given grid of sanitized cell temperatures. - * Should be run periodically. - * @param sanitizer Struct containing sanitized therm data - * @param analyzer Struct containing analyzer data + * @brief Processes and sanitizes cell temperature readings. + * + * Valid samples update the tracked minimum and maximum temperatures while + * invalid samples are filtered and tracked using the fault counter. + * + * @param sanitizer Pointer to the sanitizer data structure. + * @param analyzer Pointer to the analyzer containing raw temperature data. */ -void temp_sanitizer_run(sanitizer_t *sanitizer, analyzer_t *analyzer); +void temp_sanitizer_run(sanitizer_t *const sanitizer, const analyzer_t *const analyzer); void vSanitizer(ULONG thread_input); diff --git a/Core/Inc/datastructs.h b/Core/Inc/datastructs.h index ae4fe447..d4893459 100644 --- a/Core/Inc/datastructs.h +++ b/Core/Inc/datastructs.h @@ -85,6 +85,8 @@ typedef struct { typedef struct { float last_temp; bool valid; + bool initialized; + uint8_t fault_count; } therm_state_t; /** diff --git a/Core/Src/analyzer.c b/Core/Src/analyzer.c index c8d080c4..b65418b4 100644 --- a/Core/Src/analyzer.c +++ b/Core/Src/analyzer.c @@ -425,6 +425,7 @@ void vAnalyzer(ULONG thread_input) mutex_put(&analyzer_mutex); + set_flag(SANITIZER_FLAG); set_flag(DEBUG_FLAG); // send out telemetry data sourced from the above functions diff --git a/Core/Src/cell_temp_sanitizer.c b/Core/Src/cell_temp_sanitizer.c index c8c936c7..f550333e 100644 --- a/Core/Src/cell_temp_sanitizer.c +++ b/Core/Src/cell_temp_sanitizer.c @@ -1,112 +1,164 @@ #include "cell_temp_sanitizer.h" #include "analyzer.h" -#include "debounce.h" +#include "u_tx_flags.h" +#include #include -#include -void temp_sanitizer_init(sanitizer_t *sanitizer) +/** + * @brief Number of consecutive invalid samples before a thermistor is marked invalid. + */ +#define THERM_FAULT_COUNT_THRESHOLD (2U) + +/** + * @brief Maximum allowed temperature change between consecutive valid samples. + */ +#define TEMP_MAX_DELTA_C (5.0f) + +/** + * @brief Resets the sanitized minimum and maximum temperature tracking. + * + * @param sanitizer Pointer to the sanitizer data structure. + */ +static void reset_sanitized_min_max(sanitizer_t *const sanitizer) { - sanitizer->max_sanitized_temp.val = FLT_MIN; - sanitizer->max_sanitized_temp.cellNum = 0; - sanitizer->max_sanitized_temp.chipIndex = 0; - - sanitizer->min_sanitized_temp.val = FLT_MAX; - sanitizer->min_sanitized_temp.cellNum = 0; - sanitizer->min_sanitized_temp.chipIndex = 0; - - for (int chip = 0; chip < NUM_CHIPS; chip++) { - for (int cell = 0; cell < NUM_CELLS_PER_CHIP; cell++) { - sanitizer->sanitized_therms[chip][cell].last_temp = 0; - sanitizer->sanitized_therms[chip][cell].valid = true; - } + sanitizer->max_sanitized_temp.val = -FLT_MIN; + sanitizer->min_sanitized_temp.chipIndex = 0U; + sanitizer->max_sanitized_temp.cellNum = 0U; + + sanitizer->min_sanitized_temp.val = FLT_MAX; + sanitizer->min_sanitized_temp.chipIndex = 0U; + sanitizer->min_sanitized_temp.cellNum = 0U; +} + +/** + * @brief Records a thermistor fault and invalidates the thermistor if the fault threshold is reached. + * + * @param therm_state Pointer to the thermistor state. + */ +static void record_therm_fault(therm_state_t *const therm_state) +{ + therm_state->fault_count++; + + if (therm_state->fault_count >= THERM_FAULT_COUNT_THRESHOLD) + { + therm_state->valid = false; } } -static void sanitized_max_temp(sanitizer_t *sanitizer, analyzer_t *analyzer, int chip, int cell, float cell_temp, therm_state_t *therm_state) +/** + * @brief Updates the sanitized maximum temperature if the provided value is higher. + * + * @param sanitizer Pointer to the sanitizer instance. + * @param chip Chip index. + * @param cell Cell index. + * @param cell_temp Temperature reading. + */ +static void update_sanitized_max_temp(sanitizer_t *const sanitizer, const uint8_t chip, const uint8_t cell, const float cell_temp) { - - // update the max sanitized temp on a valid max - if (therm_state->valid) { - if (cell_temp > - sanitizer->max_sanitized_temp.val) { - sanitizer->max_sanitized_temp.val = - cell_temp; - sanitizer->max_sanitized_temp.chipIndex = - chip; - sanitizer->max_sanitized_temp.cellNum = - cell; - } - // if max is no longer valid, max is reset - } else if (sanitizer->max_sanitized_temp.cellNum == - cell && - sanitizer->max_sanitized_temp.chipIndex == - chip) { - sanitizer->max_sanitized_temp.val = analyzer->avg_temp; + if (cell_temp > sanitizer->max_sanitized_temp.val) + { + sanitizer->max_sanitized_temp.val = cell_temp; + sanitizer->max_sanitized_temp.chipIndex = chip; + sanitizer->max_sanitized_temp.cellNum = cell; } } - -static void sanitized_min_temp(sanitizer_t *sanitizer, analyzer_t *analyzer, int chip, int cell, float cell_temp, therm_state_t *therm_state) + +/** + * @brief Updates the sanitized minimum temperature if the provided value is lower. + * + * @param sanitizer Pointer to the sanitizer instance. + * @param chip Chip index. + * @param cell Cell index. + * @param cell_temp Temperature reading. + */ +static void update_sanitized_min_temp(sanitizer_t *const sanitizer, const uint8_t chip, const uint8_t cell, const float cell_temp) { + if (cell_temp < sanitizer->min_sanitized_temp.val) + { + sanitizer->min_sanitized_temp.val = cell_temp; + sanitizer->min_sanitized_temp.chipIndex = chip; + sanitizer->min_sanitized_temp.cellNum = cell; + } +} - // update the min sanitized temp on a valid min - if (therm_state->valid) { - if (cell_temp < // to get the lowest value - sanitizer->min_sanitized_temp.val) { - sanitizer->min_sanitized_temp.val = - cell_temp; - sanitizer->min_sanitized_temp.chipIndex = - chip; - sanitizer->min_sanitized_temp.cellNum = - cell; +void temp_sanitizer_init(sanitizer_t *const sanitizer) +{ + reset_sanitized_min_max(sanitizer); + + for (uint8_t chip = 0U; chip < NUM_CHIPS; chip++) { + for (uint8_t cell = 0U; cell < NUM_CELLS_PER_CHIP; cell++) { + sanitizer->sanitized_therms[chip][cell].last_temp = 0.0f; + sanitizer->sanitized_therms[chip][cell].valid = false; + sanitizer->sanitized_therms[chip][cell].initialized = false; + sanitizer->sanitized_therms[chip][cell].fault_count = 0U; } - // if min is no longer valid, min is reset - } else if (sanitizer->min_sanitized_temp.cellNum == - cell && - sanitizer->min_sanitized_temp.chipIndex == - chip) { - sanitizer->max_sanitized_temp.val = analyzer->avg_temp; } } - -void temp_sanitizer_run(sanitizer_t *sanitizer, analyzer_t *analyzer) -{ - static bool first_reading = true; - for (int chip = 0; chip < NUM_CHIPS; chip++) { - for (int cell = 0; cell < NUM_CELLS_PER_CHIP; cell++) { - therm_state_t *therm_state = - &sanitizer->sanitized_therms[chip][cell]; - - float cell_temp = analyzer->chip_data[chip].cell_temp[cell]; - if (!first_reading && (cell_temp <= 0 || cell_temp > MAX_TEMP)) { - therm_state->valid = false; - continue; +void temp_sanitizer_run(sanitizer_t *const sanitizer, const analyzer_t *const analyzer) +{ + reset_sanitized_min_max(sanitizer); + + for (uint8_t chip = 0U; chip < NUM_CHIPS; chip++) + { + for (uint8_t cell = 0U; cell < NUM_CELLS; cell++) + { + therm_state_t *const therm_state = &sanitizer->sanitized_therms[chip][cell]; + const float cell_temp = analyzer->chip_data[chip].cell_temp[cell]; + bool sample_valid = false; + const bool temp_in_range = ((cell_temp >= MIN_TEMP) && (cell_temp <= MAX_CELL_TEMP)); + + if (therm_state->initialized == false) + { + if (temp_in_range) + { + therm_state->last_temp = cell_temp; + therm_state->initialized = true; + therm_state->valid = true; + therm_state->fault_count = 0U; + sample_valid = true; + } + else + { + record_therm_fault(therm_state); + sample_valid = false; + } } - - if (!first_reading && - cell_temp > - therm_state->last_temp * - (1 + (float)TOO_DIFF_THRESHOLD)) { - therm_state->valid = false; - } else if (!first_reading && - cell_temp < - therm_state->last_temp * - (1 - - (float)TOO_DIFF_THRESHOLD)) { - therm_state->valid = false; + else if (therm_state->valid == false) + { + sample_valid = false; + } + else + { + const float temp_delta = fabsf(cell_temp - therm_state->last_temp); + + if (temp_in_range == false) + { + record_therm_fault(therm_state); + sample_valid = false; + } + else if (temp_delta > TEMP_MAX_DELTA_C) + { + record_therm_fault(therm_state); + sample_valid = false; + } + else + { + therm_state->last_temp = cell_temp; + therm_state->fault_count = 0U; + sample_valid = true; + } } - therm_state->last_temp = cell_temp; - sanitized_max_temp(sanitizer, analyzer, chip, cell, cell_temp, therm_state); - sanitized_min_temp(sanitizer, analyzer, chip, cell, cell_temp, therm_state); + if (sample_valid == true) + { + update_sanitized_max_temp(sanitizer, chip, cell, cell_temp); + update_sanitized_min_temp(sanitizer, chip, cell, cell_temp); + } } } - first_reading = false; - - } - // SANITIZER THREAD void vSanitizer(ULONG thread_input) { @@ -119,17 +171,7 @@ void vSanitizer(ULONG thread_input) temp_sanitizer_init(sanitizer); for (;;) { + set_flag(SANITIZER_FLAG); temp_sanitizer_run(sanitizer, analyzer); - tx_thread_sleep(MS_TO_TICKS(500)); } } - - - - - - - - - - From 9076ab6d09f34892370344f95868562111c5d63e Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Mon, 6 Jul 2026 22:39:30 -0400 Subject: [PATCH 02/58] Add cell temp sanitizer unit tests --- Core/Src/cell_temp_sanitizer.c | 24 ++- Tests/Src/test_cell_temp_sanitizer.c | 244 +++++++++++++++++++++++++++ Tests/ner_test.conf | 13 ++ 3 files changed, 274 insertions(+), 7 deletions(-) create mode 100644 Tests/Src/test_cell_temp_sanitizer.c diff --git a/Core/Src/cell_temp_sanitizer.c b/Core/Src/cell_temp_sanitizer.c index f550333e..82ff3403 100644 --- a/Core/Src/cell_temp_sanitizer.c +++ b/Core/Src/cell_temp_sanitizer.c @@ -2,7 +2,9 @@ #include "analyzer.h" #include "u_tx_flags.h" #include +#include #include +#include /** * @brief Number of consecutive invalid samples before a thermistor is marked invalid. @@ -10,7 +12,7 @@ #define THERM_FAULT_COUNT_THRESHOLD (2U) /** - * @brief Maximum allowed temperature change between consecutive valid samples. + * @brief Maximum allowed temperature change from the last trusted thermistor sample. */ #define TEMP_MAX_DELTA_C (5.0f) @@ -21,8 +23,8 @@ */ static void reset_sanitized_min_max(sanitizer_t *const sanitizer) { - sanitizer->max_sanitized_temp.val = -FLT_MIN; - sanitizer->min_sanitized_temp.chipIndex = 0U; + sanitizer->max_sanitized_temp.val = -FLT_MAX; + sanitizer->max_sanitized_temp.chipIndex = 0U; sanitizer->max_sanitized_temp.cellNum = 0U; sanitizer->min_sanitized_temp.val = FLT_MAX; @@ -31,7 +33,7 @@ static void reset_sanitized_min_max(sanitizer_t *const sanitizer) } /** - * @brief Records a thermistor fault and invalidates the thermistor if the fault threshold is reached. + * @brief Records a thermistor fault and marks it invalid if the threshold is reached. * * @param therm_state Pointer to the thermistor state. */ @@ -39,6 +41,7 @@ static void record_therm_fault(therm_state_t *const therm_state) { therm_state->fault_count++; + // Repeated faults mark thermistor invalid if (therm_state->fault_count >= THERM_FAULT_COUNT_THRESHOLD) { therm_state->valid = false; @@ -101,15 +104,17 @@ void temp_sanitizer_run(sanitizer_t *const sanitizer, const analyzer_t *const an for (uint8_t chip = 0U; chip < NUM_CHIPS; chip++) { - for (uint8_t cell = 0U; cell < NUM_CELLS; cell++) + for (uint8_t cell = 0U; cell < NUM_CELLS_PER_CHIP; cell++) { therm_state_t *const therm_state = &sanitizer->sanitized_therms[chip][cell]; const float cell_temp = analyzer->chip_data[chip].cell_temp[cell]; bool sample_valid = false; - const bool temp_in_range = ((cell_temp >= MIN_TEMP) && (cell_temp <= MAX_CELL_TEMP)); + const bool temp_in_range = ((cell_temp >= (float)MIN_TEMP) && (cell_temp <= (float)MAX_CELL_TEMP)); - if (therm_state->initialized == false) + // Only initialize thermistors that have not faulted out during startup + if ((therm_state->initialized == false) && (therm_state->fault_count < THERM_FAULT_COUNT_THRESHOLD)) { + // First trusted sample initializes thermistor if (temp_in_range) { therm_state->last_temp = cell_temp; @@ -120,12 +125,14 @@ void temp_sanitizer_run(sanitizer_t *const sanitizer, const analyzer_t *const an } else { + // Startup sample outside physical range record_therm_fault(therm_state); sample_valid = false; } } else if (therm_state->valid == false) { + // Invalid thermistors do not recover automatically sample_valid = false; } else @@ -134,16 +141,19 @@ void temp_sanitizer_run(sanitizer_t *const sanitizer, const analyzer_t *const an if (temp_in_range == false) { + // Reject physically invalid sample record_therm_fault(therm_state); sample_valid = false; } else if (temp_delta > TEMP_MAX_DELTA_C) { + // Reject sudden jump from last trusted value record_therm_fault(therm_state); sample_valid = false; } else { + // Sample is trusted therm_state->last_temp = cell_temp; therm_state->fault_count = 0U; sample_valid = true; diff --git a/Tests/Src/test_cell_temp_sanitizer.c b/Tests/Src/test_cell_temp_sanitizer.c new file mode 100644 index 00000000..6409bccd --- /dev/null +++ b/Tests/Src/test_cell_temp_sanitizer.c @@ -0,0 +1,244 @@ +#include "unity.h" + +#include "cell_temp_sanitizer.h" + +/* ------------------------------------------------- + * Setup / Teardown + * ------------------------------------------------- */ + +static sanitizer_t test_sanitizer; +static analyzer_t test_analyzer; + +void setUp(void) +{ + temp_sanitizer_init(&test_sanitizer); + + for (uint8_t chip = 0U; chip < NUM_CHIPS; chip++) + { + for (uint8_t cell = 0U; cell < NUM_CELLS_PER_CHIP; cell++) + { + test_analyzer.chip_data[chip].cell_temp[cell] = 25.0f; + } + } +} + +void tearDown(void) {} + +/* ------------------------------------------------- + * Cell Temperature Sanitizer + * ------------------------------------------------- */ + +void test_temp_init_defaults(void) +{ + for (uint8_t chip = 0U; chip < NUM_CHIPS; chip++) + { + for (uint8_t cell = 0U; cell < NUM_CELLS_PER_CHIP; cell++) + { + TEST_ASSERT_EQUAL_FLOAT(0.0f, test_sanitizer.sanitized_therms[chip][cell].last_temp); + TEST_ASSERT_FALSE(test_sanitizer.sanitized_therms[chip][cell].valid); + TEST_ASSERT_FALSE(test_sanitizer.sanitized_therms[chip][cell].initialized); + TEST_ASSERT_EQUAL_UINT8(0U, test_sanitizer.sanitized_therms[chip][cell].fault_count); + } + } +} + +void test_temp_valid_min_max(void) +{ + /* -------- Valid temperature samples -------- */ + + test_analyzer.chip_data[0].cell_temp[0] = 20.0f; + test_analyzer.chip_data[0].cell_temp[1] = 30.0f; + + temp_sanitizer_run(&test_sanitizer, &test_analyzer); + + TEST_ASSERT_TRUE(test_sanitizer.sanitized_therms[0][0].valid); + TEST_ASSERT_TRUE(test_sanitizer.sanitized_therms[0][0].initialized); + TEST_ASSERT_EQUAL_FLOAT(20.0f, test_sanitizer.sanitized_therms[0][0].last_temp); + + TEST_ASSERT_EQUAL_FLOAT(20.0f, test_sanitizer.min_sanitized_temp.val); + TEST_ASSERT_EQUAL_UINT8(0U, test_sanitizer.min_sanitized_temp.chipIndex); + TEST_ASSERT_EQUAL_UINT8(0U, test_sanitizer.min_sanitized_temp.cellNum); + + TEST_ASSERT_EQUAL_FLOAT(30.0f, test_sanitizer.max_sanitized_temp.val); + TEST_ASSERT_EQUAL_UINT8(0U, test_sanitizer.max_sanitized_temp.chipIndex); + TEST_ASSERT_EQUAL_UINT8(1U, test_sanitizer.max_sanitized_temp.cellNum); +} + +void test_temp_range_limits(void) +{ + /* -------- Boundary temperatures accepted -------- */ + + test_analyzer.chip_data[0].cell_temp[0] = (float)MIN_TEMP; + test_analyzer.chip_data[0].cell_temp[1] = (float)MAX_CELL_TEMP; + + temp_sanitizer_run(&test_sanitizer, &test_analyzer); + + TEST_ASSERT_TRUE(test_sanitizer.sanitized_therms[0][0].valid); + TEST_ASSERT_TRUE(test_sanitizer.sanitized_therms[0][1].valid); + + TEST_ASSERT_EQUAL_FLOAT((float)MIN_TEMP, test_sanitizer.min_sanitized_temp.val); + TEST_ASSERT_EQUAL_UINT8(0U, test_sanitizer.min_sanitized_temp.chipIndex); + TEST_ASSERT_EQUAL_UINT8(0U, test_sanitizer.min_sanitized_temp.cellNum); + + TEST_ASSERT_EQUAL_FLOAT((float)MAX_CELL_TEMP, test_sanitizer.max_sanitized_temp.val); + TEST_ASSERT_EQUAL_UINT8(0U, test_sanitizer.max_sanitized_temp.chipIndex); + TEST_ASSERT_EQUAL_UINT8(1U, test_sanitizer.max_sanitized_temp.cellNum); +} + +void test_temp_startup_range_faults(void) +{ + /* -------- Temperatures outside valid range -------- */ + + test_analyzer.chip_data[0].cell_temp[0] = (float)MIN_TEMP - 1.0f; + test_analyzer.chip_data[0].cell_temp[1] = (float)MAX_CELL_TEMP + 1.0f; + + temp_sanitizer_run(&test_sanitizer, &test_analyzer); + + TEST_ASSERT_FALSE(test_sanitizer.sanitized_therms[0][0].initialized); + TEST_ASSERT_EQUAL_UINT8(1U, test_sanitizer.sanitized_therms[0][0].fault_count); + + TEST_ASSERT_FALSE(test_sanitizer.sanitized_therms[0][1].initialized); + TEST_ASSERT_EQUAL_UINT8(1U, test_sanitizer.sanitized_therms[0][1].fault_count); + + /* -------- Consecutive faults invalidate -------- */ + + temp_sanitizer_run(&test_sanitizer, &test_analyzer); + + TEST_ASSERT_FALSE(test_sanitizer.sanitized_therms[0][0].valid); + TEST_ASSERT_FALSE(test_sanitizer.sanitized_therms[0][0].initialized); + TEST_ASSERT_EQUAL_UINT8(2U, test_sanitizer.sanitized_therms[0][0].fault_count); + + TEST_ASSERT_FALSE(test_sanitizer.sanitized_therms[0][1].valid); + TEST_ASSERT_FALSE(test_sanitizer.sanitized_therms[0][1].initialized); + TEST_ASSERT_EQUAL_UINT8(2U, test_sanitizer.sanitized_therms[0][1].fault_count); +} + +void test_temp_delta_fault(void) +{ + test_analyzer.chip_data[0].cell_temp[0] = 25.0f; + temp_sanitizer_run(&test_sanitizer, &test_analyzer); + + /* -------- Delta at threshold accepted -------- */ + + test_analyzer.chip_data[0].cell_temp[0] = 30.0f; + temp_sanitizer_run(&test_sanitizer, &test_analyzer); + + TEST_ASSERT_TRUE(test_sanitizer.sanitized_therms[0][0].valid); + TEST_ASSERT_EQUAL_FLOAT(30.0f, test_sanitizer.sanitized_therms[0][0].last_temp); + TEST_ASSERT_EQUAL_UINT8(0U, test_sanitizer.sanitized_therms[0][0].fault_count); + + /* -------- Delta above threshold rejected -------- */ + + test_analyzer.chip_data[0].cell_temp[0] = 35.1f; + temp_sanitizer_run(&test_sanitizer, &test_analyzer); + + TEST_ASSERT_TRUE(test_sanitizer.sanitized_therms[0][0].valid); + TEST_ASSERT_EQUAL_FLOAT(30.0f, test_sanitizer.sanitized_therms[0][0].last_temp); + TEST_ASSERT_EQUAL_UINT8(1U, test_sanitizer.sanitized_therms[0][0].fault_count); + + /* -------- Second fault invalidates -------- */ + + temp_sanitizer_run(&test_sanitizer, &test_analyzer); + + TEST_ASSERT_FALSE(test_sanitizer.sanitized_therms[0][0].valid); + TEST_ASSERT_EQUAL_FLOAT(30.0f, test_sanitizer.sanitized_therms[0][0].last_temp); + TEST_ASSERT_EQUAL_UINT8(2U, test_sanitizer.sanitized_therms[0][0].fault_count); +} + +void test_temp_runtime_range_fault(void) +{ + test_analyzer.chip_data[0].cell_temp[0] = 25.0f; + temp_sanitizer_run(&test_sanitizer, &test_analyzer); + + /* -------- Runtime out-of-range rejected -------- */ + + test_analyzer.chip_data[0].cell_temp[0] = (float)MAX_CELL_TEMP + 1.0f; + temp_sanitizer_run(&test_sanitizer, &test_analyzer); + + TEST_ASSERT_TRUE(test_sanitizer.sanitized_therms[0][0].valid); + TEST_ASSERT_TRUE(test_sanitizer.sanitized_therms[0][0].initialized); + TEST_ASSERT_EQUAL_FLOAT(25.0f, test_sanitizer.sanitized_therms[0][0].last_temp); + TEST_ASSERT_EQUAL_UINT8(1U, test_sanitizer.sanitized_therms[0][0].fault_count); + + /* -------- Consecutive runtime faults invalidate -------- */ + + temp_sanitizer_run(&test_sanitizer, &test_analyzer); + + TEST_ASSERT_FALSE(test_sanitizer.sanitized_therms[0][0].valid); + TEST_ASSERT_TRUE(test_sanitizer.sanitized_therms[0][0].initialized); + TEST_ASSERT_EQUAL_FLOAT(25.0f, test_sanitizer.sanitized_therms[0][0].last_temp); + TEST_ASSERT_EQUAL_UINT8(2U, test_sanitizer.sanitized_therms[0][0].fault_count); +} + +void test_temp_fault_reset(void) +{ + test_analyzer.chip_data[0].cell_temp[0] = 25.0f; + temp_sanitizer_run(&test_sanitizer, &test_analyzer); + + /* -------- Create first fault -------- */ + + test_analyzer.chip_data[0].cell_temp[0] = 31.0f; + temp_sanitizer_run(&test_sanitizer, &test_analyzer); + + TEST_ASSERT_EQUAL_UINT8(1U, test_sanitizer.sanitized_therms[0][0].fault_count); + + /* -------- Valid sample clears fault count -------- */ + + test_analyzer.chip_data[0].cell_temp[0] = 26.0f; + temp_sanitizer_run(&test_sanitizer, &test_analyzer); + + TEST_ASSERT_TRUE(test_sanitizer.sanitized_therms[0][0].valid); + TEST_ASSERT_EQUAL_UINT8(0U, test_sanitizer.sanitized_therms[0][0].fault_count); + TEST_ASSERT_EQUAL_FLOAT(26.0f, test_sanitizer.sanitized_therms[0][0].last_temp); +} + +void test_temp_invalid_ignored(void) +{ + /* -------- Thermistor becomes invalid -------- */ + + test_analyzer.chip_data[0].cell_temp[0] = (float)MAX_CELL_TEMP + 1.0f; + + temp_sanitizer_run(&test_sanitizer, &test_analyzer); + temp_sanitizer_run(&test_sanitizer, &test_analyzer); + + TEST_ASSERT_FALSE(test_sanitizer.sanitized_therms[0][0].valid); + TEST_ASSERT_FALSE(test_sanitizer.sanitized_therms[0][0].initialized); + TEST_ASSERT_EQUAL_UINT8(2U, test_sanitizer.sanitized_therms[0][0].fault_count); + + /* -------- Valid sample after invalidation -------- */ + + test_analyzer.chip_data[0].cell_temp[0] = 10.0f; + test_analyzer.chip_data[0].cell_temp[1] = 20.0f; + + temp_sanitizer_run(&test_sanitizer, &test_analyzer); + + TEST_ASSERT_FALSE(test_sanitizer.sanitized_therms[0][0].valid); + TEST_ASSERT_FALSE(test_sanitizer.sanitized_therms[0][0].initialized); + TEST_ASSERT_EQUAL_UINT8(2U, test_sanitizer.sanitized_therms[0][0].fault_count); + + /* -------- Invalid thermistor is ignored -------- */ + + TEST_ASSERT_EQUAL_FLOAT(20.0f, test_sanitizer.min_sanitized_temp.val); + TEST_ASSERT_EQUAL_UINT8(0U, test_sanitizer.min_sanitized_temp.chipIndex); + TEST_ASSERT_EQUAL_UINT8(1U, test_sanitizer.min_sanitized_temp.cellNum); +} + +/* ------------------------------------------------- + * Test Runner + * ------------------------------------------------- */ + +int main(void) +{ + UNITY_BEGIN(); + + RUN_TEST(test_temp_init_defaults); + RUN_TEST(test_temp_valid_min_max); + RUN_TEST(test_temp_range_limits); + RUN_TEST(test_temp_startup_range_faults); + RUN_TEST(test_temp_delta_fault); + RUN_TEST(test_temp_runtime_range_fault); + RUN_TEST(test_temp_fault_reset); + RUN_TEST(test_temp_invalid_ignored); + + return UNITY_END(); +} \ No newline at end of file diff --git a/Tests/ner_test.conf b/Tests/ner_test.conf index be79581b..bb11f285 100644 --- a/Tests/ner_test.conf +++ b/Tests/ner_test.conf @@ -56,6 +56,15 @@ sources = [ "Core/Src/soc.c" ] +[test-packages.cell_temp_sanitizer] +mocked-files = [ + "Drivers/Embedded-Base/threadX/inc/u_tx_flags.h" +] + +sources = [ + "Core/Src/cell_temp_sanitizer.c" +] + [test-packages.statemachine] mocked-files = [ "Core/Inc/compute.h", @@ -86,6 +95,10 @@ test-file = "Tests/Src/test_ccl.c" test-package = "soc" test-file = "Tests/Src/test_soc.c" +[tests.cell_temp_sanitizer] +test-package = "cell_temp_sanitizer" +test-file = "Tests/Src/test_cell_temp_sanitizer.c" + [tests.statemachine] test-package = "statemachine" test-file = "Tests/Src/test_statemachine.c" \ No newline at end of file From fcea6e0c8aed15478c1526f76dbde7cc16001e4e Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Mon, 6 Jul 2026 22:52:27 -0400 Subject: [PATCH 03/58] Add mutexes --- Core/Src/bms_algos.c | 4 ++++ Core/Src/cell_temp_sanitizer.c | 4 ++++ Tests/Src/test_cell_temp_sanitizer.c | 1 + Tests/ner_test.conf | 6 ++++-- 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Core/Src/bms_algos.c b/Core/Src/bms_algos.c index 0f11134a..469caa5a 100644 --- a/Core/Src/bms_algos.c +++ b/Core/Src/bms_algos.c @@ -2,6 +2,8 @@ #include "state_machine.h" #include "ccl.h" #include "dcl.h" +#include "u_tx_mutex.h" +#include "shep_mutexes.h" bool disable_pulse(state_machine_t *const state_machine) { @@ -30,12 +32,14 @@ void vBMSAlgorithms(ULONG thread_input) analyzer_t *analyzer = bms_algos_args->analyzer; for (;;) { + mutex_get(&analyzer_mutex); current_limit_algo_inputs_t algo_inputs = { .max_ocv = analyzer->max_ocv.val, .min_ocv = analyzer->min_ocv.val, .max_temp = sanitizer->max_sanitized_temp.val, .min_temp = sanitizer->min_sanitized_temp.val }; + mutex_put(&analyzer_mutex); dcl_calc_inst_limit(algo_inputs, bms_algos); ccl_calc_inst_limit(algo_inputs, bms_algos); diff --git a/Core/Src/cell_temp_sanitizer.c b/Core/Src/cell_temp_sanitizer.c index 82ff3403..1f9a16ef 100644 --- a/Core/Src/cell_temp_sanitizer.c +++ b/Core/Src/cell_temp_sanitizer.c @@ -1,6 +1,8 @@ #include "cell_temp_sanitizer.h" #include "analyzer.h" #include "u_tx_flags.h" +#include "u_tx_mutex.h" +#include "shep_mutexes.h" #include #include #include @@ -182,6 +184,8 @@ void vSanitizer(ULONG thread_input) for (;;) { set_flag(SANITIZER_FLAG); + mutex_get(&analyzer_mutex); temp_sanitizer_run(sanitizer, analyzer); + mutex_put(&analyzer_mutex); } } diff --git a/Tests/Src/test_cell_temp_sanitizer.c b/Tests/Src/test_cell_temp_sanitizer.c index 6409bccd..af319ba6 100644 --- a/Tests/Src/test_cell_temp_sanitizer.c +++ b/Tests/Src/test_cell_temp_sanitizer.c @@ -1,6 +1,7 @@ #include "unity.h" #include "cell_temp_sanitizer.h" +#include "shep_mutexes.h" /* ------------------------------------------------- * Setup / Teardown diff --git a/Tests/ner_test.conf b/Tests/ner_test.conf index bb11f285..f46ed3a8 100644 --- a/Tests/ner_test.conf +++ b/Tests/ner_test.conf @@ -58,11 +58,13 @@ sources = [ [test-packages.cell_temp_sanitizer] mocked-files = [ - "Drivers/Embedded-Base/threadX/inc/u_tx_flags.h" + "Drivers/Embedded-Base/threadX/inc/u_tx_flags.h", + "Drivers/Embedded-Base/threadX/inc/u_tx_mutex.h" ] sources = [ - "Core/Src/cell_temp_sanitizer.c" + "Core/Src/cell_temp_sanitizer.c", + "Core/Src/shep_mutexes.c" ] [test-packages.statemachine] From 4bef92b95e822287520d9fc3e274550370b16894 Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Mon, 6 Jul 2026 22:57:12 -0400 Subject: [PATCH 04/58] Fix hv plate pec --- Core/Inc/shep_tasks.h | 2 +- Core/Src/adi2950_interaction.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/Inc/shep_tasks.h b/Core/Inc/shep_tasks.h index 1cb3cd9a..48d22c9b 100644 --- a/Core/Inc/shep_tasks.h +++ b/Core/Inc/shep_tasks.h @@ -9,7 +9,7 @@ // #define DEBUG_RAW_VOLTAGES // #define DEBUG_OCV_VOLTAGES // #define DEBUG_TEMPS -// #define DEBUG_AlGOS +// #define DEBUG_ALGOS /* Initializes all ThreadX threads. * Calls to create_thread() should go in here diff --git a/Core/Src/adi2950_interaction.c b/Core/Src/adi2950_interaction.c index b643d229..07d50a06 100644 --- a/Core/Src/adi2950_interaction.c +++ b/Core/Src/adi2950_interaction.c @@ -166,7 +166,7 @@ void send_hv_plate_pec_errors_message(void) } // Clear PEC errors for next cycle - current_pec_errors = 0U; + hv_plate_pec_errors = 0U; } void set_hv_plate_chips_isospi_line(cell_asic_2950 *ic, isospi_line_2950_ line) From eb1bfb1ddce93573f01479e3dc041a58368450a2 Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Mon, 6 Jul 2026 22:58:23 -0400 Subject: [PATCH 05/58] Updated driver submodule --- Drivers/adbms | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Drivers/adbms b/Drivers/adbms index b72575ea..f338a0b4 160000 --- a/Drivers/adbms +++ b/Drivers/adbms @@ -1 +1 @@ -Subproject commit b72575eaac858ed97641ac9e4e02190224ed2e15 +Subproject commit f338a0b4fbce9d9056e8d9946851d25373bf2ea2 From 1a904939e58eb8549790c318ef137bdd6a230408 Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Mon, 6 Jul 2026 23:15:34 -0400 Subject: [PATCH 06/58] Fix DTI regen current polarity --- Core/Src/hv_plate.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Core/Src/hv_plate.c b/Core/Src/hv_plate.c index 8e15a336..b5da79a4 100644 --- a/Core/Src/hv_plate.c +++ b/Core/Src/hv_plate.c @@ -256,7 +256,9 @@ void vHvPlateData(ULONG thread_input) send_hv_plate_pec_errors_message(); hv_plate_isospi_handle_state(hv_plate, state_machine); send_max_dc_current_command(bms_algos->cont_DCL); - send_max_dc_brake_current_command(bms_algos->cont_CCL); + // DTI expects regen/brake current limit to be negative + const float max_dc_brake_current = (-1.0f * bms_algos->cont_CCL); + send_max_dc_brake_current_command(max_dc_brake_current); tx_thread_sleep(50); } From 8f3269c1a260da823fbf4a247e7c24dc276d605c Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Mon, 6 Jul 2026 23:36:25 -0400 Subject: [PATCH 07/58] Fault table fixes --- Core/Inc/bms_config.h | 1 + Core/Src/state_machine.c | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Core/Inc/bms_config.h b/Core/Inc/bms_config.h index 483a24ab..b29a7a39 100644 --- a/Core/Inc/bms_config.h +++ b/Core/Inc/bms_config.h @@ -68,6 +68,7 @@ #define LOW_CELL_TIME 55000 #define HIGH_TEMP_TIME 55000 #define MAX_CHIPTEMP_TIME 55000 +#define COMMS_FAULT_TIME 20000 // system wide base ADBMS sample rate #define SAMPLE_RATE 2 /* Hz */ diff --git a/Core/Src/state_machine.c b/Core/Src/state_machine.c index 9914d51c..e9f563c7 100644 --- a/Core/Src/state_machine.c +++ b/Core/Src/state_machine.c @@ -8,6 +8,7 @@ #include "c_utils.h" #include #include +#include #include #include "app_threadx.h" #include "shep_mutexes.h" @@ -512,7 +513,7 @@ void update_eval_table(state_machine_args_t *state_machine_args) fault_eval_table[CHARGE_LIMIT_ENFORCEMENT_FAULT] = (fault_eval_t){ .id = "Charge Current Limit", .timer = ovr_chgcurr_timer, - .data_1 = hv_plate->pack_current, + .data_1 = fabsf(hv_plate->pack_current), .optype_1 = GT, .lim_1 = bms_algos->cont_CCL, .timeout = OVER_CHG_CURR_TIME, @@ -579,7 +580,7 @@ void update_eval_table(state_machine_args_t *state_machine_args) .data_1 = state_machine->segment_comms_fault_flag, .optype_1 = GE, .lim_1 = true, - .timeout = 0, + .timeout = COMMS_FAULT_TIME, .optype_2 = NOP, // UNUSED .is_critical = false }; @@ -590,7 +591,7 @@ void update_eval_table(state_machine_args_t *state_machine_args) .data_1 = state_machine->hv_plate_comms_fault_flag, .optype_1 = GE, .lim_1 = true, - .timeout = 0, + .timeout = COMMS_FAULT_TIME, .optype_2 = NOP, // UNUSED .is_critical = true }; @@ -610,6 +611,7 @@ void vStateMachine(ULONG thread_input) analyzer_t *analyzer = state_machine_args->analyzer; state_machine->bms_state = BOOT; + init_boot(state_machine_args); state_machine->balancing_active = false; state_machine->is_charger_connected = false; From 36e177a81d42a35f5800555354245c2359e77917 Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Tue, 7 Jul 2026 01:36:08 -0400 Subject: [PATCH 08/58] Fix cell temp sanitizer flag --- Core/Src/cell_temp_sanitizer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/Src/cell_temp_sanitizer.c b/Core/Src/cell_temp_sanitizer.c index 1f9a16ef..ac82ea58 100644 --- a/Core/Src/cell_temp_sanitizer.c +++ b/Core/Src/cell_temp_sanitizer.c @@ -183,7 +183,7 @@ void vSanitizer(ULONG thread_input) temp_sanitizer_init(sanitizer); for (;;) { - set_flag(SANITIZER_FLAG); + get_flag(SANITIZER_FLAG, TX_WAIT_FOREVER); mutex_get(&analyzer_mutex); temp_sanitizer_run(sanitizer, analyzer); mutex_put(&analyzer_mutex); From 7200bb1e5a582c3b843602449396030a32d8314e Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Wed, 8 Jul 2026 00:47:15 -0400 Subject: [PATCH 09/58] State machine fixes --- Core/Inc/datastructs.h | 6 ++ Core/Inc/state_machine.h | 2 +- Core/Src/state_machine.c | 118 +++++++++++++++++++++++---------------- 3 files changed, 78 insertions(+), 48 deletions(-) diff --git a/Core/Inc/datastructs.h b/Core/Inc/datastructs.h index d4893459..7a327c55 100644 --- a/Core/Inc/datastructs.h +++ b/Core/Inc/datastructs.h @@ -106,6 +106,12 @@ typedef enum { FAULT_TIMER_EXPIRED, } fault_timer_status_t; +typedef enum { + FAULT_NONE, + FAULT_ONGOING, + FAULT_TRIGGERED, +} fault_state_t; + /** * @brief Data needed for the therm temp sanitizer */ diff --git a/Core/Inc/state_machine.h b/Core/Inc/state_machine.h index f8bae907..e2210b51 100644 --- a/Core/Inc/state_machine.h +++ b/Core/Inc/state_machine.h @@ -45,7 +45,7 @@ void sm_fault_return(state_machine_args_t *state_machine_args); * @param fault_code fault code * @return true if fault is present, false otherwise */ -bool sm_fault_eval(fault_eval_t *fault_item, fault_code_t fault_code); +fault_state_t sm_fault_eval(fault_eval_t *fault_item, fault_code_t fault_code); /** * @brief handles the state machine, calls the appropriate handler function and diff --git a/Core/Src/state_machine.c b/Core/Src/state_machine.c index e9f563c7..fbd5f767 100644 --- a/Core/Src/state_machine.c +++ b/Core/Src/state_machine.c @@ -100,9 +100,9 @@ void handle_charging(state_machine_args_t *state_machine_args) (NUM_CELLS_PER_CHIP * 2) * NUM_SEGMENTS), CHARGING_CURRENT, 0x0); - } else { start_timer(&state_machine_args->state_machine - ->charger_message_timer, 1000); + ->charger_message_timer, + 1000); } } else { send_bms_charge_message_send(0, 0, 0xFF); @@ -140,6 +140,7 @@ void init_faulted(state_machine_args_t *state_machine_args) void handle_faulted(state_machine_args_t *state_machine_args) { + compute_set_fault(true); // leave faulted if all is well if (!are_critical_faults_active()) { request_transition(state_machine_args, BOOT); @@ -154,7 +155,6 @@ void sm_handle_state(state_machine_args_t *state_machine_args) if (are_critical_faults_active()) { request_transition(state_machine_args, FAULTED); - } handler_LUT[state_machine_args->state_machine->bms_state]( @@ -183,24 +183,40 @@ void request_transition(state_machine_args_t *state_machine_args, void sm_fault_return(state_machine_args_t *state_machine_args) { - /* FAULT CHECK (Check for fuckies) */ + uint32_t fault_mask; + fault_state_t fault_state = FAULT_NONE; + + /* FAULT CHECK */ update_eval_table(state_machine_args); - bool faulted = false; - for (int i = 0; i < NUM_FAULTS; i++) { - faulted = sm_fault_eval(&fault_eval_table[i], i); - if (faulted) { - fault_flags |= (1 << i); + for (uint32_t fault_id = 0U; fault_id < (uint32_t)NUM_FAULTS; + fault_id++) { + fault_mask = ((uint32_t)1U << fault_id); + + fault_state = sm_fault_eval(&fault_eval_table[fault_id], + (fault_code_t)fault_id); + + if (fault_state == FAULT_NONE) { + (void)atomic_fetch_and(&fault_flags, ~fault_mask); + } else if (fault_state == FAULT_TRIGGERED) { + if (fault_eval_table[fault_id].is_critical) { + send_bms_critically_faulted(true); + } + + (void)atomic_fetch_or(&fault_flags, fault_mask); } else { - fault_flags &= ~(1 << i); + /* FAULT_ONGOING: do nothing */ } } } -bool sm_fault_eval(fault_eval_t *item, fault_code_t fault_code) +fault_state_t sm_fault_eval(fault_eval_t *item, fault_code_t fault_code) { - bool condition1; - bool condition2; + bool condition1 = false; + bool condition2 = false; + bool fault_present = false; + bool timer_active = false; + fault_state_t fault_state = FAULT_NONE; switch (item->optype_1) { case GT: @@ -223,8 +239,10 @@ bool sm_fault_eval(fault_eval_t *item, fault_code_t fault_code) break; case NOP: condition1 = false; + break; default: condition1 = false; + break; } switch (item->optype_2) { @@ -248,53 +266,60 @@ bool sm_fault_eval(fault_eval_t *item, fault_code_t fault_code) break; case NOP: condition2 = false; + break; default: condition2 = false; + break; } - bool fault_present = (condition1 && condition2) || - (condition1 && item->optype_2 == NOP); + fault_present = (condition1 && condition2) || + (condition1 && (item->optype_2 == NOP)); - if (!is_timer_active(&item->timer) && !fault_present) { - return false; - } + timer_active = is_timer_active(&item->timer); - if (is_timer_active(&item->timer)) { - if (!fault_present) { + if (fault_present == false) { + if (timer_active) { PRINTLN_INFO("\tFault cleared: %s\n", item->id); + cancel_timer(&item->timer); - // STOPPING TIMER MESSSAGE + + /* STOPPING TIMER MESSSAGE */ send_bms_fault_timers(FAULT_TIMER_STOPPED, fault_code, item->data_1); - return false; + } else { + /* Timer is already inactive. */ } - if (is_timer_expired(&item->timer) && fault_present) { - PRINTLN_INFO("\tFaulted: %s\n", item->id); + fault_state = FAULT_NONE; + } else { + if (timer_active == false) { + PRINTLN_INFO("\tStarting Fault Timer: %s\n", item->id); + + start_timer(&item->timer, item->timeout); - // FAULT TIMER EXPIRED MESSAGE - send_bms_fault_timers(FAULT_TIMER_EXPIRED, fault_code, + /* STARTING FAULTED TIMER MESSAGE */ + send_bms_fault_timers(FAULT_TIMER_STARTED, fault_code, item->data_1); - if (item->is_critical) { - send_bms_critically_faulted(true); - } - return true; - } - return false; + fault_state = FAULT_ONGOING; + } else { + if (is_timer_expired(&item->timer)) { + PRINTLN_INFO("\tFaulted: %s\n", item->id); - } else if (!is_timer_active(&item->timer) && fault_present) { - PRINTLN_INFO("\tStarting Fault Timer: %s\n", item->id); - start_timer(&item->timer, item->timeout); - // STARTING FAULTED TIMER MESSAGE - send_bms_fault_timers(FAULT_TIMER_STARTED, fault_code, - item->data_1); + /* FAULT TIMER EXPIRED MESSAGE */ + send_bms_fault_timers(FAULT_TIMER_EXPIRED, + fault_code, item->data_1); - return false; + cancel_timer(&item->timer); + + fault_state = FAULT_TRIGGERED; + } else { + fault_state = FAULT_ONGOING; + } + } } - PRINTLN_ERROR("Should not have reached here."); - return true; + return fault_state; } /* This charging algorithm has 3 stages @@ -411,9 +436,7 @@ bool sm_balancing_check(state_machine_args_t *state_machine_args) shutdown_active = state_machine_args->peripherals->shutdown_active; mutex_put(&shutdown_mutex); - //return !shutdown_active; - // FSAE balancing disabled safety - return false; + return !shutdown_active; } void set_segment_comms_fault(state_machine_t *state_mach) @@ -621,13 +644,14 @@ void vStateMachine(ULONG thread_input) for (;;) { sm_handle_state(state_machine_args); - + // send unimportant messages less frequently if (is_timer_expired(&telem_timer)) { send_bms_status(state_machine->bms_state, analyzer->avg_temp); - - send_bms_critically_faulted(are_critical_faults_active()); + + send_bms_critically_faulted( + are_critical_faults_active()); send_fault_status( get_fault(DISCHARGE_LIMIT_ENFORCEMENT_FAULT), From 5dfaa23e9d326d356b17d4b09af53eb52fa640a0 Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Wed, 8 Jul 2026 15:27:33 -0400 Subject: [PATCH 10/58] Fix ccl enforcement fault limit polarity --- Core/Src/state_machine.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/Src/state_machine.c b/Core/Src/state_machine.c index fbd5f767..a66e9cc8 100644 --- a/Core/Src/state_machine.c +++ b/Core/Src/state_machine.c @@ -536,9 +536,9 @@ void update_eval_table(state_machine_args_t *state_machine_args) fault_eval_table[CHARGE_LIMIT_ENFORCEMENT_FAULT] = (fault_eval_t){ .id = "Charge Current Limit", .timer = ovr_chgcurr_timer, - .data_1 = fabsf(hv_plate->pack_current), + .data_1 = hv_plate->pack_current, .optype_1 = GT, - .lim_1 = bms_algos->cont_CCL, + .lim_1 = (-1.0f * bms_algos->cont_CCL), .timeout = OVER_CHG_CURR_TIME, .optype_2 = NOP, // UNUSED .is_critical = true }; From e17201a16a73253a55743364574f2f5c5260dbc4 Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Wed, 8 Jul 2026 15:37:00 -0400 Subject: [PATCH 11/58] Fix fault table --- Core/Src/state_machine.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/Src/state_machine.c b/Core/Src/state_machine.c index a66e9cc8..f8fca269 100644 --- a/Core/Src/state_machine.c +++ b/Core/Src/state_machine.c @@ -505,7 +505,7 @@ void update_eval_table(state_machine_args_t *state_machine_args) fault_eval_table[CHARGE_LIMIT_ENFORCEMENT_FAULT].data_1 = hv_plate->pack_current; fault_eval_table[CHARGE_LIMIT_ENFORCEMENT_FAULT].lim_1 = - bms_algos->cont_CCL; + (-1.0f * bms_algos->cont_CCL); fault_eval_table[CELL_VOLTAGE_TOO_LOW].data_1 = analyzer->min_ocv.val; fault_eval_table[CELL_VOLTAGE_TOO_HIGH].data_1 = From ca1fd7f115611d366982e959d5b87edc865e6fd8 Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Wed, 8 Jul 2026 16:01:26 -0400 Subject: [PATCH 12/58] Fix ccl fault optype --- Core/Src/state_machine.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/Src/state_machine.c b/Core/Src/state_machine.c index f8fca269..1ba97ae4 100644 --- a/Core/Src/state_machine.c +++ b/Core/Src/state_machine.c @@ -537,7 +537,7 @@ void update_eval_table(state_machine_args_t *state_machine_args) (fault_eval_t){ .id = "Charge Current Limit", .timer = ovr_chgcurr_timer, .data_1 = hv_plate->pack_current, - .optype_1 = GT, + .optype_1 = LT, .lim_1 = (-1.0f * bms_algos->cont_CCL), .timeout = OVER_CHG_CURR_TIME, .optype_2 = NOP, // UNUSED From c56cf7df648c114809a6fa6c4e1324056d7a3d22 Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Wed, 8 Jul 2026 17:35:19 -0400 Subject: [PATCH 13/58] Update dcl config for p50b --- Core/Inc/current_limit_algo_config.h | 2 +- Tests/Inc/test_current_limit_algo_config.h | 2 +- Tests/Src/test_dcl.c | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Core/Inc/current_limit_algo_config.h b/Core/Inc/current_limit_algo_config.h index f6c566fa..a6c0636f 100644 --- a/Core/Inc/current_limit_algo_config.h +++ b/Core/Inc/current_limit_algo_config.h @@ -17,7 +17,7 @@ #define DCL_TEMP_MAX_C (60.0f) #define DCL_OCV_MIN_V (3.0f) #define DCL_OCV_DERATE_THRESH (3.7f) -#define DCL_MAX_CURRENT_A (135.0f) +#define DCL_MAX_CURRENT_A (180.0f) #define DCL_MIN_CURRENT_A (30.0f) #define DCL_PULSE_PERCENT (1.1f) #define DCL_COOLDOWN_PERCENT (0.9f) diff --git a/Tests/Inc/test_current_limit_algo_config.h b/Tests/Inc/test_current_limit_algo_config.h index ab7f4a81..6211a517 100644 --- a/Tests/Inc/test_current_limit_algo_config.h +++ b/Tests/Inc/test_current_limit_algo_config.h @@ -3,7 +3,7 @@ /******************************* DCL **********************************/ -#define DCL_MAX_CURRENT_A (135.0f) +#define DCL_MAX_CURRENT_A (180.0f) #define DCL_MIN_CURRENT_A (30.0f) #define DCL_PULSE_PERCENT (1.1f) #define DCL_COOLDOWN_PERCENT (0.9f) diff --git a/Tests/Src/test_dcl.c b/Tests/Src/test_dcl.c index 8d586cd8..4a3395ee 100644 --- a/Tests/Src/test_dcl.c +++ b/Tests/Src/test_dcl.c @@ -55,7 +55,7 @@ void test_inst_dcl_temperature_and_ocv_regions(void) test_inputs.min_ocv = 4.0f; dcl_calc_inst_limit(test_inputs, &test_algos); - TEST_ASSERT_EQUAL_FLOAT(82.5f, test_algos.inst_DCL); + TEST_ASSERT_EQUAL_FLOAT(105.0f, test_algos.inst_DCL); /* -------- Temperature ramp-down region -------- */ test_inputs.min_temp = 25.0f; @@ -63,7 +63,7 @@ void test_inst_dcl_temperature_and_ocv_regions(void) test_inputs.min_ocv = 4.0f; dcl_calc_inst_limit(test_inputs, &test_algos); - TEST_ASSERT_EQUAL_FLOAT(114.0f, test_algos.inst_DCL); + TEST_ASSERT_EQUAL_FLOAT(150.0f, test_algos.inst_DCL); /* -------- OCV below minimum dominates -------- */ test_inputs.min_temp = 25.0f; @@ -79,7 +79,7 @@ void test_inst_dcl_temperature_and_ocv_regions(void) test_inputs.min_ocv = 3.3f; /* between OCV_MIN and DERATE_THRESH */ dcl_calc_inst_limit(test_inputs, &test_algos); - TEST_ASSERT_EQUAL_FLOAT(75.0f, test_algos.inst_DCL); + TEST_ASSERT_EQUAL_FLOAT(94.2857f, test_algos.inst_DCL); /* -------- Fully nominal region -------- */ test_inputs.min_temp = 25.0f; @@ -112,7 +112,7 @@ void test_cont_dcl_follows_inst_limit_when_pulse_not_allowed(void) TEST_ASSERT_EQUAL_FLOAT(test_algos.inst_DCL, test_algos.cont_DCL); /* -------- Pulse eligibility lost resets behavior -------- */ - test_pack_current = 140.0f; + test_pack_current = 185.0f; test_inputs.min_ocv = 4.0f; is_timer_active_ExpectAnyArgsAndReturn(false); From b3ceb5e78250ddfc93373b07ad46baec414d3b10 Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Wed, 8 Jul 2026 18:15:10 -0400 Subject: [PATCH 14/58] Update ccl config for p50b --- Core/Inc/current_limit_algo_config.h | 2 +- Tests/Inc/test_current_limit_algo_config.h | 2 +- Tests/Src/test_ccl.c | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Core/Inc/current_limit_algo_config.h b/Core/Inc/current_limit_algo_config.h index a6c0636f..4b7acf26 100644 --- a/Core/Inc/current_limit_algo_config.h +++ b/Core/Inc/current_limit_algo_config.h @@ -35,7 +35,7 @@ #define CCL_TEMP_MAX_C (60.0f) #define CCL_OCV_MAX_V (4.2f) #define CCL_OCV_DERATE_THRESH (4.0f) -#define CCL_MAX_CURRENT_A (30.0f) +#define CCL_MAX_CURRENT_A (60.0f) #define CCL_MIN_CURRENT_A (0.0f) #define CCL_PULSE_PERCENT (1.1f) #define CCL_COOLDOWN_PERCENT (0.9f) diff --git a/Tests/Inc/test_current_limit_algo_config.h b/Tests/Inc/test_current_limit_algo_config.h index 6211a517..4169b08d 100644 --- a/Tests/Inc/test_current_limit_algo_config.h +++ b/Tests/Inc/test_current_limit_algo_config.h @@ -13,7 +13,7 @@ /******************************* CCL **********************************/ -#define CCL_MAX_CURRENT_A (30.0f) +#define CCL_MAX_CURRENT_A (60.0f) #define CCL_MIN_CURRENT_A (0.0f) #define CCL_PULSE_PERCENT (1.1f) #define CCL_COOLDOWN_PERCENT (0.9f) diff --git a/Tests/Src/test_ccl.c b/Tests/Src/test_ccl.c index 3db8a843..3f03d80f 100644 --- a/Tests/Src/test_ccl.c +++ b/Tests/Src/test_ccl.c @@ -55,7 +55,7 @@ void test_inst_ccl_temperature_and_ocv_regions(void) test_inputs.max_ocv = 4.0f; ccl_calc_inst_limit(test_inputs, &test_algos); - TEST_ASSERT_EQUAL_FLOAT(15.0f, test_algos.inst_CCL); + TEST_ASSERT_EQUAL_FLOAT(30.0f, test_algos.inst_CCL); /* -------- Temperature ramp-down region -------- */ test_inputs.min_temp = 25.0f; @@ -63,7 +63,7 @@ void test_inst_ccl_temperature_and_ocv_regions(void) test_inputs.max_ocv = 4.0f; ccl_calc_inst_limit(test_inputs, &test_algos); - TEST_ASSERT_EQUAL_FLOAT(24.0f, test_algos.inst_CCL); + TEST_ASSERT_EQUAL_FLOAT(48.0f, test_algos.inst_CCL); /* -------- OCV above maximum (hard clamp) -------- */ test_inputs.min_temp = 25.0f; @@ -79,7 +79,7 @@ void test_inst_ccl_temperature_and_ocv_regions(void) test_inputs.max_ocv = 4.12f; /* between OCV_MIN and DERATE_THRESH */ ccl_calc_inst_limit(test_inputs, &test_algos); - TEST_ASSERT_EQUAL_FLOAT(12.0f, test_algos.inst_CCL); + TEST_ASSERT_EQUAL_FLOAT(24.0f, test_algos.inst_CCL); /* -------- Fully nominal region -------- */ test_inputs.min_temp = 25.0f; @@ -112,7 +112,7 @@ void test_cont_ccl_follows_inst_limit_when_pulse_not_allowed(void) TEST_ASSERT_EQUAL_FLOAT(test_algos.inst_CCL, test_algos.cont_CCL); /* -------- Pulse eligibility lost resets behavior -------- */ - test_pack_current = -35.0f; + test_pack_current = -65.0f; test_inputs.max_ocv = 3.8f; is_timer_active_ExpectAnyArgsAndReturn(false); From 93f169b7101c739b5e8036dae6f2395326d86be0 Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Mon, 13 Jul 2026 12:30:06 -0400 Subject: [PATCH 15/58] Update submodule --- Drivers/Embedded-Base | 2 +- Drivers/Odyssey-Definitions | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Drivers/Embedded-Base b/Drivers/Embedded-Base index e699310a..d204b1ef 160000 --- a/Drivers/Embedded-Base +++ b/Drivers/Embedded-Base @@ -1 +1 @@ -Subproject commit e699310acffc73a8b185e0f318d963ca2ff6958d +Subproject commit d204b1ef82a88f240f5ed4dc8fcf4be90992369e diff --git a/Drivers/Odyssey-Definitions b/Drivers/Odyssey-Definitions index 20dcdc2d..fff6917a 160000 --- a/Drivers/Odyssey-Definitions +++ b/Drivers/Odyssey-Definitions @@ -1 +1 @@ -Subproject commit 20dcdc2d5a618d11cf713bb89f21f4a7192f1417 +Subproject commit fff6917a5ba0a0f61257b00798a25b28200bcb8c From ce23e3f1ec5900d10886bf9bb7f4832ffee85f4a Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Mon, 13 Jul 2026 16:19:21 -0400 Subject: [PATCH 16/58] Update can codegen --- Core/Inc/can_messages_rx.h | 2 ++ Core/Inc/can_messages_tx.h | 12 +++++----- Core/Src/can_messages_rx.c | 44 +++++++++++++++++++++---------------- Drivers/Odyssey-Definitions | 2 +- 4 files changed, 34 insertions(+), 26 deletions(-) diff --git a/Core/Inc/can_messages_rx.h b/Core/Inc/can_messages_rx.h index 74f9f623..090bd881 100644 --- a/Core/Inc/can_messages_rx.h +++ b/Core/Inc/can_messages_rx.h @@ -358,6 +358,7 @@ typedef struct { bool launch_control; uint8_t functional_state; bool traction_control; + uint8_t state_transition_error; } car_state_t; void receive_car_state(const can_msg_t *message, car_state_t *car_state); @@ -426,6 +427,7 @@ typedef struct { bool RTDS_FAULT; bool LV_LOW_VOLTAGE_FAULT; bool PRECHARGE_FLOATING_FAULT; + bool LATCHING_ACTIVE_FAULT; } faults_t; void receive_faults(const can_msg_t *message, faults_t *faults); diff --git a/Core/Inc/can_messages_tx.h b/Core/Inc/can_messages_tx.h index 24fe7931..91ad3f08 100644 --- a/Core/Inc/can_messages_tx.h +++ b/Core/Inc/can_messages_tx.h @@ -311,12 +311,12 @@ uint8_t send_hv_plate_diagnostics /** * Contents of this message: -* BMS/Plate/Diagnostics/EPAD - Exposed pad -* BMS/Plate/Diagnostics/VDIG - Internal digital 3V supply -* BMS/Plate/Diagnostics/VDD - VDD power supply pin -* BMS/Plate/Diagnostics/TMP2 - Secondary internal temperature sensor -* BMS/Plate/Diagnostics/VDIV - -* BMS/Plate/Diagnostics/Extra - Reserved +* BMS/HV_Plate/Diagnostics/EPAD - Exposed pad +* BMS/HV_Plate/Diagnostics/VDIG - Internal digital 3V supply +* BMS/HV_Plate/Diagnostics/VDD - VDD power supply pin +* BMS/HV_Plate/Diagnostics/TMP2 - Secondary internal temperature sensor +* BMS/HV_Plate/Diagnostics/VDIV - +* BMS/HV_Plate/Diagnostics/Extra - Reserved */ uint8_t send_hv_plate_diagnostics_second (float epad,float vdig,float vdd,float tmp2,float vdiv); diff --git a/Core/Src/can_messages_rx.c b/Core/Src/can_messages_rx.c index d9ab4862..28f03044 100644 --- a/Core/Src/can_messages_rx.c +++ b/Core/Src/can_messages_rx.c @@ -757,6 +757,9 @@ void receive_car_state(const can_msg_t *message, car_state_t *car_state) { uint64_t traction_control_mask = (1ULL << 1) - 1ULL; uint64_t traction_control_raw = (data >> 16) & traction_control_mask; car_state->traction_control = (bool)traction_control_raw; + uint64_t state_transition_error_mask = (1ULL << 8) - 1ULL; + uint64_t state_transition_error_raw = (data >> 8) & state_transition_error_mask; + car_state->state_transition_error = (uint8_t)state_transition_error_raw; } void receive_pedal_percent_pressed_values(const can_msg_t *message, pedal_percent_pressed_values_t *pedal_percent_pressed_values) { @@ -879,57 +882,60 @@ void receive_imu_gyro(const can_msg_t *message, imu_gyro_t *imu_gyro) { void receive_faults(const can_msg_t *message, faults_t *faults) { - uint16_t data_bigendian; - memcpy(&data_bigendian, message->data, 2); - uint16_t data = __builtin_bswap16(data_bigendian); + uint32_t data_bigendian; + memcpy(&data_bigendian, message->data, 4); + uint32_t data = __builtin_bswap32(data_bigendian); uint64_t CAN_OUTGOING_FAULT_mask = (1ULL << 1) - 1ULL; - uint64_t CAN_OUTGOING_FAULT_raw = (data >> 15) & CAN_OUTGOING_FAULT_mask; + uint64_t CAN_OUTGOING_FAULT_raw = (data >> 31) & CAN_OUTGOING_FAULT_mask; faults->CAN_OUTGOING_FAULT = (bool)CAN_OUTGOING_FAULT_raw; uint64_t CAN_INCOMING_FAULT_mask = (1ULL << 1) - 1ULL; - uint64_t CAN_INCOMING_FAULT_raw = (data >> 14) & CAN_INCOMING_FAULT_mask; + uint64_t CAN_INCOMING_FAULT_raw = (data >> 30) & CAN_INCOMING_FAULT_mask; faults->CAN_INCOMING_FAULT = (bool)CAN_INCOMING_FAULT_raw; uint64_t BMS_CAN_MONITOR_FAULT_mask = (1ULL << 1) - 1ULL; - uint64_t BMS_CAN_MONITOR_FAULT_raw = (data >> 13) & BMS_CAN_MONITOR_FAULT_mask; + uint64_t BMS_CAN_MONITOR_FAULT_raw = (data >> 29) & BMS_CAN_MONITOR_FAULT_mask; faults->BMS_CAN_MONITOR_FAULT = (bool)BMS_CAN_MONITOR_FAULT_raw; uint64_t LIGHTNING_CAN_MONITOR_FAULT_mask = (1ULL << 1) - 1ULL; - uint64_t LIGHTNING_CAN_MONITOR_FAULT_raw = (data >> 12) & LIGHTNING_CAN_MONITOR_FAULT_mask; + uint64_t LIGHTNING_CAN_MONITOR_FAULT_raw = (data >> 28) & LIGHTNING_CAN_MONITOR_FAULT_mask; faults->LIGHTNING_CAN_MONITOR_FAULT = (bool)LIGHTNING_CAN_MONITOR_FAULT_raw; uint64_t ONBOARD_TEMP_FAULT_mask = (1ULL << 1) - 1ULL; - uint64_t ONBOARD_TEMP_FAULT_raw = (data >> 11) & ONBOARD_TEMP_FAULT_mask; + uint64_t ONBOARD_TEMP_FAULT_raw = (data >> 27) & ONBOARD_TEMP_FAULT_mask; faults->ONBOARD_TEMP_FAULT = (bool)ONBOARD_TEMP_FAULT_raw; uint64_t IMU_ACCEL_FAULT_mask = (1ULL << 1) - 1ULL; - uint64_t IMU_ACCEL_FAULT_raw = (data >> 10) & IMU_ACCEL_FAULT_mask; + uint64_t IMU_ACCEL_FAULT_raw = (data >> 26) & IMU_ACCEL_FAULT_mask; faults->IMU_ACCEL_FAULT = (bool)IMU_ACCEL_FAULT_raw; uint64_t IMU_GYRO_FAULT_mask = (1ULL << 1) - 1ULL; - uint64_t IMU_GYRO_FAULT_raw = (data >> 9) & IMU_GYRO_FAULT_mask; + uint64_t IMU_GYRO_FAULT_raw = (data >> 25) & IMU_GYRO_FAULT_mask; faults->IMU_GYRO_FAULT = (bool)IMU_GYRO_FAULT_raw; uint64_t BSPD_PREFAULT_mask = (1ULL << 1) - 1ULL; - uint64_t BSPD_PREFAULT_raw = (data >> 8) & BSPD_PREFAULT_mask; + uint64_t BSPD_PREFAULT_raw = (data >> 24) & BSPD_PREFAULT_mask; faults->BSPD_PREFAULT = (bool)BSPD_PREFAULT_raw; uint64_t ONBOARD_BRAKE_OPEN_CIRCUIT_FAULT_mask = (1ULL << 1) - 1ULL; - uint64_t ONBOARD_BRAKE_OPEN_CIRCUIT_FAULT_raw = (data >> 7) & ONBOARD_BRAKE_OPEN_CIRCUIT_FAULT_mask; + uint64_t ONBOARD_BRAKE_OPEN_CIRCUIT_FAULT_raw = (data >> 23) & ONBOARD_BRAKE_OPEN_CIRCUIT_FAULT_mask; faults->ONBOARD_BRAKE_OPEN_CIRCUIT_FAULT = (bool)ONBOARD_BRAKE_OPEN_CIRCUIT_FAULT_raw; uint64_t ONBOARD_ACCEL_OPEN_CIRCUIT_FAULT_mask = (1ULL << 1) - 1ULL; - uint64_t ONBOARD_ACCEL_OPEN_CIRCUIT_FAULT_raw = (data >> 6) & ONBOARD_ACCEL_OPEN_CIRCUIT_FAULT_mask; + uint64_t ONBOARD_ACCEL_OPEN_CIRCUIT_FAULT_raw = (data >> 22) & ONBOARD_ACCEL_OPEN_CIRCUIT_FAULT_mask; faults->ONBOARD_ACCEL_OPEN_CIRCUIT_FAULT = (bool)ONBOARD_ACCEL_OPEN_CIRCUIT_FAULT_raw; uint64_t ONBOARD_BRAKE_SHORT_CIRCUIT_FAULT_mask = (1ULL << 1) - 1ULL; - uint64_t ONBOARD_BRAKE_SHORT_CIRCUIT_FAULT_raw = (data >> 5) & ONBOARD_BRAKE_SHORT_CIRCUIT_FAULT_mask; + uint64_t ONBOARD_BRAKE_SHORT_CIRCUIT_FAULT_raw = (data >> 21) & ONBOARD_BRAKE_SHORT_CIRCUIT_FAULT_mask; faults->ONBOARD_BRAKE_SHORT_CIRCUIT_FAULT = (bool)ONBOARD_BRAKE_SHORT_CIRCUIT_FAULT_raw; uint64_t ONBOARD_ACCEL_SHORT_CIRCUIT_FAULT_mask = (1ULL << 1) - 1ULL; - uint64_t ONBOARD_ACCEL_SHORT_CIRCUIT_FAULT_raw = (data >> 4) & ONBOARD_ACCEL_SHORT_CIRCUIT_FAULT_mask; + uint64_t ONBOARD_ACCEL_SHORT_CIRCUIT_FAULT_raw = (data >> 20) & ONBOARD_ACCEL_SHORT_CIRCUIT_FAULT_mask; faults->ONBOARD_ACCEL_SHORT_CIRCUIT_FAULT = (bool)ONBOARD_ACCEL_SHORT_CIRCUIT_FAULT_raw; uint64_t ONBOARD_PEDAL_DIFFERENCE_FAULT_mask = (1ULL << 1) - 1ULL; - uint64_t ONBOARD_PEDAL_DIFFERENCE_FAULT_raw = (data >> 3) & ONBOARD_PEDAL_DIFFERENCE_FAULT_mask; + uint64_t ONBOARD_PEDAL_DIFFERENCE_FAULT_raw = (data >> 19) & ONBOARD_PEDAL_DIFFERENCE_FAULT_mask; faults->ONBOARD_PEDAL_DIFFERENCE_FAULT = (bool)ONBOARD_PEDAL_DIFFERENCE_FAULT_raw; uint64_t RTDS_FAULT_mask = (1ULL << 1) - 1ULL; - uint64_t RTDS_FAULT_raw = (data >> 2) & RTDS_FAULT_mask; + uint64_t RTDS_FAULT_raw = (data >> 18) & RTDS_FAULT_mask; faults->RTDS_FAULT = (bool)RTDS_FAULT_raw; uint64_t LV_LOW_VOLTAGE_FAULT_mask = (1ULL << 1) - 1ULL; - uint64_t LV_LOW_VOLTAGE_FAULT_raw = (data >> 1) & LV_LOW_VOLTAGE_FAULT_mask; + uint64_t LV_LOW_VOLTAGE_FAULT_raw = (data >> 17) & LV_LOW_VOLTAGE_FAULT_mask; faults->LV_LOW_VOLTAGE_FAULT = (bool)LV_LOW_VOLTAGE_FAULT_raw; uint64_t PRECHARGE_FLOATING_FAULT_mask = (1ULL << 1) - 1ULL; - uint64_t PRECHARGE_FLOATING_FAULT_raw = (data >> 0) & PRECHARGE_FLOATING_FAULT_mask; + uint64_t PRECHARGE_FLOATING_FAULT_raw = (data >> 16) & PRECHARGE_FLOATING_FAULT_mask; faults->PRECHARGE_FLOATING_FAULT = (bool)PRECHARGE_FLOATING_FAULT_raw; + uint64_t LATCHING_ACTIVE_FAULT_mask = (1ULL << 1) - 1ULL; + uint64_t LATCHING_ACTIVE_FAULT_raw = (data >> 15) & LATCHING_ACTIVE_FAULT_mask; + faults->LATCHING_ACTIVE_FAULT = (bool)LATCHING_ACTIVE_FAULT_raw; } void receive_lv_voltage(const can_msg_t *message, lv_voltage_t *lv_voltage) { diff --git a/Drivers/Odyssey-Definitions b/Drivers/Odyssey-Definitions index fff6917a..a1977fc3 160000 --- a/Drivers/Odyssey-Definitions +++ b/Drivers/Odyssey-Definitions @@ -1 +1 @@ -Subproject commit fff6917a5ba0a0f61257b00798a25b28200bcb8c +Subproject commit a1977fc3a12bfc1a27a619211f75f9f072f6830d From d467ae6662ac96d24c726161f924c4487ed8bbef Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Mon, 13 Jul 2026 16:21:25 -0400 Subject: [PATCH 17/58] Add dti and charger voltage can msg parsing --- Core/Inc/can_handler.h | 2 ++ Core/Inc/datastructs.h | 8 +++++ Core/Inc/shep_tasks.h | 2 ++ Core/Src/can_handler.c | 69 +++++++++++++++++++++++++++++++++++++----- Core/Src/shep_tasks.c | 18 ++++++----- 5 files changed, 84 insertions(+), 15 deletions(-) diff --git a/Core/Inc/can_handler.h b/Core/Inc/can_handler.h index cf82ef8e..11f1135f 100644 --- a/Core/Inc/can_handler.h +++ b/Core/Inc/can_handler.h @@ -54,6 +54,8 @@ #define CHARGERBOX_CANID 0x18FF50E5 #define DTI_CURRENT_CANID 0x436 #define BATTBOX_DUTY_CYCLE_CANID 0xD5 +#define DTI_INPUT_VOLTAGE_CANID 0x416 +#define DTI_DC_CURRENT_CANID 0x436 #define OVERFLOW_CANID 0x6F1 #define OVERFLOW_SIZE 6 diff --git a/Core/Inc/datastructs.h b/Core/Inc/datastructs.h index 7a327c55..f7d2db79 100644 --- a/Core/Inc/datastructs.h +++ b/Core/Inc/datastructs.h @@ -430,6 +430,14 @@ typedef struct { peripherals_t *peripherals; } state_machine_args_t; +/** + * @brief args for vCanReceive + */ +typedef struct { + state_machine_args_t *state_machine_args; + hv_plate_t *hv_plate; +} can_receive_args_t; + /** * @brief args for vAnalyzer */ diff --git a/Core/Inc/shep_tasks.h b/Core/Inc/shep_tasks.h index 48d22c9b..635d90a9 100644 --- a/Core/Inc/shep_tasks.h +++ b/Core/Inc/shep_tasks.h @@ -5,6 +5,8 @@ #include "tx_api.h" #include "datastructs.h" +#define NO_LOG + // #define DEBUG_HV_PLATE // #define DEBUG_RAW_VOLTAGES // #define DEBUG_OCV_VOLTAGES diff --git a/Core/Src/can_handler.c b/Core/Src/can_handler.c index 4a651194..12568d96 100644 --- a/Core/Src/can_handler.c +++ b/Core/Src/can_handler.c @@ -36,7 +36,7 @@ uint8_t init_can1(FDCAN_HandleTypeDef *hcan) { return U_ERROR; } - uint16_t standard2[] = {CALYPSO_PWM_BAL_CANID, 0x01}; + uint16_t standard2[] = {CALYPSO_PWM_BAL_CANID, DTI_INPUT_VOLTAGE_CANID}; status = can_add_filter_standard(&can1, standard2); if (status != HAL_OK) { PRINTLN_ERROR("Failed to add standard filter to can1 (Status: %d/%s, ID1: " @@ -46,8 +46,18 @@ uint8_t init_can1(FDCAN_HandleTypeDef *hcan) { return U_ERROR; } + uint16_t standard3[] = {DTI_DC_CURRENT_CANID, 0x0}; + status = can_add_filter_standard(&can1, standard3); + if (status != HAL_OK) { + PRINTLN_ERROR("Failed to add standard filter to can1 (Status: %d/%s, ID1: " + "%d, ID2: %d).", + status, hal_status_toString(status), standard3[0], + standard3[1]); + return U_ERROR; + } + /* Add fitlers for extended IDs */ - uint32_t extended1[] = {CHARGERBOX_CANID, 0x00}; + uint32_t extended1[] = {CHARGERBOX_CANID, 0x0}; status = can_add_filter_extended(&can1, extended1); if (status != HAL_OK) { PRINTLN_ERROR("Failed to add extended filter to can1 (Status: %d/%s, ID1: " @@ -96,23 +106,56 @@ uint8_t queue_can_msg(can_msg_t can_msg) { } /** - * @brief Parses the DTI can message for pack current + * @brief Parse the DTI CAN message for pack current. * - * @param msg - * @return float + * @param msg CAN message. + * @return Pack current in amps. */ -float parse_dti_current(can_msg_t msg) { +static float parse_dti_current(can_msg_t msg) { int16_t curr = msg.data[2] << 8 | msg.data[3]; return ((float)curr) / 10; } -float parse_charger_current(can_msg_t msg) { + +/** + * @brief Parse the DTI CAN message for input voltage. + * + * @param msg CAN message. + * @return Input voltage in volts. + */ +static float parse_dti_input_voltage(can_msg_t msg) { + int16_t voltage = msg.data[6] << 8 | msg.data[7]; + return (float)voltage; +} + +/** + * @brief Parse the charger CAN message for output current. + * + * @param msg CAN message. + * @return Charger current in amps. + */ +static float parse_charger_current(can_msg_t msg) { int16_t curr = msg.data[2] << 8 | msg.data[3]; return ((float)curr) / 10; } +/** + * @brief Parse the charger CAN message for output voltage. + * + * @param msg CAN message. + * @return Charger voltage in volts. + */ +static float parse_charger_voltage(can_msg_t msg) { + int16_t voltage = msg.data[0] << 8 | msg.data[1]; + return ((float)voltage) / 10; +} + // CAN RECIEVE THREAD void vCanReceive(ULONG thread_input) { - state_machine_args_t *state_machine_args = (state_machine_args_t *)thread_input; + can_receive_args_t *can_receive_args = (can_receive_args_t *)thread_input; + state_machine_args_t *state_machine_args = can_receive_args->state_machine_args; + hv_plate_t *hv_plate = can_receive_args->hv_plate; + state_machine_t *state_machine = state_machine_args->state_machine; + can_msg_t message; for (;;) { /* Process incoming messages */ @@ -121,6 +164,8 @@ void vCanReceive(ULONG thread_input) { switch (message.id) { case CHARGERBOX_CANID: charger_message_recieved(state_machine_args); + hv_plate->ts_volts = parse_charger_voltage(message); + hv_plate->pack_current = parse_charger_current(message); break; case CALYPSO_CONTROL_CANID: control_message_fans(message); @@ -131,6 +176,14 @@ void vCanReceive(ULONG thread_input) { case CALYPSO_PWM_BAL_CANID: pwm_duty_cycle_set(message.data[0]); break; + case DTI_INPUT_VOLTAGE_CANID: + if (!state_machine->is_charger_connected) { + hv_plate->ts_volts = parse_dti_input_voltage(message); + } + break; + case DTI_DC_CURRENT_CANID: + hv_plate->pack_current = parse_dti_current(message); + break; default: break; } diff --git a/Core/Src/shep_tasks.c b/Core/Src/shep_tasks.c index c3e374c5..05b4095b 100644 --- a/Core/Src/shep_tasks.c +++ b/Core/Src/shep_tasks.c @@ -103,10 +103,10 @@ const void print_bms_stats(analyzer_t *analyzer, hv_plate_t *hv_plate, #endif #ifdef DEBUG_ALGOS - PRINTLN_INFO("Cont CCL: %.2f A, Const DCL: %.2f A\n", + PRINTLN_INFO("Cont CCL: %.2f A, Const DCL: %.2f A, ", bms_algos->cont_CCL, bms_algos->cont_DCL); - PRINTLN_INFO("Inst CCL: %.2f A, Inst DCL: %.2f A\n", + PRINTLN_INFO("Inst CCL: %.2f A, Inst DCL: %.2f A", bms_algos->inst_CCL, bms_algos->inst_DCL); #endif @@ -411,6 +411,10 @@ uint8_t shep_threads_init(TX_BYTE_POOL *byte_pool) state_machine_args.sanitizer = &sanitizer; state_machine_args.peripherals = &peripherals; + static can_receive_args_t can_receive_args = { 0 }; + can_receive_args.state_machine_args = &state_machine_args; + can_receive_args.hv_plate = &hv_plate; + static hv_plate_args_t hv_plate_args = { 0 }; hv_plate_args.hv_plate = &hv_plate; hv_plate_args.analyzer = &analyzer; @@ -462,7 +466,7 @@ uint8_t shep_threads_init(TX_BYTE_POOL *byte_pool) .size = 1024, /* Stack Size (in bytes) */ .priority = 2, /* Priority */ .threshold = 0, /* Preemption Threshold */ - .thread_input = (ULONG)&state_machine_args, /* Thread Args */ + .thread_input = (ULONG)&can_receive_args, /* Thread Args */ .time_slice = TX_NO_TIME_SLICE, /* Time Slice */ .auto_start = TX_AUTO_START, /* Auto Start */ .function = vCanReceive /* Thread Function */ @@ -514,7 +518,7 @@ uint8_t shep_threads_init(TX_BYTE_POOL *byte_pool) static thread_t _sanitizer_thread = { .name = "Sanitizer Thread", /* Name */ .size = 2048, /* Stack Size (in bytes) */ - .priority = 3, /* Priority */ + .priority = 2, /* Priority */ .threshold = 0, /* Preemption Threshold */ .thread_input = (ULONG)&sanitizer_args, /* Thread Args */ .time_slice = TX_NO_TIME_SLICE, /* Time Slice */ @@ -525,7 +529,7 @@ uint8_t shep_threads_init(TX_BYTE_POOL *byte_pool) static thread_t _bms_algorithms_thread = { .name = "BMS Algorithms Thread", /* Name */ .size = 2048, /* Stack Size (in bytes) */ - .priority = 4, /* Priority */ + .priority = 2, /* Priority */ .threshold = 0, /* Preemption Threshold */ .thread_input = (ULONG)&bms_algos_args, /* Thread Args */ .time_slice = TX_NO_TIME_SLICE, /* Time Slice */ @@ -536,7 +540,7 @@ uint8_t shep_threads_init(TX_BYTE_POOL *byte_pool) static thread_t _control_thread = { .name = "Control Thread", /* Name */ .size = 2048, /* Stack Size (in bytes) */ - .priority = 4, /* Priority */ + .priority = 3, /* Priority */ .threshold = 0, /* Preemption Threshold */ .thread_input = (ULONG)&analyzer, /* Thread Args */ .time_slice = TX_NO_TIME_SLICE, /* Time Slice */ @@ -569,7 +573,7 @@ uint8_t shep_threads_init(TX_BYTE_POOL *byte_pool) static thread_t _debug_thread = { .name = "BMS Debug Mode Thread", /* Name */ .size = 2048, /* Stack Size (in bytes) */ - .priority = 4, /* Priority */ + .priority = 3, /* Priority */ .threshold = 0, /* Preemption Threshold */ .thread_input = (ULONG)&analyzer, /* Thread Args */ .time_slice = TX_NO_TIME_SLICE, /* Time Slice */ From fe119a008034c3efa28d16e838a9ce6646d330a6 Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Mon, 13 Jul 2026 16:26:59 -0400 Subject: [PATCH 18/58] Remove board temp printf --- Core/Src/compute.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Core/Src/compute.c b/Core/Src/compute.c index 44125a1e..edb8f77d 100644 --- a/Core/Src/compute.c +++ b/Core/Src/compute.c @@ -167,7 +167,6 @@ int p3t_init(void) int p3t1755_getBoardTemp(float *temp_c) { int status = p3t1755_read_temperature(&p3t, temp_c); - PRINTLN_INFO("Read board temp: %f", *temp_c); return status; } From 1e2710a1130778557eb35b9feb1c0ce57f53b5ca Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Mon, 13 Jul 2026 17:24:17 -0400 Subject: [PATCH 19/58] Fix state machine balancing check --- Core/Src/state_machine.c | 54 +++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/Core/Src/state_machine.c b/Core/Src/state_machine.c index 1ba97ae4..de2ac08f 100644 --- a/Core/Src/state_machine.c +++ b/Core/Src/state_machine.c @@ -45,6 +45,8 @@ const HandlerFunction_t handler_LUT[NUM_STATES] = { &handle_boot, &handle_ready, void init_boot(state_machine_args_t *state_machine_args) { + cancel_timer(&state_machine_args->state_machine->charger_message_timer); + update_eval_table( state_machine_args); // initialize eval table with crit and non crit faults @@ -92,9 +94,9 @@ void handle_charging(state_machine_args_t *state_machine_args) /* Check if we should charge */ if (sm_charging_check(state_machine_args)) { /* Send CAN message, but not too often */ - if (is_timer_expired(&state_machine_args->state_machine + if (!is_timer_active(&state_machine_args->state_machine ->charger_message_timer) || - !is_timer_active(&state_machine_args->state_machine + is_timer_expired(&state_machine_args->state_machine ->charger_message_timer)) { send_bms_charge_message_send((MAX_CHARGE_VOLT * (NUM_CELLS_PER_CHIP * 2) * @@ -341,6 +343,8 @@ bool sm_charging_check(state_machine_args_t *state_machine_args) if (analyzer->max_ocv.val > MAX_CHARGE_VOLT_FLT || analyzer->max_voltage.val > MAX_CHARGE_VOLT_FLT) { state_machine->charging_stage = FAULT; + PRINTLN_INFO("Max OCV: %f", analyzer->max_ocv.val); + PRINTLN_INFO("Max Volts: %f", analyzer->max_voltage.val); return false; } @@ -407,36 +411,40 @@ bool sm_charging_check(state_machine_args_t *state_machine_args) /* if not charging stage, dont charge * (LONG_SETTLE, SHORT_SETTLE, DONE, FAULT) */ - return state_machine->charging_stage == LONG_CHARGE_UP || - state_machine->charging_stage == SHORT_CHARGE_UP; + // return state_machine->charging_stage == LONG_CHARGE_UP || + // state_machine->charging_stage == SHORT_CHARGE_UP; + return true; } // check if balancing is allowed bool sm_balancing_check(state_machine_args_t *state_machine_args) { - //state_machine_t *state_machine = state_machine_args->state_machine; analyzer_t *analyzer = state_machine_args->analyzer; + bool balancing_allowed = false; + bool shutdown_active = true; + float max_voltage = 0.0f; + float delta_voltage = 0.0f; + + mutex_get(&analyzer_mutex); + max_voltage = analyzer->max_voltage.val; + delta_voltage = analyzer->delt_voltage; + mutex_put(&analyzer_mutex); + + if ((max_voltage <= BAL_MIN_V) || (delta_voltage <= MAX_DELTA_V)) { + balancing_allowed = false; + } else { + // Do not balance if the shutdown circuit is open. + mutex_get(&shutdown_mutex); + shutdown_active = + state_machine_args->peripherals->shutdown_active; + mutex_put(&shutdown_mutex); - // TODO: replace with mutexed getter - if (analyzer->max_voltage.val <= BAL_MIN_V) - return false; - if (analyzer->delt_voltage <= MAX_DELTA_V) - return false; - - // Do not balance during settling. - // if (state_machine->charging_stage != LONG_SETTLE && - // state_machine->charging_stage != SHORT_SETTLE) { - // return false; - // } - - // Do not balance if the shutdown circuit is open. + balancing_allowed = !shutdown_active; - bool shutdown_active; - mutex_get(&shutdown_mutex); - shutdown_active = state_machine_args->peripherals->shutdown_active; - mutex_put(&shutdown_mutex); + /** @todo Prevent balancing during settling charging stages. */ + } - return !shutdown_active; + return balancing_allowed; } void set_segment_comms_fault(state_machine_t *state_mach) From 83e8435c9ee41fc4db609fd955e8079df86f377a Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Mon, 13 Jul 2026 17:32:01 -0400 Subject: [PATCH 20/58] Fix statemachine unit test --- Tests/Src/test_statemachine.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tests/Src/test_statemachine.c b/Tests/Src/test_statemachine.c index b6384cea..7bbd4f06 100644 --- a/Tests/Src/test_statemachine.c +++ b/Tests/Src/test_statemachine.c @@ -23,6 +23,7 @@ state_machine_args_t args = { .state_machine = &state_machine, void setUp(void) { analyzer.min_ocv.val = 2.6; + cancel_timer_Expect(&state_machine.charger_message_timer); init_boot(&args); } @@ -77,4 +78,4 @@ int main(void) RUN_TEST(test_eval_table); return UNITY_END(); -} \ No newline at end of file +} From ce8f5675c2bc8a26c0b916487fd3b1c628851056 Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Mon, 13 Jul 2026 18:26:35 -0400 Subject: [PATCH 21/58] Precharge updates --- Core/Src/hv_plate.c | 24 ++++++++++++++++++------ Core/Src/precharge_routine.c | 6 +++--- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/Core/Src/hv_plate.c b/Core/Src/hv_plate.c index b5da79a4..5b60d24f 100644 --- a/Core/Src/hv_plate.c +++ b/Core/Src/hv_plate.c @@ -12,6 +12,10 @@ #include "hv_plate_isospi_recovery.h" #include +static float adbms_batt_volts = 0.0f; +static float adbms_ts_volts = 0.0f; +static float adbms_pack_current = 0.0f; + #define SHUNT_RESISTANCE 0.05 / 1000 // 0.05 mOhms #define THERM_B_VAL 3380 @@ -81,12 +85,12 @@ void get_pack_current_and_batt_voltage(hv_plate_t *hv_plate) // Equation is based on resistances of voltage divider: // R1: 3.6 MOhms, R2: 9.1 kOhms - hv_plate->batt_volts = + adbms_batt_volts = (3600000 + 9100) * get_voltage_conversion(hv_plate->ic.ivbat.vbat1) / 9100; - hv_plate->pack_current = get_current_conversion(hv_plate->ic.ivbat.i1); + adbms_pack_current = get_current_conversion(hv_plate->ic.ivbat.i1); unsnap_2950(&hv_plate->ic); } @@ -99,7 +103,7 @@ void get_ts_voltage(hv_plate_t *hv_plate) // Equation is based on resistances of voltage divider: // R1: 3.6 MOhms, R2: 4.53 kOhms (+ V1P25 reference) - hv_plate->ts_volts = ((3600000 + 4530) * volts) / 4530 + 1.25; + adbms_ts_volts = ((3600000 + 4530) * volts) / 4530 + 1.25; } void get_shunt_temp(hv_plate_t *hv_plate) @@ -168,7 +172,7 @@ void vHvPlateData(ULONG thread_input) { PRINTLN_INFO("Starting HV Plate thread..."); - const uint16_t diagnostic_read_frequency = 1000; // 2s + const uint16_t diagnostic_read_frequency = 1000; // 1s nertimer_t diagnostic_read_timer; hv_plate_args_t *hv_plate_args = (hv_plate_args_t *)thread_input; @@ -193,15 +197,17 @@ void vHvPlateData(ULONG thread_input) // initialize precharge relay open reset_gpo(&hv_plate->ic, HV_CTRL_GPO); + soc_init(); + // enable reading HV set_gpo(&hv_plate->ic, HV_ENABLE_GPO); - soc_init(); - for (;;) { // get the current reading from the pack get_pack_current_and_batt_voltage(hv_plate); + hv_plate->batt_volts = analyzer->pack_voltage; + // updates the SoC value in the analyzer struct based on the pack current // received soc_handle_state(analyzer, hv_plate); @@ -234,6 +240,12 @@ void vHvPlateData(ULONG thread_input) send_hv_plate_voltages(hv_plate->batt_volts, hv_plate->ts_volts); + send_hv_plate_voltages_adbms(adbms_batt_volts, + adbms_ts_volts); + + send_pack_current_and_shunt_temp_adbms( + adbms_pack_current, hv_plate->shunt_temp); + send_pack_current_and_shunt_temp(hv_plate->pack_current, hv_plate->shunt_temp); diff --git a/Core/Src/precharge_routine.c b/Core/Src/precharge_routine.c index a5fc7ce3..c54b9c9d 100644 --- a/Core/Src/precharge_routine.c +++ b/Core/Src/precharge_routine.c @@ -2,7 +2,7 @@ #include #include "debounce.h" -#define MINIMUM_PACK_VOLTAGE 325.0f +#define MINIMUM_PACK_VOLTAGE 200.0f #define NUM_SAMPLES_FOR_AVG 5 #define PRRECHARGE_TRIGGER_THRESHOLD 0.90f @@ -162,8 +162,8 @@ void handle_precharge(prechargeconfig_t *precharge_config) debounce((precharge_config->precharge_state == PRECHARGE_OPEN || precharge_config->precharge_state == PRECHARGE_FLOATING) && precharge_state == PRECHARGE_FLOATING, &precharge_config->open_to_floating_debounce_timer, PRECHARGE_FLOATING_FAULT_TIME, send_floating_precharge_fault, precharge_config); - debounce(precharge_config->precharge_state == PRECHARGE_CLOSED && precharge_state == PRECHARGE_FLOATING, &precharge_config->closed_to_floating_debounce_timer, - PRECHARGE_TOGGLE_TIME, send_floating_precharge_fault, precharge_config); + debounce(precharge_config->precharge_state == PRECHARGE_CLOSED && (precharge_state == PRECHARGE_FLOATING || precharge_state == PRECHARGE_OPEN), + &precharge_config->closed_to_floating_debounce_timer, PRECHARGE_TOGGLE_TIME, open_relay, precharge_config); } // PRECHARGE THREAD From 04bc7dab95f538dbee2e083f8855dc83b46a1625 Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Mon, 13 Jul 2026 18:28:25 -0400 Subject: [PATCH 22/58] Fix dcl and ccl flickering --- Core/Src/hv_plate.c | 21 ++++++++++++++++++--- Core/Src/state_machine.c | 21 +++++++++------------ 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/Core/Src/hv_plate.c b/Core/Src/hv_plate.c index 5b60d24f..2fdb5f9f 100644 --- a/Core/Src/hv_plate.c +++ b/Core/Src/hv_plate.c @@ -203,6 +203,10 @@ void vHvPlateData(ULONG thread_input) set_gpo(&hv_plate->ic, HV_ENABLE_GPO); for (;;) { + float max_dc_current = 0.0f; + float max_dc_brake_current = 0.0f; + state_t bms_state = BOOT; + // get the current reading from the pack get_pack_current_and_batt_voltage(hv_plate); @@ -267,9 +271,20 @@ void vHvPlateData(ULONG thread_input) send_hv_plate_pec_errors_message(); hv_plate_isospi_handle_state(hv_plate, state_machine); - send_max_dc_current_command(bms_algos->cont_DCL); - // DTI expects regen/brake current limit to be negative - const float max_dc_brake_current = (-1.0f * bms_algos->cont_CCL); + + mutex_get(&state_mutex); + bms_state = state_machine->bms_state; + mutex_put(&state_mutex); + + if (bms_state == READY) { + mutex_get(&bms_algos_mutex); + max_dc_current = bms_algos->cont_DCL; + // DTI expects regen/brake current limit to be negative + max_dc_brake_current = (-1.0f * bms_algos->cont_CCL); + mutex_put(&bms_algos_mutex); + } + + send_max_dc_current_command(max_dc_current); send_max_dc_brake_current_command(max_dc_brake_current); tx_thread_sleep(50); diff --git a/Core/Src/state_machine.c b/Core/Src/state_machine.c index de2ac08f..56d80f53 100644 --- a/Core/Src/state_machine.c +++ b/Core/Src/state_machine.c @@ -110,10 +110,6 @@ void handle_charging(state_machine_args_t *state_machine_args) send_bms_charge_message_send(0, 0, 0xFF); } - // disable discharge and charge from the MC - send_max_dc_current_command(0); - send_max_dc_brake_current_command(0); - /* Check if we should balance */ if (sm_balancing_check(state_machine_args)) { handle_balance_cells(state_machine_args->analyzer, @@ -167,20 +163,21 @@ void request_transition(state_machine_args_t *state_machine_args, state_t next_state) { state_machine_t *state_machine = state_machine_args->state_machine; + bool transition_requested = false; mutex_get(&state_mutex); - if (state_machine->bms_state == next_state) - return; - - if (!valid_transition_from_to[state_machine->bms_state][next_state]) - return; - - state_machine_args->state_machine->bms_state = next_state; + if ((state_machine->bms_state != next_state) && + valid_transition_from_to[state_machine->bms_state][next_state]) { + state_machine->bms_state = next_state; + transition_requested = true; + } mutex_put(&state_mutex); - init_LUT[next_state](state_machine_args); + if (transition_requested) { + init_LUT[next_state](state_machine_args); + } } void sm_fault_return(state_machine_args_t *state_machine_args) From 10ea6867f17c1d29de2ae65e313e30fe075cd1f1 Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Mon, 13 Jul 2026 22:00:03 -0400 Subject: [PATCH 23/58] Fix naming --- Core/Inc/datastructs.h | 2 +- Core/Src/analyzer.c | 2 +- Core/Src/shep_tasks.c | 2 +- Core/Src/state_machine.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Core/Inc/datastructs.h b/Core/Inc/datastructs.h index f7d2db79..4be56f75 100644 --- a/Core/Inc/datastructs.h +++ b/Core/Inc/datastructs.h @@ -248,7 +248,7 @@ typedef struct { crit_cellval_t max_voltage; crit_cellval_t min_voltage; float avg_voltage; - float delt_voltage; + float delta_voltage; // OCV timer nertimer_t ocvTimer; diff --git a/Core/Src/analyzer.c b/Core/Src/analyzer.c index b65418b4..509d1864 100644 --- a/Core/Src/analyzer.c +++ b/Core/Src/analyzer.c @@ -277,7 +277,7 @@ void calc_pack_voltage_stats(analyzer_t *analyzer, acc_data_t *acc_data) analyzer->pack_voltage = total_volt; - analyzer->delt_voltage = + analyzer->delta_voltage = analyzer->max_voltage.val - analyzer->min_voltage.val; analyzer->avg_ocv = total_ocv / NUM_CELLS; diff --git a/Core/Src/shep_tasks.c b/Core/Src/shep_tasks.c index 05b4095b..0e95c47c 100644 --- a/Core/Src/shep_tasks.c +++ b/Core/Src/shep_tasks.c @@ -55,7 +55,7 @@ const void print_bms_stats(analyzer_t *analyzer, hv_plate_t *hv_plate, #ifdef DEBUG_RAW_VOLTAGES PRINTLN_INFO("Min, Max, Avg, Delta Voltages: %f, %f, %f, %f\n", analyzer->min_voltage.val, analyzer->max_voltage.val, - analyzer->avg_voltage, analyzer->delt_voltage); + analyzer->avg_voltage, analyzer->delta_voltage); PRINTLN_INFO("Raw Cell Voltages:"); for(uint8_t c = 0; c < NUM_CHIPS; c++) { diff --git a/Core/Src/state_machine.c b/Core/Src/state_machine.c index 56d80f53..b209400c 100644 --- a/Core/Src/state_machine.c +++ b/Core/Src/state_machine.c @@ -424,7 +424,7 @@ bool sm_balancing_check(state_machine_args_t *state_machine_args) mutex_get(&analyzer_mutex); max_voltage = analyzer->max_voltage.val; - delta_voltage = analyzer->delt_voltage; + delta_voltage = analyzer->delta_voltage; mutex_put(&analyzer_mutex); if ((max_voltage <= BAL_MIN_V) || (delta_voltage <= MAX_DELTA_V)) { From 95fc0f0f253e2dd4f2bb0c87d867a544a8468ac2 Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Mon, 13 Jul 2026 23:29:28 -0400 Subject: [PATCH 24/58] Use charger current when connected --- Core/Src/can_handler.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Core/Src/can_handler.c b/Core/Src/can_handler.c index 12568d96..f9c8d64c 100644 --- a/Core/Src/can_handler.c +++ b/Core/Src/can_handler.c @@ -182,7 +182,9 @@ void vCanReceive(ULONG thread_input) { } break; case DTI_DC_CURRENT_CANID: - hv_plate->pack_current = parse_dti_current(message); + if (!state_machine->is_charger_connected) { + hv_plate->pack_current = parse_dti_current(message); + } break; default: break; From 912d18981ae7b0dcf95b89c3f2cd39f74c635eca Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Tue, 14 Jul 2026 03:23:40 -0400 Subject: [PATCH 25/58] Add cell open wire detection --- Core/Inc/adi6830_interation.h | 28 ++++++-- Core/Inc/bms_config.h | 1 + Core/Inc/can_messages_tx.h | 11 ++- Core/Inc/datastructs.h | 2 + Core/Src/adi6830_interaction.c | 15 +++-- Core/Src/analyzer.c | 3 + Core/Src/can_messages_tx.c | 31 ++++++++- Core/Src/segment.c | 120 ++++++++++++++++++++++++++++----- Core/Src/shep_tasks.c | 10 +++ Core/Src/state_machine.c | 41 ++++++++++- Drivers/Odyssey-Definitions | 2 +- Tests/Src/test_statemachine.c | 2 + 12 files changed, 235 insertions(+), 31 deletions(-) diff --git a/Core/Inc/adi6830_interation.h b/Core/Inc/adi6830_interation.h index 71b4b7b4..9488e729 100644 --- a/Core/Inc/adi6830_interation.h +++ b/Core/Inc/adi6830_interation.h @@ -243,6 +243,15 @@ void write_clear_flags(cell_asic chips[NUM_CHIPS], SPI_HandleTypeDef *hspi); // --- BEGIN READ COMMANDS --- +/** + * @brief Read all C-ADC voltage registers. + * + * @param chips Array of chips. + * @param hspi SPI handle. + */ +void read_c_voltage_registers(cell_asic chips[NUM_CHIPS], + SPI_HandleTypeDef *hspi); + /** * @brief Read all filtered voltage results A-E. IIR must be on and ADC must be continous * @@ -348,11 +357,15 @@ void get_avgd_cell_voltages(cell_asic chips[NUM_CHIPS], SPI_HandleTypeDef *hspi); /** - * @brief Trigger, poll, and fetch voltages from the S-ADCs. + * @brief Trigger, poll, and fetch S-ADC voltages using an open-wire mode. * - * @param chip Array of chips to get voltage readings from. + * @param chips Array of chips. + * @param hspi SPI handle. + * @param open_wire_mode Cell inputs on which to enable open-wire excitation. */ -void get_s_adc_voltages(cell_asic chips[NUM_CHIPS], SPI_HandleTypeDef *hspi); +void get_s_adc_open_wire_voltages(cell_asic chips[NUM_CHIPS], + SPI_HandleTypeDef *hspi, + OW_C_S open_wire_mode); /** * @brief Trigger, poll, and fetch the c and s adc voltages, using instaneous redundancy. @@ -363,11 +376,18 @@ void get_c_and_s_adc_voltages(cell_asic chips[NUM_CHIPS], SPI_HandleTypeDef *hspi); /** - * @brief Starts a continous c ADC conversion with S redundancy + * @brief Start continuous C-ADC conversions. * */ void start_c_adc_conv(cell_asic chips[NUM_CHIPS], SPI_HandleTypeDef *hspi); +/** + * @brief Start continuous S-ADC conversion. + * @param chips Array of chips. + * @param hspi SPI handle. + */ +void start_s_adc_conv(cell_asic chips[NUM_CHIPS], SPI_HandleTypeDef *hspi); + // --- END ADC POLL --- #endif diff --git a/Core/Inc/bms_config.h b/Core/Inc/bms_config.h index b29a7a39..805940df 100644 --- a/Core/Inc/bms_config.h +++ b/Core/Inc/bms_config.h @@ -69,6 +69,7 @@ #define HIGH_TEMP_TIME 55000 #define MAX_CHIPTEMP_TIME 55000 #define COMMS_FAULT_TIME 20000 +#define OW_FAULT_TIME 5000 // system wide base ADBMS sample rate #define SAMPLE_RATE 2 /* Hz */ diff --git a/Core/Inc/can_messages_tx.h b/Core/Inc/can_messages_tx.h index 91ad3f08..72e6d2b8 100644 --- a/Core/Inc/can_messages_tx.h +++ b/Core/Inc/can_messages_tx.h @@ -85,10 +85,11 @@ uint8_t send_segment_isospi_communication_status * BMS/Faults/Critical/High_die_temp - Die temp ts too high * BMS/Faults/Non-Critical/Segment_Comms_fault - Lost communications with segments * BMS/Faults/Non-Critical/HV_Plate_Comms_Fault - Lost communications with HV Plate +* BMS/Faults/Non-Critical/Cell_open_wire - Open wire detected on one or more cells * BMS/Faults/Non-Critical/Extra - Reserved */ uint8_t send_fault_status -(bool dcl_enforce,bool ccl_enforce,bool low_cell_volt,bool high_cell_volt,bool high_charge_volt,bool pack_hot,bool die_temp_max,bool segment_comms,bool hv_plate_comms); +(bool dcl_enforce,bool ccl_enforce,bool low_cell_volt,bool high_cell_volt,bool high_charge_volt,bool pack_hot,bool die_temp_max,bool segment_comms,bool hv_plate_comms,bool cell_open_wire); /** * Contents of this message: @@ -172,9 +173,11 @@ uint8_t send_shepherd_version_hash * BMS/PerCell/Alpha/{4}/Burning/{6} - Whether cell is burning * BMS/PerCell/Alpha/{4}/CvS/{5} - Whether C and S ADCs read too different * BMS/PerCell/Alpha/{4}/CvS/{6} - Whether C and S ADCs read too different +* BMS/PerCell/Alpha/{4}/OW/{5} - Whether an open wire is detected on the cell +* BMS/PerCell/Alpha/{4}/OW/{6} - Whether an open wire is detected on the cell */ uint8_t send_alpha_cell_data_debug -(float therm,float voltage_a,float voltage_b,uint8_t chip_id,uint8_t cell_a,uint8_t cell_b,bool discharging_a,bool discharging_b,bool cvs_a,bool cvs_b); +(float therm,float voltage_a,float voltage_b,uint8_t chip_id,uint8_t cell_a,uint8_t cell_b,bool discharging_a,bool discharging_b,bool cvs_a,bool cvs_b,bool ow_a,bool ow_b); /** * Contents of this message: @@ -185,9 +188,11 @@ uint8_t send_alpha_cell_data_debug * BMS/PerCell/Beta/{4}/Burning/{6} - Whether cell is burning * BMS/PerCell/Beta/{4}/CvS/{5} - Whether C and S ADCs read too different * BMS/PerCell/Beta/{4}/CvS/{6} - Whether C and S ADCs read too different +* BMS/PerCell/Beta/{4}/OW/{5} - Whether an open wire is detected on the cell +* BMS/PerCell/Beta/{4}/OW/{6} - Whether an open wire is detected on the cell */ uint8_t send_beta_cell_data_debug -(float therm,float voltage_a,float voltage_b,uint8_t chip_id,uint8_t cell_a,uint8_t cell_b,bool discharging_a,bool discharging_b,bool cvs_a,bool cvs_b); +(float therm,float voltage_a,float voltage_b,uint8_t chip_id,uint8_t cell_a,uint8_t cell_b,bool discharging_a,bool discharging_b,bool cvs_a,bool cvs_b,bool ow_a,bool ow_b); /** * Contents of this message: diff --git a/Core/Inc/datastructs.h b/Core/Inc/datastructs.h index 4be56f75..c8b37342 100644 --- a/Core/Inc/datastructs.h +++ b/Core/Inc/datastructs.h @@ -56,6 +56,7 @@ typedef struct { /* Chip and Cell Diagnostic Data */ bool is_balancing[NUM_CELLS_PER_CHIP]; bool cs_fault[NUM_CELLS_PER_CHIP]; + bool ow_fault[NUM_CELLS_PER_CHIP]; float vpv; float vmv; @@ -508,6 +509,7 @@ typedef enum { HV_PLATE_COMMS_FAULT, SEGMENT_COMMS_FAULT, + CELL_OPEN_WIRE_FAULT, NUM_FAULTS, /* NUM_REACTIONARY_FAULTS = NUM_FAULTS - NUM_CONDITIONAL_FAULTS - 1 */ diff --git a/Core/Src/adi6830_interaction.c b/Core/Src/adi6830_interaction.c index d95ee730..9ade54d5 100644 --- a/Core/Src/adi6830_interaction.c +++ b/Core/Src/adi6830_interaction.c @@ -576,11 +576,12 @@ void get_avgd_cell_voltages(cell_asic chips[NUM_CHIPS], SPI_HandleTypeDef *hspi) // read_adbms_data(chips, RDFCALL, Rdfcall, ALL_GRP); // } -void get_s_adc_voltages(cell_asic chips[NUM_CHIPS], SPI_HandleTypeDef *hspi) +void get_s_adc_open_wire_voltages(cell_asic chips[NUM_CHIPS], + SPI_HandleTypeDef *hspi, + OW_C_S open_wire_mode) { - adBms6830_Adsv(NUM_CHIPS, chips, SINGLE, DCP_OFF, OW_OFF_ALL_CH); + adBms6830_Adsv(NUM_CHIPS, chips, SINGLE, DCP_OFF, open_wire_mode); adBmsPollAdc_indicator(chips, PLSADC); - read_s_voltage_registers(chips, hspi); } @@ -597,8 +598,14 @@ void get_c_and_s_adc_voltages(cell_asic chips[NUM_CHIPS], void start_c_adc_conv(cell_asic chips[NUM_CHIPS], SPI_HandleTypeDef *hspi) { - adBms6830_Adcv(NUM_CHIPS, chips, RD_ON, CONTINUOUS, DCP_OFF, RSTF_ON, + adBms6830_Adcv(NUM_CHIPS, chips, RD_OFF, CONTINUOUS, DCP_OFF, RSTF_ON, OW_OFF_ALL_CH); } +void start_s_adc_conv(cell_asic chips[NUM_CHIPS], SPI_HandleTypeDef *hspi) +{ + adBms6830_Adsv(NUM_CHIPS, chips, CONTINUOUS, DCP_OFF, + OW_OFF_ALL_CH); +} + // --- END ADC POLL --- diff --git a/Core/Src/analyzer.c b/Core/Src/analyzer.c index 509d1864..8dd0fb6b 100644 --- a/Core/Src/analyzer.c +++ b/Core/Src/analyzer.c @@ -373,6 +373,9 @@ void update_chip_status(analyzer_t *analyzer, acc_data_t *acc_data) chip_data->cs_fault[cell] = (acc_data->chips[chip].statc.cs_flt >> cell) & 1; + // OW fault status + chip_data->ow_fault[cell] = + (acc_data->chips[chip].diag_result.cell_ow[cell] != 0U); } // Chip Diagnotics diff --git a/Core/Src/can_messages_tx.c b/Core/Src/can_messages_tx.c index 917d0fff..a4e46597 100644 --- a/Core/Src/can_messages_tx.c +++ b/Core/Src/can_messages_tx.c @@ -251,7 +251,7 @@ uint8_t send_segment_isospi_communication_status } uint8_t send_fault_status -(bool dcl_enforce,bool ccl_enforce,bool low_cell_volt,bool high_cell_volt,bool high_charge_volt,bool pack_hot,bool die_temp_max,bool segment_comms,bool hv_plate_comms) +(bool dcl_enforce,bool ccl_enforce,bool low_cell_volt,bool high_cell_volt,bool high_charge_volt,bool pack_hot,bool die_temp_max,bool segment_comms,bool hv_plate_comms,bool cell_open_wire) { can_msg_t msg; msg.id = 0x89; @@ -304,6 +304,11 @@ uint8_t send_fault_status } data |= ((hv_plate_comms_i) & 0x1ULL) << 7; + uint32_t cell_open_wire_i = (uint32_t)(cell_open_wire); + if(cell_open_wire_i > 1ULL) {cell_open_wire_i = 1; + } + data |= ((cell_open_wire_i) & 0x1ULL) << 6; + uint16_t data_bigendian = __builtin_bswap16(data); memcpy(msg.data, &data_bigendian, 2); @@ -586,7 +591,7 @@ uint8_t send_overflow_notification_for_percell } uint8_t send_alpha_cell_data_debug -(float therm,float voltage_a,float voltage_b,uint8_t chip_id,uint8_t cell_a,uint8_t cell_b,bool discharging_a,bool discharging_b,bool cvs_a,bool cvs_b) +(float therm,float voltage_a,float voltage_b,uint8_t chip_id,uint8_t cell_a,uint8_t cell_b,bool discharging_a,bool discharging_b,bool cvs_a,bool cvs_b,bool ow_a,bool ow_b) { can_msg_t msg; msg.id = 0x6FA; @@ -644,6 +649,16 @@ uint8_t send_alpha_cell_data_debug } data |= ((cvs_b_i) & 0x1ULL) << 12; + uint32_t ow_a_i = (uint32_t)(ow_a); + if(ow_a_i > 1ULL) {ow_a_i = 1; + } + data |= ((ow_a_i) & 0x1ULL) << 11; + + uint32_t ow_b_i = (uint32_t)(ow_b); + if(ow_b_i > 1ULL) {ow_b_i = 1; + } + data |= ((ow_b_i) & 0x1ULL) << 10; + uint64_t data_bigendian = __builtin_bswap64(data); memcpy(msg.data, &data_bigendian, 8); @@ -651,7 +666,7 @@ uint8_t send_alpha_cell_data_debug } uint8_t send_beta_cell_data_debug -(float therm,float voltage_a,float voltage_b,uint8_t chip_id,uint8_t cell_a,uint8_t cell_b,bool discharging_a,bool discharging_b,bool cvs_a,bool cvs_b) +(float therm,float voltage_a,float voltage_b,uint8_t chip_id,uint8_t cell_a,uint8_t cell_b,bool discharging_a,bool discharging_b,bool cvs_a,bool cvs_b,bool ow_a,bool ow_b) { can_msg_t msg; msg.id = 0x6FB; @@ -709,6 +724,16 @@ uint8_t send_beta_cell_data_debug } data |= ((cvs_b_i) & 0x1ULL) << 12; + uint32_t ow_a_i = (uint32_t)(ow_a); + if(ow_a_i > 1ULL) {ow_a_i = 1; + } + data |= ((ow_a_i) & 0x1ULL) << 11; + + uint32_t ow_b_i = (uint32_t)(ow_b); + if(ow_b_i > 1ULL) {ow_b_i = 1; + } + data |= ((ow_b_i) & 0x1ULL) << 10; + uint64_t data_bigendian = __builtin_bswap64(data); memcpy(msg.data, &data_bigendian, 8); diff --git a/Core/Src/segment.c b/Core/Src/segment.c index 514ff6fe..383f54c4 100644 --- a/Core/Src/segment.c +++ b/Core/Src/segment.c @@ -13,6 +13,11 @@ #include "app_threadx.h" #include "main.h" +/** + * @brief Open-wire threshold while the S-ADC switch is active. + */ +#define CELL_OPEN_WIRE_MAX_DROP_PERCENT 15.0f + /** * @brief Initialize a chip with our default values. * @@ -160,8 +165,9 @@ void segment_retrieve_charging_data(cell_asic chips[NUM_CHIPS], // poll stuff like vref, etc. adc_and_read_aux_registers(chips, hspi); - // read from ADC convs - get_c_adc_voltages(chips, hspi); + segment_snap(chips, hspi); + read_c_voltage_registers(chips, hspi); + segment_unsnap(chips, hspi); read_status_registers(chips, hspi); @@ -191,7 +197,6 @@ void segment_retrieve_debug_data(cell_asic chips[NUM_CHIPS], // check our fault flags segment_monitor_flts(chips, hspi); - read_s_voltage_registers(chips, hspi); } void segment_restart(cell_asic chips[NUM_CHIPS], SPI_HandleTypeDef *hspi) @@ -290,6 +295,77 @@ void segment_set_dcto(cell_asic chips[NUM_CHIPS], uint8_t dcto, write_config_regs(chips, hspi); } +/** + * @brief Run cell open-wire and S-vs-C diagnostics. + * + * @param chips Array of chips. + * @param hspi SPI handle. + */ +static void segment_run_cell_open_wire_test(cell_asic chips[NUM_CHIPS], + SPI_HandleTypeDef *hspi) +{ + /* + * Open-wire and S-vs-C diagnostic sequence: + * 1. Start continuous S-ADC redundancy with discharge paused. + * 2. Wait for S-ADC synchronization and one synchronous conversion. + * 3. Read C, S, and Status C. + * 4. Run even and odd single-shot open-wire conversions. + * The final odd single-shot automatically allows discharge to resume. + */ + start_s_adc_conv(chips, hspi); + tx_thread_sleep(16); + + segment_snap(chips, hspi); + read_c_voltage_registers(chips, hspi); + read_s_voltage_registers(chips, hspi); + segment_unsnap(chips, hspi); + + read_status_register_c(chips, hspi); + + get_s_adc_open_wire_voltages(chips, hspi, OW_ON_EVEN_CH); + + for (uint8_t ic = 0; ic < NUM_CHIPS; ic++) { + for (uint8_t cell = 0; cell < NUM_CELLS_PER_CHIP; cell++) { + chips[ic].owcell.cell_ow_even[cell] = + chips[ic].scell.sc_codes[cell]; + } + } + + get_s_adc_open_wire_voltages(chips, hspi, OW_ON_ODD_CH); + + for (uint8_t ic = 0; ic < NUM_CHIPS; ic++) { + for (uint8_t cell = 0; cell < NUM_CELLS_PER_CHIP; cell++) { + const int16_t odd_code = chips[ic].scell.sc_codes[cell]; + const float even_voltage = getVoltage( + chips[ic].owcell.cell_ow_even[cell]); + const float odd_voltage = getVoltage(odd_code); + const bool even_cell = ((cell + 1U) % 2U) == 0U; + const float excited_voltage = + even_cell ? even_voltage : odd_voltage; + const float baseline_voltage = + even_cell ? odd_voltage : even_voltage; + const float drop_voltage = baseline_voltage - excited_voltage; + float drop_percent = 0.0f; + + if (baseline_voltage > 0.0f) { + drop_percent = + (drop_voltage / baseline_voltage) * 100.0f; + } + + chips[ic].owcell.cell_ow_odd[cell] = odd_code; + chips[ic].diag_result.cell_ow[cell] = + (drop_percent > CELL_OPEN_WIRE_MAX_DROP_PERCENT); + + if (chips[ic].diag_result.cell_ow[cell]) { + PRINTLN_WARNING( + "[OW] Open wire IC%u C%02u: even=%.3f V, odd=%.3f V, drop=%.3f V (%.1f%%)", + ic + 1U, cell + 1U, even_voltage, + odd_voltage, drop_voltage, drop_percent); + } + } + } +} + // GET SEGEMENT DATA THREAD void vGetSegmentData(ULONG thread_input) { @@ -300,30 +376,42 @@ void vGetSegmentData(ULONG thread_input) acc_data_t *acc_data = acc_data_args->acc_data; state_machine_t *state_machine = acc_data_args->state_machine; + HAL_NVIC_DisableIRQ(FDCAN2_IT0_IRQn); segment_init(acc_data->chips, &hspi2); - segment_isospi_break_detection_init(acc_data->chips); + HAL_NVIC_EnableIRQ(FDCAN2_IT0_IRQn); // must delay after init for ADC to start up tx_thread_sleep(500); + // Run after the monitor ADC has initialized, before normal acquisition + HAL_NVIC_DisableIRQ(FDCAN2_IT0_IRQn); + segment_run_cell_open_wire_test(acc_data->chips, &hspi2); + HAL_NVIC_EnableIRQ(FDCAN2_IT0_IRQn); + state_t prev_state = BOOT; state_t current_state = BOOT; + bool prev_balancing_active = false; nertimer_t pwm_timer; + nertimer_t open_wire_timer; // assumes a DCTO of 1 minute for PWM balancing in extended balancing mode const uint32_t pwm_update_frequency = 55000; + // Required fault-tolerant time interval + const uint32_t open_wire_test_frequency = 60000; start_timer(&pwm_timer, 0); // start timer immeditately on first run + start_timer(&open_wire_timer, open_wire_test_frequency); for (;;) { prev_state = current_state; current_state = state_machine->bms_state; + bool balancing_active = state_machine->balancing_active; HAL_NVIC_DisableIRQ(FDCAN2_IT0_IRQn); - // mute when entering any state other than balancing or charging - if (prev_state != current_state && current_state != CHARGING) { + if ((prev_state != current_state && current_state != CHARGING) || + (prev_balancing_active && !balancing_active)) { segment_mute(acc_data->chips, &hspi2); } @@ -349,26 +437,28 @@ void vGetSegmentData(ULONG thread_input) } } - if (current_state == CHARGING && - state_machine->balancing_active && + if (is_timer_expired(&open_wire_timer)) { + segment_run_cell_open_wire_test(acc_data->chips, &hspi2); + start_timer(&open_wire_timer, open_wire_test_frequency); + } + + if (current_state == CHARGING && balancing_active && is_timer_expired(&pwm_timer) && !is_timer_active(&pwm_timer)) { - segment_unmute(acc_data->chips, &hspi2); - - // single shot SADC conversion to halt an SADC continuous conversion inhibiting PWM Balancing - get_s_adc_voltages(acc_data->chips, &hspi2); - + segment_mute(acc_data->chips, &hspi2); segment_set_dcto(acc_data->chips, TIME_1MIN_OR_0_26HR, - &hspi2); - tx_thread_sleep(16); + &hspi2); segment_configure_balancing(acc_data->chips, acc_data->discharge_config, &hspi2); + read_pwm_registers(acc_data->chips, &hspi2); + segment_unmute(acc_data->chips, &hspi2); start_timer(&pwm_timer, pwm_update_frequency); } HAL_NVIC_EnableIRQ(FDCAN2_IT0_IRQn); + prev_balancing_active = balancing_active; set_flag(ANALYZER_FLAG); tx_thread_sleep(300); } diff --git a/Core/Src/shep_tasks.c b/Core/Src/shep_tasks.c index 0e95c47c..838391d7 100644 --- a/Core/Src/shep_tasks.c +++ b/Core/Src/shep_tasks.c @@ -288,6 +288,11 @@ void vDebug(ULONG thread_input) cell + 1 == NUM_CELLS_PER_CHIP ? 0 : chip_data->cs_fault[cell + + 1], + chip_data->ow_fault[cell], + cell + 1 == NUM_CELLS_PER_CHIP ? + 0 : + chip_data->ow_fault[cell + 1]); } else { send_beta_cell_data_debug( @@ -309,6 +314,11 @@ void vDebug(ULONG thread_input) cell + 1 == NUM_CELLS_PER_CHIP ? 0 : chip_data->cs_fault[cell + + 1], + chip_data->ow_fault[cell], + cell + 1 == NUM_CELLS_PER_CHIP ? + 0 : + chip_data->ow_fault[cell + 1]); } diff --git a/Core/Src/state_machine.c b/Core/Src/state_machine.c index b209400c..87face7b 100644 --- a/Core/Src/state_machine.c +++ b/Core/Src/state_machine.c @@ -30,6 +30,8 @@ const bool valid_transition_from_to[NUM_STATES][NUM_STATES] = { /* private function prototypes */ void update_eval_table(state_machine_args_t *state_machine_args); +static bool is_open_wire_fault_active(const analyzer_t *analyzer); + void request_transition(state_machine_args_t *state_machine_args, state_t next_state); @@ -482,6 +484,25 @@ bool are_critical_faults_active(void) return (fault_flags & severity_mask) != 0; } +static bool is_open_wire_fault_active(const analyzer_t *analyzer) +{ + bool open_wire_fault_active = false; + + mutex_get(&analyzer_mutex); + + for (uint8_t chip = 0U; chip < NUM_CHIPS; chip++) { + for (uint8_t cell = 0U; cell < NUM_CELLS_PER_CHIP; cell++) { + if (analyzer->chip_data[chip].ow_fault[cell]) { + open_wire_fault_active = true; + } + } + } + + mutex_put(&analyzer_mutex); + + return open_wire_fault_active; +} + void update_eval_table(state_machine_args_t *state_machine_args) { static bool initialized = false; @@ -501,6 +522,10 @@ void update_eval_table(state_machine_args_t *state_machine_args) static nertimer_t die_overtemp_timer = { 0 }; static nertimer_t segment_comms_timer = { 0 }; static nertimer_t hv_plate_comms_timer = { 0 }; + static nertimer_t open_wire_timer = { 0 }; + + const bool open_wire_fault_active = + is_open_wire_fault_active(analyzer); if (initialized) { fault_eval_table[DISCHARGE_LIMIT_ENFORCEMENT_FAULT].data_1 = @@ -527,6 +552,8 @@ void update_eval_table(state_machine_args_t *state_machine_args) state_machine->segment_comms_fault_flag; fault_eval_table[HV_PLATE_COMMS_FAULT].data_1 = state_machine->hv_plate_comms_fault_flag; + fault_eval_table[CELL_OPEN_WIRE_FAULT].data_1 = + open_wire_fault_active; } else { fault_eval_table[DISCHARGE_LIMIT_ENFORCEMENT_FAULT] = (fault_eval_t){ .id = "Discharge Current Limit", @@ -623,6 +650,17 @@ void update_eval_table(state_machine_args_t *state_machine_args) .optype_2 = NOP, // UNUSED .is_critical = true }; + + fault_eval_table[CELL_OPEN_WIRE_FAULT] = (fault_eval_t){ + .id = "Cell Open Wire Fault", + .timer = open_wire_timer, + .data_1 = open_wire_fault_active, + .optype_1 = EQ, + .lim_1 = true, + .timeout = OW_FAULT_TIME, + .optype_2 = NOP, // UNUSED + .is_critical = false + }; initialized = true; } } @@ -667,7 +705,8 @@ void vStateMachine(ULONG thread_input) get_fault(PACK_TOO_HOT), get_fault(DIE_TEMP_MAXIMUM_FAULT), get_fault(SEGMENT_COMMS_FAULT), - get_fault(HV_PLATE_COMMS_FAULT)); + get_fault(HV_PLATE_COMMS_FAULT), + get_fault(CELL_OPEN_WIRE_FAULT)); start_timer(&telem_timer, 500); } diff --git a/Drivers/Odyssey-Definitions b/Drivers/Odyssey-Definitions index a1977fc3..af267e88 160000 --- a/Drivers/Odyssey-Definitions +++ b/Drivers/Odyssey-Definitions @@ -1 +1 @@ -Subproject commit a1977fc3a12bfc1a27a619211f75f9f072f6830d +Subproject commit af267e88160ac1a2951a451072e63c18943fa4a8 diff --git a/Tests/Src/test_statemachine.c b/Tests/Src/test_statemachine.c index 7bbd4f06..7f783244 100644 --- a/Tests/Src/test_statemachine.c +++ b/Tests/Src/test_statemachine.c @@ -23,6 +23,8 @@ state_machine_args_t args = { .state_machine = &state_machine, void setUp(void) { analyzer.min_ocv.val = 2.6; + mutex_get_IgnoreAndReturn(0); + mutex_put_IgnoreAndReturn(0); cancel_timer_Expect(&state_machine.charger_message_timer); init_boot(&args); } From 28e62a16cd23a68ebd642d6b0577ba107da7b63b Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Tue, 14 Jul 2026 11:41:17 -0400 Subject: [PATCH 26/58] Add percell can msg for S adc volts --- Core/Inc/can_messages_tx.h | 16 ++++++++ Core/Src/can_messages_tx.c | 80 +++++++++++++++++++++++++++++++++++++ Core/Src/segment.c | 2 +- Drivers/Odyssey-Definitions | 2 +- 4 files changed, 98 insertions(+), 2 deletions(-) diff --git a/Core/Inc/can_messages_tx.h b/Core/Inc/can_messages_tx.h index 72e6d2b8..d0feea96 100644 --- a/Core/Inc/can_messages_tx.h +++ b/Core/Inc/can_messages_tx.h @@ -194,6 +194,22 @@ uint8_t send_alpha_cell_data_debug uint8_t send_beta_cell_data_debug (float therm,float voltage_a,float voltage_b,uint8_t chip_id,uint8_t cell_a,uint8_t cell_b,bool discharging_a,bool discharging_b,bool cvs_a,bool cvs_b,bool ow_a,bool ow_b); +/** +* Contents of this message: +* BMS/PerCell/Alpha/{3}/SADCVolts/{4} - S ADC cell voltage +* BMS/PerCell/Alpha/{3}/SADCVolts/{5} - S ADC cell voltage +*/ +uint8_t send_alpha_cell_s_adc_data +(float s_voltage_a,float s_voltage_b,uint8_t chip_id,uint8_t cell_a,uint8_t cell_b); + +/** +* Contents of this message: +* BMS/PerCell/Beta/{3}/SADCVolts/{4} - S ADC cell voltage +* BMS/PerCell/Beta/{3}/SADCVolts/{5} - S ADC cell voltage +*/ +uint8_t send_beta_cell_s_adc_data +(float s_voltage_a,float s_voltage_b,uint8_t chip_id,uint8_t cell_a,uint8_t cell_b); + /** * Contents of this message: * BMS/PerCell/Alpha/{1}/DieTemp - Temperatue of ADBMS die diff --git a/Core/Src/can_messages_tx.c b/Core/Src/can_messages_tx.c index a4e46597..f91d982e 100644 --- a/Core/Src/can_messages_tx.c +++ b/Core/Src/can_messages_tx.c @@ -740,6 +740,86 @@ uint8_t send_beta_cell_data_debug return queue_send(&can_outgoing, &msg, TX_NO_WAIT); } +uint8_t send_alpha_cell_s_adc_data +(float s_voltage_a,float s_voltage_b,uint8_t chip_id,uint8_t cell_a,uint8_t cell_b) +{ + can_msg_t msg; + msg.id = 0x6F4; + msg.id_is_extended = false; + + uint64_t data = 0; + msg.len = 8; + uint32_t s_voltage_a_i = (uint32_t)(s_voltage_a*1000); + if(s_voltage_a_i > 8191ULL) {s_voltage_a_i = 8191; + } + data |= ((s_voltage_a_i) & 0x1FFFULL) << 51; + + uint32_t s_voltage_b_i = (uint32_t)(s_voltage_b*1000); + if(s_voltage_b_i > 8191ULL) {s_voltage_b_i = 8191; + } + data |= ((s_voltage_b_i) & 0x1FFFULL) << 38; + + uint32_t chip_id_i = (uint32_t)(chip_id); + if(chip_id_i > 15ULL) {chip_id_i = 15; + } + data |= ((chip_id_i) & 0xFULL) << 34; + + uint32_t cell_a_i = (uint32_t)(cell_a); + if(cell_a_i > 15ULL) {cell_a_i = 15; + } + data |= ((cell_a_i) & 0xFULL) << 30; + + uint32_t cell_b_i = (uint32_t)(cell_b); + if(cell_b_i > 15ULL) {cell_b_i = 15; + } + data |= ((cell_b_i) & 0xFULL) << 26; + + uint64_t data_bigendian = __builtin_bswap64(data); + memcpy(msg.data, &data_bigendian, 8); + + return queue_send(&can_outgoing, &msg, TX_NO_WAIT); +} + +uint8_t send_beta_cell_s_adc_data +(float s_voltage_a,float s_voltage_b,uint8_t chip_id,uint8_t cell_a,uint8_t cell_b) +{ + can_msg_t msg; + msg.id = 0x6F5; + msg.id_is_extended = false; + + uint64_t data = 0; + msg.len = 8; + uint32_t s_voltage_a_i = (uint32_t)(s_voltage_a*1000); + if(s_voltage_a_i > 8191ULL) {s_voltage_a_i = 8191; + } + data |= ((s_voltage_a_i) & 0x1FFFULL) << 51; + + uint32_t s_voltage_b_i = (uint32_t)(s_voltage_b*1000); + if(s_voltage_b_i > 8191ULL) {s_voltage_b_i = 8191; + } + data |= ((s_voltage_b_i) & 0x1FFFULL) << 38; + + uint32_t chip_id_i = (uint32_t)(chip_id); + if(chip_id_i > 15ULL) {chip_id_i = 15; + } + data |= ((chip_id_i) & 0xFULL) << 34; + + uint32_t cell_a_i = (uint32_t)(cell_a); + if(cell_a_i > 15ULL) {cell_a_i = 15; + } + data |= ((cell_a_i) & 0xFULL) << 30; + + uint32_t cell_b_i = (uint32_t)(cell_b); + if(cell_b_i > 15ULL) {cell_b_i = 15; + } + data |= ((cell_b_i) & 0xFULL) << 26; + + uint64_t data_bigendian = __builtin_bswap64(data); + memcpy(msg.data, &data_bigendian, 8); + + return queue_send(&can_outgoing, &msg, TX_NO_WAIT); +} + uint8_t send_alpha_chip_a_debug (uint8_t chip_id,float die_temp,float vpv,float vmv,bool va_ov,bool va_uv,bool vd_ov,bool vd_uv,bool vde,bool vdel,bool spiflt,bool sleep,bool thsd,bool tmodchk,bool oscchk) { diff --git a/Core/Src/segment.c b/Core/Src/segment.c index 383f54c4..190b57a2 100644 --- a/Core/Src/segment.c +++ b/Core/Src/segment.c @@ -398,7 +398,7 @@ void vGetSegmentData(ULONG thread_input) // assumes a DCTO of 1 minute for PWM balancing in extended balancing mode const uint32_t pwm_update_frequency = 55000; // Required fault-tolerant time interval - const uint32_t open_wire_test_frequency = 60000; + const uint32_t open_wire_test_frequency = 30000; start_timer(&pwm_timer, 0); // start timer immeditately on first run start_timer(&open_wire_timer, open_wire_test_frequency); diff --git a/Drivers/Odyssey-Definitions b/Drivers/Odyssey-Definitions index af267e88..9691c392 160000 --- a/Drivers/Odyssey-Definitions +++ b/Drivers/Odyssey-Definitions @@ -1 +1 @@ -Subproject commit af267e88160ac1a2951a451072e63c18943fa4a8 +Subproject commit 9691c3929debe268f7e824b9af3099b86508d9d9 From ea27d19561c794ee90c61ade714246558d7bd8bd Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Tue, 14 Jul 2026 12:26:34 -0400 Subject: [PATCH 27/58] Send percell s-adc voltages --- Core/Inc/analyzer.h | 10 ++++++ Core/Inc/datastructs.h | 7 ++++ Core/Src/analyzer.c | 63 +++++++++++++++++++++++++++++++++-- Core/Src/segment.c | 74 +++++++++++++++++++++++------------------- Core/Src/shep_tasks.c | 1 + 5 files changed, 119 insertions(+), 36 deletions(-) diff --git a/Core/Inc/analyzer.h b/Core/Inc/analyzer.h index 2abfaf4c..51a31c93 100644 --- a/Core/Inc/analyzer.h +++ b/Core/Inc/analyzer.h @@ -27,6 +27,11 @@ void calc_pack_temps(analyzer_t *analyzer, acc_data_t *acc_data); void calc_cell_voltages(analyzer_t *analyzer, acc_data_t *acc_data, state_machine_t *state_machine); +/** + * @brief Convert the raw even and odd open-wire readings to voltages. + */ +void calc_cell_open_wire_voltages(analyzer_t *analyzer, acc_data_t *acc_data); + /** * @brief Calculate statistics about pack voltage, such as min and max cell * volt, pack and avg voltage, pack and avg OCV, and deltas. @@ -48,6 +53,11 @@ void calc_open_cell_voltage(analyzer_t *analyzer, acc_data_t *acc_data, void calc_cell_resistances(analyzer_t *analyzer, acc_data_t *acc_data, hv_plate_t *hv_plate); +/** + * @brief Detect open wires using the calculated even and odd voltages. + */ +void detect_cell_open_wire(analyzer_t *analyzer); + /** * @brief Updates the cell status of balancing and S_C_faults based on raw cell * data diff --git a/Core/Inc/datastructs.h b/Core/Inc/datastructs.h index c8b37342..a94f1451 100644 --- a/Core/Inc/datastructs.h +++ b/Core/Inc/datastructs.h @@ -46,6 +46,8 @@ typedef struct { float cell_resistance[NUM_CELLS_PER_CHIP]; float open_cell_voltage[NUM_CELLS_PER_CHIP]; float cell_voltages[NUM_CELLS_PER_CHIP]; + /* S-ADC voltages */ + float s_cell_voltages[NUM_CELLS_PER_CHIP]; /* Maximum temperature of on-board therms.*/ float on_board_temp[NUM_ONBOARD_THERMS_PER_CHIP]; @@ -58,6 +60,10 @@ typedef struct { bool cs_fault[NUM_CELLS_PER_CHIP]; bool ow_fault[NUM_CELLS_PER_CHIP]; + /* Open-Wire Diagnostic Voltages */ + float ow_even_voltage[NUM_CELLS_PER_CHIP]; + float ow_odd_voltage[NUM_CELLS_PER_CHIP]; + float vpv; float vmv; float v_res; @@ -455,6 +461,7 @@ typedef struct { typedef struct { acc_data_t *acc_data; state_machine_t *state_machine; + analyzer_t *analyzer; } acc_data_args_t; /** diff --git a/Core/Src/analyzer.c b/Core/Src/analyzer.c index 8dd0fb6b..b7dcee15 100644 --- a/Core/Src/analyzer.c +++ b/Core/Src/analyzer.c @@ -7,6 +7,7 @@ #include "serialPrintResult.h" #include "timer.h" #include "state_machine.h" +#include "u_tx_debug.h" #include "u_tx_flags.h" #include "can_messages_tx.h" #include "shep_mutexes.h" @@ -14,6 +15,11 @@ #define OCV_TIMER_DURATION 750 // in ticks +/** + * @brief Open-wire threshold while the S-ADC switch is active. + */ +#define CELL_OPEN_WIRE_MAX_DROP_PERCENT 15.0f + /** * @brief Map cells to therms (ra codes). Note beta has only 6 therms. */ @@ -190,6 +196,20 @@ void calc_cell_voltages(analyzer_t *analyzer, acc_data_t *acc_data, } +void calc_cell_open_wire_voltages(analyzer_t *analyzer, acc_data_t *acc_data) +{ + for (uint8_t chip = 0; chip < NUM_CHIPS; chip++) { + chipdata_t *chip_data = get_chip_data(analyzer, chip); + + for (uint8_t cell = 0; cell < NUM_CELLS_PER_CHIP; cell++) { + chip_data->ow_even_voltage[cell] = getVoltage( + acc_data->chips[chip].owcell.cell_ow_even[cell]); + chip_data->ow_odd_voltage[cell] = getVoltage( + acc_data->chips[chip].owcell.cell_ow_odd[cell]); + } + } +} + void calc_pack_voltage_stats(analyzer_t *analyzer, acc_data_t *acc_data) { analyzer->max_voltage.val = FLT_MIN; @@ -356,6 +376,44 @@ void calc_open_cell_voltage(analyzer_t *analyzer, acc_data_t *acc_data, } } +void detect_cell_open_wire(analyzer_t *analyzer) +{ + for (uint8_t chip = 0; chip < NUM_CHIPS; chip++) { + chipdata_t *chip_data = get_chip_data(analyzer, chip); + + for (uint8_t cell = 0; cell < NUM_CELLS_PER_CHIP; cell++) { + const float even_voltage = + chip_data->ow_even_voltage[cell]; + const float odd_voltage = chip_data->ow_odd_voltage[cell]; + const bool even_cell = ((cell + 1U) % 2U) == 0U; + const float excited_voltage = + even_cell ? even_voltage : odd_voltage; + const float baseline_voltage = + even_cell ? odd_voltage : even_voltage; + const float drop_voltage = baseline_voltage - excited_voltage; + float drop_percent = 0.0f; + + if (baseline_voltage > 0.0f) { + drop_percent = + (drop_voltage / baseline_voltage) * 100.0f; + } + + const bool was_open = chip_data->ow_fault[cell]; + const bool is_open = + drop_percent > CELL_OPEN_WIRE_MAX_DROP_PERCENT; + + chip_data->ow_fault[cell] = is_open; + + if (is_open && !was_open) { + PRINTLN_WARNING( + "[OW] Open wire IC%u C%02u: even=%.3f V, odd=%.3f V, drop=%.3f V (%.1f%%)", + chip + 1U, cell + 1U, even_voltage, + odd_voltage, drop_voltage, drop_percent); + } + } + } +} + void update_chip_status(analyzer_t *analyzer, acc_data_t *acc_data) { for (uint8_t chip = 0; chip < NUM_CHIPS; chip++) { @@ -373,9 +431,6 @@ void update_chip_status(analyzer_t *analyzer, acc_data_t *acc_data) chip_data->cs_fault[cell] = (acc_data->chips[chip].statc.cs_flt >> cell) & 1; - // OW fault status - chip_data->ow_fault[cell] = - (acc_data->chips[chip].diag_result.cell_ow[cell] != 0U); } // Chip Diagnotics @@ -421,9 +476,11 @@ void vAnalyzer(ULONG thread_input) calc_cell_temps(analyzer, acc_data); calc_pack_temps(analyzer, acc_data); calc_cell_voltages(analyzer, acc_data, state_machine); + calc_cell_open_wire_voltages(analyzer, acc_data); calc_open_cell_voltage(analyzer, acc_data, hv_plate); calc_pack_voltage_stats(analyzer, acc_data); calc_cell_resistances(analyzer, acc_data, hv_plate); + detect_cell_open_wire(analyzer); update_chip_status(analyzer, acc_data); mutex_put(&analyzer_mutex); diff --git a/Core/Src/segment.c b/Core/Src/segment.c index 190b57a2..8dea3de3 100644 --- a/Core/Src/segment.c +++ b/Core/Src/segment.c @@ -3,6 +3,7 @@ #include "adBms6830Data.h" #include "adi6830_interation.h" #include "bms_config.h" +#include "can_messages_tx.h" #include "c_utils.h" #include "charging.h" #include "datastructs.h" @@ -13,11 +14,6 @@ #include "app_threadx.h" #include "main.h" -/** - * @brief Open-wire threshold while the S-ADC switch is active. - */ -#define CELL_OPEN_WIRE_MAX_DROP_PERCENT 15.0f - /** * @brief Initialize a chip with our default values. * @@ -299,9 +295,11 @@ void segment_set_dcto(cell_asic chips[NUM_CHIPS], uint8_t dcto, * @brief Run cell open-wire and S-vs-C diagnostics. * * @param chips Array of chips. + * @param analyzer Analyzer data to update. * @param hspi SPI handle. */ static void segment_run_cell_open_wire_test(cell_asic chips[NUM_CHIPS], + analyzer_t *analyzer, SPI_HandleTypeDef *hspi) { /* @@ -316,10 +314,17 @@ static void segment_run_cell_open_wire_test(cell_asic chips[NUM_CHIPS], tx_thread_sleep(16); segment_snap(chips, hspi); - read_c_voltage_registers(chips, hspi); + // read_c_voltage_registers(chips, hspi); read_s_voltage_registers(chips, hspi); segment_unsnap(chips, hspi); + for (uint8_t chip = 0; chip < NUM_CHIPS; chip++) { + for (uint8_t cell = 0; cell < NUM_CELLS_PER_CHIP; cell++) { + analyzer->chip_data[chip].s_cell_voltages[cell] = + getVoltage(chips[chip].scell.sc_codes[cell]); + } + } + read_status_register_c(chips, hspi); get_s_adc_open_wire_voltages(chips, hspi, OW_ON_EVEN_CH); @@ -335,32 +340,31 @@ static void segment_run_cell_open_wire_test(cell_asic chips[NUM_CHIPS], for (uint8_t ic = 0; ic < NUM_CHIPS; ic++) { for (uint8_t cell = 0; cell < NUM_CELLS_PER_CHIP; cell++) { - const int16_t odd_code = chips[ic].scell.sc_codes[cell]; - const float even_voltage = getVoltage( - chips[ic].owcell.cell_ow_even[cell]); - const float odd_voltage = getVoltage(odd_code); - const bool even_cell = ((cell + 1U) % 2U) == 0U; - const float excited_voltage = - even_cell ? even_voltage : odd_voltage; - const float baseline_voltage = - even_cell ? odd_voltage : even_voltage; - const float drop_voltage = baseline_voltage - excited_voltage; - float drop_percent = 0.0f; - - if (baseline_voltage > 0.0f) { - drop_percent = - (drop_voltage / baseline_voltage) * 100.0f; - } - - chips[ic].owcell.cell_ow_odd[cell] = odd_code; - chips[ic].diag_result.cell_ow[cell] = - (drop_percent > CELL_OPEN_WIRE_MAX_DROP_PERCENT); + chips[ic].owcell.cell_ow_odd[cell] = + chips[ic].scell.sc_codes[cell]; + } + } +} - if (chips[ic].diag_result.cell_ow[cell]) { - PRINTLN_WARNING( - "[OW] Open wire IC%u C%02u: even=%.3f V, odd=%.3f V, drop=%.3f V (%.1f%%)", - ic + 1U, cell + 1U, even_voltage, - odd_voltage, drop_voltage, drop_percent); +static void segment_send_s_adc_cell_data(const analyzer_t *analyzer) +{ + for (uint8_t chip = 0; chip < NUM_CHIPS; chip++) { + const chipdata_t *chip_data = &analyzer->chip_data[chip]; + + for (uint8_t cell = 0; cell < NUM_CELLS_PER_CHIP; cell += 2) { + const bool has_cell_b = cell + 1U < NUM_CELLS_PER_CHIP; + const float s_voltage_b = has_cell_b ? + chip_data->s_cell_voltages[cell + 1U] : + 0.0f; + + if ((chip % 2U) == 0U) { + send_alpha_cell_s_adc_data( + chip_data->s_cell_voltages[cell], s_voltage_b, + chip / 2U, cell, cell + 1U); + } else { + send_beta_cell_s_adc_data( + chip_data->s_cell_voltages[cell], s_voltage_b, + chip / 2U, cell, cell + 1U); } } } @@ -375,6 +379,7 @@ void vGetSegmentData(ULONG thread_input) acc_data_t *acc_data = acc_data_args->acc_data; state_machine_t *state_machine = acc_data_args->state_machine; + analyzer_t *analyzer = acc_data_args->analyzer; HAL_NVIC_DisableIRQ(FDCAN2_IT0_IRQn); segment_init(acc_data->chips, &hspi2); @@ -386,7 +391,8 @@ void vGetSegmentData(ULONG thread_input) // Run after the monitor ADC has initialized, before normal acquisition HAL_NVIC_DisableIRQ(FDCAN2_IT0_IRQn); - segment_run_cell_open_wire_test(acc_data->chips, &hspi2); + segment_run_cell_open_wire_test(acc_data->chips, analyzer, &hspi2); + segment_send_s_adc_cell_data(analyzer); HAL_NVIC_EnableIRQ(FDCAN2_IT0_IRQn); state_t prev_state = BOOT; @@ -438,7 +444,9 @@ void vGetSegmentData(ULONG thread_input) } if (is_timer_expired(&open_wire_timer)) { - segment_run_cell_open_wire_test(acc_data->chips, &hspi2); + segment_run_cell_open_wire_test(acc_data->chips, analyzer, + &hspi2); + segment_send_s_adc_cell_data(analyzer); start_timer(&open_wire_timer, open_wire_test_frequency); } diff --git a/Core/Src/shep_tasks.c b/Core/Src/shep_tasks.c index 838391d7..6dc9f1ff 100644 --- a/Core/Src/shep_tasks.c +++ b/Core/Src/shep_tasks.c @@ -411,6 +411,7 @@ uint8_t shep_threads_init(TX_BYTE_POOL *byte_pool) static acc_data_args_t acc_data_args = { 0 }; acc_data_args.acc_data = &acc_data; acc_data_args.state_machine = &state_machine; + acc_data_args.analyzer = &analyzer; static state_machine_args_t state_machine_args = { 0 }; state_machine_args.acc_data = &acc_data; From b32e9b23bb781fe40fcde56318d7222beb45007d Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Tue, 14 Jul 2026 20:14:47 -0400 Subject: [PATCH 28/58] Update submodule --- Drivers/Odyssey-Definitions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Drivers/Odyssey-Definitions b/Drivers/Odyssey-Definitions index 9691c392..1ea12a1c 160000 --- a/Drivers/Odyssey-Definitions +++ b/Drivers/Odyssey-Definitions @@ -1 +1 @@ -Subproject commit 9691c3929debe268f7e824b9af3099b86508d9d9 +Subproject commit 1ea12a1cc1ac0673d4a6a33b0c3da4d82b35e021 From f28f1e0f7226ab914a259f581d7702b53d8adcf8 Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Tue, 14 Jul 2026 22:17:56 -0400 Subject: [PATCH 29/58] Final fixes --- Core/Src/hv_plate.c | 10 ++++++---- Core/Src/state_machine.c | 27 ++++++++++++++++----------- Drivers/Odyssey-Definitions | 2 +- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/Core/Src/hv_plate.c b/Core/Src/hv_plate.c index 2fdb5f9f..ea9b2ff6 100644 --- a/Core/Src/hv_plate.c +++ b/Core/Src/hv_plate.c @@ -181,6 +181,9 @@ void vHvPlateData(ULONG thread_input) analyzer_t *analyzer = hv_plate_args->analyzer; bms_algos_t *bms_algos = hv_plate_args->bms_algos; state_machine_t *state_machine = hv_plate_args->state_machine; + float max_dc_current = 0.0f; + float max_dc_brake_current = 0.0f; + state_t bms_state = BOOT; dcl_init(COOLDOWN_ALWAYS); ccl_init(COOLDOWN_ALWAYS); @@ -203,10 +206,6 @@ void vHvPlateData(ULONG thread_input) set_gpo(&hv_plate->ic, HV_ENABLE_GPO); for (;;) { - float max_dc_current = 0.0f; - float max_dc_brake_current = 0.0f; - state_t bms_state = BOOT; - // get the current reading from the pack get_pack_current_and_batt_voltage(hv_plate); @@ -282,6 +281,9 @@ void vHvPlateData(ULONG thread_input) // DTI expects regen/brake current limit to be negative max_dc_brake_current = (-1.0f * bms_algos->cont_CCL); mutex_put(&bms_algos_mutex); + } else { + max_dc_current = 0.0f; + max_dc_brake_current = 0.0f; } send_max_dc_current_command(max_dc_current); diff --git a/Core/Src/state_machine.c b/Core/Src/state_machine.c index b209400c..f6fe5b0c 100644 --- a/Core/Src/state_machine.c +++ b/Core/Src/state_machine.c @@ -94,10 +94,10 @@ void handle_charging(state_machine_args_t *state_machine_args) /* Check if we should charge */ if (sm_charging_check(state_machine_args)) { /* Send CAN message, but not too often */ - if (!is_timer_active(&state_machine_args->state_machine - ->charger_message_timer) || - is_timer_expired(&state_machine_args->state_machine - ->charger_message_timer)) { + if (is_timer_expired(&state_machine_args->state_machine + ->charger_message_timer) || + !is_timer_active(&state_machine_args->state_machine + ->charger_message_timer)) { send_bms_charge_message_send((MAX_CHARGE_VOLT * (NUM_CELLS_PER_CHIP * 2) * NUM_SEGMENTS), @@ -408,26 +408,33 @@ bool sm_charging_check(state_machine_args_t *state_machine_args) /* if not charging stage, dont charge * (LONG_SETTLE, SHORT_SETTLE, DONE, FAULT) */ - // return state_machine->charging_stage == LONG_CHARGE_UP || - // state_machine->charging_stage == SHORT_CHARGE_UP; - return true; + return state_machine->charging_stage == LONG_CHARGE_UP || + state_machine->charging_stage == SHORT_CHARGE_UP; } // check if balancing is allowed bool sm_balancing_check(state_machine_args_t *state_machine_args) { analyzer_t *analyzer = state_machine_args->analyzer; + state_machine_t *state_machine = state_machine_args->state_machine; bool balancing_allowed = false; bool shutdown_active = true; float max_voltage = 0.0f; float delta_voltage = 0.0f; + charge_stage_t charging_stage = FAULT; mutex_get(&analyzer_mutex); max_voltage = analyzer->max_voltage.val; delta_voltage = analyzer->delta_voltage; mutex_put(&analyzer_mutex); - if ((max_voltage <= BAL_MIN_V) || (delta_voltage <= MAX_DELTA_V)) { + mutex_get(&state_mutex); + charging_stage = state_machine->charging_stage; + mutex_put(&state_mutex); + + if ((max_voltage <= BAL_MIN_V) || (delta_voltage <= MAX_DELTA_V) || + (charging_stage == LONG_SETTLE) || + (charging_stage == SHORT_SETTLE)) { balancing_allowed = false; } else { // Do not balance if the shutdown circuit is open. @@ -436,9 +443,7 @@ bool sm_balancing_check(state_machine_args_t *state_machine_args) state_machine_args->peripherals->shutdown_active; mutex_put(&shutdown_mutex); - balancing_allowed = !shutdown_active; - - /** @todo Prevent balancing during settling charging stages. */ + balancing_allowed = shutdown_active; } return balancing_allowed; diff --git a/Drivers/Odyssey-Definitions b/Drivers/Odyssey-Definitions index a1977fc3..b3a32f16 160000 --- a/Drivers/Odyssey-Definitions +++ b/Drivers/Odyssey-Definitions @@ -1 +1 @@ -Subproject commit a1977fc3a12bfc1a27a619211f75f9f072f6830d +Subproject commit b3a32f1621076a2169694d947f0668782f5d1301 From 470335e06b99198de134092b78a483fea090746b Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Tue, 14 Jul 2026 22:29:21 -0400 Subject: [PATCH 30/58] Update odyssey --- Drivers/Odyssey-Definitions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Drivers/Odyssey-Definitions b/Drivers/Odyssey-Definitions index 1ea12a1c..b3a32f16 160000 --- a/Drivers/Odyssey-Definitions +++ b/Drivers/Odyssey-Definitions @@ -1 +1 @@ -Subproject commit 1ea12a1cc1ac0673d4a6a33b0c3da4d82b35e021 +Subproject commit b3a32f1621076a2169694d947f0668782f5d1301 From ad66231dc7d46cc1c73859095d61032ef5dcaac1 Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Wed, 15 Jul 2026 15:17:11 -0400 Subject: [PATCH 31/58] Updated implementation --- Core/Inc/adi6830_interation.h | 25 +++++-------- Core/Inc/bms_config.h | 2 +- Core/Src/adi6830_interaction.c | 12 +++---- Core/Src/segment.c | 65 ++++++++++++++++++---------------- 4 files changed, 49 insertions(+), 55 deletions(-) diff --git a/Core/Inc/adi6830_interation.h b/Core/Inc/adi6830_interation.h index 9488e729..5e6e80d5 100644 --- a/Core/Inc/adi6830_interation.h +++ b/Core/Inc/adi6830_interation.h @@ -243,15 +243,6 @@ void write_clear_flags(cell_asic chips[NUM_CHIPS], SPI_HandleTypeDef *hspi); // --- BEGIN READ COMMANDS --- -/** - * @brief Read all C-ADC voltage registers. - * - * @param chips Array of chips. - * @param hspi SPI handle. - */ -void read_c_voltage_registers(cell_asic chips[NUM_CHIPS], - SPI_HandleTypeDef *hspi); - /** * @brief Read all filtered voltage results A-E. IIR must be on and ADC must be continous * @@ -368,23 +359,23 @@ void get_s_adc_open_wire_voltages(cell_asic chips[NUM_CHIPS], OW_C_S open_wire_mode); /** - * @brief Trigger, poll, and fetch the c and s adc voltages, using instaneous redundancy. + * @brief Trigger, poll, and fetch normal S-ADC voltages. * - * @param chips Array of chips to get voltage readings of. + * @param chips Array of chips to get voltage readings from. */ -void get_c_and_s_adc_voltages(cell_asic chips[NUM_CHIPS], - SPI_HandleTypeDef *hspi); +void get_s_adc_voltages(cell_asic chips[NUM_CHIPS], + SPI_HandleTypeDef *hspi); /** - * @brief Start continuous C-ADC conversions. + * @brief Starts a continous c ADC conversion with S redundancy * */ void start_c_adc_conv(cell_asic chips[NUM_CHIPS], SPI_HandleTypeDef *hspi); /** - * @brief Start continuous S-ADC conversion. - * @param chips Array of chips. - * @param hspi SPI handle. + * @brief Start continuous S-ADC redundancy without restarting the C-ADC. + * @param chips ADBMS6830 daisy-chain devices. + * @param hspi SPI peripheral used by the daisy chain. */ void start_s_adc_conv(cell_asic chips[NUM_CHIPS], SPI_HandleTypeDef *hspi); diff --git a/Core/Inc/bms_config.h b/Core/Inc/bms_config.h index 805940df..e55c1987 100644 --- a/Core/Inc/bms_config.h +++ b/Core/Inc/bms_config.h @@ -69,7 +69,7 @@ #define HIGH_TEMP_TIME 55000 #define MAX_CHIPTEMP_TIME 55000 #define COMMS_FAULT_TIME 20000 -#define OW_FAULT_TIME 5000 +#define OW_FAULT_TIME 40000 // system wide base ADBMS sample rate #define SAMPLE_RATE 2 /* Hz */ diff --git a/Core/Src/adi6830_interaction.c b/Core/Src/adi6830_interaction.c index 9ade54d5..b95beb0f 100644 --- a/Core/Src/adi6830_interaction.c +++ b/Core/Src/adi6830_interaction.c @@ -553,7 +553,7 @@ void read_serial_id(cell_asic chips[NUM_CHIPS], SPI_HandleTypeDef *hspi) void get_c_adc_voltages(cell_asic chips[NUM_CHIPS], SPI_HandleTypeDef *hspi) { - adBms6830_Adcv(NUM_CHIPS, chips, RD_ON, SINGLE, DCP_OFF, RSTF_ON, + adBms6830_Adcv(NUM_CHIPS, chips, RD_ON, SINGLE, DCP_OFF, RSTF_OFF, OW_OFF_ALL_CH); adBmsPollAdc_indicator(chips, PLCADC); @@ -585,20 +585,18 @@ void get_s_adc_open_wire_voltages(cell_asic chips[NUM_CHIPS], read_s_voltage_registers(chips, hspi); } -void get_c_and_s_adc_voltages(cell_asic chips[NUM_CHIPS], - SPI_HandleTypeDef *hspi) +void get_s_adc_voltages(cell_asic chips[NUM_CHIPS], + SPI_HandleTypeDef *hspi) { - adBms6830_Adcv(NUM_CHIPS, chips, RD_ON, SINGLE, DCP_OFF, RSTF_OFF, - OW_OFF_ALL_CH); + adBms6830_Adsv(NUM_CHIPS, chips, SINGLE, DCP_OFF, OW_OFF_ALL_CH); adBmsPollAdc_indicator(chips, PLSADC); - read_c_voltage_registers(chips, hspi); read_s_voltage_registers(chips, hspi); } void start_c_adc_conv(cell_asic chips[NUM_CHIPS], SPI_HandleTypeDef *hspi) { - adBms6830_Adcv(NUM_CHIPS, chips, RD_OFF, CONTINUOUS, DCP_OFF, RSTF_ON, + adBms6830_Adcv(NUM_CHIPS, chips, RD_ON, CONTINUOUS, DCP_OFF, RSTF_ON, OW_OFF_ALL_CH); } diff --git a/Core/Src/segment.c b/Core/Src/segment.c index 8dea3de3..99212e4b 100644 --- a/Core/Src/segment.c +++ b/Core/Src/segment.c @@ -161,9 +161,8 @@ void segment_retrieve_charging_data(cell_asic chips[NUM_CHIPS], // poll stuff like vref, etc. adc_and_read_aux_registers(chips, hspi); - segment_snap(chips, hspi); - read_c_voltage_registers(chips, hspi); - segment_unsnap(chips, hspi); + // run a clean single-shot conversion + get_c_adc_voltages(chips, hspi); read_status_registers(chips, hspi); @@ -292,31 +291,33 @@ void segment_set_dcto(cell_asic chips[NUM_CHIPS], uint8_t dcto, } /** - * @brief Run cell open-wire and S-vs-C diagnostics. + * @brief Run the cell open-wire diagnostic. * * @param chips Array of chips. * @param analyzer Analyzer data to update. + * @param charging True when charging. * @param hspi SPI handle. */ static void segment_run_cell_open_wire_test(cell_asic chips[NUM_CHIPS], analyzer_t *analyzer, + bool charging, SPI_HandleTypeDef *hspi) { /* - * Open-wire and S-vs-C diagnostic sequence: - * 1. Start continuous S-ADC redundancy with discharge paused. - * 2. Wait for S-ADC synchronization and one synchronous conversion. - * 3. Read C, S, and Status C. - * 4. Run even and odd single-shot open-wire conversions. - * The final odd single-shot automatically allows discharge to resume. + * Open-wire sequence: + * 1. Read and store the normal S values. + * 2. Run and store the even-cell open-wire conversion. + * 3. Run and store the odd-cell open-wire conversion. + * 4. Restart continuous S outside charging. */ - start_s_adc_conv(chips, hspi); - tx_thread_sleep(16); - segment_snap(chips, hspi); - // read_c_voltage_registers(chips, hspi); - read_s_voltage_registers(chips, hspi); - segment_unsnap(chips, hspi); + if (charging) { + get_s_adc_voltages(chips, hspi); + } else { + segment_snap(chips, hspi); + read_s_voltage_registers(chips, hspi); + segment_unsnap(chips, hspi); + } for (uint8_t chip = 0; chip < NUM_CHIPS; chip++) { for (uint8_t cell = 0; cell < NUM_CELLS_PER_CHIP; cell++) { @@ -325,25 +326,27 @@ static void segment_run_cell_open_wire_test(cell_asic chips[NUM_CHIPS], } } - read_status_register_c(chips, hspi); - get_s_adc_open_wire_voltages(chips, hspi, OW_ON_EVEN_CH); - for (uint8_t ic = 0; ic < NUM_CHIPS; ic++) { + for (uint8_t chip = 0; chip < NUM_CHIPS; chip++) { for (uint8_t cell = 0; cell < NUM_CELLS_PER_CHIP; cell++) { - chips[ic].owcell.cell_ow_even[cell] = - chips[ic].scell.sc_codes[cell]; + chips[chip].owcell.cell_ow_even[cell] = + chips[chip].scell.sc_codes[cell]; } } get_s_adc_open_wire_voltages(chips, hspi, OW_ON_ODD_CH); - for (uint8_t ic = 0; ic < NUM_CHIPS; ic++) { + for (uint8_t chip = 0; chip < NUM_CHIPS; chip++) { for (uint8_t cell = 0; cell < NUM_CELLS_PER_CHIP; cell++) { - chips[ic].owcell.cell_ow_odd[cell] = - chips[ic].scell.sc_codes[cell]; + chips[chip].owcell.cell_ow_odd[cell] = + chips[chip].scell.sc_codes[cell]; } } + + if (!charging) { + start_s_adc_conv(chips, hspi); + } } static void segment_send_s_adc_cell_data(const analyzer_t *analyzer) @@ -391,7 +394,8 @@ void vGetSegmentData(ULONG thread_input) // Run after the monitor ADC has initialized, before normal acquisition HAL_NVIC_DisableIRQ(FDCAN2_IT0_IRQn); - segment_run_cell_open_wire_test(acc_data->chips, analyzer, &hspi2); + segment_run_cell_open_wire_test(acc_data->chips, analyzer, false, + &hspi2); segment_send_s_adc_cell_data(analyzer); HAL_NVIC_EnableIRQ(FDCAN2_IT0_IRQn); @@ -412,16 +416,17 @@ void vGetSegmentData(ULONG thread_input) for (;;) { prev_state = current_state; current_state = state_machine->bms_state; - bool balancing_active = state_machine->balancing_active; + const bool charging = (current_state == CHARGING); + const bool balancing_active = state_machine->balancing_active; HAL_NVIC_DisableIRQ(FDCAN2_IT0_IRQn); - if ((prev_state != current_state && current_state != CHARGING) || + if ((prev_state != current_state && !charging) || (prev_balancing_active && !balancing_active)) { segment_mute(acc_data->chips, &hspi2); } - if (current_state == CHARGING) { + if (charging) { // in charging, debug data is required to get things like die temp segment_retrieve_charging_data(acc_data->chips, &hspi2); send_segment_pec_errors_message(); @@ -445,12 +450,12 @@ void vGetSegmentData(ULONG thread_input) if (is_timer_expired(&open_wire_timer)) { segment_run_cell_open_wire_test(acc_data->chips, analyzer, - &hspi2); + charging, &hspi2); segment_send_s_adc_cell_data(analyzer); start_timer(&open_wire_timer, open_wire_test_frequency); } - if (current_state == CHARGING && balancing_active && + if (charging && balancing_active && is_timer_expired(&pwm_timer) && !is_timer_active(&pwm_timer)) { segment_mute(acc_data->chips, &hspi2); From 7b4e96d365c5708000c2562297d4fe4d8cd9a16e Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Thu, 16 Jul 2026 13:07:58 -0400 Subject: [PATCH 32/58] dcl and ccl can msg fix --- Core/Src/hv_plate.c | 9 +++------ Core/Src/state_machine.c | 4 ++++ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Core/Src/hv_plate.c b/Core/Src/hv_plate.c index ea9b2ff6..bd9fa71f 100644 --- a/Core/Src/hv_plate.c +++ b/Core/Src/hv_plate.c @@ -281,13 +281,10 @@ void vHvPlateData(ULONG thread_input) // DTI expects regen/brake current limit to be negative max_dc_brake_current = (-1.0f * bms_algos->cont_CCL); mutex_put(&bms_algos_mutex); - } else { - max_dc_current = 0.0f; - max_dc_brake_current = 0.0f; - } - send_max_dc_current_command(max_dc_current); - send_max_dc_brake_current_command(max_dc_brake_current); + send_max_dc_current_command(max_dc_current); + send_max_dc_brake_current_command(max_dc_brake_current); + } tx_thread_sleep(50); } diff --git a/Core/Src/state_machine.c b/Core/Src/state_machine.c index f6fe5b0c..e88352a5 100644 --- a/Core/Src/state_machine.c +++ b/Core/Src/state_machine.c @@ -110,6 +110,10 @@ void handle_charging(state_machine_args_t *state_machine_args) send_bms_charge_message_send(0, 0, 0xFF); } + // disable discharge and charge from the MC + send_max_dc_current_command(0); + send_max_dc_brake_current_command(0); + /* Check if we should balance */ if (sm_balancing_check(state_machine_args)) { handle_balance_cells(state_machine_args->analyzer, From d9ceb001da45a61cd93e05dbdb18c398da0916b7 Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Thu, 16 Jul 2026 16:56:19 -0400 Subject: [PATCH 33/58] Correct OCV timing --- Core/Inc/analyzer.h | 3 +-- Core/Src/analyzer.c | 40 ++++++++++++++++++---------------------- 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/Core/Inc/analyzer.h b/Core/Inc/analyzer.h index 51a31c93..fcb4ca30 100644 --- a/Core/Inc/analyzer.h +++ b/Core/Inc/analyzer.h @@ -44,8 +44,7 @@ void calc_pack_voltage_stats(analyzer_t *analyzer, acc_data_t *acc_data); * cell voltages. * */ -void calc_open_cell_voltage(analyzer_t *analyzer, acc_data_t *acc_data, - hv_plate_t *hv_plate); +void calc_open_cell_voltage(analyzer_t *analyzer, hv_plate_t *hv_plate); /** * @brief Calculate cell resistances using Rin = ( Voc - V )/I diff --git a/Core/Src/analyzer.c b/Core/Src/analyzer.c index b7dcee15..5cfb6672 100644 --- a/Core/Src/analyzer.c +++ b/Core/Src/analyzer.c @@ -326,53 +326,49 @@ void calc_cell_resistances(analyzer_t *analyzer, acc_data_t *acc_data, } } -void calc_open_cell_voltage(analyzer_t *analyzer, acc_data_t *acc_data, - hv_plate_t *hv_plate) +void calc_open_cell_voltage(analyzer_t *analyzer, hv_plate_t *hv_plate) { static bool is_first_reading = true; - /* if there is no previous data point, set inital open cell voltage to current reading */ + bool valid_cell_reading = !is_first_reading; if (is_first_reading) { - // sanity check the last cell that the reading is good, oftentimes the first - // readings are bad float last_cell = analyzer->chip_data[NUM_CHIPS - 1] .cell_voltages[NUM_CELLS_PER_CHIP - 1]; + if (last_cell > 1 && last_cell < 5) { is_first_reading = false; - start_timer(&analyzer->ocvTimer, OCV_TIMER_DURATION); - } + valid_cell_reading = true; - for (uint8_t chip = 0; chip < NUM_CHIPS; chip++) { - for (uint8_t cell = 0; cell < NUM_CELLS_PER_CHIP; - cell++) { - analyzer->chip_data[chip] - .open_cell_voltage[cell] = + for (uint8_t chip = 0; chip < NUM_CHIPS; chip++) { + for (uint8_t cell = 0; + cell < NUM_CELLS_PER_CHIP; cell++) { analyzer->chip_data[chip] - .cell_voltages[cell]; + .open_cell_voltage[cell] = + analyzer->chip_data[chip] + .cell_voltages[cell]; + } } } } - // If we are within the current threshold for open voltage measurments (1.5 mA) - if (hv_plate->pack_current < OCV_CURR_THRESH && - hv_plate->pack_current > -1 * OCV_CURR_THRESH) { - // Timer expired or not active - if (is_timer_expired(&analyzer->ocvTimer) || - !is_timer_active(&analyzer->ocvTimer)) { + if (valid_cell_reading && + fabsf(hv_plate->pack_current) < OCV_CURR_THRESH) { + if (is_timer_expired(&analyzer->ocvTimer)) { for (uint8_t chip = 0; chip < NUM_CHIPS; chip++) { for (uint8_t cell = 0; cell < NUM_CELLS_PER_CHIP; cell++) { - // Set current OCV value, ensure value is true OCV analyzer->chip_data[chip] .open_cell_voltage[cell] = analyzer->chip_data[chip] .cell_voltages[cell]; } } - } else { + } else if (!is_timer_active(&analyzer->ocvTimer)) { start_timer(&analyzer->ocvTimer, OCV_TIMER_DURATION); } + } else { + cancel_timer(&analyzer->ocvTimer); } } @@ -477,7 +473,7 @@ void vAnalyzer(ULONG thread_input) calc_pack_temps(analyzer, acc_data); calc_cell_voltages(analyzer, acc_data, state_machine); calc_cell_open_wire_voltages(analyzer, acc_data); - calc_open_cell_voltage(analyzer, acc_data, hv_plate); + calc_open_cell_voltage(analyzer, hv_plate); calc_pack_voltage_stats(analyzer, acc_data); calc_cell_resistances(analyzer, acc_data, hv_plate); detect_cell_open_wire(analyzer); From 7fec5fb1f6041b4561338850061ce8dab401aab3 Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Thu, 16 Jul 2026 16:57:39 -0400 Subject: [PATCH 34/58] Add balancing algorithm mutexes --- Core/Inc/shep_mutexes.h | 1 + Core/Src/charging.c | 10 +++++++++- Core/Src/segment.c | 3 +++ Core/Src/shep_mutexes.c | 6 ++++++ 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Core/Inc/shep_mutexes.h b/Core/Inc/shep_mutexes.h index ca1980a0..e9846fa1 100644 --- a/Core/Inc/shep_mutexes.h +++ b/Core/Inc/shep_mutexes.h @@ -13,6 +13,7 @@ extern mutex_t state_mutex; extern mutex_t bms_algos_mutex; extern mutex_t peripherals_mutex; extern mutex_t shutdown_mutex; +extern mutex_t balancing_mutex; // add more as necessary... /* API */ diff --git a/Core/Src/charging.c b/Core/Src/charging.c index bb4de752..af537ba0 100644 --- a/Core/Src/charging.c +++ b/Core/Src/charging.c @@ -4,6 +4,7 @@ #include "bms_config.h" #include "c_utils.h" #include "analyzer.h" +#include "shep_mutexes.h" /// @brief A struct to hold the original float value and the index originally, /// as that holds meaning @@ -107,18 +108,23 @@ void handle_balance_cells(analyzer_t *analyzer, acc_data_t *acc_data) static const int MAX_BAL_CHIP = 7; // the low cell, eventually they all must get there - float low = analyzer->min_ocv.val; + float low = 0.0f; // the margin above the low cell to ignore, which is usually X% of the delta, gate at 0 //float min_thresh = //fmaxf(analyzer->delt_ocv * 0.4f, 0.0f); float min_thresh = 0.02f; val_idexed_t new_ocv_map[NUM_CHIPS][NUM_CELLS_PER_CHIP] = { 0 }; + mutex_get(&analyzer_mutex); + low = analyzer->min_ocv.val; // first, sort and cleanup everything chipsSelectionSort(analyzer, new_ocv_map); + mutex_put(&analyzer_mutex); PWM_DUTY duty_cycle = pwm_duty_cycle_get(); + mutex_get(&balancing_mutex); + /* Balance all cells above the threshold, using the sorted ocv map values but * preserve the indexes*/ for (size_t chip = 0; chip < NUM_CHIPS; chip++) { @@ -141,4 +147,6 @@ void handle_balance_cells(analyzer_t *analyzer, acc_data_t *acc_data) } } } + + mutex_put(&balancing_mutex); } diff --git a/Core/Src/segment.c b/Core/Src/segment.c index 99212e4b..e8ff4360 100644 --- a/Core/Src/segment.c +++ b/Core/Src/segment.c @@ -13,6 +13,7 @@ #include "state_machine.h" #include "app_threadx.h" #include "main.h" +#include "shep_mutexes.h" /** * @brief Initialize a chip with our default values. @@ -461,9 +462,11 @@ void vGetSegmentData(ULONG thread_input) segment_mute(acc_data->chips, &hspi2); segment_set_dcto(acc_data->chips, TIME_1MIN_OR_0_26HR, &hspi2); + mutex_get(&balancing_mutex); segment_configure_balancing(acc_data->chips, acc_data->discharge_config, &hspi2); + mutex_put(&balancing_mutex); read_pwm_registers(acc_data->chips, &hspi2); segment_unmute(acc_data->chips, &hspi2); start_timer(&pwm_timer, pwm_update_frequency); diff --git a/Core/Src/shep_mutexes.c b/Core/Src/shep_mutexes.c index 8cb293c3..1db5ee2f 100644 --- a/Core/Src/shep_mutexes.c +++ b/Core/Src/shep_mutexes.c @@ -27,6 +27,11 @@ mutex_t shutdown_mutex = { .priority_inherit = TX_INHERIT /* Priority inheritance setting. */ }; +mutex_t balancing_mutex = { + .name = "Balancing Mutex", /* Name of the mutex. */ + .priority_inherit = TX_INHERIT /* Priority inheritance setting. */ +}; + /* Initializes all ThreadX mutexes. * Calls to _create_mutex() should go in here @@ -39,6 +44,7 @@ uint8_t mutexes_init() CATCH_ERROR(create_mutex(&bms_algos_mutex), U_SUCCESS); CATCH_ERROR(create_mutex(&peripherals_mutex), U_SUCCESS); CATCH_ERROR(create_mutex(&shutdown_mutex), U_SUCCESS); + CATCH_ERROR(create_mutex(&balancing_mutex), U_SUCCESS); // add more as necessary. From c2be4c842ed907720a8183e2fabf81dbc2c856e5 Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Thu, 16 Jul 2026 16:58:51 -0400 Subject: [PATCH 35/58] Cleanup charging check --- Core/Src/state_machine.c | 153 +++++++++++++++++++++------------------ 1 file changed, 81 insertions(+), 72 deletions(-) diff --git a/Core/Src/state_machine.c b/Core/Src/state_machine.c index a1ba27bb..9dfc77c4 100644 --- a/Core/Src/state_machine.c +++ b/Core/Src/state_machine.c @@ -19,6 +19,10 @@ static fault_eval_t fault_eval_table[NUM_FAULTS]; static _Atomic uint32_t severity_mask = 0; static _Atomic uint32_t fault_flags = 0; +#define LONG_CHARGE_DURATION_MS (15U * 60U * 1000U) +#define SHORT_CHARGE_DURATION_MS (20U * 1000U) +#define CHARGE_SETTLE_DURATION_MS (60U * 1000U) + const bool valid_transition_from_to[NUM_STATES][NUM_STATES] = { /* BOOT, READY, CHARGING, FAULTED */ { true, true, true, true }, /* BOOT */ @@ -32,6 +36,9 @@ void update_eval_table(state_machine_args_t *state_machine_args); static bool is_open_wire_fault_active(const analyzer_t *analyzer); +static void set_charging_stage(state_machine_t *state_machine, + charge_stage_t stage); + void request_transition(state_machine_args_t *state_machine_args, state_t next_state); @@ -45,6 +52,31 @@ const HandlerFunction_t handler_LUT[NUM_STATES] = { &handle_boot, &handle_ready, &handle_charging, &handle_faulted }; +static void set_charging_stage(state_machine_t *state_machine, + charge_stage_t stage) +{ + state_machine->charging_stage = stage; + + switch (stage) { + case LONG_CHARGE_UP: + start_timer(&state_machine->charging_stage_timer, + LONG_CHARGE_DURATION_MS); + break; + case SHORT_CHARGE_UP: + start_timer(&state_machine->charging_stage_timer, + SHORT_CHARGE_DURATION_MS); + break; + case LONG_SETTLE: + case SHORT_SETTLE: + start_timer(&state_machine->charging_stage_timer, + CHARGE_SETTLE_DURATION_MS); + break; + case DONE: + case FAULT: + break; + } +} + void init_boot(state_machine_args_t *state_machine_args) { cancel_timer(&state_machine_args->state_machine->charger_message_timer); @@ -83,9 +115,7 @@ void handle_ready(state_machine_args_t *state_machine_args) void init_charging(state_machine_args_t *state_machine_args) { - state_machine_args->state_machine->charging_stage = LONG_CHARGE_UP; - start_timer(&state_machine_args->state_machine->charging_stage_timer, - 15 * 60 * 1000); // 15 minutes + set_charging_stage(state_machine_args->state_machine, LONG_CHARGE_UP); send_max_dc_current_command(0); send_max_dc_brake_current_command(0); @@ -328,94 +358,75 @@ fault_state_t sm_fault_eval(fault_eval_t *item, fault_code_t fault_code) } /* This charging algorithm has 3 stages -* 1. Charge up until the high cell non OCV max voltage is > 4.19, pause for 1 minute every 15 minutes, repeat -* 2. Charge up until the high cell OCV max voltage is > 4.19, pause for 1 minute every 20 seconds, repeat -* 3. Stop charging :) -*/ + * 1. Charge for up to 15 minutes, pause for 1 minute, and repeat. + * 2. Once loaded max cell voltage reaches 4.19 V, charge for up to 20 seconds, + * pause for 1 minute, and repeat while settled max OCV is below 4.19 V. + * 3. Stop charging when settled max OCV reaches 4.19 V. + */ bool sm_charging_check(state_machine_args_t *state_machine_args) { state_machine_t *state_machine = state_machine_args->state_machine; analyzer_t *analyzer = state_machine_args->analyzer; - - nertimer_t *state_timer = - &state_machine_args->state_machine->charging_stage_timer; - + nertimer_t *state_timer = &state_machine->charging_stage_timer; charge_stage_t next_stage = state_machine->charging_stage; + bool charging_allowed = false; + float max_ocv = 0.0f; + float max_voltage = 0.0f; - // TODO: MUTEX GET - if (analyzer->max_ocv.val > MAX_CHARGE_VOLT_FLT || - analyzer->max_voltage.val > MAX_CHARGE_VOLT_FLT) { - state_machine->charging_stage = FAULT; - PRINTLN_INFO("Max OCV: %f", analyzer->max_ocv.val); - PRINTLN_INFO("Max Volts: %f", analyzer->max_voltage.val); - return false; - } + mutex_get(&analyzer_mutex); + max_ocv = analyzer->max_ocv.val; + max_voltage = analyzer->max_voltage.val; + mutex_put(&analyzer_mutex); - switch (state_machine->charging_stage) { - case LONG_CHARGE_UP: - if (analyzer->max_voltage.val > MAX_CHARGE_VOLT || - is_timer_expired(state_timer)) { - next_stage = LONG_SETTLE; - } - break; - case LONG_SETTLE: - if (is_timer_expired(state_timer)) { - next_stage = SHORT_CHARGE_UP; - } - break; - case SHORT_CHARGE_UP: - if (analyzer->max_ocv.val > MAX_CHARGE_VOLT || - is_timer_expired(state_timer)) { - next_stage = SHORT_SETTLE; - } - break; - case SHORT_SETTLE: - if (is_timer_expired(state_timer)) { - if (analyzer->max_ocv.val < MAX_CHARGE_VOLT) { + if (max_ocv >= MAX_CHARGE_VOLT_FLT || + max_voltage >= MAX_CHARGE_VOLT_FLT) { + set_charging_stage(state_machine, FAULT); + PRINTLN_INFO("Max OCV: %f", (double)max_ocv); + PRINTLN_INFO("Max Volts: %f", (double)max_voltage); + } else { + switch (state_machine->charging_stage) { + case LONG_CHARGE_UP: + if (max_voltage >= MAX_CHARGE_VOLT) { + next_stage = SHORT_SETTLE; + } else if (is_timer_expired(state_timer)) { + next_stage = LONG_SETTLE; + } + break; + case LONG_SETTLE: + if (is_timer_expired(state_timer)) { next_stage = - SHORT_CHARGE_UP; // continue charging - } else { - next_stage = DONE; + (max_ocv >= MAX_CHARGE_VOLT) ? + DONE : LONG_CHARGE_UP; } - } - break; - case DONE: - return false; // done charging - case FAULT: - return false; // stuck faulting until restart - } - // TODO: MUTEX RELEASE - - // Transitioning stages, start the corresponding timer lengths - if (next_stage != state_machine->charging_stage) { - switch (next_stage) { - case LONG_CHARGE_UP: - start_timer(state_timer, - 15 * 60 * 1000); // 15 minutes break; case SHORT_CHARGE_UP: - start_timer(state_timer, - 20 * 1000); // 20 seconds + if (max_voltage >= MAX_CHARGE_VOLT || + is_timer_expired(state_timer)) { + next_stage = SHORT_SETTLE; + } break; - - case LONG_SETTLE: case SHORT_SETTLE: - start_timer(state_timer, 60 * 1000); // 1 minute + if (is_timer_expired(state_timer)) { + next_stage = + (max_ocv >= MAX_CHARGE_VOLT) ? + DONE : SHORT_CHARGE_UP; + } break; - - // cases return earlier or arent possible case DONE: case FAULT: break; } - state_machine->charging_stage = next_stage; + if (next_stage != state_machine->charging_stage) { + set_charging_stage(state_machine, next_stage); + } + + charging_allowed = + state_machine->charging_stage == LONG_CHARGE_UP || + state_machine->charging_stage == SHORT_CHARGE_UP; } - /* if not charging stage, dont charge - * (LONG_SETTLE, SHORT_SETTLE, DONE, FAULT) */ - return state_machine->charging_stage == LONG_CHARGE_UP || - state_machine->charging_stage == SHORT_CHARGE_UP; + return charging_allowed; } // check if balancing is allowed @@ -434,9 +445,7 @@ bool sm_balancing_check(state_machine_args_t *state_machine_args) delta_voltage = analyzer->delta_voltage; mutex_put(&analyzer_mutex); - mutex_get(&state_mutex); charging_stage = state_machine->charging_stage; - mutex_put(&state_mutex); if ((max_voltage <= BAL_MIN_V) || (delta_voltage <= MAX_DELTA_V) || (charging_stage == LONG_SETTLE) || From 31ff3fec7588193e3bb144c0691c37d6ef54783f Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Thu, 16 Jul 2026 16:59:23 -0400 Subject: [PATCH 36/58] Add sm and charging algo unit tests --- Tests/Src/test_charging.c | 156 ++++++++++++++++++++++++++++++++++ Tests/Src/test_statemachine.c | 103 +++++++++++++++++++++- Tests/ner_test.conf | 16 +++- 3 files changed, 273 insertions(+), 2 deletions(-) create mode 100644 Tests/Src/test_charging.c diff --git a/Tests/Src/test_charging.c b/Tests/Src/test_charging.c new file mode 100644 index 00000000..782a38e2 --- /dev/null +++ b/Tests/Src/test_charging.c @@ -0,0 +1,156 @@ +#include "unity.h" + +#include + +#include "charging.h" +#include "mock_u_tx_mutex.h" + +static analyzer_t analyzer; +static acc_data_t acc_data; + +chipdata_t *get_chip_data(analyzer_t *analyzer_ptr, uint8_t chip) +{ + return &analyzer_ptr->chip_data[chip]; +} + +static void set_all_ocv(float voltage) +{ + for (uint8_t chip = 0U; chip < NUM_CHIPS; chip++) { + for (uint8_t cell = 0U; cell < NUM_CELLS_PER_CHIP; cell++) { + analyzer.chip_data[chip].open_cell_voltage[cell] = + voltage; + } + } +} + +void setUp(void) +{ + memset(&analyzer, 0, sizeof(analyzer)); + memset(&acc_data, 0, sizeof(acc_data)); + + analyzer.min_ocv.val = 3.5f; + set_all_ocv(analyzer.min_ocv.val); + pwm_duty_cycle_set(50U); + + mutex_get_IgnoreAndReturn(0U); + mutex_put_IgnoreAndReturn(0U); +} + +void tearDown(void) +{ +} + +void test_pwm_duty_cycle(void) +{ + static const struct { + uint8_t min; + uint8_t max; + PWM_DUTY duty_cycle; + } ranges[] = { + { 0U, 0U, PWM_0_0_PCT }, + { 1U, 6U, PWM_6_6_PCT }, + { 7U, 13U, PWM_13_2_PCT }, + { 14U, 19U, PWM_19_8_PCT }, + { 20U, 26U, PWM_26_4_PCT }, + { 27U, 33U, PWM_33_0_PCT }, + { 34U, 39U, PWM_39_6_PCT }, + { 40U, 46U, PWM_46_2_PCT }, + { 47U, 52U, PWM_52_8_PCT }, + { 53U, 59U, PWM_59_4_PCT }, + { 60U, 66U, PWM_66_0_PCT }, + { 67U, 72U, PWM_72_6_PCT }, + { 73U, 79U, PWM_79_2_PCT }, + { 80U, 85U, PWM_85_8_PCT }, + { 86U, 92U, PWM_92_4_PCT }, + { 93U, 100U, PWM_100_0_PCT }, + }; + + for (size_t range = 0U; range < sizeof(ranges) / sizeof(ranges[0]); + range++) { + for (uint16_t request = ranges[range].min; + request <= ranges[range].max; request++) { + pwm_duty_cycle_set((uint8_t)request); + TEST_ASSERT_EQUAL(ranges[range].duty_cycle, + pwm_duty_cycle_get()); + } + } + + for (uint16_t request = 101U; request <= UINT8_MAX; request++) { + pwm_duty_cycle_set((uint8_t)request); + TEST_ASSERT_EQUAL(PWM_0_0_PCT, pwm_duty_cycle_get()); + } +} + +void test_balance_threshold(void) +{ + const float threshold = analyzer.min_ocv.val + 0.02f; + + analyzer.chip_data[0].open_cell_voltage[3] = threshold + 0.001f; + analyzer.chip_data[0].open_cell_voltage[4] = threshold; + + handle_balance_cells(&analyzer, &acc_data); + + TEST_ASSERT_EQUAL(PWM_52_8_PCT, acc_data.discharge_config[0][3]); + TEST_ASSERT_EQUAL(PWM_0_0_PCT, acc_data.discharge_config[0][4]); +} + +void test_balance_limit(void) +{ + for (uint8_t cell = 0U; cell < 8U; cell++) { + analyzer.chip_data[0].open_cell_voltage[cell] = + 3.53f + (0.01f * cell); + } + + handle_balance_cells(&analyzer, &acc_data); + + TEST_ASSERT_EQUAL(PWM_0_0_PCT, acc_data.discharge_config[0][0]); + for (uint8_t cell = 1U; cell < 8U; cell++) { + TEST_ASSERT_EQUAL(PWM_52_8_PCT, + acc_data.discharge_config[0][cell]); + } +} + +void test_balance_indexes(void) +{ + analyzer.chip_data[0].open_cell_voltage[2] = 3.60f; + analyzer.chip_data[0].open_cell_voltage[7] = 3.70f; + analyzer.chip_data[0].open_cell_voltage[12] = 3.80f; + + handle_balance_cells(&analyzer, &acc_data); + + TEST_ASSERT_EQUAL(PWM_52_8_PCT, acc_data.discharge_config[0][2]); + TEST_ASSERT_EQUAL(PWM_52_8_PCT, acc_data.discharge_config[0][7]); + TEST_ASSERT_EQUAL(PWM_52_8_PCT, acc_data.discharge_config[0][12]); + TEST_ASSERT_EQUAL(PWM_0_0_PCT, acc_data.discharge_config[0][0]); +} + +void test_balance_clear(void) +{ + for (uint8_t chip = 0U; chip < NUM_CHIPS; chip++) { + for (uint8_t cell = 0U; cell < NUM_CELLS_PER_CHIP; cell++) { + acc_data.discharge_config[chip][cell] = PWM_100_0_PCT; + } + } + + handle_balance_cells(&analyzer, &acc_data); + + for (uint8_t chip = 0U; chip < NUM_CHIPS; chip++) { + for (uint8_t cell = 0U; cell < NUM_CELLS_PER_CHIP; cell++) { + TEST_ASSERT_EQUAL(PWM_0_0_PCT, + acc_data.discharge_config[chip][cell]); + } + } +} + +int main(void) +{ + UNITY_BEGIN(); + + RUN_TEST(test_pwm_duty_cycle); + RUN_TEST(test_balance_threshold); + RUN_TEST(test_balance_limit); + RUN_TEST(test_balance_indexes); + RUN_TEST(test_balance_clear); + + return UNITY_END(); +} diff --git a/Tests/Src/test_statemachine.c b/Tests/Src/test_statemachine.c index 7f783244..5c7472e1 100644 --- a/Tests/Src/test_statemachine.c +++ b/Tests/Src/test_statemachine.c @@ -12,17 +12,23 @@ hv_plate_t hv_plate; acc_data_t acc_data; bms_algos_t bms_algos; sanitizer_t sanitizer; +peripherals_t peripherals; state_machine_args_t args = { .state_machine = &state_machine, .analyzer = &analyzer, .hv_plate = &hv_plate, .acc_data = &acc_data, .bms_algos = &bms_algos, - .sanitizer = &sanitizer }; + .sanitizer = &sanitizer, + .peripherals = &peripherals }; void setUp(void) { analyzer.min_ocv.val = 2.6; + analyzer.max_ocv.val = 4.0f; + analyzer.max_voltage.val = 4.0f; + analyzer.delta_voltage = MAX_DELTA_V + 0.01f; + peripherals.shutdown_active = true; mutex_get_IgnoreAndReturn(0); mutex_put_IgnoreAndReturn(0); cancel_timer_Expect(&state_machine.charger_message_timer); @@ -72,12 +78,107 @@ void test_eval_table(void) TEST_ASSERT_FALSE(get_fault(DISCHARGE_LIMIT_ENFORCEMENT_FAULT)); } +void test_long_charge_cycle(void) +{ + state_machine.charging_stage = LONG_CHARGE_UP; + is_timer_expired_ExpectAndReturn(&state_machine.charging_stage_timer, + true); + start_timer_Expect(&state_machine.charging_stage_timer, 60U * 1000U); + + TEST_ASSERT_FALSE(sm_charging_check(&args)); + TEST_ASSERT_EQUAL(LONG_SETTLE, state_machine.charging_stage); + + is_timer_expired_ExpectAndReturn(&state_machine.charging_stage_timer, + true); + start_timer_Expect(&state_machine.charging_stage_timer, + 15U * 60U * 1000U); + + TEST_ASSERT_TRUE(sm_charging_check(&args)); + TEST_ASSERT_EQUAL(LONG_CHARGE_UP, state_machine.charging_stage); +} + +void test_short_charge_cycle(void) +{ + state_machine.charging_stage = LONG_CHARGE_UP; + analyzer.max_voltage.val = MAX_CHARGE_VOLT; + start_timer_Expect(&state_machine.charging_stage_timer, 60U * 1000U); + + TEST_ASSERT_FALSE(sm_charging_check(&args)); + TEST_ASSERT_EQUAL(SHORT_SETTLE, state_machine.charging_stage); + + analyzer.max_voltage.val = 4.0f; + analyzer.max_ocv.val = MAX_CHARGE_VOLT - 0.01f; + is_timer_expired_ExpectAndReturn(&state_machine.charging_stage_timer, + true); + start_timer_Expect(&state_machine.charging_stage_timer, 20U * 1000U); + + TEST_ASSERT_TRUE(sm_charging_check(&args)); + TEST_ASSERT_EQUAL(SHORT_CHARGE_UP, state_machine.charging_stage); +} + +void test_charge_done(void) +{ + state_machine.charging_stage = SHORT_SETTLE; + analyzer.max_ocv.val = MAX_CHARGE_VOLT; + is_timer_expired_ExpectAndReturn(&state_machine.charging_stage_timer, + true); + + TEST_ASSERT_FALSE(sm_charging_check(&args)); + TEST_ASSERT_EQUAL(DONE, state_machine.charging_stage); +} + +void test_charge_fault(void) +{ + state_machine.bms_state = CHARGING; + state_machine.charging_stage = LONG_CHARGE_UP; + analyzer.max_voltage.val = MAX_CHARGE_VOLT_FLT; + + TEST_ASSERT_FALSE(sm_charging_check(&args)); + TEST_ASSERT_EQUAL(FAULT, state_machine.charging_stage); + TEST_ASSERT_EQUAL(CHARGING, state_machine.bms_state); +} + +void test_balancing(void) +{ + // Balancing allowed + state_machine.charging_stage = LONG_CHARGE_UP; + TEST_ASSERT_TRUE(sm_balancing_check(&args)); + + // Long settle + state_machine.charging_stage = LONG_SETTLE; + TEST_ASSERT_FALSE(sm_balancing_check(&args)); + + // Short settle + state_machine.charging_stage = SHORT_SETTLE; + TEST_ASSERT_FALSE(sm_balancing_check(&args)); + + // Low voltage + state_machine.charging_stage = LONG_CHARGE_UP; + analyzer.max_voltage.val = BAL_MIN_V; + TEST_ASSERT_FALSE(sm_balancing_check(&args)); + analyzer.max_voltage.val = 4.0f; + + // Low voltage delta + analyzer.delta_voltage = MAX_DELTA_V; + TEST_ASSERT_FALSE(sm_balancing_check(&args)); + analyzer.delta_voltage = MAX_DELTA_V + 0.01f; + + // Shutdown inactive + peripherals.shutdown_active = false; + TEST_ASSERT_FALSE(sm_balancing_check(&args)); +} + int main(void) { UNITY_BEGIN(); RUN_TEST(test_initial_state); RUN_TEST(test_eval_table); + RUN_TEST(test_long_charge_cycle); + RUN_TEST(test_short_charge_cycle); + RUN_TEST(test_charge_done); + RUN_TEST(test_charge_fault); + RUN_TEST(test_balancing); return UNITY_END(); } diff --git a/Tests/ner_test.conf b/Tests/ner_test.conf index f46ed3a8..7515dce4 100644 --- a/Tests/ner_test.conf +++ b/Tests/ner_test.conf @@ -67,6 +67,16 @@ sources = [ "Core/Src/shep_mutexes.c" ] +[test-packages.charging] +mocked-files = [ + "Drivers/Embedded-Base/threadX/inc/u_tx_mutex.h" +] + +sources = [ + "Core/Src/charging.c", + "Core/Src/shep_mutexes.c" +] + [test-packages.statemachine] mocked-files = [ "Core/Inc/compute.h", @@ -101,6 +111,10 @@ test-file = "Tests/Src/test_soc.c" test-package = "cell_temp_sanitizer" test-file = "Tests/Src/test_cell_temp_sanitizer.c" +[tests.charging] +test-package = "charging" +test-file = "Tests/Src/test_charging.c" + [tests.statemachine] test-package = "statemachine" -test-file = "Tests/Src/test_statemachine.c" \ No newline at end of file +test-file = "Tests/Src/test_statemachine.c" From 7fadc68c0d2cb3c07c07aa4b6a594adaa7755509 Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Thu, 16 Jul 2026 17:17:17 -0400 Subject: [PATCH 37/58] can codegen fix --- Core/Inc/can_messages_rx.h | 1 - Core/Src/can_messages_rx.c | 41 ++++++++++++++++++-------------------- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/Core/Inc/can_messages_rx.h b/Core/Inc/can_messages_rx.h index 090bd881..f19b992d 100644 --- a/Core/Inc/can_messages_rx.h +++ b/Core/Inc/can_messages_rx.h @@ -427,7 +427,6 @@ typedef struct { bool RTDS_FAULT; bool LV_LOW_VOLTAGE_FAULT; bool PRECHARGE_FLOATING_FAULT; - bool LATCHING_ACTIVE_FAULT; } faults_t; void receive_faults(const can_msg_t *message, faults_t *faults); diff --git a/Core/Src/can_messages_rx.c b/Core/Src/can_messages_rx.c index 28f03044..ad4c748f 100644 --- a/Core/Src/can_messages_rx.c +++ b/Core/Src/can_messages_rx.c @@ -882,60 +882,57 @@ void receive_imu_gyro(const can_msg_t *message, imu_gyro_t *imu_gyro) { void receive_faults(const can_msg_t *message, faults_t *faults) { - uint32_t data_bigendian; - memcpy(&data_bigendian, message->data, 4); - uint32_t data = __builtin_bswap32(data_bigendian); + uint16_t data_bigendian; + memcpy(&data_bigendian, message->data, 2); + uint16_t data = __builtin_bswap16(data_bigendian); uint64_t CAN_OUTGOING_FAULT_mask = (1ULL << 1) - 1ULL; - uint64_t CAN_OUTGOING_FAULT_raw = (data >> 31) & CAN_OUTGOING_FAULT_mask; + uint64_t CAN_OUTGOING_FAULT_raw = (data >> 15) & CAN_OUTGOING_FAULT_mask; faults->CAN_OUTGOING_FAULT = (bool)CAN_OUTGOING_FAULT_raw; uint64_t CAN_INCOMING_FAULT_mask = (1ULL << 1) - 1ULL; - uint64_t CAN_INCOMING_FAULT_raw = (data >> 30) & CAN_INCOMING_FAULT_mask; + uint64_t CAN_INCOMING_FAULT_raw = (data >> 14) & CAN_INCOMING_FAULT_mask; faults->CAN_INCOMING_FAULT = (bool)CAN_INCOMING_FAULT_raw; uint64_t BMS_CAN_MONITOR_FAULT_mask = (1ULL << 1) - 1ULL; - uint64_t BMS_CAN_MONITOR_FAULT_raw = (data >> 29) & BMS_CAN_MONITOR_FAULT_mask; + uint64_t BMS_CAN_MONITOR_FAULT_raw = (data >> 13) & BMS_CAN_MONITOR_FAULT_mask; faults->BMS_CAN_MONITOR_FAULT = (bool)BMS_CAN_MONITOR_FAULT_raw; uint64_t LIGHTNING_CAN_MONITOR_FAULT_mask = (1ULL << 1) - 1ULL; - uint64_t LIGHTNING_CAN_MONITOR_FAULT_raw = (data >> 28) & LIGHTNING_CAN_MONITOR_FAULT_mask; + uint64_t LIGHTNING_CAN_MONITOR_FAULT_raw = (data >> 12) & LIGHTNING_CAN_MONITOR_FAULT_mask; faults->LIGHTNING_CAN_MONITOR_FAULT = (bool)LIGHTNING_CAN_MONITOR_FAULT_raw; uint64_t ONBOARD_TEMP_FAULT_mask = (1ULL << 1) - 1ULL; - uint64_t ONBOARD_TEMP_FAULT_raw = (data >> 27) & ONBOARD_TEMP_FAULT_mask; + uint64_t ONBOARD_TEMP_FAULT_raw = (data >> 11) & ONBOARD_TEMP_FAULT_mask; faults->ONBOARD_TEMP_FAULT = (bool)ONBOARD_TEMP_FAULT_raw; uint64_t IMU_ACCEL_FAULT_mask = (1ULL << 1) - 1ULL; - uint64_t IMU_ACCEL_FAULT_raw = (data >> 26) & IMU_ACCEL_FAULT_mask; + uint64_t IMU_ACCEL_FAULT_raw = (data >> 10) & IMU_ACCEL_FAULT_mask; faults->IMU_ACCEL_FAULT = (bool)IMU_ACCEL_FAULT_raw; uint64_t IMU_GYRO_FAULT_mask = (1ULL << 1) - 1ULL; - uint64_t IMU_GYRO_FAULT_raw = (data >> 25) & IMU_GYRO_FAULT_mask; + uint64_t IMU_GYRO_FAULT_raw = (data >> 9) & IMU_GYRO_FAULT_mask; faults->IMU_GYRO_FAULT = (bool)IMU_GYRO_FAULT_raw; uint64_t BSPD_PREFAULT_mask = (1ULL << 1) - 1ULL; - uint64_t BSPD_PREFAULT_raw = (data >> 24) & BSPD_PREFAULT_mask; + uint64_t BSPD_PREFAULT_raw = (data >> 8) & BSPD_PREFAULT_mask; faults->BSPD_PREFAULT = (bool)BSPD_PREFAULT_raw; uint64_t ONBOARD_BRAKE_OPEN_CIRCUIT_FAULT_mask = (1ULL << 1) - 1ULL; - uint64_t ONBOARD_BRAKE_OPEN_CIRCUIT_FAULT_raw = (data >> 23) & ONBOARD_BRAKE_OPEN_CIRCUIT_FAULT_mask; + uint64_t ONBOARD_BRAKE_OPEN_CIRCUIT_FAULT_raw = (data >> 7) & ONBOARD_BRAKE_OPEN_CIRCUIT_FAULT_mask; faults->ONBOARD_BRAKE_OPEN_CIRCUIT_FAULT = (bool)ONBOARD_BRAKE_OPEN_CIRCUIT_FAULT_raw; uint64_t ONBOARD_ACCEL_OPEN_CIRCUIT_FAULT_mask = (1ULL << 1) - 1ULL; - uint64_t ONBOARD_ACCEL_OPEN_CIRCUIT_FAULT_raw = (data >> 22) & ONBOARD_ACCEL_OPEN_CIRCUIT_FAULT_mask; + uint64_t ONBOARD_ACCEL_OPEN_CIRCUIT_FAULT_raw = (data >> 6) & ONBOARD_ACCEL_OPEN_CIRCUIT_FAULT_mask; faults->ONBOARD_ACCEL_OPEN_CIRCUIT_FAULT = (bool)ONBOARD_ACCEL_OPEN_CIRCUIT_FAULT_raw; uint64_t ONBOARD_BRAKE_SHORT_CIRCUIT_FAULT_mask = (1ULL << 1) - 1ULL; - uint64_t ONBOARD_BRAKE_SHORT_CIRCUIT_FAULT_raw = (data >> 21) & ONBOARD_BRAKE_SHORT_CIRCUIT_FAULT_mask; + uint64_t ONBOARD_BRAKE_SHORT_CIRCUIT_FAULT_raw = (data >> 5) & ONBOARD_BRAKE_SHORT_CIRCUIT_FAULT_mask; faults->ONBOARD_BRAKE_SHORT_CIRCUIT_FAULT = (bool)ONBOARD_BRAKE_SHORT_CIRCUIT_FAULT_raw; uint64_t ONBOARD_ACCEL_SHORT_CIRCUIT_FAULT_mask = (1ULL << 1) - 1ULL; - uint64_t ONBOARD_ACCEL_SHORT_CIRCUIT_FAULT_raw = (data >> 20) & ONBOARD_ACCEL_SHORT_CIRCUIT_FAULT_mask; + uint64_t ONBOARD_ACCEL_SHORT_CIRCUIT_FAULT_raw = (data >> 4) & ONBOARD_ACCEL_SHORT_CIRCUIT_FAULT_mask; faults->ONBOARD_ACCEL_SHORT_CIRCUIT_FAULT = (bool)ONBOARD_ACCEL_SHORT_CIRCUIT_FAULT_raw; uint64_t ONBOARD_PEDAL_DIFFERENCE_FAULT_mask = (1ULL << 1) - 1ULL; - uint64_t ONBOARD_PEDAL_DIFFERENCE_FAULT_raw = (data >> 19) & ONBOARD_PEDAL_DIFFERENCE_FAULT_mask; + uint64_t ONBOARD_PEDAL_DIFFERENCE_FAULT_raw = (data >> 3) & ONBOARD_PEDAL_DIFFERENCE_FAULT_mask; faults->ONBOARD_PEDAL_DIFFERENCE_FAULT = (bool)ONBOARD_PEDAL_DIFFERENCE_FAULT_raw; uint64_t RTDS_FAULT_mask = (1ULL << 1) - 1ULL; - uint64_t RTDS_FAULT_raw = (data >> 18) & RTDS_FAULT_mask; + uint64_t RTDS_FAULT_raw = (data >> 2) & RTDS_FAULT_mask; faults->RTDS_FAULT = (bool)RTDS_FAULT_raw; uint64_t LV_LOW_VOLTAGE_FAULT_mask = (1ULL << 1) - 1ULL; - uint64_t LV_LOW_VOLTAGE_FAULT_raw = (data >> 17) & LV_LOW_VOLTAGE_FAULT_mask; + uint64_t LV_LOW_VOLTAGE_FAULT_raw = (data >> 1) & LV_LOW_VOLTAGE_FAULT_mask; faults->LV_LOW_VOLTAGE_FAULT = (bool)LV_LOW_VOLTAGE_FAULT_raw; uint64_t PRECHARGE_FLOATING_FAULT_mask = (1ULL << 1) - 1ULL; - uint64_t PRECHARGE_FLOATING_FAULT_raw = (data >> 16) & PRECHARGE_FLOATING_FAULT_mask; + uint64_t PRECHARGE_FLOATING_FAULT_raw = (data >> 0) & PRECHARGE_FLOATING_FAULT_mask; faults->PRECHARGE_FLOATING_FAULT = (bool)PRECHARGE_FLOATING_FAULT_raw; - uint64_t LATCHING_ACTIVE_FAULT_mask = (1ULL << 1) - 1ULL; - uint64_t LATCHING_ACTIVE_FAULT_raw = (data >> 15) & LATCHING_ACTIVE_FAULT_mask; - faults->LATCHING_ACTIVE_FAULT = (bool)LATCHING_ACTIVE_FAULT_raw; } void receive_lv_voltage(const can_msg_t *message, lv_voltage_t *lv_voltage) { From ed23df9011f19b438670b3bb69eff49b33a806e1 Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Thu, 16 Jul 2026 20:03:26 -0400 Subject: [PATCH 38/58] Send balancing PWM duty cycle feedback --- Core/Inc/can_messages_tx.h | 15 +++++++++++---- Core/Inc/charging.h | 7 +++++++ Core/Src/can_handler.c | 3 +++ Core/Src/can_messages_tx.c | 19 +++++++++++++++++++ Core/Src/charging.c | 4 ++++ Drivers/Odyssey-Definitions | 2 +- 6 files changed, 45 insertions(+), 5 deletions(-) diff --git a/Core/Inc/can_messages_tx.h b/Core/Inc/can_messages_tx.h index d0feea96..208c2220 100644 --- a/Core/Inc/can_messages_tx.h +++ b/Core/Inc/can_messages_tx.h @@ -196,16 +196,16 @@ uint8_t send_beta_cell_data_debug /** * Contents of this message: -* BMS/PerCell/Alpha/{3}/SADCVolts/{4} - S ADC cell voltage -* BMS/PerCell/Alpha/{3}/SADCVolts/{5} - S ADC cell voltage +* BMS/PerCell/Alpha/{3}/S_Volts/{4} - S ADC cell voltage +* BMS/PerCell/Alpha/{3}/S_Volts/{5} - S ADC cell voltage */ uint8_t send_alpha_cell_s_adc_data (float s_voltage_a,float s_voltage_b,uint8_t chip_id,uint8_t cell_a,uint8_t cell_b); /** * Contents of this message: -* BMS/PerCell/Beta/{3}/SADCVolts/{4} - S ADC cell voltage -* BMS/PerCell/Beta/{3}/SADCVolts/{5} - S ADC cell voltage +* BMS/PerCell/Beta/{3}/S_Volts/{4} - S ADC cell voltage +* BMS/PerCell/Beta/{3}/S_Volts/{5} - S ADC cell voltage */ uint8_t send_beta_cell_s_adc_data (float s_voltage_a,float s_voltage_b,uint8_t chip_id,uint8_t cell_a,uint8_t cell_b); @@ -423,6 +423,13 @@ uint8_t send_hv_plate_voltages_adbms uint8_t send_pack_current_and_shunt_temp_adbms (float pack_current,float shunt_temp); +/** +* Contents of this message: +* BMS/Control/CellBalancing/PWMDutyCycle - Current cell balancing PWM duty cycle +*/ +uint8_t send_current_cell_balancing_pwm_duty_cycle +(uint8_t balancing_pwm_duty_cycle); + /** * Contents of this message: * BMS/Commands/Max_AC_Current_Target - This value determines the maximum allowable drive current on the AC side diff --git a/Core/Inc/charging.h b/Core/Inc/charging.h index d9b1c3f8..73652e0b 100644 --- a/Core/Inc/charging.h +++ b/Core/Inc/charging.h @@ -12,6 +12,13 @@ */ void pwm_duty_cycle_set(uint8_t duty_cycle_req_get); +/** + * @brief get the stored duty cycle + * + * @returns the stored duty cycle percentage + */ +uint8_t pwm_duty_cycle_setting_get(void); + /** * @brief get the duty cycle (atomic) * diff --git a/Core/Src/can_handler.c b/Core/Src/can_handler.c index f9c8d64c..d958b7d4 100644 --- a/Core/Src/can_handler.c +++ b/Core/Src/can_handler.c @@ -1,4 +1,5 @@ #include "can_handler.h" +#include "can_messages_tx.h" #include "control.h" #include "datastructs.h" #include "state_machine.h" @@ -175,6 +176,8 @@ void vCanReceive(ULONG thread_input) { break; case CALYPSO_PWM_BAL_CANID: pwm_duty_cycle_set(message.data[0]); + send_current_cell_balancing_pwm_duty_cycle( + pwm_duty_cycle_setting_get()); break; case DTI_INPUT_VOLTAGE_CANID: if (!state_machine->is_charger_connected) { diff --git a/Core/Src/can_messages_tx.c b/Core/Src/can_messages_tx.c index f91d982e..0b951217 100644 --- a/Core/Src/can_messages_tx.c +++ b/Core/Src/can_messages_tx.c @@ -1607,6 +1607,25 @@ uint8_t send_pack_current_and_shunt_temp_adbms return queue_send(&can_outgoing, &msg, TX_NO_WAIT); } +uint8_t send_current_cell_balancing_pwm_duty_cycle +(uint8_t balancing_pwm_duty_cycle) +{ + can_msg_t msg; + msg.id = 0x97; + msg.id_is_extended = false; + + uint8_t data = 0; + msg.len = 1; + uint32_t balancing_pwm_duty_cycle_i = (uint32_t)(balancing_pwm_duty_cycle); + if(balancing_pwm_duty_cycle_i > 255ULL) {balancing_pwm_duty_cycle_i = 255; + } + data |= ((balancing_pwm_duty_cycle_i) & 0xFFULL) << 0; + + msg.data[0] = data; + + return queue_send(&can_outgoing, &msg, TX_NO_WAIT); +} + uint8_t send_max_ac_current_command (float max_current_ac_target) { diff --git a/Core/Src/charging.c b/Core/Src/charging.c index af537ba0..1803ec5b 100644 --- a/Core/Src/charging.c +++ b/Core/Src/charging.c @@ -19,6 +19,10 @@ void pwm_duty_cycle_set(uint8_t duty_cycle_req_get) { duty_cycle_req = duty_cycle_req_get; } +uint8_t pwm_duty_cycle_setting_get(void) { + return duty_cycle_req; +} + PWM_DUTY pwm_duty_cycle_get() { switch (duty_cycle_req) { case 1 ... 6: diff --git a/Drivers/Odyssey-Definitions b/Drivers/Odyssey-Definitions index b3a32f16..142205ba 160000 --- a/Drivers/Odyssey-Definitions +++ b/Drivers/Odyssey-Definitions @@ -1 +1 @@ -Subproject commit b3a32f1621076a2169694d947f0668782f5d1301 +Subproject commit 142205ba3c277c0a93309d1ec6fe4c28b4800591 From b5d795065fdd14645d5091f8d7f4e79c84c26e81 Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Fri, 17 Jul 2026 00:17:08 -0400 Subject: [PATCH 39/58] Fixing mutexes --- Core/Inc/shep_mutexes.h | 1 - Core/Src/charging.c | 10 +--------- Core/Src/segment.c | 3 --- Core/Src/shep_mutexes.c | 7 ------- Core/Src/state_machine.c | 14 ++------------ 5 files changed, 3 insertions(+), 32 deletions(-) diff --git a/Core/Inc/shep_mutexes.h b/Core/Inc/shep_mutexes.h index e9846fa1..ca1980a0 100644 --- a/Core/Inc/shep_mutexes.h +++ b/Core/Inc/shep_mutexes.h @@ -13,7 +13,6 @@ extern mutex_t state_mutex; extern mutex_t bms_algos_mutex; extern mutex_t peripherals_mutex; extern mutex_t shutdown_mutex; -extern mutex_t balancing_mutex; // add more as necessary... /* API */ diff --git a/Core/Src/charging.c b/Core/Src/charging.c index 1803ec5b..f0098672 100644 --- a/Core/Src/charging.c +++ b/Core/Src/charging.c @@ -4,7 +4,6 @@ #include "bms_config.h" #include "c_utils.h" #include "analyzer.h" -#include "shep_mutexes.h" /// @brief A struct to hold the original float value and the index originally, /// as that holds meaning @@ -112,23 +111,18 @@ void handle_balance_cells(analyzer_t *analyzer, acc_data_t *acc_data) static const int MAX_BAL_CHIP = 7; // the low cell, eventually they all must get there - float low = 0.0f; + float low = analyzer->min_ocv.val; // the margin above the low cell to ignore, which is usually X% of the delta, gate at 0 //float min_thresh = //fmaxf(analyzer->delt_ocv * 0.4f, 0.0f); float min_thresh = 0.02f; val_idexed_t new_ocv_map[NUM_CHIPS][NUM_CELLS_PER_CHIP] = { 0 }; - mutex_get(&analyzer_mutex); - low = analyzer->min_ocv.val; // first, sort and cleanup everything chipsSelectionSort(analyzer, new_ocv_map); - mutex_put(&analyzer_mutex); PWM_DUTY duty_cycle = pwm_duty_cycle_get(); - mutex_get(&balancing_mutex); - /* Balance all cells above the threshold, using the sorted ocv map values but * preserve the indexes*/ for (size_t chip = 0; chip < NUM_CHIPS; chip++) { @@ -151,6 +145,4 @@ void handle_balance_cells(analyzer_t *analyzer, acc_data_t *acc_data) } } } - - mutex_put(&balancing_mutex); } diff --git a/Core/Src/segment.c b/Core/Src/segment.c index e8ff4360..99212e4b 100644 --- a/Core/Src/segment.c +++ b/Core/Src/segment.c @@ -13,7 +13,6 @@ #include "state_machine.h" #include "app_threadx.h" #include "main.h" -#include "shep_mutexes.h" /** * @brief Initialize a chip with our default values. @@ -462,11 +461,9 @@ void vGetSegmentData(ULONG thread_input) segment_mute(acc_data->chips, &hspi2); segment_set_dcto(acc_data->chips, TIME_1MIN_OR_0_26HR, &hspi2); - mutex_get(&balancing_mutex); segment_configure_balancing(acc_data->chips, acc_data->discharge_config, &hspi2); - mutex_put(&balancing_mutex); read_pwm_registers(acc_data->chips, &hspi2); segment_unmute(acc_data->chips, &hspi2); start_timer(&pwm_timer, pwm_update_frequency); diff --git a/Core/Src/shep_mutexes.c b/Core/Src/shep_mutexes.c index 1db5ee2f..e9ba4e81 100644 --- a/Core/Src/shep_mutexes.c +++ b/Core/Src/shep_mutexes.c @@ -27,12 +27,6 @@ mutex_t shutdown_mutex = { .priority_inherit = TX_INHERIT /* Priority inheritance setting. */ }; -mutex_t balancing_mutex = { - .name = "Balancing Mutex", /* Name of the mutex. */ - .priority_inherit = TX_INHERIT /* Priority inheritance setting. */ -}; - - /* Initializes all ThreadX mutexes. * Calls to _create_mutex() should go in here */ @@ -44,7 +38,6 @@ uint8_t mutexes_init() CATCH_ERROR(create_mutex(&bms_algos_mutex), U_SUCCESS); CATCH_ERROR(create_mutex(&peripherals_mutex), U_SUCCESS); CATCH_ERROR(create_mutex(&shutdown_mutex), U_SUCCESS); - CATCH_ERROR(create_mutex(&balancing_mutex), U_SUCCESS); // add more as necessary. diff --git a/Core/Src/state_machine.c b/Core/Src/state_machine.c index 9dfc77c4..16e37608 100644 --- a/Core/Src/state_machine.c +++ b/Core/Src/state_machine.c @@ -370,13 +370,8 @@ bool sm_charging_check(state_machine_args_t *state_machine_args) nertimer_t *state_timer = &state_machine->charging_stage_timer; charge_stage_t next_stage = state_machine->charging_stage; bool charging_allowed = false; - float max_ocv = 0.0f; - float max_voltage = 0.0f; - - mutex_get(&analyzer_mutex); - max_ocv = analyzer->max_ocv.val; - max_voltage = analyzer->max_voltage.val; - mutex_put(&analyzer_mutex); + float max_ocv = analyzer->max_ocv.val; + float max_voltage = analyzer->max_voltage.val; if (max_ocv >= MAX_CHARGE_VOLT_FLT || max_voltage >= MAX_CHARGE_VOLT_FLT) { @@ -506,8 +501,6 @@ static bool is_open_wire_fault_active(const analyzer_t *analyzer) { bool open_wire_fault_active = false; - mutex_get(&analyzer_mutex); - for (uint8_t chip = 0U; chip < NUM_CHIPS; chip++) { for (uint8_t cell = 0U; cell < NUM_CELLS_PER_CHIP; cell++) { if (analyzer->chip_data[chip].ow_fault[cell]) { @@ -515,9 +508,6 @@ static bool is_open_wire_fault_active(const analyzer_t *analyzer) } } } - - mutex_put(&analyzer_mutex); - return open_wire_fault_active; } From 4d634ee1f08c35c7253add23f20b6cd14164f657 Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Fri, 17 Jul 2026 00:27:53 -0400 Subject: [PATCH 40/58] Fix formatting --- Core/Src/shep_mutexes.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/Src/shep_mutexes.c b/Core/Src/shep_mutexes.c index e9ba4e81..8cb293c3 100644 --- a/Core/Src/shep_mutexes.c +++ b/Core/Src/shep_mutexes.c @@ -27,6 +27,7 @@ mutex_t shutdown_mutex = { .priority_inherit = TX_INHERIT /* Priority inheritance setting. */ }; + /* Initializes all ThreadX mutexes. * Calls to _create_mutex() should go in here */ From 0b6ab8d41c070763b2e4c1c110ccef338e84e111 Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Fri, 17 Jul 2026 00:32:54 -0400 Subject: [PATCH 41/58] Fix --- Core/Src/state_machine.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Core/Src/state_machine.c b/Core/Src/state_machine.c index 16e37608..0a3abe84 100644 --- a/Core/Src/state_machine.c +++ b/Core/Src/state_machine.c @@ -440,7 +440,9 @@ bool sm_balancing_check(state_machine_args_t *state_machine_args) delta_voltage = analyzer->delta_voltage; mutex_put(&analyzer_mutex); + mutex_get(&state_mutex); charging_stage = state_machine->charging_stage; + mutex_put(&state_mutex); if ((max_voltage <= BAL_MIN_V) || (delta_voltage <= MAX_DELTA_V) || (charging_stage == LONG_SETTLE) || From bf5b68a488e31373d6e4b4cabe190f17efc1bbd0 Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Fri, 17 Jul 2026 21:48:43 -0400 Subject: [PATCH 42/58] Update charging unit test --- Tests/Src/test_charging.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Tests/Src/test_charging.c b/Tests/Src/test_charging.c index 782a38e2..6e65db48 100644 --- a/Tests/Src/test_charging.c +++ b/Tests/Src/test_charging.c @@ -75,10 +75,11 @@ void test_pwm_duty_cycle(void) } } - for (uint16_t request = 101U; request <= UINT8_MAX; request++) { - pwm_duty_cycle_set((uint8_t)request); - TEST_ASSERT_EQUAL(PWM_0_0_PCT, pwm_duty_cycle_get()); - } + pwm_duty_cycle_set(101U); + TEST_ASSERT_EQUAL(PWM_0_0_PCT, pwm_duty_cycle_get()); + + pwm_duty_cycle_set(UINT8_MAX); + TEST_ASSERT_EQUAL(PWM_0_0_PCT, pwm_duty_cycle_get()); } void test_balance_threshold(void) From b036aee4c93c680167671b036273b1126fb37534 Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Sat, 18 Jul 2026 16:01:01 -0400 Subject: [PATCH 43/58] Change balancing pwm duty cycle msg format --- Core/Inc/can_messages_tx.h | 2 +- Core/Inc/charging.h | 6 ++-- Core/Src/can_messages_tx.c | 15 +++++----- Core/Src/charging.c | 60 +++++++++++++++++++++++++++++++++++-- Drivers/Odyssey-Definitions | 2 +- Tests/Src/test_charging.c | 37 +++++++++++++---------- 6 files changed, 92 insertions(+), 30 deletions(-) diff --git a/Core/Inc/can_messages_tx.h b/Core/Inc/can_messages_tx.h index 208c2220..775cdd88 100644 --- a/Core/Inc/can_messages_tx.h +++ b/Core/Inc/can_messages_tx.h @@ -428,7 +428,7 @@ uint8_t send_pack_current_and_shunt_temp_adbms * BMS/Control/CellBalancing/PWMDutyCycle - Current cell balancing PWM duty cycle */ uint8_t send_current_cell_balancing_pwm_duty_cycle -(uint8_t balancing_pwm_duty_cycle); +(float balancing_pwm_duty_cycle); /** * Contents of this message: diff --git a/Core/Inc/charging.h b/Core/Inc/charging.h index 73652e0b..aad69628 100644 --- a/Core/Inc/charging.h +++ b/Core/Inc/charging.h @@ -13,11 +13,11 @@ void pwm_duty_cycle_set(uint8_t duty_cycle_req_get); /** - * @brief get the stored duty cycle + * @brief get the selected ADBMS duty cycle * - * @returns the stored duty cycle percentage + * @returns the selected duty cycle percentage */ -uint8_t pwm_duty_cycle_setting_get(void); +float pwm_duty_cycle_setting_get(void); /** * @brief get the duty cycle (atomic) diff --git a/Core/Src/can_messages_tx.c b/Core/Src/can_messages_tx.c index 0b951217..73e83825 100644 --- a/Core/Src/can_messages_tx.c +++ b/Core/Src/can_messages_tx.c @@ -1608,20 +1608,21 @@ uint8_t send_pack_current_and_shunt_temp_adbms } uint8_t send_current_cell_balancing_pwm_duty_cycle -(uint8_t balancing_pwm_duty_cycle) +(float balancing_pwm_duty_cycle) { can_msg_t msg; msg.id = 0x97; msg.id_is_extended = false; - uint8_t data = 0; - msg.len = 1; - uint32_t balancing_pwm_duty_cycle_i = (uint32_t)(balancing_pwm_duty_cycle); - if(balancing_pwm_duty_cycle_i > 255ULL) {balancing_pwm_duty_cycle_i = 255; + uint16_t data = 0; + msg.len = 2; + uint32_t balancing_pwm_duty_cycle_i = (uint32_t)(balancing_pwm_duty_cycle*10); + if(balancing_pwm_duty_cycle_i > 1023ULL) {balancing_pwm_duty_cycle_i = 1023; } - data |= ((balancing_pwm_duty_cycle_i) & 0xFFULL) << 0; + data |= ((balancing_pwm_duty_cycle_i) & 0x3FFULL) << 6; - msg.data[0] = data; + uint16_t data_bigendian = __builtin_bswap16(data); + memcpy(msg.data, &data_bigendian, 2); return queue_send(&can_outgoing, &msg, TX_NO_WAIT); } diff --git a/Core/Src/charging.c b/Core/Src/charging.c index f0098672..bf05595a 100644 --- a/Core/Src/charging.c +++ b/Core/Src/charging.c @@ -18,8 +18,64 @@ void pwm_duty_cycle_set(uint8_t duty_cycle_req_get) { duty_cycle_req = duty_cycle_req_get; } -uint8_t pwm_duty_cycle_setting_get(void) { - return duty_cycle_req; +float pwm_duty_cycle_setting_get(void) { + float duty_cycle = 0.0f; + + switch (pwm_duty_cycle_get()) { + case PWM_0_0_PCT: + duty_cycle = 0.0f; + break; + case PWM_6_6_PCT: + duty_cycle = 6.6f; + break; + case PWM_13_2_PCT: + duty_cycle = 13.2f; + break; + case PWM_19_8_PCT: + duty_cycle = 19.8f; + break; + case PWM_26_4_PCT: + duty_cycle = 26.4f; + break; + case PWM_33_0_PCT: + duty_cycle = 33.0f; + break; + case PWM_39_6_PCT: + duty_cycle = 39.6f; + break; + case PWM_46_2_PCT: + duty_cycle = 46.2f; + break; + case PWM_52_8_PCT: + duty_cycle = 52.8f; + break; + case PWM_59_4_PCT: + duty_cycle = 59.4f; + break; + case PWM_66_0_PCT: + duty_cycle = 66.0f; + break; + case PWM_72_6_PCT: + duty_cycle = 72.6f; + break; + case PWM_79_2_PCT: + duty_cycle = 79.2f; + break; + case PWM_85_8_PCT: + duty_cycle = 85.8f; + break; + case PWM_92_4_PCT: + duty_cycle = 92.4f; + break; + case PWM_100_0_PCT: + duty_cycle = 100.0f; + break; + default: + duty_cycle = 0.0f; + break; + } + + return duty_cycle; } PWM_DUTY pwm_duty_cycle_get() { diff --git a/Drivers/Odyssey-Definitions b/Drivers/Odyssey-Definitions index 142205ba..c642beb5 160000 --- a/Drivers/Odyssey-Definitions +++ b/Drivers/Odyssey-Definitions @@ -1 +1 @@ -Subproject commit 142205ba3c277c0a93309d1ec6fe4c28b4800591 +Subproject commit c642beb57050334f3f0223237a8e32e5979b351e diff --git a/Tests/Src/test_charging.c b/Tests/Src/test_charging.c index 6e65db48..fe226c9c 100644 --- a/Tests/Src/test_charging.c +++ b/Tests/Src/test_charging.c @@ -46,23 +46,24 @@ void test_pwm_duty_cycle(void) uint8_t min; uint8_t max; PWM_DUTY duty_cycle; + float percentage; } ranges[] = { - { 0U, 0U, PWM_0_0_PCT }, - { 1U, 6U, PWM_6_6_PCT }, - { 7U, 13U, PWM_13_2_PCT }, - { 14U, 19U, PWM_19_8_PCT }, - { 20U, 26U, PWM_26_4_PCT }, - { 27U, 33U, PWM_33_0_PCT }, - { 34U, 39U, PWM_39_6_PCT }, - { 40U, 46U, PWM_46_2_PCT }, - { 47U, 52U, PWM_52_8_PCT }, - { 53U, 59U, PWM_59_4_PCT }, - { 60U, 66U, PWM_66_0_PCT }, - { 67U, 72U, PWM_72_6_PCT }, - { 73U, 79U, PWM_79_2_PCT }, - { 80U, 85U, PWM_85_8_PCT }, - { 86U, 92U, PWM_92_4_PCT }, - { 93U, 100U, PWM_100_0_PCT }, + { 0U, 0U, PWM_0_0_PCT, 0.0f }, + { 1U, 6U, PWM_6_6_PCT, 6.6f }, + { 7U, 13U, PWM_13_2_PCT, 13.2f }, + { 14U, 19U, PWM_19_8_PCT, 19.8f }, + { 20U, 26U, PWM_26_4_PCT, 26.4f }, + { 27U, 33U, PWM_33_0_PCT, 33.0f }, + { 34U, 39U, PWM_39_6_PCT, 39.6f }, + { 40U, 46U, PWM_46_2_PCT, 46.2f }, + { 47U, 52U, PWM_52_8_PCT, 52.8f }, + { 53U, 59U, PWM_59_4_PCT, 59.4f }, + { 60U, 66U, PWM_66_0_PCT, 66.0f }, + { 67U, 72U, PWM_72_6_PCT, 72.6f }, + { 73U, 79U, PWM_79_2_PCT, 79.2f }, + { 80U, 85U, PWM_85_8_PCT, 85.8f }, + { 86U, 92U, PWM_92_4_PCT, 92.4f }, + { 93U, 100U, PWM_100_0_PCT, 100.0f }, }; for (size_t range = 0U; range < sizeof(ranges) / sizeof(ranges[0]); @@ -72,14 +73,18 @@ void test_pwm_duty_cycle(void) pwm_duty_cycle_set((uint8_t)request); TEST_ASSERT_EQUAL(ranges[range].duty_cycle, pwm_duty_cycle_get()); + TEST_ASSERT_EQUAL_FLOAT(ranges[range].percentage, + pwm_duty_cycle_setting_get()); } } pwm_duty_cycle_set(101U); TEST_ASSERT_EQUAL(PWM_0_0_PCT, pwm_duty_cycle_get()); + TEST_ASSERT_EQUAL_FLOAT(0.0f, pwm_duty_cycle_setting_get()); pwm_duty_cycle_set(UINT8_MAX); TEST_ASSERT_EQUAL(PWM_0_0_PCT, pwm_duty_cycle_get()); + TEST_ASSERT_EQUAL_FLOAT(0.0f, pwm_duty_cycle_setting_get()); } void test_balance_threshold(void) From d3bc141726817a31196ff5f3eb4d916b7725637a Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Sat, 18 Jul 2026 16:03:40 -0400 Subject: [PATCH 44/58] Retry CAN TX when FIFO is busy --- Core/Src/can_handler.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Core/Src/can_handler.c b/Core/Src/can_handler.c index d958b7d4..bc2c9d32 100644 --- a/Core/Src/can_handler.c +++ b/Core/Src/can_handler.c @@ -208,7 +208,13 @@ void vCanDispatch(ULONG thread_input) { /* Process incoming messages */ while (queue_receive(&can_outgoing, &message, TX_WAIT_FOREVER) == U_SUCCESS) { - status = can_send_msg(&can1, &message); + do { + status = can_send_msg(&can1, &message); + if (status == HAL_BUSY) { + tx_thread_sleep(1U); + } + } while (status == HAL_BUSY); + if (status != U_SUCCESS) { PRINTLN_WARNING("Failed to send message (on can1) after removing from " "outgoing queue (Message ID: %ld) - Status %d", From f4ca72d4f48de35366f1d29d12a352794b5b1502 Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Sat, 18 Jul 2026 16:09:38 -0400 Subject: [PATCH 45/58] Submodule update --- Drivers/Odyssey-Definitions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Drivers/Odyssey-Definitions b/Drivers/Odyssey-Definitions index c642beb5..78cfc9c2 160000 --- a/Drivers/Odyssey-Definitions +++ b/Drivers/Odyssey-Definitions @@ -1 +1 @@ -Subproject commit c642beb57050334f3f0223237a8e32e5979b351e +Subproject commit 78cfc9c2b8d8aa7c9f0d9864d5ad23b4beb3f647 From 51e941dcb1464c227c5e385d000671e8a7ec169a Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Sat, 18 Jul 2026 16:26:49 -0400 Subject: [PATCH 46/58] codegen update --- Core/Src/can_messages_tx.c | 4 ++-- Drivers/Odyssey-Definitions | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Core/Src/can_messages_tx.c b/Core/Src/can_messages_tx.c index 73e83825..20a0a762 100644 --- a/Core/Src/can_messages_tx.c +++ b/Core/Src/can_messages_tx.c @@ -1617,9 +1617,9 @@ uint8_t send_current_cell_balancing_pwm_duty_cycle uint16_t data = 0; msg.len = 2; uint32_t balancing_pwm_duty_cycle_i = (uint32_t)(balancing_pwm_duty_cycle*10); - if(balancing_pwm_duty_cycle_i > 1023ULL) {balancing_pwm_duty_cycle_i = 1023; + if(balancing_pwm_duty_cycle_i > 65535ULL) {balancing_pwm_duty_cycle_i = 65535; } - data |= ((balancing_pwm_duty_cycle_i) & 0x3FFULL) << 6; + data |= ((balancing_pwm_duty_cycle_i) & 0xFFFFULL) << 0; uint16_t data_bigendian = __builtin_bswap16(data); memcpy(msg.data, &data_bigendian, 2); diff --git a/Drivers/Odyssey-Definitions b/Drivers/Odyssey-Definitions index 78cfc9c2..b5db222c 160000 --- a/Drivers/Odyssey-Definitions +++ b/Drivers/Odyssey-Definitions @@ -1 +1 @@ -Subproject commit 78cfc9c2b8d8aa7c9f0d9864d5ad23b4beb3f647 +Subproject commit b5db222ca575116c3be7bfc72c66fb1f369946e2 From b57f29fd1e5c1a3f2850ff83a1922d24d95fbb9d Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Sat, 18 Jul 2026 16:35:15 -0400 Subject: [PATCH 47/58] submodule --- Drivers/Odyssey-Definitions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Drivers/Odyssey-Definitions b/Drivers/Odyssey-Definitions index b5db222c..ade4cb0f 160000 --- a/Drivers/Odyssey-Definitions +++ b/Drivers/Odyssey-Definitions @@ -1 +1 @@ -Subproject commit b5db222ca575116c3be7bfc72c66fb1f369946e2 +Subproject commit ade4cb0fa838158e5849f9888c2503f2cb193c0f From 33be50f98b976a59c045c7aecd66ee08e6d47fce Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Sun, 19 Jul 2026 22:21:25 -0400 Subject: [PATCH 48/58] Update ow fault to critical --- Core/Src/state_machine.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/Src/state_machine.c b/Core/Src/state_machine.c index 0a3abe84..5d79cc39 100644 --- a/Core/Src/state_machine.c +++ b/Core/Src/state_machine.c @@ -669,7 +669,7 @@ void update_eval_table(state_machine_args_t *state_machine_args) .lim_1 = true, .timeout = OW_FAULT_TIME, .optype_2 = NOP, // UNUSED - .is_critical = false + .is_critical = true }; initialized = true; } From 1f5bd7aefbd931fbd2362b01841ddc74a1c827be Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Sun, 19 Jul 2026 22:50:53 -0400 Subject: [PATCH 49/58] Wrap PWM register read in segment API --- Core/Inc/segment.h | 3 +++ Core/Src/segment.c | 10 ++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Core/Inc/segment.h b/Core/Inc/segment.h index 451860c8..b0dca295 100644 --- a/Core/Inc/segment.h +++ b/Core/Inc/segment.h @@ -72,6 +72,9 @@ void segment_configure_balancing( PWM_DUTY discharge_config[NUM_CHIPS][NUM_CELLS_PER_CHIP], SPI_HandleTypeDef *hspi); +void segment_read_pwm_registers(cell_asic chips[NUM_CHIPS], + SPI_HandleTypeDef *hspi); + /** * @brief Returns if any cells are balancing. Must read back config register B and PWM registers. * diff --git a/Core/Src/segment.c b/Core/Src/segment.c index 99212e4b..88492ae0 100644 --- a/Core/Src/segment.c +++ b/Core/Src/segment.c @@ -281,8 +281,14 @@ void segment_configure_balancing( write_pwm_regs(chips, hspi); } +void segment_read_pwm_registers(cell_asic chips[NUM_CHIPS], + SPI_HandleTypeDef *hspi) +{ + read_pwm_registers(chips, hspi); +} + void segment_set_dcto(cell_asic chips[NUM_CHIPS], uint8_t dcto, - SPI_HandleTypeDef *hspi) + SPI_HandleTypeDef *hspi) { for (int chip = 0; chip < NUM_CHIPS; chip++) { set_discharge_timeout(&chips[chip], dcto); @@ -464,7 +470,7 @@ void vGetSegmentData(ULONG thread_input) segment_configure_balancing(acc_data->chips, acc_data->discharge_config, &hspi2); - read_pwm_registers(acc_data->chips, &hspi2); + segment_read_pwm_registers(acc_data->chips, &hspi2); segment_unmute(acc_data->chips, &hspi2); start_timer(&pwm_timer, pwm_update_frequency); } From cfb1cd5b6ad0e3163f5692d5784e0705a5d5e238 Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Sun, 19 Jul 2026 22:57:42 -0400 Subject: [PATCH 50/58] Fix docs --- Core/Inc/state_machine.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/Inc/state_machine.h b/Core/Inc/state_machine.h index e2210b51..401e7c0e 100644 --- a/Core/Inc/state_machine.h +++ b/Core/Inc/state_machine.h @@ -43,7 +43,7 @@ void sm_fault_return(state_machine_args_t *state_machine_args); * * @param fault_item fault data * @param fault_code fault code - * @return true if fault is present, false otherwise + * @return Current fault evaluation state */ fault_state_t sm_fault_eval(fault_eval_t *fault_item, fault_code_t fault_code); @@ -117,4 +117,4 @@ void handle_faulted(state_machine_args_t *state_machine_args); */ void vStateMachine(ULONG thread_input); -#endif \ No newline at end of file +#endif From c0ee8e93e00d8289d3bec77f91e5b8e05271eb27 Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Sun, 19 Jul 2026 23:17:55 -0400 Subject: [PATCH 51/58] Move boot state initialization into init_boot --- Core/Src/state_machine.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Core/Src/state_machine.c b/Core/Src/state_machine.c index 5d79cc39..923bd7d2 100644 --- a/Core/Src/state_machine.c +++ b/Core/Src/state_machine.c @@ -79,6 +79,9 @@ static void set_charging_stage(state_machine_t *state_machine, void init_boot(state_machine_args_t *state_machine_args) { + state_machine_args->state_machine->bms_state = BOOT; + state_machine_args->state_machine->balancing_active = false; + state_machine_args->state_machine->is_charger_connected = false; cancel_timer(&state_machine_args->state_machine->charger_message_timer); update_eval_table( @@ -686,10 +689,7 @@ void vStateMachine(ULONG thread_input) state_machine_t *state_machine = state_machine_args->state_machine; analyzer_t *analyzer = state_machine_args->analyzer; - state_machine->bms_state = BOOT; init_boot(state_machine_args); - state_machine->balancing_active = false; - state_machine->is_charger_connected = false; nertimer_t telem_timer; // sends unimportant telemetry messages every 500ms From 96e71949f529fb7770ab09bae9ba5598ce0afe2f Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Sun, 19 Jul 2026 23:39:08 -0400 Subject: [PATCH 52/58] Clean up open-wire fault detection logic --- Core/Inc/analyzer.h | 9 ++------- Core/Inc/datastructs.h | 4 ---- Core/Src/analyzer.c | 29 ++++++++--------------------- 3 files changed, 10 insertions(+), 32 deletions(-) diff --git a/Core/Inc/analyzer.h b/Core/Inc/analyzer.h index fcb4ca30..24d6e81f 100644 --- a/Core/Inc/analyzer.h +++ b/Core/Inc/analyzer.h @@ -27,11 +27,6 @@ void calc_pack_temps(analyzer_t *analyzer, acc_data_t *acc_data); void calc_cell_voltages(analyzer_t *analyzer, acc_data_t *acc_data, state_machine_t *state_machine); -/** - * @brief Convert the raw even and odd open-wire readings to voltages. - */ -void calc_cell_open_wire_voltages(analyzer_t *analyzer, acc_data_t *acc_data); - /** * @brief Calculate statistics about pack voltage, such as min and max cell * volt, pack and avg voltage, pack and avg OCV, and deltas. @@ -53,9 +48,9 @@ void calc_cell_resistances(analyzer_t *analyzer, acc_data_t *acc_data, hv_plate_t *hv_plate); /** - * @brief Detect open wires using the calculated even and odd voltages. + * @brief Calculate voltage drop percentages and detect open wires. */ -void detect_cell_open_wire(analyzer_t *analyzer); +void detect_cell_open_wire(analyzer_t *analyzer, acc_data_t *acc_data); /** * @brief Updates the cell status of balancing and S_C_faults based on raw cell diff --git a/Core/Inc/datastructs.h b/Core/Inc/datastructs.h index a94f1451..a86115e4 100644 --- a/Core/Inc/datastructs.h +++ b/Core/Inc/datastructs.h @@ -60,10 +60,6 @@ typedef struct { bool cs_fault[NUM_CELLS_PER_CHIP]; bool ow_fault[NUM_CELLS_PER_CHIP]; - /* Open-Wire Diagnostic Voltages */ - float ow_even_voltage[NUM_CELLS_PER_CHIP]; - float ow_odd_voltage[NUM_CELLS_PER_CHIP]; - float vpv; float vmv; float v_res; diff --git a/Core/Src/analyzer.c b/Core/Src/analyzer.c index 5cfb6672..f31ec6cf 100644 --- a/Core/Src/analyzer.c +++ b/Core/Src/analyzer.c @@ -196,20 +196,6 @@ void calc_cell_voltages(analyzer_t *analyzer, acc_data_t *acc_data, } -void calc_cell_open_wire_voltages(analyzer_t *analyzer, acc_data_t *acc_data) -{ - for (uint8_t chip = 0; chip < NUM_CHIPS; chip++) { - chipdata_t *chip_data = get_chip_data(analyzer, chip); - - for (uint8_t cell = 0; cell < NUM_CELLS_PER_CHIP; cell++) { - chip_data->ow_even_voltage[cell] = getVoltage( - acc_data->chips[chip].owcell.cell_ow_even[cell]); - chip_data->ow_odd_voltage[cell] = getVoltage( - acc_data->chips[chip].owcell.cell_ow_odd[cell]); - } - } -} - void calc_pack_voltage_stats(analyzer_t *analyzer, acc_data_t *acc_data) { analyzer->max_voltage.val = FLT_MIN; @@ -372,21 +358,23 @@ void calc_open_cell_voltage(analyzer_t *analyzer, hv_plate_t *hv_plate) } } -void detect_cell_open_wire(analyzer_t *analyzer) +void detect_cell_open_wire(analyzer_t *analyzer, acc_data_t *acc_data) { for (uint8_t chip = 0; chip < NUM_CHIPS; chip++) { chipdata_t *chip_data = get_chip_data(analyzer, chip); for (uint8_t cell = 0; cell < NUM_CELLS_PER_CHIP; cell++) { - const float even_voltage = - chip_data->ow_even_voltage[cell]; - const float odd_voltage = chip_data->ow_odd_voltage[cell]; + const float even_voltage = getVoltage( + acc_data->chips[chip].owcell.cell_ow_even[cell]); + const float odd_voltage = getVoltage( + acc_data->chips[chip].owcell.cell_ow_odd[cell]); const bool even_cell = ((cell + 1U) % 2U) == 0U; const float excited_voltage = even_cell ? even_voltage : odd_voltage; const float baseline_voltage = even_cell ? odd_voltage : even_voltage; - const float drop_voltage = baseline_voltage - excited_voltage; + const float drop_voltage = + baseline_voltage - excited_voltage; float drop_percent = 0.0f; if (baseline_voltage > 0.0f) { @@ -472,11 +460,10 @@ void vAnalyzer(ULONG thread_input) calc_cell_temps(analyzer, acc_data); calc_pack_temps(analyzer, acc_data); calc_cell_voltages(analyzer, acc_data, state_machine); - calc_cell_open_wire_voltages(analyzer, acc_data); calc_open_cell_voltage(analyzer, hv_plate); calc_pack_voltage_stats(analyzer, acc_data); calc_cell_resistances(analyzer, acc_data, hv_plate); - detect_cell_open_wire(analyzer); + detect_cell_open_wire(analyzer, acc_data); update_chip_status(analyzer, acc_data); mutex_put(&analyzer_mutex); From 83e9038f232be01683d2a8e0b2db5d0e777ceaf3 Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Sun, 19 Jul 2026 23:49:16 -0400 Subject: [PATCH 53/58] Update open-wire fault handling --- Core/Inc/analyzer.h | 3 ++- Core/Inc/datastructs.h | 1 + Core/Inc/state_machine.h | 14 ++++++++++++++ Core/Src/analyzer.c | 15 +++++++++++++-- Core/Src/state_machine.c | 36 ++++++++++++++++++------------------ 5 files changed, 48 insertions(+), 21 deletions(-) diff --git a/Core/Inc/analyzer.h b/Core/Inc/analyzer.h index 24d6e81f..88d0a49e 100644 --- a/Core/Inc/analyzer.h +++ b/Core/Inc/analyzer.h @@ -50,7 +50,8 @@ void calc_cell_resistances(analyzer_t *analyzer, acc_data_t *acc_data, /** * @brief Calculate voltage drop percentages and detect open wires. */ -void detect_cell_open_wire(analyzer_t *analyzer, acc_data_t *acc_data); +void detect_cell_open_wire(analyzer_t *analyzer, acc_data_t *acc_data, + state_machine_t *state_machine); /** * @brief Updates the cell status of balancing and S_C_faults based on raw cell diff --git a/Core/Inc/datastructs.h b/Core/Inc/datastructs.h index a86115e4..dd141c87 100644 --- a/Core/Inc/datastructs.h +++ b/Core/Inc/datastructs.h @@ -384,6 +384,7 @@ typedef struct { bool segment_comms_fault_flag; bool hv_plate_comms_fault_flag; + bool cell_open_wire_fault_flag; bool balancing_active; bool is_charger_connected; diff --git a/Core/Inc/state_machine.h b/Core/Inc/state_machine.h index 401e7c0e..73d92a8e 100644 --- a/Core/Inc/state_machine.h +++ b/Core/Inc/state_machine.h @@ -83,6 +83,20 @@ void set_hv_plate_comms_fault(state_machine_t *state_mach); */ void clear_hv_plate_comms_fault(state_machine_t *state_mach); +/** + * @brief Sets the cell open-wire fault flag. + * + * @param state_mach Pointer to the state machine data structure. + */ +void set_cell_open_wire_fault(state_machine_t *state_mach); + +/** + * @brief Clears the cell open-wire fault flag. + * + * @param state_mach Pointer to the state machine data structure. + */ +void clear_cell_open_wire_fault(state_machine_t *state_mach); + /** * @brief Determines if there is a critical fault that is active. * diff --git a/Core/Src/analyzer.c b/Core/Src/analyzer.c index f31ec6cf..71cd9f52 100644 --- a/Core/Src/analyzer.c +++ b/Core/Src/analyzer.c @@ -358,8 +358,11 @@ void calc_open_cell_voltage(analyzer_t *analyzer, hv_plate_t *hv_plate) } } -void detect_cell_open_wire(analyzer_t *analyzer, acc_data_t *acc_data) +void detect_cell_open_wire(analyzer_t *analyzer, acc_data_t *acc_data, + state_machine_t *state_machine) { + bool open_wire_fault_active = false; + for (uint8_t chip = 0; chip < NUM_CHIPS; chip++) { chipdata_t *chip_data = get_chip_data(analyzer, chip); @@ -387,6 +390,8 @@ void detect_cell_open_wire(analyzer_t *analyzer, acc_data_t *acc_data) drop_percent > CELL_OPEN_WIRE_MAX_DROP_PERCENT; chip_data->ow_fault[cell] = is_open; + open_wire_fault_active = + open_wire_fault_active || is_open; if (is_open && !was_open) { PRINTLN_WARNING( @@ -396,6 +401,12 @@ void detect_cell_open_wire(analyzer_t *analyzer, acc_data_t *acc_data) } } } + + if (open_wire_fault_active) { + set_cell_open_wire_fault(state_machine); + } else { + clear_cell_open_wire_fault(state_machine); + } } void update_chip_status(analyzer_t *analyzer, acc_data_t *acc_data) @@ -463,7 +474,7 @@ void vAnalyzer(ULONG thread_input) calc_open_cell_voltage(analyzer, hv_plate); calc_pack_voltage_stats(analyzer, acc_data); calc_cell_resistances(analyzer, acc_data, hv_plate); - detect_cell_open_wire(analyzer, acc_data); + detect_cell_open_wire(analyzer, acc_data, state_machine); update_chip_status(analyzer, acc_data); mutex_put(&analyzer_mutex); diff --git a/Core/Src/state_machine.c b/Core/Src/state_machine.c index 923bd7d2..42f3561c 100644 --- a/Core/Src/state_machine.c +++ b/Core/Src/state_machine.c @@ -34,8 +34,6 @@ const bool valid_transition_from_to[NUM_STATES][NUM_STATES] = { /* private function prototypes */ void update_eval_table(state_machine_args_t *state_machine_args); -static bool is_open_wire_fault_active(const analyzer_t *analyzer); - static void set_charging_stage(state_machine_t *state_machine, charge_stage_t stage); @@ -492,28 +490,28 @@ void clear_hv_plate_comms_fault(state_machine_t *state_mach) mutex_put(&state_mutex); } -bool get_fault(fault_code_t fault) +void set_cell_open_wire_fault(state_machine_t *state_mach) { - return (fault_flags & (1 << fault)) != 0; + mutex_get(&state_mutex); + state_mach->cell_open_wire_fault_flag = true; + mutex_put(&state_mutex); } -bool are_critical_faults_active(void) +void clear_cell_open_wire_fault(state_machine_t *state_mach) { - return (fault_flags & severity_mask) != 0; + mutex_get(&state_mutex); + state_mach->cell_open_wire_fault_flag = false; + mutex_put(&state_mutex); } -static bool is_open_wire_fault_active(const analyzer_t *analyzer) +bool get_fault(fault_code_t fault) { - bool open_wire_fault_active = false; + return (fault_flags & (1 << fault)) != 0; +} - for (uint8_t chip = 0U; chip < NUM_CHIPS; chip++) { - for (uint8_t cell = 0U; cell < NUM_CELLS_PER_CHIP; cell++) { - if (analyzer->chip_data[chip].ow_fault[cell]) { - open_wire_fault_active = true; - } - } - } - return open_wire_fault_active; +bool are_critical_faults_active(void) +{ + return (fault_flags & severity_mask) != 0; } void update_eval_table(state_machine_args_t *state_machine_args) @@ -525,6 +523,7 @@ void update_eval_table(state_machine_args_t *state_machine_args) state_machine_t *state_machine = state_machine_args->state_machine; analyzer_t *analyzer = state_machine_args->analyzer; sanitizer_t *sanitizer = state_machine_args->sanitizer; + bool open_wire_fault_active = false; static nertimer_t ovr_curr_timer = { 0 }; static nertimer_t ovr_chgcurr_timer = { 0 }; @@ -537,8 +536,9 @@ void update_eval_table(state_machine_args_t *state_machine_args) static nertimer_t hv_plate_comms_timer = { 0 }; static nertimer_t open_wire_timer = { 0 }; - const bool open_wire_fault_active = - is_open_wire_fault_active(analyzer); + mutex_get(&state_mutex); + open_wire_fault_active = state_machine->cell_open_wire_fault_flag; + mutex_put(&state_mutex); if (initialized) { fault_eval_table[DISCHARGE_LIMIT_ENFORCEMENT_FAULT].data_1 = From a6a54b4335aa9186d0e61d09b65462cae304b98d Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Mon, 20 Jul 2026 00:03:55 -0400 Subject: [PATCH 54/58] Small fix --- Core/Src/state_machine.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Core/Src/state_machine.c b/Core/Src/state_machine.c index 42f3561c..20aceab5 100644 --- a/Core/Src/state_machine.c +++ b/Core/Src/state_machine.c @@ -523,7 +523,6 @@ void update_eval_table(state_machine_args_t *state_machine_args) state_machine_t *state_machine = state_machine_args->state_machine; analyzer_t *analyzer = state_machine_args->analyzer; sanitizer_t *sanitizer = state_machine_args->sanitizer; - bool open_wire_fault_active = false; static nertimer_t ovr_curr_timer = { 0 }; static nertimer_t ovr_chgcurr_timer = { 0 }; @@ -536,10 +535,6 @@ void update_eval_table(state_machine_args_t *state_machine_args) static nertimer_t hv_plate_comms_timer = { 0 }; static nertimer_t open_wire_timer = { 0 }; - mutex_get(&state_mutex); - open_wire_fault_active = state_machine->cell_open_wire_fault_flag; - mutex_put(&state_mutex); - if (initialized) { fault_eval_table[DISCHARGE_LIMIT_ENFORCEMENT_FAULT].data_1 = hv_plate->pack_current; @@ -566,7 +561,7 @@ void update_eval_table(state_machine_args_t *state_machine_args) fault_eval_table[HV_PLATE_COMMS_FAULT].data_1 = state_machine->hv_plate_comms_fault_flag; fault_eval_table[CELL_OPEN_WIRE_FAULT].data_1 = - open_wire_fault_active; + state_machine->cell_open_wire_fault_flag; } else { fault_eval_table[DISCHARGE_LIMIT_ENFORCEMENT_FAULT] = (fault_eval_t){ .id = "Discharge Current Limit", @@ -667,7 +662,7 @@ void update_eval_table(state_machine_args_t *state_machine_args) fault_eval_table[CELL_OPEN_WIRE_FAULT] = (fault_eval_t){ .id = "Cell Open Wire Fault", .timer = open_wire_timer, - .data_1 = open_wire_fault_active, + .data_1 = state_machine->cell_open_wire_fault_flag, .optype_1 = EQ, .lim_1 = true, .timeout = OW_FAULT_TIME, From 7f1494f445e9a1cd9c7d1a3a5d169d95d3da01e4 Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Mon, 20 Jul 2026 00:16:29 -0400 Subject: [PATCH 55/58] Mutex fault flag reads --- Core/Src/state_machine.c | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/Core/Src/state_machine.c b/Core/Src/state_machine.c index 20aceab5..8a2c996f 100644 --- a/Core/Src/state_machine.c +++ b/Core/Src/state_machine.c @@ -535,6 +535,14 @@ void update_eval_table(state_machine_args_t *state_machine_args) static nertimer_t hv_plate_comms_timer = { 0 }; static nertimer_t open_wire_timer = { 0 }; + mutex_get(&state_mutex); + const bool segment_comms_fault = + state_machine->segment_comms_fault_flag; + const bool hv_plate_comms_fault = + state_machine->hv_plate_comms_fault_flag; + const bool ow_fault = state_machine->cell_open_wire_fault_flag; + mutex_put(&state_mutex); + if (initialized) { fault_eval_table[DISCHARGE_LIMIT_ENFORCEMENT_FAULT].data_1 = hv_plate->pack_current; @@ -557,11 +565,11 @@ void update_eval_table(state_machine_args_t *state_machine_args) fault_eval_table[DIE_TEMP_MAXIMUM_FAULT].data_1 = analyzer->max_chiptemp.val; fault_eval_table[SEGMENT_COMMS_FAULT].data_1 = - state_machine->segment_comms_fault_flag; + segment_comms_fault; fault_eval_table[HV_PLATE_COMMS_FAULT].data_1 = - state_machine->hv_plate_comms_fault_flag; + hv_plate_comms_fault; fault_eval_table[CELL_OPEN_WIRE_FAULT].data_1 = - state_machine->cell_open_wire_fault_flag; + ow_fault; } else { fault_eval_table[DISCHARGE_LIMIT_ENFORCEMENT_FAULT] = (fault_eval_t){ .id = "Discharge Current Limit", @@ -640,8 +648,8 @@ void update_eval_table(state_machine_args_t *state_machine_args) fault_eval_table[SEGMENT_COMMS_FAULT] = (fault_eval_t){ .id = "Segment Comms Fault", .timer = segment_comms_timer, - .data_1 = state_machine->segment_comms_fault_flag, - .optype_1 = GE, + .data_1 = segment_comms_fault, + .optype_1 = EQ, .lim_1 = true, .timeout = COMMS_FAULT_TIME, .optype_2 = NOP, // UNUSED @@ -651,8 +659,8 @@ void update_eval_table(state_machine_args_t *state_machine_args) fault_eval_table[HV_PLATE_COMMS_FAULT] = (fault_eval_t){ .id = "HV Plate Comms Fault", .timer = hv_plate_comms_timer, - .data_1 = state_machine->hv_plate_comms_fault_flag, - .optype_1 = GE, + .data_1 = hv_plate_comms_fault, + .optype_1 = EQ, .lim_1 = true, .timeout = COMMS_FAULT_TIME, .optype_2 = NOP, // UNUSED @@ -662,7 +670,7 @@ void update_eval_table(state_machine_args_t *state_machine_args) fault_eval_table[CELL_OPEN_WIRE_FAULT] = (fault_eval_t){ .id = "Cell Open Wire Fault", .timer = open_wire_timer, - .data_1 = state_machine->cell_open_wire_fault_flag, + .data_1 = ow_fault, .optype_1 = EQ, .lim_1 = true, .timeout = OW_FAULT_TIME, From 1e960e69d4ed16fc85d7ae4e8b8e44a4ea6f2d12 Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Mon, 20 Jul 2026 15:21:17 -0400 Subject: [PATCH 56/58] dcl config update --- Core/Inc/current_limit_algo_config.h | 2 +- Tests/Src/test_dcl.c | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Core/Inc/current_limit_algo_config.h b/Core/Inc/current_limit_algo_config.h index 4b7acf26..cb07a091 100644 --- a/Core/Inc/current_limit_algo_config.h +++ b/Core/Inc/current_limit_algo_config.h @@ -16,7 +16,7 @@ #define DCL_TEMP_RAMP_DOWN_START_C (50.0f) #define DCL_TEMP_MAX_C (60.0f) #define DCL_OCV_MIN_V (3.0f) -#define DCL_OCV_DERATE_THRESH (3.7f) +#define DCL_OCV_DERATE_THRESH (3.2f) #define DCL_MAX_CURRENT_A (180.0f) #define DCL_MIN_CURRENT_A (30.0f) #define DCL_PULSE_PERCENT (1.1f) diff --git a/Tests/Src/test_dcl.c b/Tests/Src/test_dcl.c index 4a3395ee..c0f1ae69 100644 --- a/Tests/Src/test_dcl.c +++ b/Tests/Src/test_dcl.c @@ -76,15 +76,15 @@ void test_inst_dcl_temperature_and_ocv_regions(void) /* -------- OCV derating region -------- */ test_inputs.min_temp = 25.0f; test_inputs.max_temp = 30.0f; - test_inputs.min_ocv = 3.3f; /* between OCV_MIN and DERATE_THRESH */ + test_inputs.min_ocv = 3.1f; /* between OCV_MIN and DERATE_THRESH */ dcl_calc_inst_limit(test_inputs, &test_algos); - TEST_ASSERT_EQUAL_FLOAT(94.2857f, test_algos.inst_DCL); + TEST_ASSERT_EQUAL_FLOAT(105.0f, test_algos.inst_DCL); /* -------- Fully nominal region -------- */ test_inputs.min_temp = 25.0f; test_inputs.max_temp = 30.0f; - test_inputs.min_ocv = 4.0f; + test_inputs.min_ocv = 3.2f; dcl_calc_inst_limit(test_inputs, &test_algos); TEST_ASSERT_EQUAL_FLOAT(DCL_MAX_CURRENT_A, test_algos.inst_DCL); @@ -103,7 +103,7 @@ void test_cont_dcl_follows_inst_limit_when_pulse_not_allowed(void) /* -------- Below pulse enable margin -------- */ test_pack_current = 100.0f; - test_inputs.min_ocv = 3.2f; + test_inputs.min_ocv = 3.1f; test_inputs.max_temp = 35.0f; test_inputs.min_temp = 31.0f; @@ -370,4 +370,4 @@ int main(void) RUN_TEST(test_cont_dcl_pulse_state_transitions); return UNITY_END(); -} \ No newline at end of file +} From 15c5841a1818a69cb2a41412313e6fea3864160a Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Mon, 20 Jul 2026 18:22:25 -0400 Subject: [PATCH 57/58] Switch soc to curve based --- Core/Inc/datastructs.h | 1 + Core/Src/soc.c | 56 ++++++++++++++++++++++++++++---------- Tests/Src/test_soc.c | 61 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 14 deletions(-) diff --git a/Core/Inc/datastructs.h b/Core/Inc/datastructs.h index dd141c87..63d568da 100644 --- a/Core/Inc/datastructs.h +++ b/Core/Inc/datastructs.h @@ -222,6 +222,7 @@ typedef struct { */ typedef enum { SOC_STATE_INIT_FROM_OCV, + SOC_STATE_OCV_ESTIMATION, SOC_STATE_COULOMB_COUNTING } soc_state_t; diff --git a/Core/Src/soc.c b/Core/Src/soc.c index 56c58a39..b4aad28b 100644 --- a/Core/Src/soc.c +++ b/Core/Src/soc.c @@ -4,6 +4,10 @@ #include "c_utils.h" #include "datastructs.h" +#ifndef ENABLE_COULOMB_COUNTING +#define ENABLE_COULOMB_COUNTING false +#endif /* ENABLE_COULOMB_COUNTING */ + /** * @brief Nominal pack capacity used for coulomb counting integration. * @@ -62,6 +66,31 @@ static float get_soc_from_ocv(float ocv) return soc; } +/** + * @brief Update SoC from the minimum cell OCV. + * + * @param analyzer Analyzer containing pack SoC and OCV data. + * @return true when the OCV is valid. + */ +static bool update_soc_from_ocv(analyzer_t *const analyzer) +{ + const float min_ocv = analyzer->min_ocv.val; + const bool is_ocv_valid = (min_ocv >= MIN_VOLT) && + (min_ocv <= MAX_VOLT); + + if (is_ocv_valid) { + float soc = get_soc_from_ocv(min_ocv); + + if (soc > 1.0f) { + soc = 1.0f; + } + + analyzer->soc = soc; + } + + return is_ocv_valid; +} + /** * @brief Compute pack SoC drift from OCV-based estimate. * @@ -82,7 +111,11 @@ void soc_init(void) soc_data.soc_reinit_request = false; soc_data.soc_drift = 0.0f; soc_data.prev_time = 0U; +#if ENABLE_COULOMB_COUNTING soc_data.soc_state = SOC_STATE_INIT_FROM_OCV; +#else + soc_data.soc_state = SOC_STATE_OCV_ESTIMATION; +#endif } void soc_request_reinit_from_ocv(void) @@ -93,9 +126,9 @@ void soc_request_reinit_from_ocv(void) void soc_handle_state(analyzer_t *const analyzer, const hv_plate_t *const hv_plate) { - const float min_ocv = analyzer->min_ocv.val; const float pack_current = hv_plate->pack_current; +#if ENABLE_COULOMB_COUNTING /** * Initialize or reinitialize SoC from OCV * Used when current data is unreliable/unavailable @@ -104,21 +137,16 @@ void soc_handle_state(analyzer_t *const analyzer, if (soc_data.soc_reinit_request == true) { soc_data.soc_state = SOC_STATE_INIT_FROM_OCV; } +#endif switch (soc_data.soc_state) { - case SOC_STATE_INIT_FROM_OCV: { - const bool is_ocv_valid = (min_ocv >= MIN_VOLT) && - (min_ocv <= MAX_VOLT); - - if (is_ocv_valid) { - float soc_from_ocv = get_soc_from_ocv(min_ocv); - - if (soc_from_ocv > 1.0f) { - soc_from_ocv = 1.0f; - } - - analyzer->soc = soc_from_ocv; + case SOC_STATE_OCV_ESTIMATION: { + (void)update_soc_from_ocv(analyzer); + break; + } + case SOC_STATE_INIT_FROM_OCV: { + if (update_soc_from_ocv(analyzer)) { soc_data.prev_time = tx_time_get(); soc_data.soc_reinit_request = false; @@ -177,4 +205,4 @@ void soc_handle_state(analyzer_t *const analyzer, float get_soc_drift(void) { return soc_data.soc_drift; -} \ No newline at end of file +} diff --git a/Tests/Src/test_soc.c b/Tests/Src/test_soc.c index 97ed0085..c3ee0991 100644 --- a/Tests/Src/test_soc.c +++ b/Tests/Src/test_soc.c @@ -23,6 +23,8 @@ ULONG _tx_time_get(VOID) static analyzer_t analyzer; static hv_plate_t hv_plate; +extern soc_data_t soc_data; + /* ------------------------------------------------- * Setup / Teardown * ------------------------------------------------- */ @@ -55,6 +57,24 @@ void test_soc_initializes_from_ocv(void) TEST_ASSERT_EQUAL_FLOAT(0.615728f, analyzer.soc); } +/* ------------------------------------------------- + * Test: SOC remains unchanged without a new OCV + * ------------------------------------------------- */ + +void test_soc_ocv_holds_without_update(void) +{ + analyzer.min_ocv.val = 3.8f; + + soc_handle_state(&analyzer, &hv_plate); + + hv_plate.pack_current = 100.0f; + test_tx_ticks += 100U; + + soc_handle_state(&analyzer, &hv_plate); + + TEST_ASSERT_EQUAL_FLOAT(0.615728f, analyzer.soc); +} + /* ------------------------------------------------- * Test: SOC decreases during discharge * ------------------------------------------------- */ @@ -62,6 +82,7 @@ void test_soc_initializes_from_ocv(void) void test_soc_coulomb_discharge(void) { analyzer.min_ocv.val = 3.8f; + soc_data.soc_state = SOC_STATE_INIT_FROM_OCV; /* Initial OCV init */ soc_handle_state(&analyzer, &hv_plate); @@ -81,6 +102,7 @@ void test_soc_coulomb_discharge(void) void test_soc_coulomb_charge(void) { analyzer.min_ocv.val = 3.8f; + soc_data.soc_state = SOC_STATE_INIT_FROM_OCV; /* Initial OCV init */ soc_handle_state(&analyzer, &hv_plate); @@ -93,6 +115,42 @@ void test_soc_coulomb_charge(void) TEST_ASSERT_EQUAL_FLOAT(0.615784f, analyzer.soc); } +/* ------------------------------------------------- + * Test: Reinitialization keeps OCV estimation active + * ------------------------------------------------- */ + +void test_soc_reinit_stays_ocv(void) +{ + analyzer.min_ocv.val = 3.8f; + + soc_handle_state(&analyzer, &hv_plate); + soc_request_reinit_from_ocv(); + soc_handle_state(&analyzer, &hv_plate); + + hv_plate.pack_current = 100.0f; + test_tx_ticks += 100U; + soc_handle_state(&analyzer, &hv_plate); + + TEST_ASSERT_EQUAL_FLOAT(0.615728f, analyzer.soc); +} + +/* ------------------------------------------------- + * Test: SOC updates from a new OCV + * ------------------------------------------------- */ + +void test_soc_updates_from_ocv(void) +{ + analyzer.min_ocv.val = 3.8f; + + soc_handle_state(&analyzer, &hv_plate); + + analyzer.min_ocv.val = 4.0f; + + soc_handle_state(&analyzer, &hv_plate); + + TEST_ASSERT_EQUAL_FLOAT(0.819626f, analyzer.soc); +} + /* ------------------------------------------------- * Test: Invalid OCV does not initialize, then recovers * ------------------------------------------------- */ @@ -126,6 +184,9 @@ int main(void) RUN_TEST(test_soc_initializes_from_ocv); RUN_TEST(test_soc_coulomb_discharge); RUN_TEST(test_soc_coulomb_charge); + RUN_TEST(test_soc_ocv_holds_without_update); + RUN_TEST(test_soc_reinit_stays_ocv); + RUN_TEST(test_soc_updates_from_ocv); RUN_TEST(test_soc_invalid_ocv_then_valid); return UNITY_END(); From 563ec7814ccdeaf64e0032bf30bd97247886c43a Mon Sep 17 00:00:00 2001 From: KSMehta11 Date: Tue, 21 Jul 2026 00:03:49 -0400 Subject: [PATCH 58/58] Send balancing duty cycle periodically --- Core/Inc/bms_config.h | 2 +- Core/Src/can_handler.c | 2 -- Core/Src/control.c | 3 +++ 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Core/Inc/bms_config.h b/Core/Inc/bms_config.h index e55c1987..4cc6d175 100644 --- a/Core/Inc/bms_config.h +++ b/Core/Inc/bms_config.h @@ -54,7 +54,7 @@ #define OCV_CURR_THRESH 0.5f /* in A */ // Charging settings -#define CHARGING_CURRENT 3.5f +#define CHARGING_CURRENT 5.0f #define CHARGE_SETL_TIMEOUT 30000 // 1 minute, may need adjustment #define CHARGE_SETL_TIMEUP 120000 // 5 minutes, may need adjustment diff --git a/Core/Src/can_handler.c b/Core/Src/can_handler.c index bc2c9d32..2706b886 100644 --- a/Core/Src/can_handler.c +++ b/Core/Src/can_handler.c @@ -176,8 +176,6 @@ void vCanReceive(ULONG thread_input) { break; case CALYPSO_PWM_BAL_CANID: pwm_duty_cycle_set(message.data[0]); - send_current_cell_balancing_pwm_duty_cycle( - pwm_duty_cycle_setting_get()); break; case DTI_INPUT_VOLTAGE_CANID: if (!state_machine->is_charger_connected) { diff --git a/Core/Src/control.c b/Core/Src/control.c index 622baca1..32bc136a 100644 --- a/Core/Src/control.c +++ b/Core/Src/control.c @@ -1,6 +1,7 @@ #include "control.h" #include "can_messages_tx.h" +#include "charging.h" #include "datastructs.h" #include "main.h" #include "shep_mutexes.h" @@ -146,6 +147,8 @@ void vControl(ULONG thread_input) (uint8_t)(device_fan0.current_duty >> 8)); send_fan_duty_cycle_percentage( (uint8_t)(device_fan0.current_duty >> 8)); + send_current_cell_balancing_pwm_duty_cycle( + pwm_duty_cycle_setting_get()); start_timer(&update_loop_timer, TELEMETRY_LOOP_TIMEOUT); }