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.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
17#include "teensy/mblock_mul.h"
18#include <chips/AD840X.h>
19#include <chips/DAC60508.h>
20#include <chips/SR74HC16X.h>
21#include <chips/SR74HCT595.h>
22
23extern int abs_clamp(float in, int min, int max);
24
25FLASHMEM blocks::MMulBlock *blocks::MMulBlock::from_entity_classifier(entities::EntityClassifier classifier,
26 const bus::addr_t block_address) {
27 if (!classifier or classifier.class_enum != CLASS_ or classifier.type != static_cast<uint8_t>(TYPE))
28 return nullptr;
29
30 // Currently, there are no different variants
31 if (classifier.variant != entities::EntityClassifier::DEFAULT_)
32 return nullptr;
33
34 SLOT slot = block_address % 8 == 4 ? SLOT::M0 : SLOT::M1;
35 // Return default implementation
36 if (classifier.version < entities::Version(1))
37 return nullptr;
38 if (classifier.version < entities::Version(1, 1)) {
39 auto *new_block = new MMulBlock(slot, new MMulBlockHAL_V_1_0_X(block_address));
40 new_block->classifier = classifier;
41 return new_block;
42 }
43 if (classifier.version <= entities::Version(1, 2)) {
44 auto *new_block = new MMulBlock_FullAutoCalibration(slot, new MMulBlockHAL_V_1_1_X(block_address));
45 new_block->classifier = classifier;
46 return new_block;
47 }
48 if (classifier.version == entities::Version(1, -1)) {
49 auto *new_block = new MMulBlock_FullAutoCalibration(slot, new MMulBlockHAL_V_1_M1_X(block_address));
50 new_block->classifier = classifier;
51 return new_block;
52 }
53 return nullptr;
54}
55
56FLASHMEM bool blocks::MMulBlock::calibrate(platform::Cluster *cluster, carrier::Carrier *carrier) {
57 LOG(ANABRID_DEBUG_CALIBRATION, __PRETTY_FUNCTION__);
58
59 // Our first simple calibration process has been developed empirically and goes
60 // 1. Set all inputs to zero
61 // - The input offsets are rather small, so inputs are 0+-epsilon
62 // - The output is then roughly 0 + epsilon^2 - offset_z ~= -offset_z
63 // 2. Measure output and use it as a first estimate of offset_z
64 // 3. Set inputs to 0 and 1 and change the first one's offset to get an output close to zero
65
66 if (!MBlock::calibrate(cluster, carrier))
67 return false;
68
69 // TODO: keep old circuits alive
70 cluster->reset(entities::ResetAction::CIRCUIT_RESET);
71
72 // We can't iterativly calibrate, so we need to delete the old calibration values.
73 reset(entities::ResetAction::CALIBRATION_RESET);
74 bool success = true;
75
76 // Connect zeros to all our inputs
77 LOG(ANABRID_DEBUG_CALIBRATION, "Calibrating output offsets...");
78 for (auto idx : SLOT_INPUT_IDX_RANGE())
79 if (!cluster->add_constant(UBlock::Transmission_Mode::GROUND, slot_to_global_io_index(idx), 0.0f,
80 slot_to_global_io_index(idx)))
81 return false; // Fatal error
82 if (!cluster->write_to_hardware())
83 return false; // Fatal error
84
85 // When setting routes we need to calibrate them
86 if (!carrier->calibrate_routes_in_cluster(*cluster))
87 LOG(ANABRID_DEBUG_CALIBRATION, "Calibrating routes failed!");
88
89 // Measure offset_z and set it
90 auto read_outputs = daq::average(daq::sample, 4, 10);
91
92 for (auto idx = 0u; idx < NUM_MULTIPLIERS; idx++) {
93 calibration[idx].offset_z = MMulBlockHAL_V_1_0_X::float_to_raw_calibration(-read_outputs[idx]);
94 if (!hardware->write_calibration_output_offset(idx, calibration[idx].offset_z))
95 success = false; // out of range
96 }
97 delay(10); // Small transient time
98
99 // Set some inputs to one
100 LOG(ANABRID_DEBUG_CALIBRATION, "Calibrating x input offsets...");
101 cluster->ublock->change_b_side_transmission_mode(UBlock::Transmission_Mode::POS_REF); // Set constant source
102 for (auto idx = 0u; idx < NUM_MULTIPLIERS; idx++) {
103 if (!cluster->cblock->set_factor(slot_to_global_io_index(idx * 2), 1.0f)) // Setting 1.0 to Y inputs
104 return false; // fatal error
105 }
106 if (!cluster->cblock->write_to_hardware())
107 return false; // fatal error
108 // When changing a factor, we always have to calibrate offset
109 if (!cluster->calibrate_offsets())
110 return false; // fatal error
111
112 delay(10);
113
114 const float target_precision = 0.0005f;
115 const int max_loops = 100;
116
117 std::bitset<NUM_MULTIPLIERS> done_channels;
118 int loop_count = 0;
119
120 // start varying the x input offsets
121 while (!done_channels.all()) {
122 read_outputs = daq::average(daq::sample, 4, 10);
123
124 for (auto idx = 0u; idx < NUM_MULTIPLIERS; idx++) {
125 if (fabs(read_outputs[idx]) < target_precision) {
126 done_channels[idx] = true; // Already precice
127 continue;
128 }
129 done_channels[idx] = false;
130
131 // Little bounded proportional controller, to decrease calibration time
132 calibration[idx].offset_x += abs_clamp(read_outputs[idx] * 6000.0f, 1, 50);
133
134 if (!hardware->write_calibration_input_offsets(idx, calibration[idx].offset_x,
135 calibration[idx].offset_y)) {
136 calibration[idx].offset_x = std::clamp(calibration[idx].offset_x, MMulBlockHAL_V_1_0_X::RAW_ZERO,
137 MMulBlockHAL_V_1_0_X::RAW_MAX);
138 success = false; // Out of bounds for factor
139 done_channels[idx] = true;
140 LOG(ANABRID_DEBUG_CALIBRATION, "Calibrating out of bounds!");
141 continue;
142 }
143 }
144 delay(10); // Small transient time
145
146 loop_count++;
147 if (loop_count > max_loops) {
148 LOG(ANABRID_DEBUG_CALIBRATION, "Calibration timed out!");
149 break;
150 }
151 }
152
153 LOG(ANABRID_DEBUG_CALIBRATION, "Calibrating y input offsets...");
154 for (auto idx = 0u; idx < NUM_MULTIPLIERS; idx++) {
155 if (!cluster->cblock->set_factor(slot_to_global_io_index(idx * 2),
156 0.0f)) // Setting 0.0 to Y inputs which previously were 1.0
157 return false; // fatal error
158
159 if (!cluster->cblock->set_factor(slot_to_global_io_index(idx * 2 + 1), 1.0f)) // Setting 1.0 to X inputs
160 return false; // fatal error
161 }
162 if (!cluster->cblock->write_to_hardware())
163 return false; // fatal error
164 // When changing a factor, we always have to calibrate offset
165 if (!cluster->calibrate_offsets())
166 return false; // fatal error
167
168 delay(10);
169
170 read_outputs = daq::average(daq::sample, 4, 10);
171
172 done_channels.reset();
173 loop_count = 0;
174
175 // start varying the y input offsets
176 while (!done_channels.all()) {
177 read_outputs = daq::average(daq::sample, 4, 10);
178
179 for (auto idx = 0u; idx < NUM_MULTIPLIERS; idx++) {
180 if (fabs(read_outputs[idx]) < target_precision) {
181 done_channels[idx] = true; // Already precice
182 continue;
183 }
184 done_channels[idx] = false;
185
186 // Little bounded proportional controller, to decrease calibration time
187 calibration[idx].offset_y += abs_clamp(read_outputs[idx] * 6000.0f, 1, 50);
188
189 if (!hardware->write_calibration_input_offsets(idx, calibration[idx].offset_x,
190 calibration[idx].offset_y)) {
191 calibration[idx].offset_y = std::clamp(calibration[idx].offset_y, MMulBlockHAL_V_1_0_X::RAW_ZERO,
192 MMulBlockHAL_V_1_0_X::RAW_MAX);
193 success = false; // Out of bounds for factor
194 done_channels[idx] = true;
195 LOG(ANABRID_DEBUG_CALIBRATION, "Calibrating out of bounds!");
196 continue;
197 }
198 }
199 delay(10); // Small transient time
200
201 loop_count++;
202 if (loop_count > max_loops) {
203 LOG(ANABRID_DEBUG_CALIBRATION, "Calibration timed out!");
204 break;
205 }
206 }
207
208 return success;
209}
210
211// V1.-1.0
212
214 blocks::MBlock::SLOT slot, blocks::MMulBlockHAL_FullAutoCalibration *hardware)
215 : MMulBlock(slot, hardware), hardware(hardware) {}
216
218 auto res = MMulBlock::write_calibration_to_hardware();
219 if (!res)
220 return res;
221
222 for (size_t i = 0; i < MMulBlock::NUM_MULTIPLIERS; i++) {
223 if (!hardware->write_calibration_gain(i, calibration[i].gain))
224 return utils::status(777, "MMulBlock::calibration from json for multiplier %d values gain not accepted",
225 i);
226 }
227 return utils::status::success();
228}
229
230FLASHMEM bool blocks::MMulBlock_FullAutoCalibration::calibrate(platform::Cluster *cluster,
231 carrier::Carrier *carrier) {
232 LOG(ANABRID_DEBUG_CALIBRATION, __PRETTY_FUNCTION__);
233
234 // First do regular calibration
235 bool success = MMulBlock::calibrate(cluster, carrier);
236
237 // Automatic gain calibration can only be done by version 1.1
238 LOG(ANABRID_DEBUG_CALIBRATION, "Calibrating output gains...");
239
240 for (auto idx = 0u; idx < NUM_MULTIPLIERS; idx++) {
241 if (!cluster->cblock->set_factor(slot_to_global_io_index(idx * 2),
242 1.0f)) // Setting 1.0 to Y inputs which previously were 0.0
243 return false; // fatal error
244 }
245 if (!cluster->cblock->write_to_hardware())
246 return false; // fatal error
247 // When changing a factor, we always have to calibrate offset
248 if (!cluster->calibrate_offsets())
249 return false; // fatal error
250
251 delay(10);
252
253 const float target_precision = 0.0005f;
254 const int max_loops = 100;
255
256 std::bitset<NUM_MULTIPLIERS> done_channels;
257 int loop_count = 0;
258
259 // start varying the gain factors
260 while (!done_channels.all()) {
261 auto read_outputs = daq::average(daq::sample, 4, 10);
262
263 for (auto idx = 0u; idx < NUM_MULTIPLIERS; idx++) {
264 if (fabs(read_outputs[idx] - 1.0f) < target_precision) {
265 done_channels[idx] = true; // Already precice
266 continue;
267 }
268 done_channels[idx] = false;
269
270 // Little bounded proportional controller, to decrease calibration time
271 int step = abs_clamp((read_outputs[idx] - 1.0f) * -1000.0f, 1, 70);
272
273 if (calibration[idx].gain + step > 0xff || calibration[idx].gain + step < 0) {
274 success = false; // Out of bounds for factor
275 done_channels[idx] = true;
276 LOG(ANABRID_DEBUG_CALIBRATION, "Calibrating out of bounds!");
277 continue;
278 }
279
280 calibration[idx].gain += step;
281
282 if (!hardware->write_calibration_gain(idx, calibration[idx].gain)) {
283 success = false; // Other issues
284 done_channels[idx] = true;
285 continue;
286 }
287 }
288 delay(10); // Small transient time
289
290 loop_count++;
291 if (loop_count > max_loops) {
292 LOG(ANABRID_DEBUG_CALIBRATION, "Calibration timed out!");
293 break;
294 }
295 }
296
297 return success;
298}
299
300// Hardware abstraction layer
301
302FLASHMEM blocks::MMulBlockHAL_V_1_0_X::MMulBlockHAL_V_1_0_X(bus::addr_t block_address)
303 : MMulBlockHAL_Parent(block_address),
304 f_overload_flags_reset(bus::address_from_tuple(block_address, 3)),
305 f_overload_flags(bus::replace_function_idx(block_address, 2)),
306 f_calibration_dac_0(bus::address_from_tuple(block_address, 4), 2.0f),
307 f_calibration_dac_1(bus::address_from_tuple(block_address, 5), 2.0f) {}
308
309FLASHMEM bool blocks::MMulBlockHAL::init() { return MBlockHAL::init(); }
310
312 bool error = true;
313 error &= f_calibration_dac_0.init();
314 error &= f_calibration_dac_0.set_external_reference();
315 error &= f_calibration_dac_0.set_double_gain();
316 error &= f_calibration_dac_1.init();
317 error &= f_calibration_dac_1.set_external_reference();
318 error &= f_calibration_dac_1.set_double_gain();
319 error &= MMulBlockHAL::init();
320 return error;
321}
322
323FLASHMEM void blocks::MMulBlockHAL_V_1_0_X::reset_overload_flags() { f_overload_flags_reset.trigger(); }
324
325FLASHMEM bool blocks::MMulBlockHAL_V_1_0_X::write_calibration_input_offsets(uint8_t idx, uint16_t offset_x,
326 uint16_t offset_y) {
327 return f_calibration_dac_0.set_channel_raw(idx * 2 + 1, offset_x) and
328 f_calibration_dac_0.set_channel_raw(idx * 2, offset_y);
329}
330
331FLASHMEM bool blocks::MMulBlockHAL_V_1_0_X::write_calibration_output_offset(uint8_t idx, uint16_t offset_z) {
332 return f_calibration_dac_1.set_channel_raw(idx, offset_z);
333}
334
336 return f_overload_flags.read8();
337}
338
340 : MMulBlockHAL_V_1_0_X(block_address), f_gain_ch0_1(bus::replace_function_idx(block_address, 6)),
341 f_gain_ch2_3(bus::replace_function_idx(block_address, 7)) {}
342
343FLASHMEM bool blocks::MMulBlockHAL_V_1_M1_X::write_calibration_gain(uint8_t idx, uint8_t gain) {
344 if (idx < 2)
345 return f_gain_ch0_1.write_channel_raw(idx, gain);
346 else if (idx < 4)
347 return f_gain_ch2_3.write_channel_raw(idx - 2, gain);
348 else
349 return false;
350}
351
352FLASHMEM blocks::MMulBlockHAL_V_1_1_X::MMulBlockHAL_V_1_1_X(bus::addr_t block_address)
353 : MMulBlockHAL_V_1_0_X(block_address), f_gain(bus::replace_function_idx(block_address, 6)) {}
354
355FLASHMEM bool blocks::MMulBlockHAL_V_1_1_X::write_calibration_gain(uint8_t idx, uint8_t gain) {
356 if (idx == 0)
357 return f_gain.write_channel_raw(2, gain);
358 else if (idx == 1)
359 return f_gain.write_channel_raw(0, gain);
360 else if (idx == 2)
361 return f_gain.write_channel_raw(1, gain);
362 if (idx == 3)
363 return f_gain.write_channel_raw(3, gain);
364 return false;
365}
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
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)
MMulBlock_FullAutoCalibration(SLOT slot, blocks::MMulBlockHAL_FullAutoCalibration *hardware)
bool calibrate(platform::Cluster *cluster, carrier::Carrier *carrier) override
utils::status write_calibration_to_hardware() override
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
static constexpr int success
Definition flasher.cpp:275
Definition bus.h:21
Definition daq.h:14