REDAC HybridController
Firmware for LUCIDAC/REDAC Teensy
Loading...
Searching...
No Matches
mblock_mul.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_mul.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
17extern int abs_clamp(float in, int min, int max);
18
20 if (hardware)
21 return hardware->get_entity_eui();
22 return {};
23}
24
26 // MMulBlock does not expect any configuration currently.
27 // But due to automation, some may still be sent.
28 // Thus we accept any configuration containing only empty values or similar.
29 for (auto cfgItr = cfg.begin(); cfgItr != cfg.end(); ++cfgItr) {
30 if (cfgItr->key() == "calibration") {
31 auto res = _config_elements_from_json(cfgItr->value());
32 if (!res)
33 return res;
34 } else {
35 return utils::status(771, "MMulBlock: Unknown configuration key");
36 }
37 }
39}
40
41FLASHMEM utils::status blocks::MMulBlock::_config_elements_from_json(const JsonVariantConst &cfg) {
42 return utils::status("MMulBlock currently does not accept configuration");
43
44 // Note: The following is a workaround to get manual calibration data
45 // into the system, for release v1.0.
46
47 auto map = cfg.as<JsonObjectConst>();
48 if (!map.containsKey("offset_x") || map["offset_x"].size() != MMulBlock::NUM_MULTIPLIERS)
49 return utils::status(772, "Missing offset_x (need 4 entries)");
50 if (!map.containsKey("offset_y") || map["offset_y"].size() != MMulBlock::NUM_MULTIPLIERS)
51 return utils::status(773, "Missing offset_y (need 4 entries)");
52 if (!map.containsKey("offset_z") || map["offset_z"].size() != MMulBlock::NUM_MULTIPLIERS)
53 return utils::status(774, "Missing offset_z (need 4 entries)");
54 for (size_t i = 0; i < MMulBlock::NUM_MULTIPLIERS; i++) {
55 calibration[i].offset_x = map["offset_x"][i];
56 calibration[i].offset_y = map["offset_y"][i];
57 calibration[i].offset_z = map["offset_z"][i];
58 }
59
60 auto res = write_calibration_to_hardware();
61 if (!res) {
62 return res.attach("via MMUlBlock::config_elements_from_json");
63 }
64
66}
67
68FLASHMEM utils::status blocks::MMulBlock::write_to_hardware() { return write_calibration_to_hardware(); }
69
71 for (size_t i = 0; i < MMulBlock::NUM_MULTIPLIERS; i++) {
72 if (!hardware->write_calibration_input_offsets(i, calibration[i].offset_x, calibration[i].offset_y))
73 return utils::status(
74 775, "MMulBlock::calibration from json for multiplier %d values offset_x, offset_y not accepted", i);
75 if (!hardware->write_calibration_output_offset(i, calibration[i].offset_z))
76 return utils::status(
77 776, "MMulBlock::calibration from json for multiplier %d values offset_z not accepted", i);
78 }
80}
81
82FLASHMEM void blocks::MMulBlock::config_self_to_json(JsonObject &cfg) {
83 auto json_calibration = cfg.createNestedObject("calibration");
84 auto offset_x = json_calibration.createNestedArray("offset_x");
85 auto offset_y = json_calibration.createNestedArray("offset_y");
86 auto offset_z = json_calibration.createNestedArray("offset_z");
87
88 for (int i = 0; i < MMulBlock::NUM_MULTIPLIERS; i++) {
89 offset_x.add(calibration[i].offset_x);
90 offset_y.add(calibration[i].offset_y);
91 offset_z.add(calibration[i].offset_z);
92 }
93}
94
95FLASHMEM blocks::MMulBlock *blocks::MMulBlock::from_entity_classifier(entities::EntityClassifier classifier,
96 const bus::addr_t block_address) {
97 if (!classifier or classifier.class_enum != CLASS_ or classifier.type != static_cast<uint8_t>(TYPE))
98 return nullptr;
99
100 // Currently, there are no different variants
101 if (classifier.variant != entities::EntityClassifier::DEFAULT_)
102 return nullptr;
103
104 SLOT slot = block_address % 8 == 4 ? SLOT::M0 : SLOT::M1;
105 // Return default implementation
106 if (classifier.version < entities::Version(1))
107 return nullptr;
108 if (classifier.version < entities::Version(1, 1)) {
109 auto *new_block = new MMulBlock(slot, new MMulBlockHAL_V_1_0_X(block_address));
110 new_block->classifier = classifier;
111 return new_block;
112 }
113 if (classifier.version <= entities::Version(1, 2)) {
114 auto *new_block = new MMulBlock_FullAutoCalibration(slot, new MMulBlockHAL_V_1_1_X(block_address));
115 new_block->classifier = classifier;
116 return new_block;
117 }
118 if (classifier.version == entities::Version(1, -1)) {
119 auto *new_block = new MMulBlock_FullAutoCalibration(slot, new MMulBlockHAL_V_1_M1_X(block_address));
120 new_block->classifier = classifier;
121 return new_block;
122 }
123 return nullptr;
124}
125
126FLASHMEM blocks::MMulBlock::MMulBlock(SLOT slot, MMulBlockHAL *hardware)
127 : MBlock(slot, hardware), hardware(hardware) {
128 classifier.type = static_cast<uint8_t>(TYPE);
129}
130
131FLASHMEM bool blocks::MMulBlock::init() {
132 // TODO: Remove once hardware pointer is part of FunctionBlock base class
133 return FunctionBlock::init() and hardware->init();
134}
135
137 FunctionBlock::reset(action);
138
140 for (auto idx = 0u; idx < MMulBlock::NUM_MULTIPLIERS; idx++)
141 calibration[idx] = {};
142 }
143
145 hardware->reset_overload_flags();
146 }
147}
148
150 LOG(ANABRID_DEBUG_CALIBRATION, __PRETTY_FUNCTION__);
151
152 // Our first simple calibration process has been developed empirically and goes
153 // 1. Set all inputs to zero
154 // - The input offsets are rather small, so inputs are 0+-epsilon
155 // - The output is then roughly 0 + epsilon^2 - offset_z ~= -offset_z
156 // 2. Measure output and use it as a first estimate of offset_z
157 // 3. Set inputs to 0 and 1 and change the first one's offset to get an output close to zero
158
159 if (!MBlock::calibrate(cluster, carrier))
160 return false;
161
162 // TODO: keep old circuits alive
164
165 // We can't iterativly calibrate, so we need to delete the old calibration values.
167 bool success = true;
168
169 // Connect zeros to all our inputs
170 LOG(ANABRID_DEBUG_CALIBRATION, "Calibrating output offsets...");
171 for (auto idx : SLOT_INPUT_IDX_RANGE())
172 if (!cluster->add_constant(UBlock::Transmission_Mode::GROUND, slot_to_global_io_index(idx), 0.0f,
173 slot_to_global_io_index(idx)))
174 return false; // Fatal error
175 if (!cluster->write_to_hardware())
176 return false; // Fatal error
177
178 // When setting routes we need to calibrate them
179 if (!carrier->calibrate_routes_in_cluster(*cluster))
180 LOG(ANABRID_DEBUG_CALIBRATION, "Calibrating routes failed!");
181
182 // Measure offset_z and set it
183 auto read_outputs = daq::average(daq::sample, 4, 10);
184
185 for (auto idx = 0u; idx < NUM_MULTIPLIERS; idx++) {
186 calibration[idx].offset_z = MMulBlockHAL_V_1_0_X::float_to_raw_calibration(-read_outputs[idx]);
187 if (!hardware->write_calibration_output_offset(idx, calibration[idx].offset_z))
188 success = false; // out of range
189 }
190 delay(10); // Small transient time
191
192 // Set some inputs to one
193 LOG(ANABRID_DEBUG_CALIBRATION, "Calibrating x input offsets...");
194 cluster->ublock->change_b_side_transmission_mode(UBlock::Transmission_Mode::POS_REF); // Set constant source
195 for (auto idx = 0u; idx < NUM_MULTIPLIERS; idx++) {
196 if (!cluster->cblock->set_factor(slot_to_global_io_index(idx * 2), 1.0f)) // Setting 1.0 to Y inputs
197 return false; // fatal error
198 }
199 if (!cluster->cblock->write_to_hardware())
200 return false; // fatal error
201 // When changing a factor, we always have to calibrate offset
202 if (!cluster->calibrate_offsets())
203 return false; // fatal error
204
205 delay(10);
206
207 const float target_precision = 0.0005f;
208 const int max_loops = 100;
209
210 std::bitset<NUM_MULTIPLIERS> done_channels;
211 int loop_count = 0;
212
213 // start varying the x input offsets
214 while (!done_channels.all()) {
215 read_outputs = daq::average(daq::sample, 4, 10);
216
217 for (auto idx = 0u; idx < NUM_MULTIPLIERS; idx++) {
218 if (fabs(read_outputs[idx]) < target_precision) {
219 done_channels[idx] = true; // Already precice
220 continue;
221 }
222 done_channels[idx] = false;
223
224 // Little bounded proportional controller, to decrease calibration time
225 calibration[idx].offset_x += abs_clamp(read_outputs[idx] * 6000.0f, 1, 50);
226
227 if (!hardware->write_calibration_input_offsets(idx, calibration[idx].offset_x,
228 calibration[idx].offset_y)) {
229 calibration[idx].offset_x =
230 std::clamp(calibration[idx].offset_x, functions::DAC60508::RAW_ZERO, functions::DAC60508::RAW_MAX);
231 success = false; // Out of bounds for factor
232 done_channels[idx] = true;
233 LOG(ANABRID_DEBUG_CALIBRATION, "Calibrating out of bounds!");
234 continue;
235 }
236 }
237 delay(10); // Small transient time
238
239 loop_count++;
240 if (loop_count > max_loops) {
241 LOG(ANABRID_DEBUG_CALIBRATION, "Calibration timed out!");
242 break;
243 }
244 }
245
246 LOG(ANABRID_DEBUG_CALIBRATION, "Calibrating y input offsets...");
247 for (auto idx = 0u; idx < NUM_MULTIPLIERS; idx++) {
248 if (!cluster->cblock->set_factor(slot_to_global_io_index(idx * 2),
249 0.0f)) // Setting 0.0 to Y inputs which previously were 1.0
250 return false; // fatal error
251
252 if (!cluster->cblock->set_factor(slot_to_global_io_index(idx * 2 + 1), 1.0f)) // Setting 1.0 to X inputs
253 return false; // fatal error
254 }
255 if (!cluster->cblock->write_to_hardware())
256 return false; // fatal error
257 // When changing a factor, we always have to calibrate offset
258 if (!cluster->calibrate_offsets())
259 return false; // fatal error
260
261 delay(10);
262
263 read_outputs = daq::average(daq::sample, 4, 10);
264
265 done_channels.reset();
266 loop_count = 0;
267
268 // start varying the y input offsets
269 while (!done_channels.all()) {
270 read_outputs = daq::average(daq::sample, 4, 10);
271
272 for (auto idx = 0u; idx < NUM_MULTIPLIERS; idx++) {
273 if (fabs(read_outputs[idx]) < target_precision) {
274 done_channels[idx] = true; // Already precice
275 continue;
276 }
277 done_channels[idx] = false;
278
279 // Little bounded proportional controller, to decrease calibration time
280 calibration[idx].offset_y += abs_clamp(read_outputs[idx] * 6000.0f, 1, 50);
281
282 if (!hardware->write_calibration_input_offsets(idx, calibration[idx].offset_x,
283 calibration[idx].offset_y)) {
284 calibration[idx].offset_y =
285 std::clamp(calibration[idx].offset_y, functions::DAC60508::RAW_ZERO, functions::DAC60508::RAW_MAX);
286 success = false; // Out of bounds for factor
287 done_channels[idx] = true;
288 LOG(ANABRID_DEBUG_CALIBRATION, "Calibrating out of bounds!");
289 continue;
290 }
291 }
292 delay(10); // Small transient time
293
294 loop_count++;
295 if (loop_count > max_loops) {
296 LOG(ANABRID_DEBUG_CALIBRATION, "Calibration timed out!");
297 break;
298 }
299 }
300
301 return success;
302}
303
304FLASHMEM
305const std::array<blocks::MultiplierCalibration, blocks::MMulBlock::NUM_MULTIPLIERS> &
307 return calibration;
308}
309
310FLASHMEM blocks::MultiplierCalibration blocks::MMulBlock::get_calibration(uint8_t mul_idx) const {
311 if (mul_idx >= NUM_MULTIPLIERS)
312 return {};
313 return calibration[mul_idx];
314}
315
316// V1.-1.0
317
320 if (!res)
321 return res;
322
323 for (size_t i = 0; i < MMulBlock::NUM_MULTIPLIERS; i++) {
324 if (!hardware->write_calibration_gain(i, calibration[i].gain))
325 return utils::status(777, "MMulBlock::calibration from json for multiplier %d values gain not accepted",
326 i);
327 }
328 return utils::status::success();
329}
330
333 LOG(ANABRID_DEBUG_CALIBRATION, __PRETTY_FUNCTION__);
334
335 // First do regular calibration
336 bool success = MMulBlock::calibrate(cluster, carrier);
337
338 // Automatic gain calibration can only be done by version 1.1
339 LOG(ANABRID_DEBUG_CALIBRATION, "Calibrating output gains...");
340
341 for (auto idx = 0u; idx < NUM_MULTIPLIERS; idx++) {
342 if (!cluster->cblock->set_factor(slot_to_global_io_index(idx * 2),
343 1.0f)) // Setting 1.0 to Y inputs which previously were 0.0
344 return false; // fatal error
345 }
346 if (!cluster->cblock->write_to_hardware())
347 return false; // fatal error
348 // When changing a factor, we always have to calibrate offset
349 if (!cluster->calibrate_offsets())
350 return false; // fatal error
351
352 delay(10);
353
354 const float target_precision = 0.0005f;
355 const int max_loops = 100;
356
357 std::bitset<NUM_MULTIPLIERS> done_channels;
358 int loop_count = 0;
359
360 // start varying the gain factors
361 while (!done_channels.all()) {
362 auto read_outputs = daq::average(daq::sample, 4, 10);
363
364 for (auto idx = 0u; idx < NUM_MULTIPLIERS; idx++) {
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) * -1000.0f, 1, 70);
373
374 if (calibration[idx].gain + step > 0xff || calibration[idx].gain + step < 0) {
375 success = false; // Out of bounds for factor
376 done_channels[idx] = true;
377 LOG(ANABRID_DEBUG_CALIBRATION, "Calibrating out of bounds!");
378 continue;
379 }
380
381 calibration[idx].gain += step;
382
383 if (!hardware->write_calibration_gain(idx, calibration[idx].gain)) {
384 success = false; // Other issues
385 done_channels[idx] = true;
386 continue;
387 }
388 }
389 delay(10); // Small transient time
390
391 loop_count++;
392 if (loop_count > max_loops) {
393 LOG(ANABRID_DEBUG_CALIBRATION, "Calibration timed out!");
394 break;
395 }
396 }
397
398 return success;
399}
400
401// Hardware abstraction layer
402
404 : f_meta(block_address), f_overload_flags_reset(bus::address_from_tuple(block_address, 3)),
405 f_overload_flags(bus::replace_function_idx(block_address, 2)),
406 f_calibration_dac_0(bus::address_from_tuple(block_address, 4), 2.0f),
407 f_calibration_dac_1(bus::address_from_tuple(block_address, 5), 2.0f) {}
408
409FLASHMEM bool blocks::MMulBlockHAL::init() { return MBlockHAL::init(); }
410
412 bool error = true;
413 error &= f_calibration_dac_0.init();
414 error &= f_calibration_dac_0.set_external_reference();
415 error &= f_calibration_dac_0.set_double_gain();
416 error &= f_calibration_dac_1.init();
417 error &= f_calibration_dac_1.set_external_reference();
418 error &= f_calibration_dac_1.set_double_gain();
419 error &= MMulBlockHAL::init();
420 return error;
421}
422
423FLASHMEM void blocks::MMulBlockHAL_V_1_0_X::reset_overload_flags() { f_overload_flags_reset.trigger(); }
424
425FLASHMEM bool blocks::MMulBlockHAL_V_1_0_X::write_calibration_input_offsets(uint8_t idx, uint16_t offset_x,
426 uint16_t offset_y) {
427 return f_calibration_dac_0.set_channel_raw(idx * 2 + 1, offset_x) and
428 f_calibration_dac_0.set_channel_raw(idx * 2, offset_y);
429}
430
431FLASHMEM bool blocks::MMulBlockHAL_V_1_0_X::write_calibration_output_offset(uint8_t idx, uint16_t offset_z) {
432 return f_calibration_dac_1.set_channel_raw(idx, offset_z);
433}
434
436 return f_overload_flags.read8();
437}
438
442
444 : MMulBlockHAL_FullAutoCalibration(block_address),
445 f_gain_ch0_1(bus::replace_function_idx(block_address, 6)),
446 f_gain_ch2_3(bus::replace_function_idx(block_address, 7)) {}
447
448FLASHMEM bool blocks::MMulBlockHAL_V_1_M1_X::write_calibration_gain(uint8_t idx, uint8_t gain) {
449 if (idx < 2)
450 return f_gain_ch0_1.write_channel_raw(idx, gain);
451 else if (idx < 4)
452 return f_gain_ch2_3.write_channel_raw(idx - 2, gain);
453 else
454 return false;
455}
456
458 : MMulBlockHAL_FullAutoCalibration(block_address), f_gain(bus::replace_function_idx(block_address, 6)) {}
459
460FLASHMEM bool blocks::MMulBlockHAL_V_1_1_X::write_calibration_gain(uint8_t idx, uint8_t gain) {
461 if (idx == 0)
462 return f_gain.write_channel_raw(2, gain);
463 else if (idx == 1)
464 return f_gain.write_channel_raw(0, gain);
465 else if (idx == 2)
466 return f_gain.write_channel_raw(1, gain);
467 if (idx == 3)
468 return f_gain.write_channel_raw(3, gain);
469 return false;
470}
utils::status write_to_hardware() override
returns true in case of success
Definition cblock.cpp:43
bool set_factor(uint8_t idx, float factor)
Definition cblock.cpp:31
virtual std::array< uint8_t, 8 > get_entity_eui() const =0
bool init() override
Definition mblock.cpp:75
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
bool write_calibration_output_offset(uint8_t idx, uint16_t offset_z) override
MMulBlockHAL_V_1_0_X(bus::addr_t block_address)
void reset_overload_flags() override
bool write_calibration_input_offsets(uint8_t idx, uint16_t offset_x, uint16_t offset_y) override
static uint16_t float_to_raw_calibration(float value)
Takes in voltage recieved by multiplier, returns DAC raw value.
Definition mblock_mul.h:48
MMulBlockHAL_V_1_1_X(bus::addr_t block_address)
bool write_calibration_gain(uint8_t idx, uint8_t gain) override
bool write_calibration_gain(uint8_t idx, uint8_t gain) override
MMulBlockHAL_V_1_M1_X(bus::addr_t block_address)
bool init() override
MMulBlock_FullAutoCalibration(SLOT slot, blocks::MMulBlockHAL_FullAutoCalibration *hardware)
bool calibrate(platform::Cluster *cluster, carrier::Carrier *carrier) override
utils::status write_calibration_to_hardware() override
Multplier Math Block (or: Math Multplier Block, hence MMulBlock).
Definition mblock_mul.h:105
void config_self_to_json(JsonObject &cfg) override
static MMulBlock * from_entity_classifier(entities::EntityClassifier classifier, bus::addr_t block_address)
bool init() override
const std::array< MultiplierCalibration, NUM_MULTIPLIERS > & get_calibration() const
void reset(entities::ResetAction action)
utils::status config_self_from_json(JsonObjectConst cfg) override
utils::status _config_elements_from_json(const JsonVariantConst &cfg)
static constexpr uint8_t NUM_MULTIPLIERS
Definition mblock_mul.h:113
MMulBlockHAL * hardware
Definition mblock_mul.h:122
MMulBlock(SLOT slot, MMulBlockHAL *hardware)
virtual utils::status write_calibration_to_hardware()
utils::status write_to_hardware() override
metadata::eui_t get_entity_eui() const override
static constexpr auto TYPE
Definition mblock_mul.h:108
bool calibrate(platform::Cluster *cluster, carrier::Carrier *carrier) override
void change_b_side_transmission_mode(const Transmission_Mode mode)
Changes the transmission mode of the alternative ublock switches.
Definition ublock.cpp:205
Top-level hierarchy controlled by a single microcontroller.
Definition carrier.h:38
virtual void reset(ResetAction action)
Definition base.h:155
virtual bool init()
returns true in case of success
Definition base.h:153
static constexpr uint16_t RAW_ZERO
Definition DAC60508.h:30
static constexpr uint16_t RAW_MAX
Definition DAC60508.h:31
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
bool calibrate_offsets()
Definition cluster.cpp:74
void reset(entities::ResetAction action)
Definition cluster.cpp:279
blocks::CBlock * cblock
Definition cluster.h:28
A recoverable error, inspired from https://abseil.io/docs/cpp/guides/status and https://github....
Definition error.h:35
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 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
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