-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(thermocycler-gen2): add filtering to peltier output (#435)
* Added a PeltierFilter class to constrain peltier outputs during closed loop control
- Loading branch information
Showing
7 changed files
with
165 additions
and
9 deletions.
There are no files selected for viewing
59 changes: 59 additions & 0 deletions
59
stm32-modules/include/thermocycler-gen2/thermocycler-gen2/peltier_filter.hpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
/** | ||
* @file peltier_filter.hpp | ||
* @brief Implements a simple filter on the output power of a peltier to | ||
* enforce a maximum ∆power/sec limit. | ||
*/ | ||
|
||
#pragma once | ||
|
||
#include "systemwide.h" | ||
|
||
namespace peltier_filter { | ||
|
||
using PowerPerSec = double; | ||
|
||
/** Number of seconds in 100ms */ | ||
static constexpr double ONE_HUNDRED_MS = 0.1; | ||
/** | ||
* Maximum rate of change is -100% to 100% in one hundred milliseconds. | ||
* This effectively means that changing from max cooling to max heating | ||
* will take 100ms. | ||
*/ | ||
static constexpr PowerPerSec MAX_DELTA = (2.0 / ONE_HUNDRED_MS); | ||
|
||
/** | ||
* Provides a simple filter on Peltier power values to ease the stress on | ||
* on the peltiers over their lifetime. | ||
*/ | ||
class PeltierFilter { | ||
public: | ||
/** | ||
* @brief Reset the filter. This should be called whenever a peltier is | ||
* disabled. | ||
*/ | ||
auto reset() -> void; | ||
|
||
/** | ||
* @brief Set a new peltier power value and filter it based on the | ||
* last value that was set. | ||
* | ||
* @param setting The desired power, in the range [-1.0, 1.0] | ||
* @param delta_sec The number of seconds that have elapsed since the | ||
* last setting. | ||
* @return The power that should be set on the peltier. | ||
*/ | ||
[[nodiscard]] auto set_filtered(double setting, double delta_sec) -> double; | ||
|
||
/** | ||
* @brief Get the last filtered setting for this peltier. | ||
* | ||
* @return The most recent filtered setting | ||
*/ | ||
[[nodiscard]] auto get_last() const -> double; | ||
|
||
private: | ||
/** The last setting for this peltier.*/ | ||
double _last = 0.0; | ||
}; | ||
|
||
} // namespace peltier_filter |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
#include "thermocycler-gen2/peltier_filter.hpp" | ||
|
||
#include <algorithm> | ||
#include <cstdlib> | ||
|
||
using namespace peltier_filter; | ||
|
||
auto PeltierFilter::reset() -> void { _last = 0.0F; } | ||
|
||
[[nodiscard]] auto PeltierFilter::set_filtered(double setting, double delta_sec) | ||
-> double { | ||
setting = std::clamp(setting, -1.0, 1.0); | ||
const auto max_change = delta_sec * MAX_DELTA; | ||
if (std::abs(setting - _last) > max_change) { | ||
// Just change by the max change for this tick | ||
auto change = (setting > _last) ? max_change : -max_change; | ||
setting = _last + change; | ||
} | ||
_last = setting; | ||
return _last; | ||
} | ||
|
||
[[nodiscard]] auto PeltierFilter::get_last() const -> double { return _last; } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
50 changes: 50 additions & 0 deletions
50
stm32-modules/thermocycler-gen2/tests/test_peltier_filter.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
|
||
#include <vector> | ||
|
||
#include "catch2/catch.hpp" | ||
#include "thermocycler-gen2/peltier_filter.hpp" | ||
|
||
TEST_CASE("peltier filter functionality") { | ||
using namespace peltier_filter; | ||
auto subject = PeltierFilter(); | ||
REQUIRE(subject.get_last() == 0.0F); | ||
WHEN("setting power outside of the filter limits") { | ||
const auto TIME_DELTA = GENERATE(0.01, 0.02); | ||
const auto SETTING = GENERATE(1.0, -1.0); | ||
|
||
auto result = subject.set_filtered(SETTING, TIME_DELTA); | ||
THEN("the result is filtered") { | ||
auto expected = MAX_DELTA * TIME_DELTA * (SETTING > 0 ? 1 : -1); | ||
REQUIRE_THAT(result, Catch::Matchers::WithinAbs(expected, 0.01)); | ||
AND_WHEN("doing it again") { | ||
result = subject.set_filtered(SETTING, TIME_DELTA); | ||
THEN("the result is doubled") { | ||
REQUIRE_THAT( | ||
result, Catch::Matchers::WithinAbs(expected * 2, 0.01)); | ||
} | ||
} | ||
} | ||
THEN("getting the last result matches expected") { | ||
REQUIRE(subject.get_last() == result); | ||
} | ||
} | ||
WHEN("setting power within filter limits") { | ||
const auto TIME_DELTA = GENERATE(0.1, 0.5, 1.0); | ||
const auto SETTING = GENERATE(1.0, -1.0, -0.245, 0.64); | ||
auto result = subject.set_filtered(SETTING, TIME_DELTA); | ||
THEN("the result is not filtered at all") { | ||
REQUIRE_THAT(result, Catch::Matchers::WithinAbs(SETTING, 0.01)); | ||
} | ||
} | ||
WHEN("setting power at 10ms intervals") { | ||
const auto TIME_DELTA = 0.01; | ||
THEN("it increments as expected") { | ||
std::vector<double> expected = {0.2, 0.4, 0.6, 0.8, 1.0, 1.0}; | ||
std::vector<double> result; | ||
for (auto i = 0; i < 6; ++i) { | ||
result.push_back(subject.set_filtered(1.0, TIME_DELTA)); | ||
} | ||
REQUIRE_THAT(result, Catch::Matchers::Approx(expected)); | ||
} | ||
} | ||
} |