Skip to content

Commit a3b0c7e

Browse files
authored
fix(thermocycler-gen2): handle systick overflow in thermal control tasks (#432)
1 parent 4c8fd16 commit a3b0c7e

File tree

3 files changed

+72
-4
lines changed

3 files changed

+72
-4
lines changed

stm32-modules/include/thermocycler-gen2/thermocycler-gen2/lid_heater_task.hpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@ class LidHeaterTask {
157157
requires LidHeaterExecutionPolicy<Policy>
158158
auto visit_message(const messages::LidTempReadComplete& msg, Policy& policy)
159159
-> void {
160+
constexpr Milliseconds time_overflow_amount = Milliseconds(
161+
std::numeric_limits<decltype(msg.timestamp_ms)>::max());
160162
auto old_error_bitmap = _state.error_bitmap;
161163
auto current_time = Milliseconds(msg.timestamp_ms);
162164
handle_temperature_conversion(msg.lid_temp, _thermistor);
@@ -174,9 +176,12 @@ class LidHeaterTask {
174176

175177
// If we're in a controlling state, we now update the heater output
176178
if (_state.system_status == State::CONTROLLING) {
179+
auto time_delta = current_time - _last_update;
180+
if (time_delta.count() < 0) {
181+
time_delta += time_overflow_amount;
182+
}
177183
auto power = update_control(
178-
std::chrono::duration_cast<Seconds>(current_time - _last_update)
179-
.count());
184+
std::chrono::duration_cast<Seconds>(time_delta).count());
180185
auto ret = policy.set_heater_power(power);
181186
if (!ret) {
182187
policy.set_heater_power(0.0F);

stm32-modules/include/thermocycler-gen2/thermocycler-gen2/thermal_plate_task.hpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,8 @@ class ThermalPlateTask {
278278
requires ThermalPlateExecutionPolicy<Policy>
279279
auto visit_message(const messages::ThermalPlateTempReadComplete& msg,
280280
Policy& policy) -> void {
281+
constexpr Milliseconds time_overflow_amount = Milliseconds(
282+
std::numeric_limits<decltype(msg.timestamp_ms)>::max());
281283
auto old_error_bitmap = _state.error_bitmap;
282284
auto current_time = Milliseconds(msg.timestamp_ms);
283285

@@ -327,8 +329,12 @@ class ThermalPlateTask {
327329
}
328330

329331
if (_state.system_status == State::CONTROLLING) {
330-
update_control(policy, std::chrono::duration_cast<Seconds>(
331-
current_time - _last_update));
332+
auto time_delta = current_time - _last_update;
333+
if (time_delta.count() < 0) {
334+
time_delta += time_overflow_amount;
335+
}
336+
update_control(policy,
337+
std::chrono::duration_cast<Seconds>(time_delta));
332338
send_current_state();
333339
} else if (_state.system_status == State::IDLE) {
334340
send_current_state();

stm32-modules/thermocycler-gen2/tests/test_thermal_plate_task.cpp

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1233,4 +1233,61 @@ TEST_CASE("thermal plate error flag handling") {
12331233
}
12341234
}
12351235
}
1236+
}
1237+
1238+
TEST_CASE("thermal plate tick overflow handling") {
1239+
constexpr uint32_t max_tick = std::numeric_limits<uint32_t>::max();
1240+
const uint32_t ms_diff = GENERATE(10, 1000, 10000);
1241+
constexpr float temperature = 15;
1242+
constexpr uint32_t total_hold = 5000;
1243+
GIVEN("a thermal plate task with valid temperatures holding temp") {
1244+
auto tasks = TaskBuilder::build();
1245+
auto &plate_queue = tasks->get_thermal_plate_queue();
1246+
auto &host_queue = tasks->get_host_comms_queue();
1247+
auto valid_adc = _converter.backconvert(temperature);
1248+
auto read_message = messages::ThermalPlateTempReadComplete{
1249+
.heat_sink = valid_adc,
1250+
.front_right = valid_adc,
1251+
.front_center = valid_adc,
1252+
.front_left = valid_adc,
1253+
.back_right = valid_adc,
1254+
.back_center = valid_adc,
1255+
.back_left = valid_adc,
1256+
.timestamp_ms = max_tick - 100};
1257+
std::ignore = plate_queue.try_send(read_message);
1258+
tasks->run_thermal_plate_task();
1259+
auto command = messages::SetPlateTemperatureMessage{
1260+
.id = 123,
1261+
.setpoint = temperature,
1262+
.hold_time = total_hold,
1263+
.volume = 1,
1264+
};
1265+
std::ignore = plate_queue.try_send(command);
1266+
tasks->run_thermal_plate_task();
1267+
1268+
read_message.timestamp_ms = max_tick - 50;
1269+
std::ignore = plate_queue.try_send(read_message);
1270+
tasks->run_thermal_plate_task();
1271+
1272+
read_message.timestamp_ms = max_tick - 25;
1273+
std::ignore = plate_queue.try_send(read_message);
1274+
tasks->run_thermal_plate_task();
1275+
1276+
WHEN("sending a thermistor update that overflows the tick count") {
1277+
read_message.timestamp_ms += ms_diff;
1278+
std::ignore = plate_queue.try_send(read_message);
1279+
tasks->run_thermal_plate_task();
1280+
THEN("the new hold time makes sense") {
1281+
auto expected = total_hold - (ms_diff / 1000.0);
1282+
host_queue.backing_deque.clear();
1283+
auto get_message = messages::GetPlateTempMessage{.id = 321};
1284+
std::ignore = plate_queue.try_send(get_message);
1285+
tasks->run_thermal_plate_task();
1286+
auto response = std::get<messages::GetPlateTempResponse>(
1287+
host_queue.backing_deque.front());
1288+
REQUIRE_THAT(response.time_remaining,
1289+
Catch::Matchers::WithinAbs(expected, 0.01));
1290+
}
1291+
}
1292+
}
12361293
}

0 commit comments

Comments
 (0)