diff --git a/Core/Inc/adi6830_interation.h b/Core/Inc/adi6830_interation.h index 71b4b7b4..5e6e80d5 100644 --- a/Core/Inc/adi6830_interation.h +++ b/Core/Inc/adi6830_interation.h @@ -348,19 +348,23 @@ 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. + * @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 Starts a continous c ADC conversion with S redundancy @@ -368,6 +372,13 @@ 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); +/** + * @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); + // --- END ADC POLL --- #endif diff --git a/Core/Inc/analyzer.h b/Core/Inc/analyzer.h index 2abfaf4c..88d0a49e 100644 --- a/Core/Inc/analyzer.h +++ b/Core/Inc/analyzer.h @@ -39,8 +39,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 @@ -48,6 +47,12 @@ 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 Calculate voltage drop percentages and detect open wires. + */ +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 * data diff --git a/Core/Inc/bms_config.h b/Core/Inc/bms_config.h index 483a24ab..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 @@ -68,6 +68,8 @@ #define LOW_CELL_TIME 55000 #define HIGH_TEMP_TIME 55000 #define MAX_CHIPTEMP_TIME 55000 +#define COMMS_FAULT_TIME 20000 +#define OW_FAULT_TIME 40000 // system wide base ADBMS sample rate #define SAMPLE_RATE 2 /* Hz */ 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/can_messages_rx.h b/Core/Inc/can_messages_rx.h index 74f9f623..f19b992d 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); diff --git a/Core/Inc/can_messages_tx.h b/Core/Inc/can_messages_tx.h index 24fe7931..775cdd88 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,27 @@ 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: +* 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}/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); /** * Contents of this message: @@ -311,12 +332,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); @@ -402,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 +(float 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/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/charging.h b/Core/Inc/charging.h index d9b1c3f8..aad69628 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 selected ADBMS duty cycle + * + * @returns the selected duty cycle percentage + */ +float pwm_duty_cycle_setting_get(void); + /** * @brief get the duty cycle (atomic) * diff --git a/Core/Inc/current_limit_algo_config.h b/Core/Inc/current_limit_algo_config.h index f6c566fa..cb07a091 100644 --- a/Core/Inc/current_limit_algo_config.h +++ b/Core/Inc/current_limit_algo_config.h @@ -16,8 +16,8 @@ #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_MAX_CURRENT_A (135.0f) +#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) #define DCL_COOLDOWN_PERCENT (0.9f) @@ -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/Core/Inc/datastructs.h b/Core/Inc/datastructs.h index ae4fe447..63d568da 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]; @@ -56,6 +58,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; @@ -85,6 +88,8 @@ typedef struct { typedef struct { float last_temp; bool valid; + bool initialized; + uint8_t fault_count; } therm_state_t; /** @@ -104,6 +109,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 */ @@ -211,6 +222,7 @@ typedef struct { */ typedef enum { SOC_STATE_INIT_FROM_OCV, + SOC_STATE_OCV_ESTIMATION, SOC_STATE_COULOMB_COUNTING } soc_state_t; @@ -240,7 +252,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; @@ -373,6 +385,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; @@ -422,6 +435,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 */ @@ -438,6 +459,7 @@ typedef struct { typedef struct { acc_data_t *acc_data; state_machine_t *state_machine; + analyzer_t *analyzer; } acc_data_args_t; /** @@ -492,6 +514,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/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/Inc/shep_tasks.h b/Core/Inc/shep_tasks.h index 1cb3cd9a..635d90a9 100644 --- a/Core/Inc/shep_tasks.h +++ b/Core/Inc/shep_tasks.h @@ -5,11 +5,13 @@ #include "tx_api.h" #include "datastructs.h" +#define NO_LOG + // #define DEBUG_HV_PLATE // #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/Inc/state_machine.h b/Core/Inc/state_machine.h index f8bae907..73d92a8e 100644 --- a/Core/Inc/state_machine.h +++ b/Core/Inc/state_machine.h @@ -43,9 +43,9 @@ 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 */ -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 @@ -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. * @@ -117,4 +131,4 @@ void handle_faulted(state_machine_args_t *state_machine_args); */ void vStateMachine(ULONG thread_input); -#endif \ No newline at end of file +#endif 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) diff --git a/Core/Src/adi6830_interaction.c b/Core/Src/adi6830_interaction.c index d95ee730..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); @@ -576,22 +576,21 @@ 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); } -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); } @@ -601,4 +600,10 @@ void start_c_adc_conv(cell_asic chips[NUM_CHIPS], SPI_HandleTypeDef *hspi) 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 c8d080c4..71cd9f52 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. */ @@ -277,7 +283,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; @@ -306,53 +312,100 @@ 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); + } +} + +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); + + for (uint8_t cell = 0; cell < NUM_CELLS_PER_CHIP; 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; + 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; + open_wire_fault_active = + open_wire_fault_active || 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); + } + } + } + + if (open_wire_fault_active) { + set_cell_open_wire_fault(state_machine); + } else { + clear_cell_open_wire_fault(state_machine); } } @@ -418,13 +471,15 @@ 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_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, acc_data, state_machine); update_chip_status(analyzer, acc_data); 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/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/can_handler.c b/Core/Src/can_handler.c index 4a651194..2706b886 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" @@ -36,7 +37,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 +47,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 +107,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 +165,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 +177,16 @@ 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: + if (!state_machine->is_charger_connected) { + hv_plate->pack_current = parse_dti_current(message); + } + break; default: break; } @@ -150,7 +206,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", diff --git a/Core/Src/can_messages_rx.c b/Core/Src/can_messages_rx.c index d9ab4862..ad4c748f 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) { diff --git a/Core/Src/can_messages_tx.c b/Core/Src/can_messages_tx.c index 917d0fff..20a0a762 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,96 @@ 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); + + 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); @@ -1502,6 +1607,26 @@ 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 +(float balancing_pwm_duty_cycle) +{ + can_msg_t msg; + msg.id = 0x97; + msg.id_is_extended = false; + + 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 > 65535ULL) {balancing_pwm_duty_cycle_i = 65535; + } + data |= ((balancing_pwm_duty_cycle_i) & 0xFFFFULL) << 0; + + uint16_t data_bigendian = __builtin_bswap16(data); + memcpy(msg.data, &data_bigendian, 2); + + 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/cell_temp_sanitizer.c b/Core/Src/cell_temp_sanitizer.c index c8c936c7..ac82ea58 100644 --- a/Core/Src/cell_temp_sanitizer.c +++ b/Core/Src/cell_temp_sanitizer.c @@ -1,112 +1,176 @@ #include "cell_temp_sanitizer.h" #include "analyzer.h" -#include "debounce.h" +#include "u_tx_flags.h" +#include "u_tx_mutex.h" +#include "shep_mutexes.h" +#include +#include #include -#include +#include + +/** + * @brief Number of consecutive invalid samples before a thermistor is marked invalid. + */ +#define THERM_FAULT_COUNT_THRESHOLD (2U) + +/** + * @brief Maximum allowed temperature change from the last trusted thermistor sample. + */ +#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_MAX; + sanitizer->max_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; +} -void temp_sanitizer_init(sanitizer_t *sanitizer) +/** + * @brief Records a thermistor fault and marks it invalid if the threshold is reached. + * + * @param therm_state Pointer to the thermistor state. + */ +static void record_therm_fault(therm_state_t *const therm_state) { - 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; - } + therm_state->fault_count++; + + // Repeated faults mark thermistor invalid + 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_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 >= (float)MIN_TEMP) && (cell_temp <= (float)MAX_CELL_TEMP)); + + // 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; + therm_state->initialized = true; + therm_state->valid = true; + therm_state->fault_count = 0U; + sample_valid = true; + } + else + { + // Startup sample outside physical range + 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) + { + // Invalid thermistors do not recover automatically + sample_valid = false; + } + else + { + const float temp_delta = fabsf(cell_temp - therm_state->last_temp); + + 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; + } } - 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 +183,9 @@ void vSanitizer(ULONG thread_input) temp_sanitizer_init(sanitizer); for (;;) { + get_flag(SANITIZER_FLAG, TX_WAIT_FOREVER); + mutex_get(&analyzer_mutex); temp_sanitizer_run(sanitizer, analyzer); - tx_thread_sleep(MS_TO_TICKS(500)); + mutex_put(&analyzer_mutex); } } - - - - - - - - - - diff --git a/Core/Src/charging.c b/Core/Src/charging.c index bb4de752..bf05595a 100644 --- a/Core/Src/charging.c +++ b/Core/Src/charging.c @@ -18,6 +18,66 @@ void pwm_duty_cycle_set(uint8_t duty_cycle_req_get) { duty_cycle_req = duty_cycle_req_get; } +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() { switch (duty_cycle_req) { case 1 ... 6: 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; } 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); } diff --git a/Core/Src/hv_plate.c b/Core/Src/hv_plate.c index 8e15a336..bd9fa71f 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; @@ -177,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); @@ -193,15 +200,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 +243,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); @@ -255,8 +270,21 @@ 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); + + 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/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 diff --git a/Core/Src/segment.c b/Core/Src/segment.c index 514ff6fe..88492ae0 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" @@ -160,7 +161,7 @@ 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 + // run a clean single-shot conversion get_c_adc_voltages(chips, hspi); read_status_registers(chips, hspi); @@ -191,7 +192,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) @@ -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); @@ -290,6 +296,89 @@ void segment_set_dcto(cell_asic chips[NUM_CHIPS], uint8_t dcto, write_config_regs(chips, hspi); } +/** + * @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 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. + */ + + 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++) { + analyzer->chip_data[chip].s_cell_voltages[cell] = + getVoltage(chips[chip].scell.sc_codes[cell]); + } + } + + get_s_adc_open_wire_voltages(chips, hspi, OW_ON_EVEN_CH); + + for (uint8_t chip = 0; chip < NUM_CHIPS; chip++) { + for (uint8_t cell = 0; cell < NUM_CELLS_PER_CHIP; 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 chip = 0; chip < NUM_CHIPS; chip++) { + for (uint8_t cell = 0; cell < NUM_CELLS_PER_CHIP; 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) +{ + 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); + } + } + } +} + // GET SEGEMENT DATA THREAD void vGetSegmentData(ULONG thread_input) { @@ -299,35 +388,51 @@ 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); - 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, analyzer, false, + &hspi2); + segment_send_s_adc_cell_data(analyzer); + 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 = 30000; 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; + const bool charging = (current_state == CHARGING); + const 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 && !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(); @@ -349,26 +454,30 @@ 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, analyzer, + charging, &hspi2); + segment_send_s_adc_cell_data(analyzer); + start_timer(&open_wire_timer, open_wire_test_frequency); + } + + if (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); + segment_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 c3e374c5..6dc9f1ff 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++) { @@ -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 @@ -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]); } @@ -401,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; @@ -411,6 +422,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 +477,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 +529,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 +540,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 +551,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 +584,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 */ 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/Core/Src/state_machine.c b/Core/Src/state_machine.c index 9914d51c..8a2c996f 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" @@ -18,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 */ @@ -29,6 +34,9 @@ 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 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); @@ -42,8 +50,38 @@ 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) { + 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( state_machine_args); // initialize eval table with crit and non crit faults @@ -78,9 +116,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); @@ -92,16 +128,16 @@ void handle_charging(state_machine_args_t *state_machine_args) if (sm_charging_check(state_machine_args)) { /* Send CAN message, but not too often */ if (is_timer_expired(&state_machine_args->state_machine - ->charger_message_timer) || + ->charger_message_timer) || !is_timer_active(&state_machine_args->state_machine - ->charger_message_timer)) { + ->charger_message_timer)) { send_bms_charge_message_send((MAX_CHARGE_VOLT * (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); @@ -139,6 +175,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); @@ -153,7 +190,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]( @@ -164,42 +200,59 @@ 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) { - /* 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: @@ -222,8 +275,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) { @@ -247,172 +302,164 @@ 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 -* 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; - - // 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; - return false; - } - - 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) { + bool charging_allowed = false; + 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) { + 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 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; + 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; - // TODO: replace with mutexed getter - if (analyzer->max_voltage.val <= BAL_MIN_V) - return false; - if (analyzer->delt_voltage <= MAX_DELTA_V) - return false; + mutex_get(&analyzer_mutex); + max_voltage = analyzer->max_voltage.val; + delta_voltage = analyzer->delta_voltage; + mutex_put(&analyzer_mutex); - // Do not balance during settling. - // if (state_machine->charging_stage != LONG_SETTLE && - // state_machine->charging_stage != SHORT_SETTLE) { - // return false; - // } + mutex_get(&state_mutex); + charging_stage = state_machine->charging_stage; + mutex_put(&state_mutex); - // Do not balance if the shutdown circuit is open. + 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. + mutex_get(&shutdown_mutex); + shutdown_active = + state_machine_args->peripherals->shutdown_active; + mutex_put(&shutdown_mutex); - bool shutdown_active; - mutex_get(&shutdown_mutex); - shutdown_active = state_machine_args->peripherals->shutdown_active; - mutex_put(&shutdown_mutex); + balancing_allowed = shutdown_active; + } - //return !shutdown_active; - // FSAE balancing disabled safety - return false; + return balancing_allowed; } void set_segment_comms_fault(state_machine_t *state_mach) @@ -443,6 +490,20 @@ void clear_hv_plate_comms_fault(state_machine_t *state_mach) mutex_put(&state_mutex); } +void set_cell_open_wire_fault(state_machine_t *state_mach) +{ + mutex_get(&state_mutex); + state_mach->cell_open_wire_fault_flag = true; + mutex_put(&state_mutex); +} + +void clear_cell_open_wire_fault(state_machine_t *state_mach) +{ + mutex_get(&state_mutex); + state_mach->cell_open_wire_fault_flag = false; + mutex_put(&state_mutex); +} + bool get_fault(fault_code_t fault) { return (fault_flags & (1 << fault)) != 0; @@ -472,6 +533,15 @@ 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 }; + + 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 = @@ -481,7 +551,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 = @@ -495,9 +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 = + ow_fault; } else { fault_eval_table[DISCHARGE_LIMIT_ENFORCEMENT_FAULT] = (fault_eval_t){ .id = "Discharge Current Limit", @@ -513,8 +585,8 @@ 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, - .lim_1 = bms_algos->cont_CCL, + .optype_1 = LT, + .lim_1 = (-1.0f * bms_algos->cont_CCL), .timeout = OVER_CHG_CURR_TIME, .optype_2 = NOP, // UNUSED .is_critical = true }; @@ -576,10 +648,10 @@ 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 = 0, + .timeout = COMMS_FAULT_TIME, .optype_2 = NOP, // UNUSED .is_critical = false }; @@ -587,10 +659,21 @@ 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 = 0, + .timeout = COMMS_FAULT_TIME, + .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 = ow_fault, + .optype_1 = EQ, + .lim_1 = true, + .timeout = OW_FAULT_TIME, .optype_2 = NOP, // UNUSED .is_critical = true }; @@ -609,9 +692,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; - state_machine->balancing_active = false; - state_machine->is_charger_connected = false; + init_boot(state_machine_args); nertimer_t telem_timer; // sends unimportant telemetry messages every 500ms @@ -619,13 +700,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), @@ -636,7 +718,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/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..ade4cb0f 160000 --- a/Drivers/Odyssey-Definitions +++ b/Drivers/Odyssey-Definitions @@ -1 +1 @@ -Subproject commit 20dcdc2d5a618d11cf713bb89f21f4a7192f1417 +Subproject commit ade4cb0fa838158e5849f9888c2503f2cb193c0f 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 diff --git a/Tests/Inc/test_current_limit_algo_config.h b/Tests/Inc/test_current_limit_algo_config.h index ab7f4a81..4169b08d 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) @@ -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); diff --git a/Tests/Src/test_cell_temp_sanitizer.c b/Tests/Src/test_cell_temp_sanitizer.c new file mode 100644 index 00000000..af319ba6 --- /dev/null +++ b/Tests/Src/test_cell_temp_sanitizer.c @@ -0,0 +1,245 @@ +#include "unity.h" + +#include "cell_temp_sanitizer.h" +#include "shep_mutexes.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/Src/test_charging.c b/Tests/Src/test_charging.c new file mode 100644 index 00000000..fe226c9c --- /dev/null +++ b/Tests/Src/test_charging.c @@ -0,0 +1,162 @@ +#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; + float percentage; + } ranges[] = { + { 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]); + 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()); + 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) +{ + 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_dcl.c b/Tests/Src/test_dcl.c index 8d586cd8..c0f1ae69 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; @@ -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(75.0f, 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; @@ -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); @@ -370,4 +370,4 @@ int main(void) RUN_TEST(test_cont_dcl_pulse_state_transitions); return UNITY_END(); -} \ 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(); diff --git a/Tests/Src/test_statemachine.c b/Tests/Src/test_statemachine.c index b6384cea..5c7472e1 100644 --- a/Tests/Src/test_statemachine.c +++ b/Tests/Src/test_statemachine.c @@ -12,17 +12,26 @@ 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); init_boot(&args); } @@ -69,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(); -} \ No newline at end of file +} diff --git a/Tests/ner_test.conf b/Tests/ner_test.conf index be79581b..7515dce4 100644 --- a/Tests/ner_test.conf +++ b/Tests/ner_test.conf @@ -56,6 +56,27 @@ sources = [ "Core/Src/soc.c" ] +[test-packages.cell_temp_sanitizer] +mocked-files = [ + "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/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", @@ -86,6 +107,14 @@ 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.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"