diff --git a/can_bus/hexray/BMS/BMS_rx.json b/can_bus/hexray/BMS/BMS_rx.json index 3553e25997..fee04e0c1c 100644 --- a/can_bus/hexray/BMS/BMS_rx.json +++ b/can_bus/hexray/BMS/BMS_rx.json @@ -9,6 +9,7 @@ "Debug_CanMode", "Debug_Charging", "Debug_SetBSPDTestCurrent", - "Debug_CellBalancing" + "Debug_CellBalancing", + "Debug_ResetSoc" ] } diff --git a/can_bus/hexray/BMS/BMS_tx.json b/can_bus/hexray/BMS/BMS_tx.json index 616721ad32..d3a0317792 100644 --- a/can_bus/hexray/BMS/BMS_tx.json +++ b/can_bus/hexray/BMS/BMS_tx.json @@ -189,19 +189,25 @@ "log_cycle_time" : 100 } }, - "HvBatteryProperties": { + "VoltageAndChargeStats": { "msg_id": 415, "cycle_time": 1000, - "description": "HV Battery Properties", + "description": "State of Charge Metrics", "signals": { - "HvBatterySoc": { - "start_bit": 0, - "bits": 8, + "SocPercent": { + "resolution": 0.1, "min": 0, "max": 100, - "scale": 1, - "offset": 0, "unit": "%" + }, + "SocCoulombs": { + "resolution": 1, + "min": 0, + "max": 100000, + "unit": "C" + }, + "SocDirty": { + "bits": 1 } } }, diff --git a/firmware/hexray/BMS/CMakeLists.txt b/firmware/hexray/BMS/CMakeLists.txt index 9268252464..edee6789c4 100644 --- a/firmware/hexray/BMS/CMakeLists.txt +++ b/firmware/hexray/BMS/CMakeLists.txt @@ -25,6 +25,7 @@ set(IO_SHARED_SRCS "${SHARED_IO_INCLUDE_DIR_CPP}/io_time.cpp" "${SHARED_IO_INCLUDE_DIR_CPP}/io_semaphore.cpp" "${SHARED_IO_INCLUDE_DIR_CPP}/io_shdnLoopNode.cpp" + "${SHARED_IO_INCLUDE_DIR_CPP}/io_filesystem.cpp" "${SHARED_IO_INCLUDE_DIR_CPP}/io_notify.cpp" ) set(IO_INCLUDE_DIRS @@ -40,6 +41,7 @@ set(HW_SHARED_SRCS "${SHARED_HW_INCLUDE_DIR_CPP}/hw_spi.cpp" "${SHARED_HW_INCLUDE_DIR_CPP}/hw_pwmOutput.cpp" "${SHARED_HW_INCLUDE_DIR_CPP}/hw_can_hx.cpp" + "${SHARED_HW_INCLUDE_DIR_CPP}/hw_sd.cpp" "${SHARED_HW_INCLUDE_DIR_CPP}/hw_bootup.cpp" "${SHARED_HW_INCLUDE_DIR_CPP}/hw_resetReason.cpp" "${SHARED_HW_INCLUDE_DIR_CPP}/hw_bootup.cpp" @@ -135,7 +137,7 @@ if ("${TARGET}" STREQUAL "binary") "${CMAKE_CURRENT_BINARY_DIR}/app" ) - target_link_libraries("hexray_BMS_app.elf" PRIVATE "hexray_BMS_stm32" "hexray_BMS_jsoncan" "hexray_BMS_commit_info" "m") + target_link_libraries("hexray_BMS_app.elf" PRIVATE "hexray_BMS_stm32" "hexray_BMS_jsoncan" "hexray_BMS_commit_info" "m" "logfs_cm7") target_compile_definitions("hexray_BMS_app.elf" PRIVATE BMS) add_chimera_stm32h7("hexray_BMS_chimera" "${CHIMERA_SRCS}" "${CHIMERA_INCLUDE_DIRS}" "hexray_chimera_v2_proto_cm7") diff --git a/firmware/hexray/BMS/src/app/app_soc.cpp b/firmware/hexray/BMS/src/app/app_soc.cpp new file mode 100644 index 0000000000..98718f3874 --- /dev/null +++ b/firmware/hexray/BMS/src/app/app_soc.cpp @@ -0,0 +1,173 @@ +#include "app_soc.hpp" +#include "app_canTx.hpp" +#include "app_math.hpp" +#include "app_socStorage.hpp" +#include "app_tractiveSystem.hpp" +#include "io_irs.hpp" +#include "io_time.hpp" + +#include +#include +#include + +namespace +{ +constexpr double MS_TO_S = 0.001; +constexpr double AH_TO_COULOMBS = 3600.0; + +constexpr uint32_t V_TO_SOC_LUT_SIZE = 201U; +constexpr float LUT_BASE_SOC = 0.0f; + +constexpr double STATE_OF_HEALTH = 1.0f; +// TODO: Replace with cell capacity and cells in parallel constants in app_segments once that's finished +constexpr double SERIES_ELEMENT_FULL_CHARGE_C = 3.0 * AH_TO_COULOMBS * 4.0 * STATE_OF_HEALTH; + +// Charge in cell in coulombs. +double soc_charge_c = -1.0; + +// Charge loss at time t-1. +float soc_prev_current_A = 0.0f; + +// Indicates if SOC from SD was invalid at startup. +bool soc_is_dirty = true; + +uint32_t soc_prev_update_ms = 0U; + +// index 0 represents 0.0% SoC, final index represents 100%. 0.5% increments. +constexpr std::array ocv_soc_lut = { { + 2.478484f, 2.548948f, 2.555629f, 2.636801f, 2.717973f, 2.795096f, 2.845899f, 2.896701f, 2.947503f, 2.979689f, + 3.009237f, 3.038785f, 3.062674f, 3.083212f, 3.103750f, 3.124092f, 3.144124f, 3.164156f, 3.183431f, 3.197409f, + 3.211386f, 3.225364f, 3.240896f, 3.256680f, 3.272465f, 3.284931f, 3.295543f, 3.304156f, 3.306352f, 3.317410f, + 3.331939f, 3.344696f, 3.356658f, 3.368619f, 3.380526f, 3.391995f, 3.403385f, 3.414583f, 3.421610f, 3.426887f, + 3.432164f, 3.436899f, 3.440721f, 3.444543f, 3.448370f, 3.452317f, 3.456320f, 3.460323f, 3.465373f, 3.470832f, + 3.476372f, 3.482455f, 3.488869f, 3.495323f, 3.501739f, 3.508092f, 3.514425f, 3.520623f, 3.526513f, 3.532403f, + 3.538241f, 3.543478f, 3.548635f, 3.553792f, 3.558754f, 3.563582f, 3.568411f, 3.573168f, 3.577760f, 3.582344f, + 3.586892f, 3.591139f, 3.595281f, 3.599423f, 3.603309f, 3.607144f, 3.610956f, 3.614661f, 3.618294f, 3.621917f, + 3.625508f, 3.629068f, 3.632628f, 3.636199f, 3.639814f, 3.643430f, 3.647046f, 3.650764f, 3.654502f, 3.658240f, + 3.662069f, 3.666019f, 3.669968f, 3.673983f, 3.678140f, 3.682339f, 3.686552f, 3.690932f, 3.695370f, 3.699882f, + 3.704528f, 3.709202f, 3.713828f, 3.718485f, 3.723182f, 3.727879f, 3.732598f, 3.737347f, 3.742095f, 3.746847f, + 3.751637f, 3.756426f, 3.761216f, 3.766030f, 3.770867f, 3.775704f, 3.780582f, 3.785519f, 3.790488f, 3.795490f, + 3.800562f, 3.805651f, 3.810753f, 3.815940f, 3.821127f, 3.826319f, 3.831529f, 3.836744f, 3.841952f, 3.847058f, + 3.852135f, 3.857211f, 3.862209f, 3.867022f, 3.871836f, 3.876640f, 3.881274f, 3.885880f, 3.890486f, 3.894997f, + 3.899466f, 3.903929f, 3.908409f, 3.912903f, 3.917498f, 3.922166f, 3.926984f, 3.931814f, 3.936749f, 3.941997f, + 3.947246f, 3.952528f, 3.958148f, 3.963841f, 3.969533f, 3.975359f, 3.981300f, 3.987241f, 3.993173f, 3.999034f, + 4.004879f, 4.010713f, 4.016239f, 4.021650f, 4.027055f, 4.032055f, 4.036909f, 4.041577f, 4.045854f, 4.049776f, + 4.053605f, 4.057009f, 4.059732f, 4.062456f, 4.065076f, 4.066936f, 4.068797f, 4.070658f, 4.072325f, 4.073932f, + 4.075540f, 4.077227f, 4.079082f, 4.080939f, 4.082910f, 4.085239f, 4.087742f, 4.090254f, 4.093629f, 4.097167f, + 4.100969f, 4.105953f, 4.111315f, 4.117198f, 4.124902f, 4.134033f, 4.143165f, 4.155217f, 4.171970f, 4.188723f, + 4.205476f, +} }; + +float getSocFromOcv(const float voltage) +{ + if (!std::isfinite(voltage) || (voltage <= ocv_soc_lut[0U])) + { + return LUT_BASE_SOC; + } + + if (voltage > ocv_soc_lut[V_TO_SOC_LUT_SIZE - 1U]) + { + return LUT_BASE_SOC + static_cast(V_TO_SOC_LUT_SIZE - 1U) * 0.5f; + } + + uint32_t lower_index = 0U; + uint32_t upper_index = V_TO_SOC_LUT_SIZE - 1U; + + while (lower_index < upper_index) + { + const uint32_t mid_index = lower_index + (upper_index - lower_index) / 2U; + + if (voltage > ocv_soc_lut[mid_index]) + { + lower_index = mid_index + 1U; + } + else + { + upper_index = mid_index; + } + } + + return LUT_BASE_SOC + static_cast(lower_index) * 0.5f; +} + +uint32_t getMinSocCoulombs() +{ + if (soc_charge_c < 0.0) + { + return 0U; + } + return static_cast(soc_charge_c); +} + +void updateSocStats() +{ + const float current = app::ts::getCurrent(); + + const uint32_t current_time_ms = io::time::getCurrentMs(); + const uint32_t elapsed_time_ms = current_time_ms - soc_prev_update_ms; + soc_prev_update_ms = current_time_ms; + + const double elapsed_time_s = static_cast(elapsed_time_ms) * MS_TO_S; + + app::math::trapezoidalRule(soc_charge_c, soc_prev_current_A, current, elapsed_time_s); +} + +} // namespace + +namespace app::soc +{ +void init() +{ + if (const auto saved_soc_percent = app::socStorage::readSocFromSd(); + saved_soc_percent && IS_IN_RANGE(0.0f, 100.0f, *saved_soc_percent)) + { + soc_charge_c = static_cast(*saved_soc_percent) / 100.0 * SERIES_ELEMENT_FULL_CHARGE_C; + soc_is_dirty = false; + } + else + { + soc_prev_current_A = 0.0f; + soc_is_dirty = true; + soc_charge_c = -1.0; + } + + soc_prev_update_ms = io::time::getCurrentMs(); +} + +bool getDirty() +{ + return soc_is_dirty; +} + +float getMinSocPercent() +{ + return static_cast(soc_charge_c / SERIES_ELEMENT_FULL_CHARGE_C * 100.0); +} + +void resetSocFromVoltage() +{ + // TODO: Use minimum cell voltage once app::segments is migrated in Hexray BMS. + (void)getSocFromOcv(0.0f); + soc_is_dirty = true; +} + +void resetSocCustomValue(const float soc_percent) +{ + soc_charge_c = static_cast(soc_percent) / 100.0 * SERIES_ELEMENT_FULL_CHARGE_C; + + soc_is_dirty = true; +} + +void broadcast() +{ + app::can_tx::BMS_SocPercent_set(getMinSocPercent()); + app::can_tx::BMS_SocCoulombs_set(getMinSocCoulombs()); + app::can_tx::BMS_SocDirty_set(getDirty()); + if (io::irs::negativeState() == app::can_utils::ContactorState::CONTACTOR_STATE_CLOSED && + io::irs::positiveState() == app::can_utils::ContactorState::CONTACTOR_STATE_CLOSED) + { + updateSocStats(); + } +} + +} // namespace app::soc diff --git a/firmware/hexray/BMS/src/app/app_soc.hpp b/firmware/hexray/BMS/src/app/app_soc.hpp new file mode 100644 index 0000000000..a18a6bf06b --- /dev/null +++ b/firmware/hexray/BMS/src/app/app_soc.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include + +namespace app::soc +{ +/** + * @brief Initialize the SOC module. + */ +void init(); + +/** + * @brief Return whether the SoC estimate is dirty and needs to be updated. + */ +bool getDirty(); + +/** + * @brief Get current minimum SoC in percent. + */ +float getMinSocPercent(); + +/** + * @brief Reset SoC estimate using a voltage-based method. + */ +void resetSocFromVoltage(); + +/** + * @brief Reset SoC estimate using a specific value in percent. + */ +void resetSocCustomValue(float soc_percent); + +/** + * @brief Broadcast SOC related CAN signals. + */ +void broadcast(); + +} // namespace app::soc diff --git a/firmware/hexray/BMS/src/app/app_socStorage.cpp b/firmware/hexray/BMS/src/app/app_socStorage.cpp new file mode 100644 index 0000000000..929b1d47d6 --- /dev/null +++ b/firmware/hexray/BMS/src/app/app_socStorage.cpp @@ -0,0 +1,147 @@ +#include "app_socStorage.hpp" +#include "io_log.hpp" +#include "io_filesystem.hpp" +#include "io_filesystems.hpp" + +#include +#include +#include + +namespace +{ +constexpr const char *SOC_STORAGE_PATH = "/soc.txt"; +constexpr uint32_t SOC_TENTHS_UNAVAILABLE = UINT32_MAX; + +bool soc_storage_ready = false; +std::optional soc_fd = std::nullopt; +uint32_t soc_last_saved_tenths = SOC_TENTHS_UNAVAILABLE; + +constexpr uint32_t socPercentToTenths(const float soc_percent) +{ + return static_cast(soc_percent * 10.0f + 0.5f); +} + +constexpr bool isValidSocPercent(const float soc_percent) +{ + return soc_percent > 0.0f && soc_percent < 100.0f; +} + +constexpr bool isValidSocTenths(const uint32_t soc_tenths) +{ + return soc_tenths > 0U && soc_tenths < 1000U; +} + +std::expected read(float &saved_soc_percent) +{ + saved_soc_percent = -1.0f; + + auto buf = std::span(reinterpret_cast(&saved_soc_percent), sizeof(saved_soc_percent)); + if (auto err = fs.read(soc_fd.value(), buf, sizeof(saved_soc_percent)); !err) + { + return std::unexpected(ErrorCode::READ_FAILED); + } + return {}; +} + +std::expected write(const float soc_percent) +{ + auto buf = std::span(reinterpret_cast(const_cast(&soc_percent)), sizeof(soc_percent)); + if (auto err = fs.write(soc_fd.value(), buf, sizeof(soc_percent)); !err) + { + return std::unexpected(ErrorCode::WRITE_FAILED); + } + + if (auto err = fs.sync(soc_fd.value()); !err) + { + return std::unexpected(ErrorCode::WRITE_FAILED); + } + return {}; +} +} // namespace + +namespace app::socStorage +{ + +void init() +{ + soc_storage_ready = false; + + if (auto open_result = fs.open(SOC_STORAGE_PATH)) + { + soc_fd = *open_result; + } + else + { + soc_fd = std::nullopt; + LOG_ERROR("SoC storage unavailable: failed to open file"); + return; + } + + if (const auto result = readSocFromSd(); !result || !isValidSocPercent(result.value())) + { + LOG_WARN("SoC storage warning: failed to read valid SoC, attempting to reset."); + constexpr float invalid_soc_percent = -1.0f; + if (const auto write_result = writeSocToSd(invalid_soc_percent); !write_result) + { + LOG_ERROR("SoC storage error: failed to reset invalid SoC"); + return; + } + } + + soc_storage_ready = true; +} + +bool isAvailable() +{ + return soc_storage_ready; +} + +std::expected readSocFromSd() +{ + float saved_soc_percent = -1.0f; + + RETURN_IF_ERR(read(saved_soc_percent)); + + if (isValidSocPercent(saved_soc_percent)) + { + soc_last_saved_tenths = socPercentToTenths(saved_soc_percent); + } + + return saved_soc_percent; +} + +std::expected writeSocToSd(const float soc_percent) +{ + if (!isValidSocPercent(soc_percent)) + { + return std::unexpected(ErrorCode::INVALID_ARGS); + } + + RETURN_IF_ERR(write(soc_percent)); + soc_last_saved_tenths = socPercentToTenths(soc_percent); + + return {}; +} + +uint32_t getLastWrittenSocTenths() +{ + if (isValidSocTenths(soc_last_saved_tenths)) + { + return soc_last_saved_tenths; + } + + return SOC_TENTHS_UNAVAILABLE; +} + +std::expected convertSocToTenths(const float soc_percent) +{ + if (!isValidSocPercent(soc_percent)) + { + return std::unexpected(ErrorCode::INVALID_ARGS); + } + + const uint32_t soc_tenths = socPercentToTenths(soc_percent); + return soc_tenths; +} + +} // namespace app::socStorage diff --git a/firmware/hexray/BMS/src/app/app_socStorage.hpp b/firmware/hexray/BMS/src/app/app_socStorage.hpp new file mode 100644 index 0000000000..949420ea73 --- /dev/null +++ b/firmware/hexray/BMS/src/app/app_socStorage.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include +#include + +#include "io_filesystem.hpp" +#include "util_errorCodes.hpp" + +namespace app::socStorage +{ + +/** + * Initialize SoC persistence storage using the filesystem layer. + */ +void init(); + +/** + * Return whether or not SoC persistence storage is available. + */ +bool isAvailable(); + +/** + * @brief Read SoC value from SD storage. + */ +std::expected readSocFromSd(); + +/** + * @brief Write SoC value to SD storage. + */ +std::expected writeSocToSd(float soc_percent); + +/** + * @brief Get the last SoC value written to SD card in tenths of a percent. + */ +uint32_t getLastWrittenSocTenths(); + +/** + * @brief Get the SoC value to save to SD in tenths of a percent when valid. + */ +std::expected convertSocToTenths(float soc_percent); + +} // namespace app::socStorage diff --git a/firmware/hexray/BMS/src/app/states/app_initState.cpp b/firmware/hexray/BMS/src/app/states/app_initState.cpp index b3d0ebcd14..f542b6cc78 100644 --- a/firmware/hexray/BMS/src/app/states/app_initState.cpp +++ b/firmware/hexray/BMS/src/app/states/app_initState.cpp @@ -1,4 +1,6 @@ #include "app_states.hpp" +#include "app_tractiveSystem.hpp" +#include "app_soc.hpp" #include "io_irs.hpp" #include "io_charger.hpp" #include "app_canRx.hpp" @@ -31,14 +33,14 @@ namespace initState // const bool ts_discharged = true; // ONLY RUN THIS WHEN CELLS HAVE HAD TIME TO SETTLE - // if (app_canRx_Debug_ResetSoc_MinCellV_get()) - // { - // app_soc_resetSocFromVoltage(); - // } - // else if (app_canRx_Debug_ResetSoc_CustomEnable_get()) - // { - // app_soc_resetSocCustomValue(app_canRx_Debug_ResetSoc_CustomVal_get()); - // } + if (app::can_rx::Debug_ResetSoc_MinCellV_get()) + { + app::soc::resetSocFromVoltage(); + } + else if (app::can_rx::Debug_ResetSoc_CustomEnable_get()) + { + app::soc::resetSocCustomValue(app::can_rx::Debug_ResetSoc_CustomVal_get()); + } if (irs_negative_closed && ts_discharged) { diff --git a/firmware/hexray/BMS/src/hw/hw_sds.cpp b/firmware/hexray/BMS/src/hw/hw_sds.cpp new file mode 100644 index 0000000000..151ac540e4 --- /dev/null +++ b/firmware/hexray/BMS/src/hw/hw_sds.cpp @@ -0,0 +1,20 @@ +#include "hw_sds.hpp" + +#include "main.h" +#include "hw_gpios.hpp" + +hw::SdCard sd1(hsd1, 1000U, sd_cd); + +namespace hw +{ +const SdCard &getSdFromHandle(const SD_HandleTypeDef *hsd) +{ + if (hsd == &sd1.getHsd()) + { + return sd1; + } + + Error_Handler(); + return sd1; +} +} // namespace hw diff --git a/firmware/hexray/BMS/src/hw/hw_sds.hpp b/firmware/hexray/BMS/src/hw/hw_sds.hpp new file mode 100644 index 0000000000..2722d0a164 --- /dev/null +++ b/firmware/hexray/BMS/src/hw/hw_sds.hpp @@ -0,0 +1,4 @@ +#pragma once +#include "hw_sd.hpp" + +extern hw::SdCard sd1; diff --git a/firmware/hexray/BMS/src/io/io_filesystems.cpp b/firmware/hexray/BMS/src/io/io_filesystems.cpp new file mode 100644 index 0000000000..c0c8290378 --- /dev/null +++ b/firmware/hexray/BMS/src/io/io_filesystems.cpp @@ -0,0 +1,4 @@ +#include "io_filesystems.hpp" +#include "hw_sds.hpp" + +io::FileSystem fs{ sd1 }; diff --git a/firmware/hexray/BMS/src/io/io_filesystems.hpp b/firmware/hexray/BMS/src/io/io_filesystems.hpp new file mode 100644 index 0000000000..c22eb2c01e --- /dev/null +++ b/firmware/hexray/BMS/src/io/io_filesystems.hpp @@ -0,0 +1,4 @@ +#pragma once +#include "io_filesystem.hpp" + +extern io::FileSystem fs; diff --git a/firmware/hexray/BMS/src/jobs.cpp b/firmware/hexray/BMS/src/jobs.cpp index 4d40245d75..18d2e39d0b 100644 --- a/firmware/hexray/BMS/src/jobs.cpp +++ b/firmware/hexray/BMS/src/jobs.cpp @@ -5,6 +5,13 @@ // app #include "app_bmsShdnLoop.hpp" +#include "app_tractiveSystem.hpp" +#include "app_irs.hpp" +#include "app_heartbeatMonitors.hpp" +#include "app_heartbeatMonitor.hpp" +#include "app_soc.hpp" +#include "app_jsoncan.hpp" +#include "app_canUtils.hpp" #include "app_canAlerts.hpp" #include "app_canRx.hpp" #include "app_canTx.hpp" @@ -67,14 +74,13 @@ void jobs_init() can_rx_queue.init(); can_tx_queue.init(); - app::can_tx::BMS_Hash_set(GIT_COMMIT_HASH); app::can_tx::BMS_Clean_set(GIT_COMMIT_CLEAN); app::can_tx::BMS_Heartbeat_set(true); io::can_tx::BMS_Bootup_sendAperiodic(); app::precharge::init(); app::segments::alerts::init(); - + app::soc::init(); app::StateMachine::init(&app::states::init_state); app::can_tx::BMS_Heartbeat_set(true); } @@ -164,6 +170,7 @@ void jobs_run100Hz_tick() void jobs_run1kHz_tick() { io::can_tx::enqueueOtherPeriodicMsgs(io::time::getCurrentMs()); + app::soc::broadcast(); } static io::notify::Notifier sync_done; diff --git a/firmware/hexray/BMS/src/jobs.hpp b/firmware/hexray/BMS/src/jobs.hpp index 28c8da1fa2..cf1186a3bd 100644 --- a/firmware/hexray/BMS/src/jobs.hpp +++ b/firmware/hexray/BMS/src/jobs.hpp @@ -1,5 +1,7 @@ #pragma once +#include + void jobs_init(); void jobs_run1Hz_tick(); void jobs_run100Hz_tick(); @@ -7,6 +9,7 @@ void jobs_run1kHz_tick(); void jobs_runAdbmsVoltages_tick(); void jobs_runAdbmsConfigs_tick(); void jobs_runAdbmsAux_tick(); +void jobs_runSdCard_tick(uint32_t rounded_soc); // Kick the ADBMS config task to sync ASAP instead of waiting for its next periodic // tick. The config task runs the on-demand pass at its normal priority. diff --git a/firmware/hexray/BMS/src/tasks.cpp b/firmware/hexray/BMS/src/tasks.cpp index a2b3175e1d..45584f0caf 100644 --- a/firmware/hexray/BMS/src/tasks.cpp +++ b/firmware/hexray/BMS/src/tasks.cpp @@ -1,7 +1,10 @@ #include "tasks.h" + #include "jobs.hpp" #include "main.h" +#include "app_soc.hpp" +#include "app_socStorage.hpp" #include "app_jsoncan.hpp" #include "app_canTx.hpp" #include "app_canUtils.hpp" @@ -10,6 +13,7 @@ #include "io_canQueues.hpp" #include "io_time.hpp" #include "io_canRx.hpp" +#include "io_filesystems.hpp" #include "hw_adcs.hpp" #include "hw_watchdog.hpp" @@ -23,12 +27,15 @@ #include "hw_pwms.hpp" #include "hw_runTimeStat.hpp" -constexpr size_t TASK_COUNT = 8; +#include + +constexpr size_t TASK_COUNT = 9; [[noreturn]] static void tasks_run1Hz(void *arg); [[noreturn]] static void tasks_run100Hz(void *arg); [[noreturn]] static void tasks_run1kHz(void *arg); [[noreturn]] static void tasks_runCanTx(void *arg); [[noreturn]] static void tasks_runCanRx(void *arg); +[[noreturn]] static void tasks_runSdCard(void *arg); [[noreturn]] static void tasks_runAdbmsVoltages(void *arg); [[noreturn]] static void tasks_runAdbmsConfigs(void *arg); [[noreturn]] static void tasks_runAdbmsAux(void *arg); @@ -41,12 +48,14 @@ static hw::rtos::StaticTask::StaticTaskStack<512> TaskCanTxStack; static hw::rtos::StaticTask::StaticTaskStack<1024 * 3> TaskAdbmsVoltagesStack; static hw::rtos::StaticTask::StaticTaskStack<1024 * 3> TaskAdbmsConfigsStack; static hw::rtos::StaticTask::StaticTaskStack<1024 * 3> TaskAdbmsAuxStack; +static hw::rtos::StaticTask::StaticTaskStack<512> TaskSdCardStack; static hw::rtos::StaticTask Task1kHz(osPriorityRealtime, "Task1kHz", tasks_run1kHz, Task1kHzStack); static hw::rtos::StaticTask Task1Hz(osPriorityAboveNormal, "Task1Hz", tasks_run1Hz, Task1HzStack); static hw::rtos::StaticTask Task100Hz(osPriorityHigh, "Task100Hz", tasks_run100Hz, Task100HzStack); static hw::rtos::StaticTask TaskCanRx(osPriorityBelowNormal, "TaskCanRx", tasks_runCanRx, TaskCanRxStack); static hw::rtos::StaticTask TaskCanTx(osPriorityBelowNormal, "TaskCanTx", tasks_runCanTx, TaskCanTxStack); +static hw::rtos::StaticTask TaskSdCard(osPriorityLow, "TaskSdCard", tasks_runSdCard, TaskSdCardStack); static hw::rtos::StaticTask TaskAdbmsVoltages(osPriorityNormal, "TaskAdbmsVoltages", tasks_runAdbmsVoltages, TaskAdbmsVoltagesStack); static hw::rtos::StaticTask @@ -125,8 +134,14 @@ void tasks_run1Hz(void *arg) { // SEGGER_SYSVIEW_MarkStart(100); jobs_run1Hz_tick(); - // SEGGER_SYSVIEW_MarkStop(100); - // SEGGER_SYSVIEW_MarkStart(101); + + const float min_soc_percent = app::soc::getMinSocPercent(); + + if (auto soc_tenths = app::socStorage::convertSocToTenths(min_soc_percent); soc_tenths.has_value()) + { + xTaskNotify((TaskHandle_t)TaskSdCard.id(), *soc_tenths, eSetValueWithOverwrite); + } + watchdog1hz.checkIn(); // SEGGER_SYSVIEW_MarkStop(101); // SEGGER_SYSVIEW_MarkStart(102); @@ -161,6 +176,7 @@ void tasks_run1kHz(void *arg) hw::watchdog::instance &watchdog1khz = monitor.spawn_instance(period_ms + watchdog_grace_period_ms); uint32_t start_ticks = osKernelGetTickCount(); + forever { jobs_run1kHz_tick(); @@ -222,6 +238,23 @@ void tasks_runCanRx(void *arg) } } +[[noreturn]] static void tasks_runSdCard(void *arg) +{ + forever + { + uint32_t rounded_soc = 0U; + if (xTaskNotifyWait(0, ULONG_MAX, &rounded_soc, portMAX_DELAY) == pdTRUE) + { + const uint32_t last_written_soc = app::socStorage::getLastWrittenSocTenths(); + const bool soc_storage_available = app::socStorage::isAvailable(); + if (soc_storage_available && last_written_soc != rounded_soc) + { + LOG_IF_ERR(app::socStorage::writeSocToSd((float)rounded_soc / 10.0f)); + } + } + } +} + void tasks_runAdbmsVoltages(void *arg) { const uint32_t period_ms = 500U; @@ -271,6 +304,7 @@ void BMS_StartAllTasks() Task100Hz.start(); TaskCanRx.start(); TaskCanTx.start(); + TaskSdCard.start(); TaskAdbmsVoltages.start(); TaskAdbmsConfigs.start(); TaskAdbmsAux.start(); @@ -300,6 +334,11 @@ void tasks_init() fdcan2.init(); shdn_en.writePin(true); + if (const auto result = fs.init(); !result) + { + LOG_ERROR("Failed to initialize filesystem"); + } + const ResetReason reset_reason = hw::resetReason::get(); app::can_tx::BMS_ResetReason_set(static_cast(reset_reason)); diff --git a/firmware/shared/srcpp/util/util_errorCodes.hpp b/firmware/shared/srcpp/util/util_errorCodes.hpp index f43f0838ca..455dead431 100644 --- a/firmware/shared/srcpp/util/util_errorCodes.hpp +++ b/firmware/shared/srcpp/util/util_errorCodes.hpp @@ -17,6 +17,8 @@ enum class ErrorCode ERROR_INDETERMINATE, POLL_INVALID, NUM_EXIT_CODES, + WRITE_FAILED, + READ_FAILED }; template using result = std::expected; @@ -45,6 +47,10 @@ constexpr const char *error_code_to_string(const ErrorCode code) return "Invalid reading (sentinel value)"; case ErrorCode::ERROR_INDETERMINATE: return "Indeterminate error"; + case ErrorCode::WRITE_FAILED: + return "Write operation failed"; + case ErrorCode::READ_FAILED: + return "Read operation failed"; case ErrorCode::POLL_INVALID: return "Poll invalid"; case ErrorCode::NUM_EXIT_CODES: