diff --git a/bootloader/firmware/stm32G4/CMakeLists.txt b/bootloader/firmware/stm32G4/CMakeLists.txt index fc7625e56..7fc4988d5 100644 --- a/bootloader/firmware/stm32G4/CMakeLists.txt +++ b/bootloader/firmware/stm32G4/CMakeLists.txt @@ -170,8 +170,8 @@ endmacro() foreach_revision( PROJECT_NAME bootloader-hepa-uv CALL_FOREACH_REV hepauv_bootloader_loop - REVISIONS a1 b1 - SOURCES hepauv_sources hepauv_sources + REVISIONS a1 b1 c1 + SOURCES hepauv_sources hepauv_sources hepauv_sources NO_CREATE_IMAGE_HEX NO_CREATE_INSTALL_RULES ) diff --git a/cpp-utils/include/ot_utils/freertos/freertos_sleep.hpp b/cpp-utils/include/ot_utils/freertos/freertos_sleep.hpp new file mode 100644 index 000000000..18a589b8e --- /dev/null +++ b/cpp-utils/include/ot_utils/freertos/freertos_sleep.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "FreeRTOS.h" +#include "task.h" + +namespace ot_utils { +namespace freertos_sleep { + +static void sleep(uint32_t time_ms) { if (time_ms > 0) vTaskDelay(pdMS_TO_TICKS(time_ms)); } + +} // namespace freertos_sleep +} // namespace ot_utils diff --git a/hepa-uv/firmware/CMakeLists.txt b/hepa-uv/firmware/CMakeLists.txt index 509def804..f6f1d6eaa 100644 --- a/hepa-uv/firmware/CMakeLists.txt +++ b/hepa-uv/firmware/CMakeLists.txt @@ -44,7 +44,6 @@ set(HEPAUV_SRCS_A1 ) set(HEPAUV_SRCS_B1 ${HEPAUV_SRCS_A1}) set(HEPAUV_SRCS_C1 ${HEPAUV_SRCS_B1}) -set(HEPAUV_SRCS_C2 ${HEPAUV_SRCS_C1}) macro(hepa_uv_loop) set(_driver_suffix ${PROJECT_NAME}_${REVISION}) @@ -84,12 +83,12 @@ endmacro() foreach_revision( PROJECT_NAME hepa-uv - REVISIONS a1 b1 - SOURCES HEPAUV_SRCS_A1 HEPAUV_SRCS_B1 + REVISIONS a1 b1 c1 + SOURCES HEPAUV_SRCS_A1 HEPAUV_SRCS_B1 HEPAUV_SRCS_C1 CALL_FOREACH_REV hepa_uv_loop) alias_for_revision(PROJECT_NAME hepa-uv REVISION a1 REVISION_ALIAS proto) -alias_for_revision(PROJECT_NAME hepa-uv REVISION b1 REVISION_ALIAS rev1) +alias_for_revision(PROJECT_NAME hepa-uv REVISION c1 REVISION_ALIAS rev1) add_clang_tidy_target( TARGET_NAME hepa-uv-lint diff --git a/hepa-uv/firmware/main_rev1.cpp b/hepa-uv/firmware/main_rev1.cpp index 4bfed1d3a..a724e3fc7 100644 --- a/hepa-uv/firmware/main_rev1.cpp +++ b/hepa-uv/firmware/main_rev1.cpp @@ -1,4 +1,5 @@ #include +#include // clang-format off #include "FreeRTOS.h" @@ -92,6 +93,17 @@ class EEPromHardwareInterface }; static auto eeprom_hw_iface = EEPromHardwareInterface(); +#if PCB_PRIMARY_REVISION == 'a' || PCB_PRIMARY_REVISION == 'b' +static constexpr std::optional safety_relay_active = + std::nullopt; +#else +static std::optional safety_relay_active = + gpio::PinConfig{// NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast) + .port = nSAFETY_ACTIVE_MCU_PORT, + .pin = nSAFETY_ACTIVE_MCU_PIN, + .active_setting = nSAFETY_ACTIVE_AS}; +#endif + static auto gpio_drive_pins = gpio_drive_hardware::GpioDrivePins{ .door_open = gpio::PinConfig{ @@ -114,8 +126,8 @@ static auto gpio_drive_pins = gpio_drive_hardware::GpioDrivePins{ .uv_push_button = gpio::PinConfig{ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast) - .port = UV_NO_MCU_PORT, - .pin = UV_NO_MCU_PIN, + .port = nUV_PRESSED_PORT, + .pin = nUV_PRESSED_PIN, }, .hepa_on_off = gpio::PinConfig{ @@ -123,11 +135,14 @@ static auto gpio_drive_pins = gpio_drive_hardware::GpioDrivePins{ .port = HEPA_ON_OFF_PORT, .pin = HEPA_ON_OFF_PIN, .active_setting = HEPA_ON_OFF_AS}, - .uv_on_off = gpio::PinConfig{ - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast) - .port = UV_ON_OFF_MCU_PORT, - .pin = UV_ON_OFF_MCU_PIN, - .active_setting = UV_ON_OFF_AS}}; + .uv_on_off = + gpio::PinConfig{ + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast) + .port = UV_ON_OFF_MCU_PORT, + .pin = UV_ON_OFF_MCU_PIN, + .active_setting = UV_ON_OFF_AS}, + .safety_relay_active = safety_relay_active, +}; static auto& hepauv_queues = hepauv_tasks::get_main_queues(); @@ -140,7 +155,7 @@ extern "C" void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { case DOOR_OPEN_MCU_PIN: case REED_SW_MCU_PIN: case HEPA_NO_MCU_PIN: - case UV_NO_MCU_PIN: + case nUV_PRESSED_PIN: if (hepauv_queues.hepa_queue != nullptr) { static_cast(hepauv_queues.hepa_queue->try_write_isr( GPIOInterruptChanged{.pin = GPIO_Pin})); diff --git a/hepa-uv/firmware/utility_gpio.c b/hepa-uv/firmware/utility_gpio.c index 405ba81bf..3ab288d69 100644 --- a/hepa-uv/firmware/utility_gpio.c +++ b/hepa-uv/firmware/utility_gpio.c @@ -94,10 +94,10 @@ void uv_push_button_input_gpio_init(void) { __HAL_RCC_GPIOC_CLK_ENABLE(); /*Configure GPIO pin UV_NO_MCU : PC2 */ GPIO_InitTypeDef GPIO_InitStruct = {0}; - GPIO_InitStruct.Pin = UV_NO_MCU_PIN; + GPIO_InitStruct.Pin = nUV_PRESSED_PIN; GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING; GPIO_InitStruct.Pull = GPIO_NOPULL; - HAL_GPIO_Init(UV_NO_MCU_PORT, &GPIO_InitStruct); + HAL_GPIO_Init(nUV_PRESSED_PORT, &GPIO_InitStruct); } /** @@ -134,6 +134,22 @@ void uv_on_off_output_init() { HAL_GPIO_Init(UV_ON_OFF_MCU_PORT, &GPIO_InitStruct); } +/** + * @brief nSAFETY_ACTIVE_MCU GPIO Initialization Function + * @param None + * @retval None +*/ +void safety_relay_active_input_init() { + /* GPIO Ports Clock Enable */ + __HAL_RCC_GPIOB_CLK_ENABLE(); + /*Configure GPIO pin nSAFETY_ACTIVE_MCU: PB5 */ + GPIO_InitTypeDef GPIO_InitStruct = {0}; + GPIO_InitStruct.Pin = nSAFETY_ACTIVE_MCU_PIN; + GPIO_InitStruct.Mode = GPIO_MODE_INPUT; + GPIO_InitStruct.Pull = GPIO_NOPULL; + HAL_GPIO_Init(nSAFETY_ACTIVE_MCU_PORT, &GPIO_InitStruct); +} + /** * @brief NVIC EXTI interrupt priority Initialization * @param None @@ -168,4 +184,5 @@ void utility_gpio_init(void) { hepa_on_off_output_init(); uv_push_button_input_gpio_init(); uv_on_off_output_init(); + safety_relay_active_input_init(); } diff --git a/include/bootloader/core/ids.h b/include/bootloader/core/ids.h index b3c7ae1b9..e805bb401 100644 --- a/include/bootloader/core/ids.h +++ b/include/bootloader/core/ids.h @@ -165,6 +165,7 @@ typedef enum { can_errorcode_over_pressure = 0xd, can_errorcode_door_open = 0xe, can_errorcode_reed_open = 0xf, + can_errorcode_safety_relay_inactive = 0x11, } CANErrorCode; /** Tool types detected on Head. */ diff --git a/include/can/core/ids.hpp b/include/can/core/ids.hpp index dad0695f5..a0195eff7 100644 --- a/include/can/core/ids.hpp +++ b/include/can/core/ids.hpp @@ -167,6 +167,7 @@ enum class ErrorCode { over_pressure = 0xd, door_open = 0xe, reed_open = 0xf, + safety_relay_inactive = 0x11, }; /** Error Severity levels. */ diff --git a/include/can/core/messages.hpp b/include/can/core/messages.hpp index a60eedc3c..1b4011965 100644 --- a/include/can/core/messages.hpp +++ b/include/can/core/messages.hpp @@ -1712,6 +1712,7 @@ struct GetHepaUVStateResponse uint8_t uv_light_on; uint32_t remaining_time_s; uint16_t uv_current_ma; + uint8_t safety_relay_active; template auto serialize(Output body, Limit limit) const -> uint8_t { @@ -1720,6 +1721,7 @@ struct GetHepaUVStateResponse iter = bit_utils::int_to_bytes(uv_light_on, iter, limit); iter = bit_utils::int_to_bytes(remaining_time_s, iter, limit); iter = bit_utils::int_to_bytes(uv_current_ma, iter, limit); + iter = bit_utils::int_to_bytes(safety_relay_active, iter, limit); return iter - body; } diff --git a/include/hepa-uv/core/uv_task.hpp b/include/hepa-uv/core/uv_task.hpp index 317a0644c..06caf062d 100644 --- a/include/hepa-uv/core/uv_task.hpp +++ b/include/hepa-uv/core/uv_task.hpp @@ -9,6 +9,7 @@ #include "hepa-uv/core/messages.hpp" #include "hepa-uv/firmware/gpio_drive_hardware.hpp" #include "hepa-uv/firmware/uv_control_hardware.hpp" +#include "ot_utils/freertos/freertos_sleep.hpp" #include "ot_utils/freertos/freertos_timer.hpp" namespace uv_task { @@ -16,6 +17,7 @@ namespace uv_task { // How long to keep the UV light on in seconds. static constexpr uint32_t DELAY_S = 60 * 15; // 15 minutes static constexpr uint32_t MAX_DELAY_S = 60 * 60; // 1hr max timeout +static constexpr uint32_t DEBOUNCE_MS = 250; // button debounce using TaskMessage = uv_task_messages::TaskMessage; @@ -33,11 +35,15 @@ class UVMessageHandler { can_client{can_client}, _timer( "UVTask", [ThisPtr = this] { ThisPtr->timer_callback(); }, - DELAY_S * 1000) { + DELAY_S * 1000), + debounce_timer( + "UVTaskDebounce", [ThisPtr = this] { ThisPtr->debounce_cb(); }, + DEBOUNCE_MS) { // get current state uv_push_button = gpio::is_set(drive_pins.uv_push_button); door_closed = gpio::is_set(drive_pins.door_open); reed_switch_set = gpio::is_set(drive_pins.reed_switch); + update_safety_relay_state(); // turn off UV Ballast gpio::reset(drive_pins.uv_on_off); } @@ -58,6 +64,19 @@ class UVMessageHandler { uv_push_button = false; } + // callback to debounce the irq signals + void debounce_cb() { + debounce_timer.stop(); + set_uv_light_state(uv_push_button, uv_off_timeout_s); + } + + // Helper to update safety relay state + void update_safety_relay_state() { + if (drive_pins.safety_relay_active.has_value()) + safety_relay_active = + gpio::is_set(drive_pins.safety_relay_active.value()); + } + void visit(const std::monostate &) {} // Handle GPIO EXTI Interrupts here @@ -67,17 +86,16 @@ class UVMessageHandler { return; } + // debounce + if (debounce_timer.is_running()) return; + debounce_timer.start(); + // update states - if (m.pin == drive_pins.uv_push_button.pin) { + door_closed = gpio::is_set(drive_pins.door_open); + reed_switch_set = gpio::is_set(drive_pins.reed_switch); + update_safety_relay_state(); + if (m.pin == drive_pins.uv_push_button.pin) uv_push_button = !uv_push_button; - } else if (m.pin == drive_pins.door_open.pin) { - door_closed = gpio::is_set(drive_pins.door_open); - } else if (m.pin == drive_pins.reed_switch.pin) { - reed_switch_set = gpio::is_set(drive_pins.reed_switch); - } - - // Drive the UV light - set_uv_light_state(uv_push_button, uv_off_timeout_s); } void visit(const can::messages::SetHepaUVStateRequest &m) { @@ -89,13 +107,15 @@ class UVMessageHandler { } void visit(const can::messages::GetHepaUVStateRequest &m) { + update_safety_relay_state(); uv_current_ma = uv_hardware.get_uv_light_current(); auto resp = can::messages::GetHepaUVStateResponse{ .message_index = m.message_index, .timeout_s = uv_off_timeout_s, .uv_light_on = uv_light_on, .remaining_time_s = (_timer.get_remaining_time() / 1000), - .uv_current_ma = uv_current_ma}; + .uv_current_ma = uv_current_ma, + .safety_relay_active = safety_relay_active}; can_client.send_can_message(can::ids::NodeId::host, resp); } @@ -153,8 +173,32 @@ class UVMessageHandler { if (_timer.is_running()) _timer.stop(); } - // Update the voltage usage of the uv light + // wait 10ms for safety relay, then update the states + ot_utils::freertos_sleep::sleep(100); uv_current_ma = uv_hardware.get_uv_light_current(); + update_safety_relay_state(); + if (uv_light_on && !safety_relay_active) { + // we tried to set the uv light, but the relay is not active + if (_timer.is_running()) { + gpio::reset(drive_pins.uv_on_off); + _timer.stop(); + led_control_client.send_led_control_message( + led_control_task_messages::PushButtonLED(UV_BUTTON, 0, 0, + 50, 0)); + } + // send error + auto msg = can::messages::ErrorMessage{ + .message_index = 0, + .severity = can::ids::ErrorSeverity::warning, + .error_code = can::ids::ErrorCode::safety_relay_inactive, + }; + can_client.send_can_message(can::ids::NodeId::host, msg); + + uv_push_button = false; + uv_light_on = false; + uv_current_ma = 0; + return; + } // TODO: send state change CAN message to host } @@ -162,6 +206,7 @@ class UVMessageHandler { // state tracking variables bool door_closed = false; bool reed_switch_set = false; + bool safety_relay_active = false; bool uv_push_button = false; bool uv_light_on = false; uint32_t uv_off_timeout_s = DELAY_S; @@ -172,6 +217,7 @@ class UVMessageHandler { LEDControlClient &led_control_client; CanClient &can_client; ot_utils::freertos_timer::FreeRTOSTimer _timer; + ot_utils::freertos_timer::FreeRTOSTimer debounce_timer; }; /** diff --git a/include/hepa-uv/firmware/gpio_drive_hardware.hpp b/include/hepa-uv/firmware/gpio_drive_hardware.hpp index 517f435c9..1eaa3911b 100644 --- a/include/hepa-uv/firmware/gpio_drive_hardware.hpp +++ b/include/hepa-uv/firmware/gpio_drive_hardware.hpp @@ -1,9 +1,12 @@ #pragma once +#include + #include "common/firmware/gpio.hpp" namespace gpio_drive_hardware { +// NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init) struct GpioDrivePins { gpio::PinConfig door_open; gpio::PinConfig reed_switch; @@ -11,6 +14,7 @@ struct GpioDrivePins { gpio::PinConfig uv_push_button; gpio::PinConfig hepa_on_off; gpio::PinConfig uv_on_off; + std::optional safety_relay_active = std::nullopt; }; } // namespace gpio_drive_hardware \ No newline at end of file diff --git a/include/hepa-uv/firmware/utility_gpio.h b/include/hepa-uv/firmware/utility_gpio.h index 7c383e1f2..90a8cbfc4 100644 --- a/include/hepa-uv/firmware/utility_gpio.h +++ b/include/hepa-uv/firmware/utility_gpio.h @@ -88,12 +88,9 @@ void utility_gpio_init(); #define UV_ON_OFF_MCU_PORT GPIOA #define UV_ON_OFF_MCU_PIN GPIO_PIN_4 #define UV_ON_OFF_AS GPIO_PIN_RESET -// UV_NO_MCU PC2 -#define UV_NO_MCU_PORT GPIOC -#define UV_NO_MCU_PIN GPIO_PIN_2 -// UV_ADC PA3 -#define UV_ADC_PORT GPIOC -#define UV_ADC_PIN GPIO_PIN_3 +// nUV_PRESSED PC2 +#define nUV_PRESSED_PORT GPIOC +#define nUV_PRESSED_PIN GPIO_PIN_2 // UV_B_CTRL PC5 #define UV_B_CTRL_PORT GPIOC #define UV_B_CTRL_PIN GPIO_PIN_5 @@ -105,4 +102,8 @@ void utility_gpio_init(); #define UV_R_CTRL_PIN GPIO_PIN_1 // UV_W_CTRL PB2 #define UV_W_CTRL_PORT GPIOB -#define UV_W_CTRL_PIN GPIO_PIN_2 \ No newline at end of file +#define UV_W_CTRL_PIN GPIO_PIN_2 +// nSAFETY_ACTIVE_MCU PB5 +#define nSAFETY_ACTIVE_MCU_PORT GPIOB +#define nSAFETY_ACTIVE_MCU_PIN GPIO_PIN_5 +#define nSAFETY_ACTIVE_AS GPIO_PIN_RESET \ No newline at end of file