REDAC HybridController
Firmware for LUCIDAC/REDAC Teensy
Loading...
Searching...
No Matches
mblock_int.cpp
Go to the documentation of this file.
1// Copyright (c) 2024 anabrid GmbH
2// Contact: https://www.anabrid.com/licensing/
3// SPDX-License-Identifier: MIT OR GPL-2.0-or-later
4
5#include <algorithm>
6#include <bitset>
7
8#include "block/mblock_int.h"
9#include "utils/logging.h"
10
11#include "entity/entity.h"
12#include "etl/crc.h"
13
14#include "carrier/carrier.h"
15#include "carrier/cluster.h"
16#include "mode/mode.h"
17
18extern int abs_clamp(float in, int min, int max);
19
21 if (hardware)
22 return hardware->get_entity_eui();
23 return {};
24}
25
27 : blocks::MBlock{slot, hardware}, hardware(hardware), ic_values{}, time_factors{} {
28 classifier.type = static_cast<uint8_t>(TYPE);
31}
32
33FLASHMEM bool blocks::MIntBlock::init() {
35 return false;
36 if (!hardware->init())
37 return false;
38 return true;
39}
40
41FLASHMEM const std::array<float, 8> &blocks::MIntBlock::get_ic_values() const { return ic_values; }
42
43FLASHMEM float blocks::MIntBlock::get_ic_value(uint8_t idx) const {
44 if (idx >= ic_values.size())
45 return 0.0f;
46 return ic_values[idx];
47}
48
49FLASHMEM bool blocks::MIntBlock::set_ic_values(const std::array<float, 8> &ic_values_) {
50 for (auto idx = 0u; idx < ic_values_.size(); idx++)
51 if (!set_ic_value(idx, ic_values_[idx]))
52 return false;
53 return true;
54}
55
56FLASHMEM bool blocks::MIntBlock::set_ic_values(float value) {
57 for (auto idx = 0u; idx < ic_values.size(); idx++)
58 if (!set_ic_value(idx, value))
59 return false;
60 return true;
61}
62
63FLASHMEM bool blocks::MIntBlock::set_ic_value(uint8_t idx, float value) {
64 if (idx >= ic_values.size())
65 return false;
66 if (value > 1.0f or value < -1.0f)
67 return false;
68 ic_values[idx] = value;
69 return true;
70}
71
72FLASHMEM void blocks::MIntBlock::reset_ic_values() { std::fill(ic_values.begin(), ic_values.end(), 0.0f); }
73
74FLASHMEM const std::array<unsigned int, 8> &blocks::MIntBlock::get_time_factors() const {
75 return time_factors;
76}
77
78FLASHMEM unsigned int blocks::MIntBlock::get_time_factor(uint8_t idx) const {
79 if (idx >= time_factors.size())
80 return 0;
81 return time_factors[idx];
82}
83
84FLASHMEM bool blocks::MIntBlock::set_time_factors(const std::array<unsigned int, 8> &time_factors_) {
85 for (auto idx = 0u; idx < time_factors_.size(); idx++)
86 if (!set_time_factor(idx, time_factors_[idx]))
87 return false;
88 return true;
89}
90
91FLASHMEM bool blocks::MIntBlock::set_time_factors(unsigned int k) {
92 for (auto idx = 0u; idx < time_factors.size(); idx++)
93 if (!set_time_factor(idx, k))
94 return false;
95 return true;
96}
97
98FLASHMEM bool blocks::MIntBlock::set_time_factor(uint8_t int_idx, unsigned int k) {
99 if (!(k == 100 or k == 10000))
100 return false;
101 if (int_idx >= NUM_INTEGRATORS)
102 return false;
103 time_factors[int_idx] = k;
104 return true;
105}
106
108 // Copying solves a strange linker issue "relocation against ... in read-only section `.text'"
109 auto default_ = DEFAULT_TIME_FACTOR;
110 std::fill(std::begin(time_factors), std::end(time_factors), default_);
111}
112
114 // Write IC values one channel at a time
115 for (decltype(ic_values.size()) i = 0; i < ic_values.size(); i++) {
116 if (!hardware->write_ic(i, ic_values[i])) {
117 LOG(ANABRID_PEDANTIC, __PRETTY_FUNCTION__);
118 return "Error writing IC values to hardware.";
119 }
120 }
121 // Write time factor switches by converting to bitset
122 std::bitset<NUM_INTEGRATORS> time_factor_switches{};
123 for (auto idx = 0u; idx < time_factors.size(); idx++)
124 if (time_factors[idx] != DEFAULT_TIME_FACTOR)
125 time_factor_switches.set(idx);
126 if (!hardware->write_time_factor_switches(time_factor_switches))
127 return "Error writing time factor switches.";
128
129 return utils::status::success();
130}
131
133 FunctionBlock::reset(action);
134
136 reset_ic_values();
137 reset_time_factors();
138 }
139
141 // !!ATTENTION!! This breaks a lot of things but is neccessary and the ONLY
142 // way to reset overloads on the MIntBlock.
145 }
146}
147
149#ifdef ANABRID_DEBUG_ENTITY_CONFIG
150 Serial.println(__PRETTY_FUNCTION__);
151#endif
152 for (auto cfgItr = cfg.begin(); cfgItr != cfg.end(); ++cfgItr) {
153 if (cfgItr->key() == "elements") {
154 auto res = _config_elements_from_json(cfgItr->value());
155 if (!res)
156 return res;
157 } else {
158 return utils::status("Unknown configuration key.");
159 }
160 }
161 return utils::status::success();
162}
163
165 if (cfg.is<JsonArrayConst>()) {
166 auto ints_cfg = cfg.as<JsonArrayConst>();
167 if (ints_cfg.size() != NUM_INTEGRATORS) {
168 return utils::status("MIntBlock: Provided %d elments but NUM_INTEGRATORS=%d.", ints_cfg.size(),
169 NUM_INTEGRATORS);
170 }
171 for (size_t i = 0; i < ints_cfg.size(); i++) {
172 if (!ints_cfg[i].containsKey("ic") or !ints_cfg[i]["ic"].is<float>())
173 return utils::status("MIntBlock, element %d: Requiring IC as float", i);
174 if (!set_ic_value(i, ints_cfg[i]["ic"]))
175 return utils::status("MIntBlock, element %d: Illegal IC %f", i, ints_cfg[i]["ic"]);
176 if (ints_cfg[i].containsKey("k")) {
177 if (!ints_cfg[i]["k"].is<unsigned int>())
178 return utils::status("MIntBlock, element %d: Requiring time factor 'k' as int", i);
179 if (!set_time_factor(i, ints_cfg[i]["k"]))
180 return utils::status("MIntBlock, element %d: Illegal time factor %d", i, ints_cfg[i]["k"]);
181 }
182 }
183 return utils::status::success();
184 } else if (cfg.is<JsonObjectConst>()) {
185 for (JsonPairConst keyval : cfg.as<JsonObjectConst>()) {
186 // Value is an object, with any key being optional
187 if (!keyval.value().is<JsonObjectConst>())
188 return utils::status("MIntBlock configuration value needs to be an object");
189 // TODO: Check conversion from string to number
190 auto int_idx = std::stoul(keyval.key().c_str());
191 if (int_idx >= NUM_INTEGRATORS)
192 return utils::status("MIntBlock: Integrator index %d >= NUM_INTEGRATORS = %d", int_idx,
193 NUM_INTEGRATORS);
194 auto int_cfg = keyval.value().as<JsonObjectConst>();
195 if (int_cfg.containsKey("ic")) {
196 if (!int_cfg["ic"].is<float>())
197 return utils::status("MIntBlock: Integrator %d IC must be float", int_idx);
198 if (!set_ic_value(int_idx, int_cfg["ic"]))
199 return utils::status("MIntBlock: Integrator %d IC is illegal: %f", int_idx, int_cfg["ic"]);
200 }
201 if (int_cfg.containsKey("k")) {
202 if (!int_cfg["k"].is<unsigned int>())
203 return utils::status("MIntBlock: Integrator %d k must be integer", int_idx);
204 if (!set_time_factor(int_idx, int_cfg["k"]))
205 return utils::status("MIntBlock: Integrator %d time factor k illegal: %d", int_idx, int_cfg["k"]);
206 }
207 }
208 return utils::status::success();
209 }
210 return utils::status("MIntBlock: Configuration must either be array or object");
211}
212
213FLASHMEM void blocks::MIntBlock::config_self_to_json(JsonObject &cfg) {
214 Entity::config_self_to_json(cfg);
215 auto ints_cfg = cfg.createNestedArray("elements");
216 for (size_t i = 0; i < NUM_INTEGRATORS; i++) {
217 auto int_cfg = ints_cfg.createNestedObject();
218 int_cfg["ic"] = ic_values[i];
219 int_cfg["k"] = time_factors[i];
220 }
221}
222
223FLASHMEM blocks::MIntBlock *blocks::MIntBlock::from_entity_classifier(entities::EntityClassifier classifier,
224 const bus::addr_t block_address) {
225 if (!classifier or classifier.class_enum != CLASS_ or classifier.type != static_cast<uint8_t>(TYPE))
226 return nullptr;
227
228 // Currently, there are no different variants
229 if (classifier.variant != entities::EntityClassifier::DEFAULT_)
230 return nullptr;
231
232 SLOT slot = block_address % 8 == 4 ? SLOT::M0 : SLOT::M1;
233 if (classifier.version < entities::Version(1))
234 return nullptr;
235 if (classifier.version < entities::Version(1, 1)) {
236 auto *new_block = new MIntBlock(slot, new MIntBlockHAL_V_1_0_X(block_address));
237 new_block->classifier = classifier;
238 return new_block;
239 }
240 if (classifier.version < entities::Version(1, 2)) {
241 auto *new_block = new MIntBlock_V_1_1_X(slot, new MIntBlockHAL_V_1_1_X(block_address));
242 new_block->classifier = classifier;
243 return new_block;
244 }
245 return nullptr;
246}
247
248// V1.1.0
249
251 MIntBlock::reset(action);
252
254 for (auto idx = 0u; idx < MIntBlock::NUM_INTEGRATORS; idx++)
255 calibration[idx] = {};
256 }
257}
258
259FLASHMEM
260const std::array<blocks::IntegratorCalibration, blocks::MIntBlock_V_1_1_X::NUM_INTEGRATORS> &
262 return calibration;
263}
264
265FLASHMEM blocks::IntegratorCalibration blocks::MIntBlock_V_1_1_X::get_calibration(uint8_t int_idx) const {
266 if (int_idx >= NUM_INTEGRATORS)
267 return {};
268 return calibration[int_idx];
269}
270
272 auto res = MIntBlock::write_to_hardware();
273 if (!res)
274 return res;
275
276 return write_calibration_to_hardware();
277}
278
280 LOG(ANABRID_DEBUG_CALIBRATION, __PRETTY_FUNCTION__);
281
282 if (!MBlock::calibrate(cluster, carrier))
283 return false;
284
285 // Currently this is the only integrator version which can be calibrated automatically
286 bool success = true;
287
288 // TODO: keep old circuits alive
290
291 LOG(ANABRID_DEBUG_CALIBRATION, "Connecting calibration signals!");
292
293 // Connect +0.1 to all integrator inputs. 0.1 because we have high precision and a long integration time.
294 for (auto idx : SLOT_INPUT_IDX_RANGE())
295 if (!cluster->add_constant(UBlock::Transmission_Mode::POS_REF, slot_to_global_io_index(idx), 1.0f,
296 slot_to_global_io_index(idx)))
297 return "Could not connect constants to inputs."; // Fatal error
298
299 cluster->ublock->change_reference_magnitude(UBlock::Reference_Magnitude::ONE_TENTH);
300
301 if (!cluster->write_to_hardware())
302 return "Error while writing new configuration to hardware."; // Fatal error
303
304 // When setting routes we need to calibrate them
305 if (!carrier->calibrate_routes_in_cluster(*cluster))
306 LOG(ANABRID_DEBUG_CALIBRATION, "Calibrating routes failed!");
307
308 LOG(ANABRID_DEBUG_CALIBRATION, "Starting ramp integration on fast mode!");
309 set_time_factors(10000);
310 set_ic_values(-1.0f);
311 if (!write_to_hardware())
312 return false;
313
314 success &= _gain_calibration(false); // Fast time constant
315 LOG(ANABRID_DEBUG_CALIBRATION, "Finished ramp integration on fast mode!");
316
317 LOG(ANABRID_DEBUG_CALIBRATION, "Starting ramp integration on slow mode!");
318 set_time_factors(100);
319 if (!write_to_hardware())
320 return false;
321
322 success &= _gain_calibration(true); // Slow time constant
323 LOG(ANABRID_DEBUG_CALIBRATION, "Finished ramp integration on slow mode!");
324
325 // Measure end value
326 return success;
327}
328
330 for (size_t i = 0; i < NUM_INTEGRATORS; i++) {
331 if (!hardware->write_time_factor_gain(i, time_factors[i] == 10000 ? calibration[i].time_factor_gain_fast
332 : calibration[i].time_factor_gain_slow))
333 return utils::status::failure();
334 }
335 return utils::status::success();
336}
337
338FLASHMEM bool blocks::MIntBlock_V_1_1_X::_gain_calibration(bool use_slow_integration) {
339 bool success = true;
340 const float target_precision = 0.001f;
341 const int max_loops = 100;
342
343 // TODO: currently we cant reach the other four channels
344 std::bitset<NUM_INTEGRATORS> done_channels;
345 int loop_count = 0;
346
347 // start varying the x input offsets
348 while (!done_channels.all()) {
349 // Start calculation
352 use_slow_integration ? 200'000'000 : 2'000'000, mode::OnOverload::IGNORE,
354 LOG(ANABRID_DEBUG_CALIBRATION, "Timer failed!");
355
358 }
359
360 auto read_outputs = daq::average(daq::sample, 4, 10);
361
362 for (auto idx = 0u; idx < NUM_INTEGRATORS; idx++) {
363 uint8_t *time_factor_gain = use_slow_integration ? &calibration[idx].time_factor_gain_slow
364 : &calibration[idx].time_factor_gain_fast;
365 if (fabs(read_outputs[idx] + 1.0f) < target_precision) {
366 done_channels[idx] = true; // Already precice
367 continue;
368 }
369 done_channels[idx] = false;
370
371 // Little bounded proportional controller, to decrease calibration time
372 int step = abs_clamp((read_outputs[idx] + 1.0f) * -800.0f, 1, 70);
373
374 if (*time_factor_gain + step > 0xff || *time_factor_gain + step < 0) {
375 *time_factor_gain = std::clamp((int)(*time_factor_gain) + step, 0, 0xff);
376 success = false; // Out of bounds for factor
377 done_channels[idx] = true;
378 } else {
379 *time_factor_gain += step;
380 }
381
382 if (!hardware->write_time_factor_gain(idx, *time_factor_gain)) {
383 success = false; // Other issues
384 done_channels[idx] = true;
385 continue;
386 }
387 }
388 delay(10); // Small transient time
389
390 loop_count++;
391 if (loop_count > max_loops) {
392 LOG(ANABRID_DEBUG_CALIBRATION, "Calibration timed out!");
393 return false;
394 }
395 }
396
397 return success;
398}
399
401 : MIntBlock(slot, hardware), hardware(hardware) {}
402
403// Hardware abstraction layer
404
406 : f_meta(block_address), f_ic_dac(bus::replace_function_idx(block_address, 4), 2.0f),
407 f_time_factor(bus::replace_function_idx(block_address, 5), true),
408 f_time_factor_sync(bus::replace_function_idx(block_address, 6)),
409 f_time_factor_reset(bus::replace_function_idx(block_address, 7)),
410 f_overload_flags(bus::replace_function_idx(block_address, 2)),
411 f_overload_flags_reset(bus::replace_function_idx(block_address, 3)) {}
412
414 if (!MIntBlockHAL::init())
415 return false;
416 return f_ic_dac.init() and f_ic_dac.set_external_reference(true) and f_ic_dac.set_double_gain(true);
417}
418
419FLASHMEM bool blocks::MIntBlockHAL_V_1_0_X::write_ic(uint8_t idx, float ic) {
421 return false;
422 // Note: The output is level-shifted, such that IC = 2V - output.
423 return f_ic_dac.set_channel(idx, ic + 1.0f);
424}
425
426FLASHMEM bool blocks::MIntBlockHAL_V_1_0_X::write_time_factor_switches(std::bitset<8> switches) {
427 if (!f_time_factor.transfer8(static_cast<uint8_t>(switches.to_ulong())))
428 return false;
429 f_time_factor_sync.trigger();
430 return true;
431}
432
434 return f_overload_flags.read8();
435}
436
437FLASHMEM void blocks::MIntBlockHAL_V_1_0_X::reset_overload_flags() { f_overload_flags_reset.trigger(); }
438
440 : MIntBlockHAL_V_1_0_X(block_address), f_time_factor_gain_0_3(bus::replace_function_idx(block_address, 8)),
441 f_time_factor_gain_4_7(bus::replace_function_idx(block_address, 9)) {}
442
443FLASHMEM bool blocks::MIntBlockHAL_V_1_1_X::write_time_factor_switches(std::bitset<8> switches) {
444 // In version 1.1, a new shift register has been chained behind the time factors shift register,
445 // instead of recieving a separate function address, so we can only set them together.
446 // If we try to set only the time facors, we shift their old state into the shift register behind,
447 // and would change the limeters enable randomly.
448 // So we need to read out the current value and change only the lower 8 bits.
449 uint16_t current;
450 if (!f_time_factor.transfer16(static_cast<uint8_t>(switches.to_ulong()), &current))
451 return false;
452 auto new_ = (current & 0xFF00) | static_cast<uint8_t>(switches.to_ulong());
453 if (!f_time_factor.transfer16(new_))
454 return false;
455 f_time_factor_sync.trigger();
456 return true;
457}
458
459FLASHMEM bool blocks::MIntBlockHAL_V_1_1_X::write_limiters_enable(std::bitset<8> limiters) {
460 // In version 1.1, a new shift register has been chained behind the time factors shift register,
461 // instead of recieving a separate function address, so we can only set them together.
462 // If we try to set only the limiters, we also shift data into the time factors switches,
463 // and would change them.
464 // So we need to read out the current value and change only the upper 8 bits.
465 uint16_t current;
466 if (!f_time_factor.transfer16(static_cast<uint16_t>(limiters.to_ulong()) << 8, &current))
467 return false;
468 auto new_ = (current & 0x00FF) | (static_cast<uint16_t>(limiters.to_ulong()) << 8);
469 if (!f_time_factor.transfer16(new_))
470 return false;
471 f_time_factor_sync.trigger();
472 return true;
473}
474
475FLASHMEM bool
477 std::bitset<8> limiters) {
478 // In version 1.1, a new shift register has been chained behind the time factors shift register,
479 // instead of recieving a separate function address, so we can only set them together.
480 if (!f_time_factor.transfer16(static_cast<uint16_t>(limiters.to_ulong()) << 8 |
481 static_cast<uint8_t>(switches.to_ulong())))
482 return false;
483 f_time_factor_sync.trigger();
484 return true;
485}
486
487FLASHMEM bool blocks::MIntBlockHAL_V_1_1_X::write_ic(uint8_t idx, float ic) {
489 return false;
490 // Note: The output is level-shifted differently than on Version 1.0, such that IC = output - 2V.
491 return f_ic_dac.set_channel(idx, 1.0f - ic);
492}
493
494FLASHMEM bool blocks::MIntBlockHAL_V_1_1_X::write_time_factor_gain(uint8_t idx, uint8_t gain) {
496 return false;
497
498 if (idx < 4)
499 return f_time_factor_gain_0_3.write_channel_raw(idx, gain);
500 else
501 return f_time_factor_gain_4_7.write_channel_raw(idx - 4, gain);
502}
virtual std::array< uint8_t, 8 > get_entity_eui() const =0
virtual bool init()
Definition base.h:15
A REDAC Math block (M-Block) is represented by this class.
Definition mblock.h:43
virtual bool calibrate(platform::Cluster *cluster, carrier::Carrier *carrier)
Definition mblock.h:111
std::bitset< 8 > read_overload_flags() override
MIntBlockHAL_V_1_0_X(bus::addr_t block_address)
bool write_ic(uint8_t idx, float ic) override
bool write_time_factor_switches(std::bitset< 8 > switches) override
void reset_overload_flags() override
virtual bool write_time_factor_gain(uint8_t idx, uint8_t gain)
virtual bool write_limiters_enable(std::bitset< 8 > limiters)
bool write_time_factor_switches(std::bitset< 8 > switches) override
MIntBlockHAL_V_1_1_X(bus::addr_t block_address)
bool write_ic(uint8_t idx, float ic) override
virtual bool write_time_factor_switches_and_limiters_enable(std::bitset< 8 > switches, std::bitset< 8 > limiters)
utils::status write_calibration_to_hardware()
void reset(entities::ResetAction action) override
MIntBlock_V_1_1_X(SLOT slot, MIntBlockHAL_V_1_1_X *hardware)
bool _gain_calibration(bool use_slow_integration)
const std::array< IntegratorCalibration, NUM_INTEGRATORS > & get_calibration() const
utils::status write_to_hardware() override
returns true in case of success
bool calibrate(platform::Cluster *cluster, carrier::Carrier *carrier) override
void config_self_to_json(JsonObject &cfg) override
Serialize the configuration of this entity to a JsonObject.
const std::array< unsigned int, 8 > & get_time_factors() const
float get_ic_value(uint8_t idx) const
static MIntBlock * from_entity_classifier(entities::EntityClassifier classifier, bus::addr_t block_address)
utils::status _config_elements_from_json(const JsonVariantConst &cfg)
MIntBlock(SLOT slot, MIntBlockHAL *hardware)
bool set_time_factor(uint8_t int_idx, unsigned int k)
static constexpr auto TYPE
Definition mblock_int.h:87
bool init() override
returns true in case of success
const std::array< float, 8 > & get_ic_values() const
bool set_ic_value(uint8_t idx, float value)
void reset(entities::ResetAction action) override
metadata::eui_t get_entity_eui() const override
MIntBlockHAL * hardware
Definition mblock_int.h:104
bool set_time_factors(unsigned int k)
utils::status config_self_from_json(JsonObjectConst cfg) override
Deserialize a new configuration for this entity from a JsonObject.
unsigned int get_time_factor(uint8_t idx) const
static constexpr uint8_t NUM_INTEGRATORS
Definition mblock_int.h:92
bool set_ic_values(float value)
utils::status write_to_hardware() override
returns true in case of success
void change_reference_magnitude(Reference_Magnitude ref)
Definition ublock.cpp:229
Top-level hierarchy controlled by a single microcontroller.
Definition carrier.h:38
EntityClassifier classifier
Definition base.h:217
virtual void reset(ResetAction action)
Definition base.h:155
virtual bool init()
returns true in case of success
Definition base.h:153
static bool is_done()
Definition mode.cpp:470
static void reset()
Definition mode.cpp:399
static void force_start()
Definition mode.cpp:367
static bool init(unsigned long long ic_time_ns, unsigned long long op_time_ns, mode::OnOverload on_overload=mode::OnOverload::HALT, mode::OnExtHalt on_ext_halt=mode::OnExtHalt::IGNORE, mode::Sync sync=mode::Sync::NONE)
Definition mode.cpp:89
static void to_ic()
Definition mode.cpp:58
static void enable()
Definition mode.cpp:40
The Lucidac class represents a single cluster.
Definition cluster.h:20
bool add_constant(blocks::UBlock::Transmission_Mode signal_type, uint8_t u_out, float c_factor, uint8_t i_out)
Definition cluster.cpp:246
utils::status write_to_hardware() override
returns true in case of success
Definition cluster.cpp:219
blocks::UBlock * ublock
Definition cluster.h:27
void reset(entities::ResetAction action)
Definition cluster.cpp:279
A recoverable error, inspired from https://abseil.io/docs/cpp/guides/status and https://github....
Definition error.h:35
static status failure()
Syntactic sugar for failure.
Definition error.h:107
static status success()
Syntactic sugar for success.
Definition error.h:104
static constexpr int success
Definition flasher.cpp:275
#define LOG(LOG_FLAG, message)
Definition logging.h:36
int abs_clamp(float in, int min, int max)
Definition mblock.cpp:16
int abs_clamp(float in, int min, int max)
Definition mblock.cpp:16
Definition base.h:10
Definition bus.h:22
uint16_t addr_t
Definition bus.h:27
std::array< T, NUM_CHANNELS > average(std::array< T, NUM_CHANNELS >(*sample_function)(), size_t samples=100, unsigned int delay_us=33)
Acquire an averaged sample (either raw or float).
Definition daq.h:96
std::array< float, NUM_CHANNELS > sample()
Acquire one one-demand sample. Can not be used during a continuous acquisition.
Definition daq.cpp:519
std::array< uint8_t, 8 > eui_t
Definition base.h:20
constexpr unsigned int DEFAULT_IC_TIME
Definition mode.h:23
static constexpr uint8_t OVERLOAD_RESET
Definition base.h:104
static constexpr uint8_t CALIBRATION_RESET
Definition base.h:103
bool has(uint8_t other)
Definition base.h:111
static constexpr uint8_t CIRCUIT_RESET
Definition base.h:102