Skip to content

Commit 2843bb1

Browse files
vegano1y3rsh
authored andcommitted
feat(hepa-uv): add support for the safety relay in the new revision of the Hepa/UV module. (#782)
1 parent 8738ed7 commit 2843bb1

File tree

11 files changed

+133
-35
lines changed

11 files changed

+133
-35
lines changed

bootloader/firmware/stm32G4/CMakeLists.txt

+2-2
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,8 @@ endmacro()
170170
foreach_revision(
171171
PROJECT_NAME bootloader-hepa-uv
172172
CALL_FOREACH_REV hepauv_bootloader_loop
173-
REVISIONS a1 b1
174-
SOURCES hepauv_sources hepauv_sources
173+
REVISIONS a1 b1 c1
174+
SOURCES hepauv_sources hepauv_sources hepauv_sources
175175
NO_CREATE_IMAGE_HEX
176176
NO_CREATE_INSTALL_RULES
177177
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#pragma once
2+
3+
#include "FreeRTOS.h"
4+
#include "task.h"
5+
6+
namespace ot_utils {
7+
namespace freertos_sleep {
8+
9+
static void sleep(uint32_t time_ms) { if (time_ms > 0) vTaskDelay(pdMS_TO_TICKS(time_ms)); }
10+
11+
} // namespace freertos_sleep
12+
} // namespace ot_utils

hepa-uv/firmware/CMakeLists.txt

+3-4
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ set(HEPAUV_SRCS_A1
4444
)
4545
set(HEPAUV_SRCS_B1 ${HEPAUV_SRCS_A1})
4646
set(HEPAUV_SRCS_C1 ${HEPAUV_SRCS_B1})
47-
set(HEPAUV_SRCS_C2 ${HEPAUV_SRCS_C1})
4847

4948
macro(hepa_uv_loop)
5049
set(_driver_suffix ${PROJECT_NAME}_${REVISION})
@@ -84,12 +83,12 @@ endmacro()
8483

8584
foreach_revision(
8685
PROJECT_NAME hepa-uv
87-
REVISIONS a1 b1
88-
SOURCES HEPAUV_SRCS_A1 HEPAUV_SRCS_B1
86+
REVISIONS a1 b1 c1
87+
SOURCES HEPAUV_SRCS_A1 HEPAUV_SRCS_B1 HEPAUV_SRCS_C1
8988
CALL_FOREACH_REV hepa_uv_loop)
9089

9190
alias_for_revision(PROJECT_NAME hepa-uv REVISION a1 REVISION_ALIAS proto)
92-
alias_for_revision(PROJECT_NAME hepa-uv REVISION b1 REVISION_ALIAS rev1)
91+
alias_for_revision(PROJECT_NAME hepa-uv REVISION c1 REVISION_ALIAS rev1)
9392

9493
add_clang_tidy_target(
9594
TARGET_NAME hepa-uv-lint

hepa-uv/firmware/main_rev1.cpp

+23-8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include <array>
2+
#include <optional>
23

34
// clang-format off
45
#include "FreeRTOS.h"
@@ -92,6 +93,17 @@ class EEPromHardwareInterface
9293
};
9394
static auto eeprom_hw_iface = EEPromHardwareInterface();
9495

96+
#if PCB_PRIMARY_REVISION == 'a' || PCB_PRIMARY_REVISION == 'b'
97+
static constexpr std::optional<gpio::PinConfig> safety_relay_active =
98+
std::nullopt;
99+
#else
100+
static std::optional<gpio::PinConfig> safety_relay_active =
101+
gpio::PinConfig{// NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)
102+
.port = nSAFETY_ACTIVE_MCU_PORT,
103+
.pin = nSAFETY_ACTIVE_MCU_PIN,
104+
.active_setting = nSAFETY_ACTIVE_AS};
105+
#endif
106+
95107
static auto gpio_drive_pins = gpio_drive_hardware::GpioDrivePins{
96108
.door_open =
97109
gpio::PinConfig{
@@ -114,20 +126,23 @@ static auto gpio_drive_pins = gpio_drive_hardware::GpioDrivePins{
114126
.uv_push_button =
115127
gpio::PinConfig{
116128
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)
117-
.port = UV_NO_MCU_PORT,
118-
.pin = UV_NO_MCU_PIN,
129+
.port = nUV_PRESSED_PORT,
130+
.pin = nUV_PRESSED_PIN,
119131
},
120132
.hepa_on_off =
121133
gpio::PinConfig{
122134
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)
123135
.port = HEPA_ON_OFF_PORT,
124136
.pin = HEPA_ON_OFF_PIN,
125137
.active_setting = HEPA_ON_OFF_AS},
126-
.uv_on_off = gpio::PinConfig{
127-
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)
128-
.port = UV_ON_OFF_MCU_PORT,
129-
.pin = UV_ON_OFF_MCU_PIN,
130-
.active_setting = UV_ON_OFF_AS}};
138+
.uv_on_off =
139+
gpio::PinConfig{
140+
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)
141+
.port = UV_ON_OFF_MCU_PORT,
142+
.pin = UV_ON_OFF_MCU_PIN,
143+
.active_setting = UV_ON_OFF_AS},
144+
.safety_relay_active = safety_relay_active,
145+
};
131146

132147
static auto& hepauv_queues = hepauv_tasks::get_main_queues();
133148

@@ -140,7 +155,7 @@ extern "C" void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
140155
case DOOR_OPEN_MCU_PIN:
141156
case REED_SW_MCU_PIN:
142157
case HEPA_NO_MCU_PIN:
143-
case UV_NO_MCU_PIN:
158+
case nUV_PRESSED_PIN:
144159
if (hepauv_queues.hepa_queue != nullptr) {
145160
static_cast<void>(hepauv_queues.hepa_queue->try_write_isr(
146161
GPIOInterruptChanged{.pin = GPIO_Pin}));

hepa-uv/firmware/utility_gpio.c

+19-2
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,10 @@ void uv_push_button_input_gpio_init(void) {
9494
__HAL_RCC_GPIOC_CLK_ENABLE();
9595
/*Configure GPIO pin UV_NO_MCU : PC2 */
9696
GPIO_InitTypeDef GPIO_InitStruct = {0};
97-
GPIO_InitStruct.Pin = UV_NO_MCU_PIN;
97+
GPIO_InitStruct.Pin = nUV_PRESSED_PIN;
9898
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
9999
GPIO_InitStruct.Pull = GPIO_NOPULL;
100-
HAL_GPIO_Init(UV_NO_MCU_PORT, &GPIO_InitStruct);
100+
HAL_GPIO_Init(nUV_PRESSED_PORT, &GPIO_InitStruct);
101101
}
102102

103103
/**
@@ -134,6 +134,22 @@ void uv_on_off_output_init() {
134134
HAL_GPIO_Init(UV_ON_OFF_MCU_PORT, &GPIO_InitStruct);
135135
}
136136

137+
/**
138+
* @brief nSAFETY_ACTIVE_MCU GPIO Initialization Function
139+
* @param None
140+
* @retval None
141+
*/
142+
void safety_relay_active_input_init() {
143+
/* GPIO Ports Clock Enable */
144+
__HAL_RCC_GPIOB_CLK_ENABLE();
145+
/*Configure GPIO pin nSAFETY_ACTIVE_MCU: PB5 */
146+
GPIO_InitTypeDef GPIO_InitStruct = {0};
147+
GPIO_InitStruct.Pin = nSAFETY_ACTIVE_MCU_PIN;
148+
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
149+
GPIO_InitStruct.Pull = GPIO_NOPULL;
150+
HAL_GPIO_Init(nSAFETY_ACTIVE_MCU_PORT, &GPIO_InitStruct);
151+
}
152+
137153
/**
138154
* @brief NVIC EXTI interrupt priority Initialization
139155
* @param None
@@ -168,4 +184,5 @@ void utility_gpio_init(void) {
168184
hepa_on_off_output_init();
169185
uv_push_button_input_gpio_init();
170186
uv_on_off_output_init();
187+
safety_relay_active_input_init();
171188
}

include/bootloader/core/ids.h

+1
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ typedef enum {
165165
can_errorcode_over_pressure = 0xd,
166166
can_errorcode_door_open = 0xe,
167167
can_errorcode_reed_open = 0xf,
168+
can_errorcode_safety_relay_inactive = 0x11,
168169
} CANErrorCode;
169170

170171
/** Tool types detected on Head. */

include/can/core/ids.hpp

+1
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ enum class ErrorCode {
167167
over_pressure = 0xd,
168168
door_open = 0xe,
169169
reed_open = 0xf,
170+
safety_relay_inactive = 0x11,
170171
};
171172

172173
/** Error Severity levels. */

include/can/core/messages.hpp

+2
Original file line numberDiff line numberDiff line change
@@ -1712,6 +1712,7 @@ struct GetHepaUVStateResponse
17121712
uint8_t uv_light_on;
17131713
uint32_t remaining_time_s;
17141714
uint16_t uv_current_ma;
1715+
uint8_t safety_relay_active;
17151716

17161717
template <bit_utils::ByteIterator Output, typename Limit>
17171718
auto serialize(Output body, Limit limit) const -> uint8_t {
@@ -1720,6 +1721,7 @@ struct GetHepaUVStateResponse
17201721
iter = bit_utils::int_to_bytes(uv_light_on, iter, limit);
17211722
iter = bit_utils::int_to_bytes(remaining_time_s, iter, limit);
17221723
iter = bit_utils::int_to_bytes(uv_current_ma, iter, limit);
1724+
iter = bit_utils::int_to_bytes(safety_relay_active, iter, limit);
17231725
return iter - body;
17241726
}
17251727

include/hepa-uv/core/uv_task.hpp

+58-12
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@
99
#include "hepa-uv/core/messages.hpp"
1010
#include "hepa-uv/firmware/gpio_drive_hardware.hpp"
1111
#include "hepa-uv/firmware/uv_control_hardware.hpp"
12+
#include "ot_utils/freertos/freertos_sleep.hpp"
1213
#include "ot_utils/freertos/freertos_timer.hpp"
1314

1415
namespace uv_task {
1516

1617
// How long to keep the UV light on in seconds.
1718
static constexpr uint32_t DELAY_S = 60 * 15; // 15 minutes
1819
static constexpr uint32_t MAX_DELAY_S = 60 * 60; // 1hr max timeout
20+
static constexpr uint32_t DEBOUNCE_MS = 250; // button debounce
1921

2022
using TaskMessage = uv_task_messages::TaskMessage;
2123

@@ -33,11 +35,15 @@ class UVMessageHandler {
3335
can_client{can_client},
3436
_timer(
3537
"UVTask", [ThisPtr = this] { ThisPtr->timer_callback(); },
36-
DELAY_S * 1000) {
38+
DELAY_S * 1000),
39+
debounce_timer(
40+
"UVTaskDebounce", [ThisPtr = this] { ThisPtr->debounce_cb(); },
41+
DEBOUNCE_MS) {
3742
// get current state
3843
uv_push_button = gpio::is_set(drive_pins.uv_push_button);
3944
door_closed = gpio::is_set(drive_pins.door_open);
4045
reed_switch_set = gpio::is_set(drive_pins.reed_switch);
46+
update_safety_relay_state();
4147
// turn off UV Ballast
4248
gpio::reset(drive_pins.uv_on_off);
4349
}
@@ -58,6 +64,19 @@ class UVMessageHandler {
5864
uv_push_button = false;
5965
}
6066

67+
// callback to debounce the irq signals
68+
void debounce_cb() {
69+
debounce_timer.stop();
70+
set_uv_light_state(uv_push_button, uv_off_timeout_s);
71+
}
72+
73+
// Helper to update safety relay state
74+
void update_safety_relay_state() {
75+
if (drive_pins.safety_relay_active.has_value())
76+
safety_relay_active =
77+
gpio::is_set(drive_pins.safety_relay_active.value());
78+
}
79+
6180
void visit(const std::monostate &) {}
6281

6382
// Handle GPIO EXTI Interrupts here
@@ -67,17 +86,16 @@ class UVMessageHandler {
6786
return;
6887
}
6988

89+
// debounce
90+
if (debounce_timer.is_running()) return;
91+
debounce_timer.start();
92+
7093
// update states
71-
if (m.pin == drive_pins.uv_push_button.pin) {
94+
door_closed = gpio::is_set(drive_pins.door_open);
95+
reed_switch_set = gpio::is_set(drive_pins.reed_switch);
96+
update_safety_relay_state();
97+
if (m.pin == drive_pins.uv_push_button.pin)
7298
uv_push_button = !uv_push_button;
73-
} else if (m.pin == drive_pins.door_open.pin) {
74-
door_closed = gpio::is_set(drive_pins.door_open);
75-
} else if (m.pin == drive_pins.reed_switch.pin) {
76-
reed_switch_set = gpio::is_set(drive_pins.reed_switch);
77-
}
78-
79-
// Drive the UV light
80-
set_uv_light_state(uv_push_button, uv_off_timeout_s);
8199
}
82100

83101
void visit(const can::messages::SetHepaUVStateRequest &m) {
@@ -89,13 +107,15 @@ class UVMessageHandler {
89107
}
90108

91109
void visit(const can::messages::GetHepaUVStateRequest &m) {
110+
update_safety_relay_state();
92111
uv_current_ma = uv_hardware.get_uv_light_current();
93112
auto resp = can::messages::GetHepaUVStateResponse{
94113
.message_index = m.message_index,
95114
.timeout_s = uv_off_timeout_s,
96115
.uv_light_on = uv_light_on,
97116
.remaining_time_s = (_timer.get_remaining_time() / 1000),
98-
.uv_current_ma = uv_current_ma};
117+
.uv_current_ma = uv_current_ma,
118+
.safety_relay_active = safety_relay_active};
99119
can_client.send_can_message(can::ids::NodeId::host, resp);
100120
}
101121

@@ -153,15 +173,40 @@ class UVMessageHandler {
153173
if (_timer.is_running()) _timer.stop();
154174
}
155175

156-
// Update the voltage usage of the uv light
176+
// wait 10ms for safety relay, then update the states
177+
ot_utils::freertos_sleep::sleep(100);
157178
uv_current_ma = uv_hardware.get_uv_light_current();
179+
update_safety_relay_state();
180+
if (uv_light_on && !safety_relay_active) {
181+
// we tried to set the uv light, but the relay is not active
182+
if (_timer.is_running()) {
183+
gpio::reset(drive_pins.uv_on_off);
184+
_timer.stop();
185+
led_control_client.send_led_control_message(
186+
led_control_task_messages::PushButtonLED(UV_BUTTON, 0, 0,
187+
50, 0));
188+
}
189+
// send error
190+
auto msg = can::messages::ErrorMessage{
191+
.message_index = 0,
192+
.severity = can::ids::ErrorSeverity::warning,
193+
.error_code = can::ids::ErrorCode::safety_relay_inactive,
194+
};
195+
can_client.send_can_message(can::ids::NodeId::host, msg);
196+
197+
uv_push_button = false;
198+
uv_light_on = false;
199+
uv_current_ma = 0;
200+
return;
201+
}
158202

159203
// TODO: send state change CAN message to host
160204
}
161205

162206
// state tracking variables
163207
bool door_closed = false;
164208
bool reed_switch_set = false;
209+
bool safety_relay_active = false;
165210
bool uv_push_button = false;
166211
bool uv_light_on = false;
167212
uint32_t uv_off_timeout_s = DELAY_S;
@@ -172,6 +217,7 @@ class UVMessageHandler {
172217
LEDControlClient &led_control_client;
173218
CanClient &can_client;
174219
ot_utils::freertos_timer::FreeRTOSTimer _timer;
220+
ot_utils::freertos_timer::FreeRTOSTimer debounce_timer;
175221
};
176222

177223
/**
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
#pragma once
22

3+
#include <optional>
4+
35
#include "common/firmware/gpio.hpp"
46

57
namespace gpio_drive_hardware {
68

9+
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)
710
struct GpioDrivePins {
811
gpio::PinConfig door_open;
912
gpio::PinConfig reed_switch;
1013
gpio::PinConfig hepa_push_button;
1114
gpio::PinConfig uv_push_button;
1215
gpio::PinConfig hepa_on_off;
1316
gpio::PinConfig uv_on_off;
17+
std::optional<gpio::PinConfig> safety_relay_active = std::nullopt;
1418
};
1519

1620
} // namespace gpio_drive_hardware

include/hepa-uv/firmware/utility_gpio.h

+8-7
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,9 @@ void utility_gpio_init();
8888
#define UV_ON_OFF_MCU_PORT GPIOA
8989
#define UV_ON_OFF_MCU_PIN GPIO_PIN_4
9090
#define UV_ON_OFF_AS GPIO_PIN_RESET
91-
// UV_NO_MCU PC2
92-
#define UV_NO_MCU_PORT GPIOC
93-
#define UV_NO_MCU_PIN GPIO_PIN_2
94-
// UV_ADC PA3
95-
#define UV_ADC_PORT GPIOC
96-
#define UV_ADC_PIN GPIO_PIN_3
91+
// nUV_PRESSED PC2
92+
#define nUV_PRESSED_PORT GPIOC
93+
#define nUV_PRESSED_PIN GPIO_PIN_2
9794
// UV_B_CTRL PC5
9895
#define UV_B_CTRL_PORT GPIOC
9996
#define UV_B_CTRL_PIN GPIO_PIN_5
@@ -105,4 +102,8 @@ void utility_gpio_init();
105102
#define UV_R_CTRL_PIN GPIO_PIN_1
106103
// UV_W_CTRL PB2
107104
#define UV_W_CTRL_PORT GPIOB
108-
#define UV_W_CTRL_PIN GPIO_PIN_2
105+
#define UV_W_CTRL_PIN GPIO_PIN_2
106+
// nSAFETY_ACTIVE_MCU PB5
107+
#define nSAFETY_ACTIVE_MCU_PORT GPIOB
108+
#define nSAFETY_ACTIVE_MCU_PIN GPIO_PIN_5
109+
#define nSAFETY_ACTIVE_AS GPIO_PIN_RESET

0 commit comments

Comments
 (0)