diff --git a/.vscode/launch.json b/.vscode/launch.json index 7d9b30d41..14289e2ec 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -239,4 +239,4 @@ } } ] -} \ No newline at end of file +} diff --git a/can_library/configs/nodes/PDU.json b/can_library/configs/nodes/PDU.json index d18ce9023..d0fdf6056 100644 --- a/can_library/configs/nodes/PDU.json +++ b/can_library/configs/nodes/PDU.json @@ -32,7 +32,7 @@ "unit": "mV" } ], - "msg_period": 500, + "msg_period": 100, "msg_priority": 4 }, { @@ -52,7 +52,7 @@ "unit": "mA" } ], - "msg_period": 500, + "msg_period": 100, "msg_priority": 4 }, { @@ -82,7 +82,7 @@ "length": 12 } ], - "msg_period": 500, + "msg_period": 100, "msg_priority": 4 }, { @@ -102,7 +102,7 @@ "length": 12 } ], - "msg_period": 500, + "msg_period": 100, "msg_priority": 4 }, { @@ -140,7 +140,7 @@ "length": 12 } ], - "msg_period": 500, + "msg_period": 100, "msg_priority": 4 }, { @@ -197,7 +197,7 @@ "unit": "LPM" } ], - "msg_period": 200, + "msg_period": 100, "msg_priority": 5 }, { @@ -211,7 +211,7 @@ "unit": "C" } ], - "msg_period": 500, + "msg_period": 100, "msg_priority": 5 }, { diff --git a/source/pdu/CMakeLists.txt b/source/pdu/CMakeLists.txt index a07efd8e1..44d0f909f 100644 --- a/source/pdu/CMakeLists.txt +++ b/source/pdu/CMakeLists.txt @@ -4,4 +4,4 @@ add_firmware_component( NAME pdu LINKER_SCRIPT "STM32F407VGTx_FLASH" LIBS "can_node_PDU;CMSIS_F407;PHAL_F407;FREERTOS_F407;BANGBANG;HEARTBEAT;WATCHDOG" -) \ No newline at end of file +) diff --git a/source/pdu/README.md b/source/pdu/README.md index 29746be4e..c4306ebab 100644 --- a/source/pdu/README.md +++ b/source/pdu/README.md @@ -1,2 +1,85 @@ -## PDU -PDU is responsible for controlling and monitoring critical power rails and cooling loops. \ No newline at end of file +# PDU Firmware + +PDU controls and monitors low-voltage power rails and cooling hardware for the car. + +## Responsibilities + +- Control switchable rails (GPIO outputs). +- Sample ADC channels for rail voltage/current telemetry. +- Scan rail nFAULT lines and report/latch faults. +- Control fan/pump/hxfan outputs and fan PWM duty cycles. +- Publish PDU telemetry and coolant output status over CAN. + +## Module Layout + +- `main.c` + - Board init, peripheral init, task definitions, task startup order. + - No business logic beyond orchestration. + +- `state.h/.c` + - Shared runtime state (`g_pdu_state`) for all PDU logic. + +- `switches.h/.c` + - Switch command/read API. + - ADC and mux measurement pipeline. + - Rail current/voltage conversions. + - Default-rail enable sequence. + +- `faults.h/.c` + - Table-driven nFAULT polling. + - One-fault-slot-per-cycle update cadence. + - Rail fault LED behavior. + +- `cooling.h/.c` + - Cooling policy computation. + - Cooling actuation (switches + fan PWM). + - Cooling CAN output publication. + +- `cooling_bangbang.h/.c` + - Optional bang-bang policy backend. + - Compiled in but disabled by default. + +- `telemetry.h/.c` + - Power/thermal telemetry CAN publication. + - Flowrate telemetry CAN publication. + +- Hardware adapter modules (kept separate): + - `led/` + - `fan_control/` + - `flow_rate/` + +## Runtime State (`g_pdu_state`) + +The shared state contains: + +- `rail_voltage_mv` for 24V/5V/3V3 measurements. +- `switch_current_ma[]` for sensed currents (switch and rail channels). +- `mux_adc_counts[]` and `next_mux_channel` for mux-sampled inputs. +- `next_rail_fault_index` for staggered rail fault scanning. +- `cooling_command` for requested/applied cooling outputs. + +## Task Model + +Defined in `main.c`: + +- `CAN_rx_update` (0 ms) +- `CAN_tx_update` (5 ms) +- `switches_periodic` (15 ms) +- `cooling_periodic` (100 ms) +- `faults_periodic` (100 ms) +- `fault_library_periodic` (100 ms) +- `telemetry_flow_periodic` (200 ms) +- `LED_periodic` (500 ms) +- `telemetry_power_periodic` (500 ms) +- Heartbeat task (LED sweep callback) + +## Cooling Backend: Bang-Bang (Disabled by Default) + +The bang-bang backend is present for future policy work, but disabled to preserve current behavior. + +- Toggle: `COOLING_ENABLE_BANGBANG` in `cooling_bangbang.h` + - `0` (default): backend is a no-op. + - `1`: motor temp based bang-bang updates are applied. + +Current non-bangbang behavior remains the active policy path. + diff --git a/source/pdu/auto_switch/auto_switch.c b/source/pdu/auto_switch/auto_switch.c deleted file mode 100644 index f3287dac2..000000000 --- a/source/pdu/auto_switch/auto_switch.c +++ /dev/null @@ -1,333 +0,0 @@ -/** - * @file auto_switch.c - * @author Gavin Zyonse (gzyonse@purdue.edu) - * @brief - * @version 1.0 - * @date 2023-11-09 - * - * @copyright Copyright (c) 2023 - * - */ -#include "auto_switch.h" - -#include "can_library/faults_common.h" -#include "can_library/generated/PDU.h" -#include "common/phal/gpio.h" -#include "led.h" -#include "main.h" - -// Initialize struct -auto_switches_t auto_switches; -static uint16_t mux_readings[6]; -static uint8_t mux_index; - -// Static function declarations -static void updateCurrent(); -static void updateVoltage(); -static uint16_t calcCurrent_HP(uint16_t); -static uint16_t calcCurrent_LP(uint16_t); -static void calcCurrent_Total(); -static uint16_t calcVoltage(uint16_t, int, int); -static void setMuxChannel(uint8_t channel); -static void updateMux(); - -// Called periodically, Calculates current through each switch in mA -void updateCurrent() { - // High power switches (direct ADC) - auto_switches.current[SW_PUMP_1] = calcCurrent_HP(adc_readings.pump_1_imon); - auto_switches.current[SW_PUMP_2] = calcCurrent_HP(adc_readings.pump_2_imon); - auto_switches.current[SW_SDC] = calcCurrent_HP(adc_readings.sdc_imon); - auto_switches.current[SW_HXFAN] = calcCurrent_HP(adc_readings.hxfan_imon); - - // High power switches (mux-sensed) - auto_switches.current[SW_FAN_1] = calcCurrent_HP(mux_readings[0]); - auto_switches.current[SW_FAN_2] = calcCurrent_HP(mux_readings[1]); - auto_switches.current[SW_FAN_3] = calcCurrent_HP(mux_readings[2]); - auto_switches.current[SW_FAN_4] = calcCurrent_HP(mux_readings[3]); - auto_switches.current[SW_AMK1] = calcCurrent_HP(mux_readings[4]); - auto_switches.current[SW_AMK2] = calcCurrent_HP(mux_readings[5]); - - // Low power switches - auto_switches.current[SW_DASH] = calcCurrent_LP(adc_readings.dash_cs); - auto_switches.current[SW_ABOX] = calcCurrent_LP(adc_readings.abox_cs); - auto_switches.current[SW_MAIN] = calcCurrent_LP(adc_readings.main_cs); - auto_switches.current[SW_DLFR] = calcCurrent_LP(adc_readings.dlfr_cs); - auto_switches.current[SW_DLBK] = calcCurrent_LP(adc_readings.dlbk_cs); - - // Upstream CS - calcCurrent_Total(); -} - -// Called periodically, Updates voltage for each rail in mV -void updateVoltage() { - auto_switches.voltage.in_24v = calcVoltage(adc_readings.v24_vs, LV_24V_R1, LV_24V_R2); - auto_switches.voltage.out_5v = calcVoltage(adc_readings.v5_vs, LV_5V_R1, LV_5V_R2); - auto_switches.voltage.out_3v3 = calcVoltage(adc_readings.v3v3_vs, LV_3V3_R1, LV_3V3_R2); -} - -static void updateMux() { - mux_readings[mux_index] = adc_readings.mux_out; - mux_index = (uint8_t)((mux_index + 1U) % 6U); - setMuxChannel(mux_index); -} - -uint16_t getMuxReading(uint8_t channel) { - if (channel >= 6U) { - return 0; - } - - return mux_readings[channel]; -} - -static void setMuxChannel(uint8_t channel) { - PHAL_writeGPIO(MUX_CTRL_A_GPIO_Port, MUX_CTRL_A_Pin, (channel & 0x01U) != 0U); - PHAL_writeGPIO(MUX_CTRL_B_GPIO_Port, MUX_CTRL_B_Pin, (channel & 0x02U) != 0U); - PHAL_writeGPIO(MUX_CTRL_C_GPIO_Port, MUX_CTRL_C_Pin, (channel & 0x04U) != 0U); -} - -// Current helper functions -uint16_t calcCurrent_HP(uint16_t current) { - current = current * ADC_REF_mV / ADC_MAX; // Convert to mV - current = current * (HP_CS_R1 + HP_CS_R2) / HP_CS_R2; - current = current * HP_CS_R3 / (HP_CS_R1 + HP_CS_R2); - return current; -} - -uint16_t calcCurrent_LP(uint16_t current) { - current = current * ADC_REF_mV / ADC_MAX; // Convert to mA - return current; -} - -// CS signals for upstream 24V and 5V (total current through each) -void calcCurrent_Total() { - // 24V current - uint16_t current = adc_readings.v24_cs; - current = current * ADC_REF_mV / ADC_MAX; // Convert to mV - current = current / HP_CS_R_SENSE / CS_GAIN; - auto_switches.current[CS_24V] = current; - - // 5V current - current = adc_readings.v5_cs; - current = current * ADC_REF_mV / ADC_MAX; // Convert to mA - auto_switches.current[CS_5V] = current; -} - -// Converts ADC voltage reading to mV -uint16_t calcVoltage(uint16_t voltage, int r1, int r2) { - // Compensate for voltage divider - voltage = voltage * (r1 + r2) / r2; - // Scale for 12-bit adc at 3.3v - voltage = voltage * ADC_REF_mV / ADC_MAX; - return voltage; -} - -// Enable or disable switches by name -void setSwitch(switches_t auto_switch_enum, bool state) { - switch (auto_switch_enum) { - case CS_24V: - case CS_5V: - case CS_SWITCH_COUNT: - return; - case SW_PUMP_1: - PHAL_writeGPIO(PUMP_1_CTRL_GPIO_Port, PUMP_1_CTRL_Pin, state); - LED_control(LED_PUMP_1, state); - break; - case SW_PUMP_2: - PHAL_writeGPIO(PUMP_2_CTRL_GPIO_Port, PUMP_2_CTRL_Pin, state); - LED_control(LED_PUMP_2, state); - break; - case SW_SDC: - // NoToggle switch (always on) - LED_control(LED_SDC, state); - break; - case SW_AMK1: - case SW_AMK2: - // NoToggle switches (always on, no LED on driver) - break; - case SW_HXFAN: - PHAL_writeGPIO(HXFAN_CTRL_GPIO_Port, HXFAN_CTRL_Pin, state); - LED_control(LED_HXFAN, state); - break; - case SW_FAN_1: - PHAL_writeGPIO(FAN_1_CTRL_GPIO_Port, FAN_1_CTRL_Pin, state); - break; - case SW_FAN_2: - PHAL_writeGPIO(FAN_2_CTRL_GPIO_Port, FAN_2_CTRL_Pin, state); - break; - case SW_FAN_3: - PHAL_writeGPIO(FAN_3_CTRL_GPIO_Port, FAN_3_CTRL_Pin, state); - break; - case SW_FAN_4: - PHAL_writeGPIO(FAN_4_CTRL_GPIO_Port, FAN_4_CTRL_Pin, state); - break; - case SW_BLT: - PHAL_writeGPIO(BLT_CTRL_GPIO_Port, BLT_CTRL_Pin, state); - LED_control(LED_BLT, state); - break; - case SW_CRIT_5V: - PHAL_writeGPIO(CRIT_5V_CTRL_GPIO_Port, CRIT_5V_CTRL_Pin, state); - LED_control(LED_5V_CRIT, state); - break; - case SW_MAIN: - LED_control(LED_MAIN, state); - break; - case SW_DLFR: - PHAL_writeGPIO(DLFR_CTRL_GPIO_Port, DLFR_CTRL_Pin, state); - LED_control(LED_DLFR, state); - break; - case SW_DLBK: - PHAL_writeGPIO(DLBK_CTRL_GPIO_Port, DLBK_CTRL_Pin, state); - LED_control(LED_DLBK, state); - break; - case SW_ABOX: - LED_control(LED_ABOX, state); - break; - case SW_DASH: - LED_control(LED_DASH, state); - break; - case SW_TV: - PHAL_writeGPIO(TV_CTRL_GPIO_Port, TV_CTRL_Pin, state); - LED_control(LED_TV, state); - break; - case SW_DAQ: - // NoToggle switch (always on) - LED_control(LED_DAQ, state); - break; - case SW_FAN_5V: - PHAL_writeGPIO(FAN_5V_CTRL_GPIO_Port, FAN_5V_CTRL_Pin, state); - LED_control(LED_5V_FAN, state); - break; - } -} - -// Get switch state and return it -bool getSwitchStatus(switches_t auto_switch_enum) { - bool status; - switch (auto_switch_enum) { - case SW_PUMP_1: - status = PHAL_readGPIO(PUMP_1_CTRL_GPIO_Port, PUMP_1_CTRL_Pin); - break; - case SW_PUMP_2: - status = PHAL_readGPIO(PUMP_2_CTRL_GPIO_Port, PUMP_2_CTRL_Pin); - break; - case SW_HXFAN: - status = PHAL_readGPIO(HXFAN_CTRL_GPIO_Port, HXFAN_CTRL_Pin); - break; - case SW_FAN_1: - status = PHAL_readGPIO(FAN_1_CTRL_GPIO_Port, FAN_1_CTRL_Pin); - break; - case SW_FAN_2: - status = PHAL_readGPIO(FAN_2_CTRL_GPIO_Port, FAN_2_CTRL_Pin); - break; - case SW_FAN_3: - status = PHAL_readGPIO(FAN_3_CTRL_GPIO_Port, FAN_3_CTRL_Pin); - break; - case SW_FAN_4: - status = PHAL_readGPIO(FAN_4_CTRL_GPIO_Port, FAN_4_CTRL_Pin); - break; - case SW_BLT: - status = PHAL_readGPIO(BLT_CTRL_GPIO_Port, BLT_CTRL_Pin); - break; - case SW_CRIT_5V: - status = PHAL_readGPIO(CRIT_5V_CTRL_GPIO_Port, CRIT_5V_CTRL_Pin); - break; - case SW_TV: - status = PHAL_readGPIO(TV_CTRL_GPIO_Port, TV_CTRL_Pin); - break; - case SW_FAN_5V: - status = PHAL_readGPIO(FAN_5V_CTRL_GPIO_Port, FAN_5V_CTRL_Pin); - break; - case SW_DLFR: - status = PHAL_readGPIO(DLFR_CTRL_GPIO_Port, DLFR_CTRL_Pin); - break; - case SW_DLBK: - status = PHAL_readGPIO(DLBK_CTRL_GPIO_Port, DLBK_CTRL_Pin); - break; - default: - status = 1; // Non-controllable switches are always on - } - - return status; -} - -void autoSwitchPeriodic() { - updateMux(); - updateCurrent(); - updateVoltage(); -} - -void checkSwitchFaults() { - // Get status of all switches - uint8_t dash_faulted = !PHAL_readGPIO(DASH_NFLT_GPIO_Port, DASH_NFLT_Pin); - uint8_t abox_faulted = !PHAL_readGPIO(ABOX_NFLT_GPIO_Port, ABOX_NFLT_Pin); - uint8_t main_faulted = !PHAL_readGPIO(MAIN_NFLT_GPIO_Port, MAIN_NFLT_Pin); - uint8_t vcrit_faulted = !PHAL_readGPIO(CRIT_5V_NFLT_GPIO_Port, CRIT_5V_NFLT_Pin); - uint8_t vnc_faulted = !PHAL_readGPIO(TV_NFLT_GPIO_Port, TV_NFLT_Pin); - uint8_t dlfr_faulted = !PHAL_readGPIO(DLFR_NFLT_GPIO_Port, DLFR_NFLT_Pin); - uint8_t dlbk_faulted = !PHAL_readGPIO(DLBK_NFLT_GPIO_Port, DLBK_NFLT_Pin); - uint8_t bullet_faulted = !PHAL_readGPIO(BLT_NFLT_GPIO_Port, BLT_NFLT_Pin); - uint8_t fan5v_faulted = !PHAL_readGPIO(FAN_5V_NFLT_GPIO_Port, FAN_5V_NFLT_Pin); - (void) fan5v_faulted; // not used for now - - static uint8_t fault_num = 0; - // Set fault - this is too much for our 1ms window, so send each fault separately - switch (fault_num++) { - case 0: - update_fault(FAULT_ID_DASH_RAIL, dash_faulted); - LED_control( - LED_DASH, - is_latched(FAULT_ID_DASH_RAIL) ? LED_BLINK : LED_OFF - ); - break; - case 1: - update_fault(FAULT_ID_ABOX_RAIL, abox_faulted); - LED_control( - LED_ABOX, - is_latched(FAULT_ID_ABOX_RAIL) ? LED_BLINK : LED_OFF - ); - break; - case 2: - update_fault(FAULT_ID_MAIN_RAIL, main_faulted); - LED_control( - LED_MAIN, - is_latched(FAULT_ID_MAIN_RAIL) ? LED_BLINK : LED_OFF - ); - break; - case 3: - update_fault(FAULT_ID_V_CRIT, vcrit_faulted); - LED_control( - LED_5V_CRIT, - is_latched(FAULT_ID_V_CRIT) ? LED_BLINK : LED_OFF - ); - break; - case 4: - update_fault(FAULT_ID_V_NONCRIT, vnc_faulted); - LED_control( - LED_TV, - is_latched(FAULT_ID_V_NONCRIT) ? LED_BLINK : LED_OFF - ); - break; - case 5: - update_fault(FAULT_ID_FRONT_DRIVELINE_RAIL, dlfr_faulted); - LED_control( - LED_DLFR, - is_latched(FAULT_ID_FRONT_DRIVELINE_RAIL) ? LED_BLINK : LED_OFF - ); - break; - case 6: - update_fault(FAULT_ID_REAR_DRIVELINE_RAIL, dlbk_faulted); - LED_control( - LED_DLBK, - is_latched(FAULT_ID_REAR_DRIVELINE_RAIL) ? LED_BLINK : LED_OFF - ); - break; - case 7: - update_fault(FAULT_ID_BULLET_RAIL, bullet_faulted); - LED_control( - LED_BLT, - is_latched(FAULT_ID_BULLET_RAIL) ? LED_BLINK : LED_OFF - ); - fault_num = 0; - break; - } -} diff --git a/source/pdu/auto_switch/auto_switch.h b/source/pdu/auto_switch/auto_switch.h deleted file mode 100644 index aca76d1f6..000000000 --- a/source/pdu/auto_switch/auto_switch.h +++ /dev/null @@ -1,133 +0,0 @@ -/** - * @file auto_switch.h - * @author Gavin Zyonse (gzyonse@purdue.edu) - * @brief - * @version 1.0 - * @date 2023-11-09 - * - * @copyright Copyright (c) 2023 - * - */ -#ifndef _AUTO_SWITCH_H_ -#define _AUTO_SWITCH_H_ - -#include -#include -#include - -// Static variables -#define SHUNT_R 10000000 -#define ADC_MAX 4095 - -// Voltage sense resistors -#define LV_24V_R1 47000 // Ohms -#define LV_24V_R2 3400 -#define LV_5V_R1 4300 -#define LV_5V_R2 3400 -#define LV_3V3_R1 4300 -#define LV_3V3_R2 10000 -#define AMK_24V_R1 124300 -#define AMK_24V_R2 10000 - -// HP Current sense resistors -#define HP_CS_R1 180 // Ohms -#define HP_CS_R2 330 // Ohms -#define HP_CS_R3 500 // Ohms - -// Upstream Current sense -#define HP_CS_R_SENSE 0.002 // Ohms -#define CS_GAIN 100 - -// Enumeration -typedef enum { - // High power switches - SW_PUMP_1, - SW_PUMP_2, - SW_SDC, - SW_HXFAN, - // High power switches (mux-sensed) - SW_FAN_1, - SW_FAN_2, - SW_FAN_3, - SW_FAN_4, - SW_AMK1, - SW_AMK2, - - // Low power switches - SW_DASH, - SW_ABOX, - SW_MAIN, - SW_DLFR, - SW_DLBK, - - // Not actually switches - CS_24V, - CS_5V, - - // Number of switches with CS signals (used for array bounds) - // If switch has a current sense circuit, PLACE IT ABOVE THIS COMMENT - CS_SWITCH_COUNT, - - // Low power switches (no CS) - SW_BLT, - // 5V switches (no CS) - SW_CRIT_5V, - SW_TV, - SW_DAQ, - SW_FAN_5V -} switches_t; - -// Structures -typedef struct { - uint16_t in_24v; - uint16_t out_5v; - uint16_t out_3v3; -} voltage_t; // Voltage in mV - -typedef struct { - uint16_t current[CS_SWITCH_COUNT]; // Current in mA - voltage_t voltage; -} auto_switches_t; - -extern auto_switches_t auto_switches; - -// Function declarations -/** - * @brief Enable or disable a switch - * - * @param auto_switch_enum Switch to enable or disable - * @param state 1 to enable, 0 to disable -*/ -void setSwitch(switches_t auto_switch_enum, bool state); - -/** - * @brief Check if a switch is enabled - * - * @param auto_switch_enum Switch to check - * @return 1 if enabled, 0 if disabled -*/ -bool getSwitchStatus(switches_t auto_switch_enum); - -/** - * @brief Combined function that calls updateVoltage() - * and updateCurrent() - * - * Voltage on each rail is stored at: - * auto_switches.voltage.in_24v - * auto_switches.voltage.out_5v - * auto_switches.voltage.out_3v3 - * - * Current through each switch is stored at: - * auto_switches.current[auto_switch_enum] -*/ -void autoSwitchPeriodic(); - -/** - * @brief Checks the status of various auto switches to ensure rails are ok. Sets fault and activates blink indicator if not.. - * - */ -void checkSwitchFaults(); - -uint16_t getMuxReading(uint8_t channel); - -#endif diff --git a/source/pdu/cooling/cooling.c b/source/pdu/cooling/cooling.c index cb3d9da01..3d943fd22 100644 --- a/source/pdu/cooling/cooling.c +++ b/source/pdu/cooling/cooling.c @@ -1,226 +1,99 @@ -/** - * @file cooling.c - * @author Nicolas Vera (nverapae@purdue.edu) - * @brief - * @version 0.1 - * @date 2024-2-29 - * - * @copyright Copyright (c) 2024 - * - */ #include "cooling.h" -#include "auto_switch.h" -#include "common/bangbang/bangbang.h" +#include "can_library/generated/PDU.h" +#include "cooling_bangbang.h" #include "fan_control.h" -#include "led.h" - -volatile cooling_request_t cr; -static void calculate_cooling_periodic(); - -static void set_pump1_on(); -static void set_pump1_off(); -static void set_fan1_on(); -static void set_fan1_off(); -static void set_pump2_on(); -static void set_pump2_off(); -static void set_fan2_on(); -static void set_fan2_off(); - -static bool batt_fan_autospeed; -static bool motor_fan_autospeed; - -INIT_BANG_BANG(batt_pump_controller, - BATT_PUMP_ON_TEMP_C, - BATT_PUMP_OFF_TEMP_C, - set_pump1_on, - set_pump1_off, - BANGBANG_MIN_SWITCH_MS) - -INIT_BANG_BANG(batt_fan_controller, - BATT_FAN_ON_TEMP_C, - BATT_FAN_OFF_TEMP_C, - set_fan1_on, - set_fan1_off, - BANGBANG_MIN_SWITCH_MS) - -INIT_BANG_BANG(motor_pump_controller, - MOTOR_PUMP_ON_TEMP_C, - MOTOR_PUMP_OFF_TEMP_C, - set_pump2_on, - set_pump2_off, - BANGBANG_MIN_SWITCH_MS) - -INIT_BANG_BANG(motor_fan_controller, - MOTOR_FAN_ON_TEMP_C, - MOTOR_FAN_OFF_TEMP_C, - set_fan2_on, - set_fan2_off, - BANGBANG_MIN_SWITCH_MS) - -void coolingInit() { - cr.fan1_speed = 10; - cr.fan2_speed = 10; - cr.fan3_speed = 10; - cr.fan4_speed = 10; - cr.fan1_status = true; - cr.fan2_status = true; - cr.fan3_status = true; - cr.fan4_status = true; - cr.pump1_status = true; - cr.pump2_status = true; - cr.hxfan_status = true; - batt_fan_autospeed = false; - motor_fan_autospeed = false; -} +#include "state.h" +#include "switches.h" -void update_cooling_periodic() { - // Update switch statuses based off values in cr struct - calculate_cooling_periodic(); - - setSwitch(SW_FAN_1, cr.fan1_status); - setSwitch(SW_FAN_2, cr.fan2_status); - setSwitch(SW_FAN_3, cr.fan3_status); - setSwitch(SW_FAN_4, cr.fan4_status); - setSwitch(SW_PUMP_1, cr.pump1_status); - setSwitch(SW_PUMP_2, cr.pump2_status); - setSwitch(SW_HXFAN, cr.hxfan_status); - - // Set fan speeds based off of fan speeds in cr struct - if (cr.fan1_status) - setFan1Speed(cr.fan1_speed); - else - setFan1Speed(0); - if (cr.fan2_status) - setFan2Speed(cr.fan2_speed); - else - setFan2Speed(0); - if (cr.fan3_status) - setFan3Speed(cr.fan3_speed); - else - setFan3Speed(0); - if (cr.fan4_status) - setFan4Speed(cr.fan4_speed); - else - setFan4Speed(0); - - CAN_SEND_coolant_out(cr.fan1_speed, - cr.fan2_speed, - cr.pump2_status, - cr.hxfan_status, - cr.pump1_status); -} +typedef enum { + COOLING_STATE_AUTO = 0, + COOLING_STATE_MANUAL = 1, +} cooling_state_t; + +static cooling_state_t cooling_state = COOLING_STATE_AUTO; +static cooling_state_t next_cooling_state = COOLING_STATE_AUTO; + +static inline void auto_periodic(void) { + g_pdu_state.cooling_command.fan1_enabled = true; + if (g_pdu_state.cooling_command.batt_fan_autospeed_enabled) { + g_pdu_state.cooling_command.fan1_percent = 100; + } + + g_pdu_state.cooling_command.fan2_enabled = true; + if (g_pdu_state.cooling_command.motor_fan_autospeed_enabled) { + g_pdu_state.cooling_command.fan2_percent = 100; + } + + g_pdu_state.cooling_command.fan3_enabled = g_pdu_state.cooling_command.fan1_enabled; + g_pdu_state.cooling_command.fan3_percent = g_pdu_state.cooling_command.fan1_percent; + g_pdu_state.cooling_command.fan4_enabled = g_pdu_state.cooling_command.fan2_enabled; + g_pdu_state.cooling_command.fan4_percent = g_pdu_state.cooling_command.fan2_percent; -static void calculate_cooling_periodic() { - // GPS not stale implies GPS fix - // bool not_moving = !can_data.gps_speed.stale && can_data.gps_speed.gps_speed <= GPS_SPEED_MOVING; - // bool not_moving = true; // TODO temp until we have GPS data - - - // uint32_t now_ms = OS_TICKS; - - // PUMP1: Battery pumps - // Enable if above 30C - // TODO handle stale - // if (can_data.max_cell_temp.stale) { - // batt_pump_controller.last_switch_ms = now_ms; - // set_pump1_off(); - // } else { - // bangbang_update(&batt_pump_controller, can_data.max_cell_temp.max_temp / 10.0f, now_ms); - // } - - // // FAN1: Battery fans - // // Enable battery fans only when car stopped since there is little airflow - // if (can_data.max_cell_temp.stale || !not_moving) { - // batt_fan_controller.last_switch_ms = now_ms; - // set_fan1_off(); - // } else { - // bangbang_update(&batt_fan_controller, can_data.max_cell_temp.max_temp / 10.0f, now_ms); - // } - - // PUMP2: Motor pumps - // Enable motor pumps if above 60C - // if (can_data.motor_temps.stale) { - // motor_pump_controller.last_switch_ms = now_ms; - // set_pump2_off(); - // } else { - // float motor_temp = (float)MAX(can_data.motor_temps.front_right, - // can_data.motor_temps.front_left); - // bangbang_update(&motor_pump_controller, motor_temp, now_ms); - // } - - // // FAN2: Motor fans - // // Enable motor fans if (above 100C) || (car stopped && temp > 60) - // if (can_data.motor_temps.stale) { - // motor_fan_controller.last_switch_ms = now_ms; - // set_fan2_off(); - // } else { - // float motor_temp = (float)MAX(can_data.motor_temps.front_left, - // can_data.motor_temps.front_right); - // if (not_moving) { - // bangbang_update(&motor_fan_controller, motor_temp, now_ms); - // } else if (motor_temp < MOTOR_COOLING_ENABLE_TEMP) { - // motor_fan_controller.last_switch_ms = now_ms; - // set_fan2_off(); - // } else { - // bangbang_update(&motor_fan_controller, motor_temp, now_ms); - // } - // } - - // todo cooling - - set_fan1_on(); - set_fan2_on(); - // todo: turn this on when pumps are harnessed - // set_pump1_on(); - // set_pump2_on(); - - cr.fan3_status = cr.fan1_status; - cr.fan3_speed = cr.fan1_speed; - cr.fan4_status = cr.fan2_status; - cr.fan4_speed = cr.fan2_speed; + cooling_bangbang_update(&g_pdu_state.cooling_command); } -static void set_pump1_on() { - cr.pump1_status = true; +static inline void manual_periodic(void) { + // Dashboard manual commands are not wired yet, so fall back to AUTO behavior. + auto_periodic(); } -static void set_pump1_off() { - cr.pump1_status = false; +static inline void set_switches(void) { + switches_set_state(SW_FAN_1, g_pdu_state.cooling_command.fan1_enabled); + switches_set_state(SW_FAN_2, g_pdu_state.cooling_command.fan2_enabled); + switches_set_state(SW_FAN_3, g_pdu_state.cooling_command.fan3_enabled); + switches_set_state(SW_FAN_4, g_pdu_state.cooling_command.fan4_enabled); + switches_set_state(SW_PUMP_1, g_pdu_state.cooling_command.pump1_enabled); + switches_set_state(SW_PUMP_2, g_pdu_state.cooling_command.pump2_enabled); + switches_set_state(SW_HXFAN, g_pdu_state.cooling_command.hxfan_enabled); } -static void set_fan1_on() { - cr.fan1_status = true; - if (batt_fan_autospeed) { - cr.fan1_speed = 100; - } +static inline void set_fans(void) { + setFan1Speed(g_pdu_state.cooling_command.fan1_enabled ? g_pdu_state.cooling_command.fan1_percent : 0); + setFan2Speed(g_pdu_state.cooling_command.fan2_enabled ? g_pdu_state.cooling_command.fan2_percent : 0); + setFan3Speed(g_pdu_state.cooling_command.fan3_enabled ? g_pdu_state.cooling_command.fan3_percent : 0); + setFan4Speed(g_pdu_state.cooling_command.fan4_enabled ? g_pdu_state.cooling_command.fan4_percent : 0); } -static void set_fan1_off() { - cr.fan1_status = false; - if (batt_fan_autospeed) { - cr.fan1_speed = 0; +static inline void cooling_fsm_periodic(void) { + next_cooling_state = cooling_state; + + switch (cooling_state) { + case COOLING_STATE_AUTO: + auto_periodic(); + break; + + case COOLING_STATE_MANUAL: + manual_periodic(); + break; + + default: + next_cooling_state = COOLING_STATE_AUTO; + break; } -} -static void set_pump2_on() { - cr.pump2_status = true; + cooling_state = next_cooling_state; } -static void set_pump2_off() { - cr.pump2_status = false; +static inline void cooling_send_outputs(void) { + CAN_SEND_coolant_out( + g_pdu_state.cooling_command.fan1_percent, + g_pdu_state.cooling_command.fan2_percent, + g_pdu_state.cooling_command.pump2_enabled, + g_pdu_state.cooling_command.hxfan_enabled, + g_pdu_state.cooling_command.pump1_enabled + ); } -static void set_fan2_on() { - cr.fan2_status = true; - if (motor_fan_autospeed) { - cr.fan2_speed = 100; - } +void cooling_init(void) { + cooling_state = COOLING_STATE_AUTO; + next_cooling_state = COOLING_STATE_AUTO; + cooling_bangbang_init(); } -static void set_fan2_off() { - cr.fan2_status = false; - if (motor_fan_autospeed) { - cr.fan2_speed = 0; - } +void cooling_periodic(void) { + cooling_fsm_periodic(); + set_switches(); + set_fans(); + cooling_send_outputs(); } diff --git a/source/pdu/cooling/cooling.h b/source/pdu/cooling/cooling.h index 07b8d5ac2..2b3990db2 100644 --- a/source/pdu/cooling/cooling.h +++ b/source/pdu/cooling/cooling.h @@ -1,74 +1,7 @@ -/** - * @file cooling.h - * @author Nicolas Vera (nverapae@purdue.edu) - * @brief - * @version 0.1 - * @date 2024-2-29 - * - * @copyright Copyright (c) 2024 - * - */ -#ifndef _COOLING_H_ -#define _COOLING_H_ +#ifndef COOLING_H +#define COOLING_H -#include "can_library/generated/PDU.h" +void cooling_init(void); +void cooling_periodic(void); -#define GPS_SPEED_MOVING 2 // TODO determine -#define MOTOR_COOLING_ENABLE_TEMP 60 -#define MOTOR_COOLING_MAX_TEMP 100 -#define BATT_COOLING_ENABLE_TEMP 30 - -#define BATT_PUMP_ON_TEMP_C BATT_COOLING_ENABLE_TEMP -#define BATT_PUMP_OFF_TEMP_C (BATT_COOLING_ENABLE_TEMP - 2) -#define BATT_FAN_ON_TEMP_C BATT_COOLING_ENABLE_TEMP -#define BATT_FAN_OFF_TEMP_C (BATT_COOLING_ENABLE_TEMP - 2) - -#define MOTOR_PUMP_ON_TEMP_C MOTOR_COOLING_ENABLE_TEMP -#define MOTOR_PUMP_OFF_TEMP_C (MOTOR_COOLING_ENABLE_TEMP - 5) - -#define MOTOR_FAN_ON_TEMP_C MOTOR_COOLING_MAX_TEMP -#define MOTOR_FAN_OFF_TEMP_C (MOTOR_COOLING_MAX_TEMP - 5) - -#define MOTOR_FAN_STOP_ON_TEMP_C MOTOR_COOLING_ENABLE_TEMP -#define MOTOR_FAN_STOP_OFF_TEMP_C (MOTOR_COOLING_ENABLE_TEMP - 5) - -#define BANGBANG_MIN_SWITCH_MS 1000 - -typedef struct { - uint16_t fan1_speed; // value from 0-100 - uint16_t fan2_speed; // value from 0-100 - uint16_t fan3_speed; // value from 0-100 - uint16_t fan4_speed; // value from 0-100 - bool pump1_status; - bool pump2_status; - bool fan1_status; - bool fan2_status; - bool fan3_status; - bool fan4_status; - bool hxfan_status; -} cooling_request_t; - -/** - * @brief Initializes cooling_request struct values to 0 - * - * @param - * @return - */ -void coolingInit(); - -/** - * @brief Periodic function that sets switches and fan speeds based off of values in the cooling_request struct - * - * @param - * @return - */ -void update_cooling_periodic(); - -/** - * @brief Callback function for cooling_driver_request message sent from dashboard, updates values in cooling_request struct based off signal values - * - * @param *msg_data_a CAN msg data - * @return - */ -void cooling_driver_request_CALLBACK(void); -#endif +#endif // COOLING_H diff --git a/source/pdu/cooling/cooling_bangbang.c b/source/pdu/cooling/cooling_bangbang.c new file mode 100644 index 000000000..8a9c74f78 --- /dev/null +++ b/source/pdu/cooling/cooling_bangbang.c @@ -0,0 +1,130 @@ +#include "cooling_bangbang.h" + +#if COOLING_ENABLE_BANGBANG + +#include "common/bangbang/bangbang.h" +#include "can_library/generated/PDU.h" + +static constexpr float MOTOR_PUMP_ON_TEMP_C = 60.0f; +static constexpr float MOTOR_PUMP_OFF_TEMP_C = 55.0f; +static constexpr float MOTOR_FAN_ON_TEMP_C = 100.0f; +static constexpr float MOTOR_FAN_OFF_TEMP_C = 95.0f; +static constexpr float MOTOR_FAN_STOP_ON_TEMP_C = 60.0f; +static constexpr uint32_t BANGBANG_MIN_SWITCH_MS = 1000; + +static pdu_cooling_command_t *g_active_command; + +static void set_pump2_on(void) { + g_active_command->pump2_enabled = true; +} + +static void set_pump2_off(void) { + g_active_command->pump2_enabled = false; +} + +static void set_fan2_on(void) { + g_active_command->fan2_enabled = true; + g_active_command->fan2_percent = 100; +} + +static void set_fan2_off(void) { + g_active_command->fan2_enabled = false; + g_active_command->fan2_percent = 0; +} + +INIT_BANG_BANG( + motor_pump_controller, + MOTOR_PUMP_ON_TEMP_C, + MOTOR_PUMP_OFF_TEMP_C, + set_pump2_on, + set_pump2_off, + BANGBANG_MIN_SWITCH_MS +) + +INIT_BANG_BANG( + motor_fan_controller, + MOTOR_FAN_ON_TEMP_C, + MOTOR_FAN_OFF_TEMP_C, + set_fan2_on, + set_fan2_off, + BANGBANG_MIN_SWITCH_MS +) + +static float cooling_hottest_motor_temp_c(void) { + float hottest = can_data.motor_temps.front_left; + + if (can_data.motor_temps.front_right > hottest) { + hottest = can_data.motor_temps.front_right; + } + + if (can_data.motor_temps.rear_left > hottest) { + hottest = can_data.motor_temps.rear_left; + } + + if (can_data.motor_temps.rear_right > hottest) { + hottest = can_data.motor_temps.rear_right; + } + + return hottest; +} + +static bool cooling_not_moving(void) { + if (can_data.main_hb.is_stale()) { + return true; + } + + return can_data.main_hb.car_state != CAR_STATE_READY2DRIVE; +} + +bool cooling_bangbang_is_enabled(void) { + return true; +} + +void cooling_bangbang_init(void) { + motor_pump_controller.is_on = false; + motor_fan_controller.is_on = false; + motor_pump_controller.last_switch_ms = 0; + motor_fan_controller.last_switch_ms = 0; +} + +void cooling_bangbang_update(pdu_cooling_command_t *cooling_command) { + if (cooling_command == nullptr) { + return; + } + + g_active_command = cooling_command; + + if (can_data.motor_temps.is_stale()) { + return; + } + + float motor_temp_c = cooling_hottest_motor_temp_c(); + uint32_t now_ms = OS_TICKS; + + bangbang_update(&motor_pump_controller, motor_temp_c, now_ms); + + bool not_moving = cooling_not_moving(); + if (not_moving) { + bangbang_update(&motor_fan_controller, motor_temp_c, now_ms); + } else if (motor_temp_c < MOTOR_FAN_STOP_ON_TEMP_C) { + motor_fan_controller.last_switch_ms = now_ms; + set_fan2_off(); + } else { + bangbang_update(&motor_fan_controller, motor_temp_c, now_ms); + } +} + +#else + +bool cooling_bangbang_is_enabled(void) { + return false; +} + +void cooling_bangbang_init(void) { +} + +void cooling_bangbang_update(pdu_cooling_command_t *cooling_command) { + (void)cooling_command; +} + +#endif diff --git a/source/pdu/cooling/cooling_bangbang.h b/source/pdu/cooling/cooling_bangbang.h new file mode 100644 index 000000000..43384215f --- /dev/null +++ b/source/pdu/cooling/cooling_bangbang.h @@ -0,0 +1,16 @@ +#ifndef COOLING_BANGBANG_H +#define COOLING_BANGBANG_H + +#include + +#include "state.h" + +#ifndef COOLING_ENABLE_BANGBANG +#define COOLING_ENABLE_BANGBANG 0 +#endif + +bool cooling_bangbang_is_enabled(void); +void cooling_bangbang_init(void); +void cooling_bangbang_update(pdu_cooling_command_t *cooling_command); + +#endif // COOLING_BANGBANG_H diff --git a/source/pdu/fan_control/fan_control.c b/source/pdu/cooling/fan_control/fan_control.c similarity index 100% rename from source/pdu/fan_control/fan_control.c rename to source/pdu/cooling/fan_control/fan_control.c diff --git a/source/pdu/fan_control/fan_control.h b/source/pdu/cooling/fan_control/fan_control.h similarity index 100% rename from source/pdu/fan_control/fan_control.h rename to source/pdu/cooling/fan_control/fan_control.h diff --git a/source/pdu/faults/faults.c b/source/pdu/faults/faults.c new file mode 100644 index 000000000..6119782a6 --- /dev/null +++ b/source/pdu/faults/faults.c @@ -0,0 +1,44 @@ +#include "faults.h" + +#include "can_library/faults_common.h" +#include "led.h" +#include "main.h" +#include "state.h" + +#include "common/phal/gpio.h" + +typedef struct { + fault_id_t fault_id; + GPIO_TypeDef *nflt_port; + uint8_t nflt_pin; + int led_id; +} pdu_rail_fault_map_t; + +static const pdu_rail_fault_map_t PDU_RAIL_FAULT_MAP[] = { + {.fault_id = FAULT_ID_DASH_RAIL, .nflt_port = DASH_NFLT_GPIO_Port, .nflt_pin = DASH_NFLT_Pin, .led_id = LED_DASH}, + {.fault_id = FAULT_ID_ABOX_RAIL, .nflt_port = ABOX_NFLT_GPIO_Port, .nflt_pin = ABOX_NFLT_Pin, .led_id = LED_ABOX}, + {.fault_id = FAULT_ID_MAIN_RAIL, .nflt_port = MAIN_NFLT_GPIO_Port, .nflt_pin = MAIN_NFLT_Pin, .led_id = LED_MAIN}, + {.fault_id = FAULT_ID_V_CRIT, .nflt_port = CRIT_5V_NFLT_GPIO_Port, .nflt_pin = CRIT_5V_NFLT_Pin, .led_id = LED_5V_CRIT}, + {.fault_id = FAULT_ID_V_NONCRIT, .nflt_port = TV_NFLT_GPIO_Port, .nflt_pin = TV_NFLT_Pin, .led_id = LED_TV}, + {.fault_id = FAULT_ID_FRONT_DRIVELINE_RAIL, .nflt_port = DLFR_NFLT_GPIO_Port, .nflt_pin = DLFR_NFLT_Pin, .led_id = LED_DLFR}, + {.fault_id = FAULT_ID_REAR_DRIVELINE_RAIL, .nflt_port = DLBK_NFLT_GPIO_Port, .nflt_pin = DLBK_NFLT_Pin, .led_id = LED_DLBK}, + {.fault_id = FAULT_ID_BULLET_RAIL, .nflt_port = BLT_NFLT_GPIO_Port, .nflt_pin = BLT_NFLT_Pin, .led_id = LED_BLT}, +}; + +void faults_init(void) { + g_pdu_state.next_rail_fault_index = 0; +} + +void faults_periodic(void) { + const uint8_t index = g_pdu_state.next_rail_fault_index; + const pdu_rail_fault_map_t *fault = &PDU_RAIL_FAULT_MAP[index]; + + const bool is_faulted = !PHAL_readGPIO(fault->nflt_port, fault->nflt_pin); + update_fault(fault->fault_id, is_faulted); + LED_control(fault->led_id, is_latched(fault->fault_id) ? LED_BLINK : LED_OFF); + + g_pdu_state.next_rail_fault_index = index + 1U; + if (g_pdu_state.next_rail_fault_index >= (sizeof(PDU_RAIL_FAULT_MAP) / sizeof(PDU_RAIL_FAULT_MAP[0]))) { + g_pdu_state.next_rail_fault_index = 0; + } +} diff --git a/source/pdu/faults/faults.h b/source/pdu/faults/faults.h new file mode 100644 index 000000000..9a705622e --- /dev/null +++ b/source/pdu/faults/faults.h @@ -0,0 +1,7 @@ +#ifndef FAULTS_H +#define FAULTS_H + +void faults_init(void); +void faults_periodic(void); + +#endif // FAULTS_H diff --git a/source/pdu/main.c b/source/pdu/main.c index 03c06934c..104a33b8a 100644 --- a/source/pdu/main.c +++ b/source/pdu/main.c @@ -7,27 +7,28 @@ * @author Irving Wang (irvingw@purdue.edu) */ -/* System Includes */ +#include "main.h" + +#include "can_library/generated/PDU.h" #include "can_library/faults_common.h" -#include "common/common_defs/common_defs.h" #include "common/freertos/freertos.h" +#include "common/heartbeat/heartbeat.h" #include "common/phal/adc.h" #include "common/phal/can.h" #include "common/phal/dma.h" #include "common/phal/gpio.h" #include "common/phal/rcc.h" -#include "common/heartbeat/heartbeat.h" #include "common/utils/countof.h" #include "common/watchdog/watchdog.h" -/* Module Includes */ -#include "auto_switch.h" -#include "can_library/generated/PDU.h" #include "cooling.h" #include "fan_control.h" +#include "faults.h" #include "flow_rate.h" #include "led.h" -#include "main.h" +#include "state.h" +#include "switches.h" +#include "telemetry.h" GPIOInitConfig_t gpio_config[] = { // Status Indicators @@ -233,71 +234,37 @@ extern uint32_t PLLClockRateHz; void HardFault_Handler(); -/* Task functions */ -void send_iv_readings(); -void send_flowrates(); +static void heartbeat_led_sweep(void) { + static int led_index = 0; + static bool decrement = false; -void sparkle_leds() { - static int led_number; - static bool led_decrement = false; - if (led_number < MAX_NUM_LED && !led_decrement) { - led_number++; - LED_control(led_number, LED_ON); - } else if (led_number >= MAX_NUM_LED && !led_decrement) { - led_decrement = true; + if (!decrement) { + LED_control(led_index, LED_ON); + led_index++; + if (led_index >= MAX_NUM_LED) { + led_index = MAX_NUM_LED - 1; + decrement = true; + } } else { - led_number--; - LED_control(led_number, LED_OFF); + LED_control(led_index, LED_OFF); + if (led_index == 0) { + decrement = false; + } else { + led_index--; + } } } -void send_flowrates() { - CAN_SEND_flowrates(getFlowRate1(), getFlowRate2()); -} - -void send_iv_readings() { - // Set LV Batt faults - // set_fault(FAULT_ID_PDU_LV_BATT_FIFTY, auto_switches.voltage.in_24v); - update_fault(FAULT_ID_LV_GETTING_LOW, auto_switches.voltage.in_24v); - update_fault(FAULT_ID_LV_CRITICAL_LOW, auto_switches.voltage.in_24v); - // Send CAN messages containing voltage and current data - CAN_SEND_v_rails(auto_switches.voltage.in_24v, - auto_switches.voltage.out_5v, - auto_switches.voltage.out_3v3, - 0); // amk_24v removed: mux ch4/5 are current sense, not voltage - CAN_SEND_rail_currents(auto_switches.current[CS_24V], auto_switches.current[CS_5V]); - CAN_SEND_pump_and_fan_current(auto_switches.current[SW_PUMP_1], - auto_switches.current[SW_PUMP_2], - auto_switches.current[SW_FAN_1], - auto_switches.current[SW_FAN_2]); - CAN_SEND_fan_current2(auto_switches.current[SW_FAN_3], auto_switches.current[SW_FAN_4]); - CAN_SEND_other_currents(auto_switches.current[SW_SDC], - auto_switches.current[SW_HXFAN], - auto_switches.current[SW_DASH], - auto_switches.current[SW_ABOX], - auto_switches.current[SW_MAIN]); - - // DS8626 Rev 10 pg 139 - // Reference voltage is 3.3 - // 12 bit adc max is 4095 - // Voltage at 25 degrees c is 0.76 - // Average slope is 2.5mV per degree c - float vsense = (adc_readings.internal_therm / (float)4095) * 3.3f; - float temp = ((vsense - 0.76f) / 0.0025f) + 25.0f; - CAN_SEND_pdu_temps((uint16_t)temp); -} - // Thread Defines DEFINE_CAN_TASKS(); -DEFINE_TASK(autoSwitchPeriodic, 15, osPriorityNormal, STACK_512); -DEFINE_TASK(update_cooling_periodic, 100, osPriorityNormal, STACK_1024); +DEFINE_TASK(switches_periodic, 15, osPriorityNormal, STACK_512); +DEFINE_TASK(cooling_periodic, 100, osPriorityNormal, STACK_1024); DEFINE_TASK(LED_periodic, 500, osPriorityLow, STACK_512); -// DEFINE_TASK(send_iv_readings, 500, osPriorityLow, STACK_1024); // ! wtf this is causing a crash -DEFINE_TASK(checkSwitchFaults, 100, osPriorityLow, STACK_512); -DEFINE_TASK(send_flowrates, 200, osPriorityLow, STACK_256); +DEFINE_TASK(faults_periodic, 100, osPriorityLow, STACK_512); DEFINE_TASK(fault_library_periodic, 100, osPriorityLow, STACK_1024); +DEFINE_TASK(telemetry_10hz, 100, osPriorityLow, STACK_1024); DEFINE_WATCHDOG_TASK(); -DEFINE_HEARTBEAT_TASK(sparkle_leds); +DEFINE_HEARTBEAT_TASK(heartbeat_led_sweep); int main() { // Hardware Initialization @@ -327,34 +294,25 @@ int main() { } PHAL_writeGPIO(LED_CTRL_BLANK_GPIO_Port, LED_CTRL_BLANK_Pin, 1); + state_init_defaults(); + switches_init(); + faults_init(); + fanControlInit(); - coolingInit(); + cooling_init(); flowRateInit(); - // Initialize default 'ON' rails - setSwitch(SW_SDC, 1); - setSwitch(SW_DAQ, 1); - setSwitch(SW_TV, 1); - setSwitch(SW_MAIN, 1); - setSwitch(SW_ABOX, 1); - setSwitch(SW_DASH, 1); - setSwitch(SW_CRIT_5V, 1); - setSwitch(SW_BLT, 1); - setSwitch(SW_HXFAN, 1); - setSwitch(SW_DLBK, 1); - setSwitch(SW_PUMP_1, 1); - setSwitch(SW_PUMP_2, 1); + switches_enable_default_rails(); osKernelInitialize(); START_CAN_TASKS(); - START_TASK(autoSwitchPeriodic); - START_TASK(update_cooling_periodic); + START_TASK(switches_periodic); + START_TASK(cooling_periodic); START_TASK(LED_periodic); - // START_TASK(send_iv_readings); - START_TASK(checkSwitchFaults); - START_TASK(send_flowrates); + START_TASK(faults_periodic); START_TASK(fault_library_periodic); + START_TASK(telemetry_10hz); START_WATCHDOG_TASK(); START_HEARTBEAT_TASK(); @@ -371,4 +329,4 @@ void HardFault_Handler() { while (1) { __asm__("NOP"); // spin } -} \ No newline at end of file +} diff --git a/source/pdu/state/state.c b/source/pdu/state/state.c new file mode 100644 index 000000000..8380a1f13 --- /dev/null +++ b/source/pdu/state/state.c @@ -0,0 +1,23 @@ +#include "state.h" + +#include + +pdu_state_t g_pdu_state; + +void state_init_defaults(void) { + memset(&g_pdu_state, 0, sizeof(g_pdu_state)); + + g_pdu_state.next_mux_channel = 0; + g_pdu_state.next_rail_fault_index = 0; + g_pdu_state.cooling_command.fan1_percent = 10; + g_pdu_state.cooling_command.fan2_percent = 10; + g_pdu_state.cooling_command.fan3_percent = 10; + g_pdu_state.cooling_command.fan4_percent = 10; + g_pdu_state.cooling_command.fan1_enabled = true; + g_pdu_state.cooling_command.fan2_enabled = true; + g_pdu_state.cooling_command.fan3_enabled = true; + g_pdu_state.cooling_command.fan4_enabled = true; + g_pdu_state.cooling_command.pump1_enabled = true; + g_pdu_state.cooling_command.pump2_enabled = true; + g_pdu_state.cooling_command.hxfan_enabled = true; +} diff --git a/source/pdu/state/state.h b/source/pdu/state/state.h new file mode 100644 index 000000000..959357bac --- /dev/null +++ b/source/pdu/state/state.h @@ -0,0 +1,44 @@ +#ifndef STATE_H +#define STATE_H + +#include +#include + +#include "switches.h" + +typedef struct { + uint8_t fan1_percent; + uint8_t fan2_percent; + uint8_t fan3_percent; + uint8_t fan4_percent; + bool fan1_enabled; + bool fan2_enabled; + bool fan3_enabled; + bool fan4_enabled; + bool pump1_enabled; + bool pump2_enabled; + bool hxfan_enabled; + bool batt_fan_autospeed_enabled; + bool motor_fan_autospeed_enabled; +} pdu_cooling_command_t; + +typedef struct { + uint16_t in_24v_mv; + uint16_t out_5v_mv; + uint16_t out_3v3_mv; +} pdu_rail_voltage_mv_t; + +typedef struct { + pdu_rail_voltage_mv_t rail_voltage_mv; + uint16_t switch_current_ma[CS_SWITCH_COUNT]; + uint16_t mux_adc_counts[6]; + uint8_t next_mux_channel; + uint8_t next_rail_fault_index; + pdu_cooling_command_t cooling_command; +} pdu_state_t; + +extern pdu_state_t g_pdu_state; + +void state_init_defaults(void); + +#endif // STATE_H diff --git a/source/pdu/switches/switches.c b/source/pdu/switches/switches.c new file mode 100644 index 000000000..887258398 --- /dev/null +++ b/source/pdu/switches/switches.c @@ -0,0 +1,199 @@ +#include "switches.h" + +#include + +#include "led.h" +#include "main.h" +#include "state.h" + +#include "common/phal/gpio.h" + +static constexpr uint16_t ADC_MAX_COUNTS = 4095; + +static constexpr int LV_24V_R1 = 47000; +static constexpr int LV_24V_R2 = 3400; +static constexpr int LV_5V_R1 = 4300; +static constexpr int LV_5V_R2 = 3400; +static constexpr int LV_3V3_R1 = 4300; +static constexpr int LV_3V3_R2 = 10000; + +static constexpr int HP_CS_R1 = 180; +static constexpr int HP_CS_R2 = 330; +static constexpr int HP_CS_R3 = 500; + +static constexpr float HP_CS_R_SENSE = 0.002f; +static constexpr uint16_t CS_GAIN = 100; + +typedef struct { + switches_t switch_id; + GPIO_TypeDef *ctrl_port; + uint8_t ctrl_pin; + bool has_ctrl_output; + int8_t led_id; +} pdu_switch_output_t; + +static constexpr int8_t LED_NONE = -1; + +static const pdu_switch_output_t PDU_SWITCH_OUTPUTS[] = { + {.switch_id = SW_PUMP_1, .ctrl_port = PUMP_1_CTRL_GPIO_Port, .ctrl_pin = PUMP_1_CTRL_Pin, .has_ctrl_output = true, .led_id = LED_PUMP_1}, + {.switch_id = SW_PUMP_2, .ctrl_port = PUMP_2_CTRL_GPIO_Port, .ctrl_pin = PUMP_2_CTRL_Pin, .has_ctrl_output = true, .led_id = LED_PUMP_2}, + {.switch_id = SW_SDC, .ctrl_port = nullptr, .ctrl_pin = 0, .has_ctrl_output = false, .led_id = LED_SDC}, + {.switch_id = SW_HXFAN, .ctrl_port = HXFAN_CTRL_GPIO_Port, .ctrl_pin = HXFAN_CTRL_Pin, .has_ctrl_output = true, .led_id = LED_HXFAN}, + {.switch_id = SW_FAN_1, .ctrl_port = FAN_1_CTRL_GPIO_Port, .ctrl_pin = FAN_1_CTRL_Pin, .has_ctrl_output = true, .led_id = LED_NONE}, + {.switch_id = SW_FAN_2, .ctrl_port = FAN_2_CTRL_GPIO_Port, .ctrl_pin = FAN_2_CTRL_Pin, .has_ctrl_output = true, .led_id = LED_NONE}, + {.switch_id = SW_FAN_3, .ctrl_port = FAN_3_CTRL_GPIO_Port, .ctrl_pin = FAN_3_CTRL_Pin, .has_ctrl_output = true, .led_id = LED_NONE}, + {.switch_id = SW_FAN_4, .ctrl_port = FAN_4_CTRL_GPIO_Port, .ctrl_pin = FAN_4_CTRL_Pin, .has_ctrl_output = true, .led_id = LED_NONE}, + {.switch_id = SW_AMK1, .ctrl_port = nullptr, .ctrl_pin = 0, .has_ctrl_output = false, .led_id = LED_NONE}, + {.switch_id = SW_AMK2, .ctrl_port = nullptr, .ctrl_pin = 0, .has_ctrl_output = false, .led_id = LED_NONE}, + {.switch_id = SW_DASH, .ctrl_port = nullptr, .ctrl_pin = 0, .has_ctrl_output = false, .led_id = LED_DASH}, + {.switch_id = SW_ABOX, .ctrl_port = nullptr, .ctrl_pin = 0, .has_ctrl_output = false, .led_id = LED_ABOX}, + {.switch_id = SW_MAIN, .ctrl_port = nullptr, .ctrl_pin = 0, .has_ctrl_output = false, .led_id = LED_MAIN}, + {.switch_id = SW_DLFR, .ctrl_port = DLFR_CTRL_GPIO_Port, .ctrl_pin = DLFR_CTRL_Pin, .has_ctrl_output = true, .led_id = LED_DLFR}, + {.switch_id = SW_DLBK, .ctrl_port = DLBK_CTRL_GPIO_Port, .ctrl_pin = DLBK_CTRL_Pin, .has_ctrl_output = true, .led_id = LED_DLBK}, + {.switch_id = SW_BLT, .ctrl_port = BLT_CTRL_GPIO_Port, .ctrl_pin = BLT_CTRL_Pin, .has_ctrl_output = true, .led_id = LED_BLT}, + {.switch_id = SW_CRIT_5V, .ctrl_port = CRIT_5V_CTRL_GPIO_Port, .ctrl_pin = CRIT_5V_CTRL_Pin, .has_ctrl_output = true, .led_id = LED_5V_CRIT}, + {.switch_id = SW_TV, .ctrl_port = TV_CTRL_GPIO_Port, .ctrl_pin = TV_CTRL_Pin, .has_ctrl_output = true, .led_id = LED_TV}, + {.switch_id = SW_DAQ, .ctrl_port = nullptr, .ctrl_pin = 0, .has_ctrl_output = false, .led_id = LED_DAQ}, + {.switch_id = SW_FAN_5V, .ctrl_port = FAN_5V_CTRL_GPIO_Port, .ctrl_pin = FAN_5V_CTRL_Pin, .has_ctrl_output = true, .led_id = LED_5V_FAN}, +}; + +static const pdu_switch_output_t *switches_get_output(switches_t switch_id) { + for (uint32_t i = 0; i < (sizeof(PDU_SWITCH_OUTPUTS) / sizeof(PDU_SWITCH_OUTPUTS[0])); i++) { + if (PDU_SWITCH_OUTPUTS[i].switch_id == switch_id) { + return &PDU_SWITCH_OUTPUTS[i]; + } + } + + return nullptr; +} + +static uint16_t switches_convert_high_power_current_ma(uint16_t adc_counts) { + adc_counts = adc_counts * ADC_REF_mV / ADC_MAX_COUNTS; + adc_counts = adc_counts * (HP_CS_R1 + HP_CS_R2) / HP_CS_R2; + adc_counts = adc_counts * HP_CS_R3 / (HP_CS_R1 + HP_CS_R2); + return adc_counts; +} + +static uint16_t switches_convert_low_power_current_ma(uint16_t adc_counts) { + adc_counts = adc_counts * ADC_REF_mV / ADC_MAX_COUNTS; + return adc_counts; +} + +static uint16_t switches_convert_voltage_mv(uint16_t adc_counts, int r1_ohm, int r2_ohm) { + adc_counts = adc_counts * (r1_ohm + r2_ohm) / r2_ohm; + adc_counts = adc_counts * ADC_REF_mV / ADC_MAX_COUNTS; + return adc_counts; +} + +static void switches_select_mux_channel(uint8_t channel) { + PHAL_writeGPIO(MUX_CTRL_A_GPIO_Port, MUX_CTRL_A_Pin, (channel & 0x01U) != 0U); + PHAL_writeGPIO(MUX_CTRL_B_GPIO_Port, MUX_CTRL_B_Pin, (channel & 0x02U) != 0U); + PHAL_writeGPIO(MUX_CTRL_C_GPIO_Port, MUX_CTRL_C_Pin, (channel & 0x04U) != 0U); +} + +static void switches_update_mux_measurements(void) { + const uint8_t sampled_channel = g_pdu_state.next_mux_channel; + + g_pdu_state.mux_adc_counts[sampled_channel] = adc_readings.mux_out; + g_pdu_state.next_mux_channel = (uint8_t)((sampled_channel + 1U) % 6U); + + switches_select_mux_channel(g_pdu_state.next_mux_channel); +} + +static void switches_update_currents(void) { + g_pdu_state.switch_current_ma[SW_PUMP_1] = switches_convert_high_power_current_ma(adc_readings.pump_1_imon); + g_pdu_state.switch_current_ma[SW_PUMP_2] = switches_convert_high_power_current_ma(adc_readings.pump_2_imon); + g_pdu_state.switch_current_ma[SW_SDC] = switches_convert_high_power_current_ma(adc_readings.sdc_imon); + g_pdu_state.switch_current_ma[SW_HXFAN] = switches_convert_high_power_current_ma(adc_readings.hxfan_imon); + + g_pdu_state.switch_current_ma[SW_FAN_1] = switches_convert_high_power_current_ma(g_pdu_state.mux_adc_counts[0]); + g_pdu_state.switch_current_ma[SW_FAN_2] = switches_convert_high_power_current_ma(g_pdu_state.mux_adc_counts[1]); + g_pdu_state.switch_current_ma[SW_FAN_3] = switches_convert_high_power_current_ma(g_pdu_state.mux_adc_counts[2]); + g_pdu_state.switch_current_ma[SW_FAN_4] = switches_convert_high_power_current_ma(g_pdu_state.mux_adc_counts[3]); + g_pdu_state.switch_current_ma[SW_AMK1] = switches_convert_high_power_current_ma(g_pdu_state.mux_adc_counts[4]); + g_pdu_state.switch_current_ma[SW_AMK2] = switches_convert_high_power_current_ma(g_pdu_state.mux_adc_counts[5]); + + g_pdu_state.switch_current_ma[SW_DASH] = switches_convert_low_power_current_ma(adc_readings.dash_cs); + g_pdu_state.switch_current_ma[SW_ABOX] = switches_convert_low_power_current_ma(adc_readings.abox_cs); + g_pdu_state.switch_current_ma[SW_MAIN] = switches_convert_low_power_current_ma(adc_readings.main_cs); + g_pdu_state.switch_current_ma[SW_DLFR] = switches_convert_low_power_current_ma(adc_readings.dlfr_cs); + g_pdu_state.switch_current_ma[SW_DLBK] = switches_convert_low_power_current_ma(adc_readings.dlbk_cs); + + uint16_t current_mv = adc_readings.v24_cs; + current_mv = current_mv * ADC_REF_mV / ADC_MAX_COUNTS; + current_mv = (uint16_t)(current_mv / HP_CS_R_SENSE / CS_GAIN); + g_pdu_state.switch_current_ma[CS_24V] = current_mv; + + current_mv = adc_readings.v5_cs; + current_mv = current_mv * ADC_REF_mV / ADC_MAX_COUNTS; + g_pdu_state.switch_current_ma[CS_5V] = current_mv; +} + +static void switches_update_voltages(void) { + g_pdu_state.rail_voltage_mv.in_24v_mv = switches_convert_voltage_mv(adc_readings.v24_vs, LV_24V_R1, LV_24V_R2); + g_pdu_state.rail_voltage_mv.out_5v_mv = switches_convert_voltage_mv(adc_readings.v5_vs, LV_5V_R1, LV_5V_R2); + g_pdu_state.rail_voltage_mv.out_3v3_mv = switches_convert_voltage_mv(adc_readings.v3v3_vs, LV_3V3_R1, LV_3V3_R2); +} + +void switches_init(void) { + g_pdu_state.next_mux_channel = 0; + memset(g_pdu_state.mux_adc_counts, 0, sizeof(g_pdu_state.mux_adc_counts)); + switches_select_mux_channel(g_pdu_state.next_mux_channel); +} + +void switches_periodic(void) { + switches_update_mux_measurements(); + switches_update_currents(); + switches_update_voltages(); +} + +void switches_set_state(switches_t switch_id, bool enabled) { + if (switch_id == CS_24V || switch_id == CS_5V || switch_id == CS_SWITCH_COUNT) { + return; + } + + const pdu_switch_output_t *output = switches_get_output(switch_id); + if (output == nullptr) { + return; + } + + if (output->has_ctrl_output) { + PHAL_writeGPIO(output->ctrl_port, output->ctrl_pin, enabled); + } + + if (output->led_id != LED_NONE) { + LED_control(output->led_id, enabled ? LED_ON : LED_OFF); + } +} + +bool switches_is_enabled(switches_t switch_id) { + const pdu_switch_output_t *output = switches_get_output(switch_id); + if (output == nullptr || !output->has_ctrl_output) { + return true; + } + + return PHAL_readGPIO(output->ctrl_port, output->ctrl_pin); +} + +uint16_t switches_get_mux_adc_counts(uint8_t channel) { + if (channel >= 6U) { + return 0; + } + + return g_pdu_state.mux_adc_counts[channel]; +} + +void switches_enable_default_rails(void) { + switches_set_state(SW_SDC, true); + switches_set_state(SW_DAQ, true); + switches_set_state(SW_TV, true); + switches_set_state(SW_MAIN, true); + switches_set_state(SW_ABOX, true); + switches_set_state(SW_DASH, true); + switches_set_state(SW_CRIT_5V, true); + switches_set_state(SW_BLT, true); + switches_set_state(SW_HXFAN, true); + switches_set_state(SW_DLBK, true); + switches_set_state(SW_PUMP_1, true); + switches_set_state(SW_PUMP_2, true); +} diff --git a/source/pdu/switches/switches.h b/source/pdu/switches/switches.h new file mode 100644 index 000000000..cf34bc531 --- /dev/null +++ b/source/pdu/switches/switches.h @@ -0,0 +1,50 @@ +#ifndef SWITCHES_H +#define SWITCHES_H + +#include +#include + +typedef enum { + // High power switches + SW_PUMP_1 = 0, + SW_PUMP_2 = 1, + SW_SDC = 2, + SW_HXFAN = 3, + + // High power switches (mux-sensed) + SW_FAN_1 = 4, + SW_FAN_2 = 5, + SW_FAN_3 = 6, + SW_FAN_4 = 7, + SW_AMK1 = 8, + SW_AMK2 = 9, + + // Low power switches + SW_DASH = 10, + SW_ABOX = 11, + SW_MAIN = 12, + SW_DLFR = 13, + SW_DLBK = 14, + + // Not switch outputs + CS_24V = 15, + CS_5V = 16, + CS_SWITCH_COUNT = 17, + + // Low power switches (no current-sense) + SW_BLT = 18, + // 5V switches (no current-sense) + SW_CRIT_5V = 19, + SW_TV = 20, + SW_DAQ = 21, + SW_FAN_5V = 22 +} switches_t; + +void switches_init(void); +void switches_periodic(void); +void switches_enable_default_rails(void); +void switches_set_state(switches_t switch_id, bool enabled); +bool switches_is_enabled(switches_t switch_id); +uint16_t switches_get_mux_adc_counts(uint8_t channel); + +#endif // SWITCHES_H diff --git a/source/pdu/telemetry/telemetry.c b/source/pdu/telemetry/telemetry.c new file mode 100644 index 000000000..85ad3ddaa --- /dev/null +++ b/source/pdu/telemetry/telemetry.c @@ -0,0 +1,72 @@ +#include "telemetry.h" + +#include "can_library/faults_common.h" +#include "can_library/generated/PDU.h" +#include "can_library/generated/VCAN.h" +#include "flow_rate.h" +#include "main.h" +#include "source/a_box/telemetry/telemetry.h" +#include "state.h" +#include "switches.h" + +static uint16_t telemetry_internal_temp_c(uint16_t internal_therm_adc_counts) { + float vsense = (internal_therm_adc_counts / (float)4095) * 3.3f; + float temp_c = ((vsense - 0.76f) / 0.0025f) + 25.0f; + if (temp_c <= 0.0f) { + return 0; + } + + if (temp_c >= 65535.0f) { + return 65535; + } + + return (uint16_t)temp_c; +} + +/** + * @brief Reports telemetry data at 10 Hz rate + * Includes: Rail Voltages and Currents, Flow Rates + */ +static_assert(V_RAILS_PERIOD_MS == TELEMETRY_10HZ_PERIOD_MS); +static_assert(OTHER_CURRENTS_PERIOD_MS == TELEMETRY_10HZ_PERIOD_MS); +static_assert(PUMP_AND_FAN_CURRENT_PERIOD_MS == TELEMETRY_10HZ_PERIOD_MS); +static_assert(V_RAILS_PERIOD_MS == TELEMETRY_10HZ_PERIOD_MS); +static_assert(FAN_CURRENT2_PERIOD_MS == TELEMETRY_10HZ_PERIOD_MS); +static_assert(FLOWRATES_PERIOD_MS == TELEMETRY_10HZ_PERIOD_MS); +static_assert(RAIL_CURRENTS_PERIOD_MS == TELEMETRY_10HZ_PERIOD_MS); +static_assert(PDU_TEMPS_PERIOD_MS == TELEMETRY_10HZ_PERIOD_MS); + +void telemetry_10hz(void) { + update_fault(FAULT_ID_LV_GETTING_LOW, g_pdu_state.rail_voltage_mv.in_24v_mv); + update_fault(FAULT_ID_LV_CRITICAL_LOW, g_pdu_state.rail_voltage_mv.in_24v_mv); + + CAN_SEND_v_rails( + g_pdu_state.rail_voltage_mv.in_24v_mv, + g_pdu_state.rail_voltage_mv.out_5v_mv, + g_pdu_state.rail_voltage_mv.out_3v3_mv, + 0 + ); + + CAN_SEND_rail_currents(g_pdu_state.switch_current_ma[CS_24V], g_pdu_state.switch_current_ma[CS_5V]); + + CAN_SEND_pump_and_fan_current( + g_pdu_state.switch_current_ma[SW_PUMP_1], + g_pdu_state.switch_current_ma[SW_PUMP_2], + g_pdu_state.switch_current_ma[SW_FAN_1], + g_pdu_state.switch_current_ma[SW_FAN_2] + ); + + CAN_SEND_fan_current2(g_pdu_state.switch_current_ma[SW_FAN_3], g_pdu_state.switch_current_ma[SW_FAN_4]); + + CAN_SEND_other_currents( + g_pdu_state.switch_current_ma[SW_SDC], + g_pdu_state.switch_current_ma[SW_HXFAN], + g_pdu_state.switch_current_ma[SW_DASH], + g_pdu_state.switch_current_ma[SW_ABOX], + g_pdu_state.switch_current_ma[SW_MAIN] + ); + + CAN_SEND_pdu_temps(telemetry_internal_temp_c(adc_readings.internal_therm)); + + CAN_SEND_flowrates(getFlowRate1(), getFlowRate2()); +} diff --git a/source/pdu/telemetry/telemetry.h b/source/pdu/telemetry/telemetry.h new file mode 100644 index 000000000..b86827a88 --- /dev/null +++ b/source/pdu/telemetry/telemetry.h @@ -0,0 +1,9 @@ +#ifndef TELEMETRY_H +#define TELEMETRY_H + +#include + +static constexpr uint32_t TELEMETRY_10HZ_PERIOD_MS = 100; +void telemetry_10hz(void); + +#endif // TELEMETRY_H