REDAC HybridController
Firmware for LUCIDAC/REDAC Teensy
Loading...
Searching...
No Matches
calibration.cpp
Go to the documentation of this file.
1// Copyright (c) 2024 anabrid GmbH
2// Contact: https://www.anabrid.com/licensing/
3//
4// SPDX-License-Identifier: MIT OR GPL-2.0-or-later
5
6#include "proto/main.pb.h"
7#include "redac/calibration.h"
8#include "handlers/calibration.h"
9
10#include "utils/timer.h"
11
12#include <Arduino.h>
13#include <utility>
14
15namespace platform{
16
17net::EthernetUDP CalibrationBase::net_group;
18//net::EthernetUDP CalibrationBase::net_receive;
19
20Box<UdpMessageOutputStream> CalibrationBase::broadcast_output;
21Box<UdpMessageInputStream> CalibrationBase::broadcast_input;
22//Box<UdpMessageInputStream> CalibrationBase::data_input;
23
24void CalibrationBase::begin() {
25 broadcast_output = std::make_unique<UdpMessageOutputStream>(&net_group, net_group_ip, net_group_port);
26 broadcast_input = std::make_unique<UdpMessageInputStream>(&net_group);
27 //data_input = std::make_unique<UdpMessageInputStream>(&net_group);
28 if (!net_group.beginMulticast(net_group_ip, net_group_port))
29 LOG_ALWAYS("unable to open net_group_port")
30
31 //if (!net_receive.begin(net_receive_port))
32 // LOG_ALWAYS("unable to open net_receive_port")
33}
34
35void CalibrationBase::clear(uint32_t timeout_ms) {
36 pb_Envelope envelope = pb_Envelope_init_zero;
37 while (broadcast_input->has_message())
38 IGNORE(broadcast_input->read(envelope, timeout_ms));
39
40 //while (data_input->has_message())
41 // IGNORE(data_input->read(envelope, timeout_ms));
42}
43
44CalibrationBase::CalibrationBase(REDAC &redac)
45 : redac(redac){
46 // TODO: Check block null pointer
47 // TODO: Check assumptions of calibration (e.g. no signal is used locally and externally)
48}
49
50UnitResult CalibrationBase::send(const pb_Envelope &envelope) {
51 return broadcast_output->write(envelope);
52}
53
54// searches where lane i_in_lane gets used in summation and prepares this summation node to be routed to an
55// M-Block ID lane. This does not preserve any connections. It also automatically connects the ADCs to the
56// chosen output. i.e. the signal in cluster 0 gets routed to ADC 0 and so on.
57UnitResult CalibrationBase::prepare_lane(uint8_t in_lane) {
58 for (const auto &cluster : redac.clusters) {
59 bool signal_upscaled = TRY(redac.routing.is_source_upscaled(cluster.get_cluster_idx(), in_lane));
60 // Set lane to constant 0.1 or 1
61
62 auto ref = signal_upscaled
63 ? blocks::UBlock::Reference_Magnitude::ONE_TENTH
64 : blocks::UBlock::Reference_Magnitude::ONE;
65
66 constexpr auto mode = blocks::UBlockHAL::Transmission_Mode::POS_REF;
67 TRY_TRUE(cluster.ublock->hardware->write_transmission_modes_and_ref({mode, mode}, ref));
68 TRY_TRUE(cluster.cblock->hardware->write_factor(in_lane, 1.0));
69
70 // Find used output lane
71 auto original_i_config = cluster.iblock->get_outputs();
72 uint8_t original_output_lane = 0xff;
73
74 for (uint8_t lane = 0; lane < original_i_config.size(); lane++) {
75 if (original_i_config[lane] & blocks::IBlockHAL::INPUT_BITMASK(in_lane)) {
76 original_output_lane = lane;
77 break;
78 }
79 }
80
81 std::array<uint32_t, 16> new_outputs{};
82 if (original_output_lane < 16) {
83 uint8_t id_in_lane = cluster2id_lane[cluster.get_cluster_idx()];
84 new_outputs[id_in_lane] = original_i_config[original_output_lane];
85 }
86
87 TRY_TRUE(cluster.iblock->hardware->write_outputs(new_outputs));
88 }
89
90 return UnitResult::ok();
91}
92
93UnitResult CalibrationBase::do_initial() {
94 // Entry into this function is synchronized with +-10μs jitter
95
96 Sync timer;
97 // Set all signals to zero
98 // Takes ~2ms for all clusters
99
100 constexpr auto pos = blocks::UBlock::Transmission_Mode::POS_REF;
101 constexpr auto one = blocks::UBlock::Reference_Magnitude::ONE;
102 for (auto &cluster : redac.clusters) {
103 TRY_TRUE(cluster.ublock->hardware->write_transmission_modes_and_ref({pos, pos}, one));
104
105 for (size_t lane = 0; lane < 32 ; ++lane) {
106 TRY_TRUE(cluster.cblock->hardware->write_factor(lane, 0.0));
107 }
108 }
109
110 // Account for timing variances on other boards
111 // This could most likely be reduced further, especially considering track_time below
112 timer.sync_at_us(100);
113
114 // Do SH-block offset correction for each cluster in parallel
115 // Takes track_delay + inject_delay + 100μs for write_to_hardware
116 // TODO: Refactor into carrier convenience function
117 for (auto &cluster : redac.clusters) {
118 cluster.shblock->hardware->set_state(blocks::SHState::TRACK);
119 }
120 timer.sync_at_us(10000);
121 for (auto &cluster : redac.clusters) {
122 cluster.shblock->hardware->set_state(blocks::SHState::INJECT);
123 }
124 timer.sync_at_us(5000);
125
126 // TODO: Move into prepare function
127 std::array<carrier::ADCChannel, 8> adc_channels;
128 for (auto &cluster : redac.clusters) {
129 // Check if M-Block with ID lane is reachable
130 blocks::MBlock *id_lane_block = nullptr;
131 if (cluster.m0block->has_id_lanes())
132 id_lane_block = cluster.m0block;
133 if (cluster.m1block->has_id_lanes())
134 id_lane_block = cluster.m1block;
135
136 if (id_lane_block == nullptr)
137 return UnitResult::err("No M-Block with ID lane found in this cluster!");
138
139 auto id_connections = id_lane_block->ID_OUTPUT_CONNECTIONS();
140 size_t lane = 0;
141 for (; lane < id_connections.size(); lane++) {
142 if (id_connections[lane] == -1) continue;
143 uint8_t id_in_lane = id_lane_block->slot_to_global_io_index(id_connections[lane]);
144 uint8_t id_out_lane = id_lane_block->slot_to_global_io_index(lane);
145
146 cluster2id_lane.push_back(id_in_lane);
147 adc_channels[cluster.get_cluster_idx()].src = id_out_lane + cluster.get_cluster_idx() * 16;
148 break;
149 }
150
151 if (lane == id_connections.size())
152 return UnitResult::err("No M-Block with ID lane found!");
153 }
154
155 TRY(redac.write_adcs_to_hardware(adc_channels));
156 return UnitResult::ok();
157}
158
159UnitResult CalibrationBase::do_lane(uint8_t lane) {
160 Sync timer;
161 // Entry into this function is synchronized with +-10μs jitter
162 // Set this signal to 1 or 0.1
163 // Takes ~100μs for all clusters, mostly in write_to_hardware
164 TRY(prepare_lane(lane));
165
166 // Account for timing variances on other boards and possible signal transients
167 // This could most likely be reduced further
168 TRY_TRUE(timer.sync_at_us(5000));
169
170 // Measure signal here
171 auto gain_measure = daq::average(daq::sample, 4, 10);
172
173 // Wait before resetting signal (important to reduce problems caused by jitter)
174 TRY_TRUE(timer.sync_at_us(5000));
175 // Reset signal to zero and measure offset
176 // Takes ~100μs
177 for (auto &cluster : redac.clusters) {
178 TRY_TRUE(cluster.cblock->hardware->write_factor(lane, 0.0));
179 }
180
181 // Account for timing variances on other boards and possible signal transients
182 // This could most likely be reduced further
183 TRY_TRUE(timer.sync_at_us(5000));
184
185 auto offset_measure = daq::average(daq::sample, 4, 10);
186
187 // Analyse data
188 bool gain_correction_exceeding = false;
189 for (size_t cluster_idx = 0; cluster_idx < redac.clusters.size(); cluster_idx++) {
190 if (!TRY(redac.routing.is_sink_present(cluster_idx, lane)))
191 continue;
192
193 bool signal_upscaled = TRY(redac.routing.is_source_upscaled(cluster_idx, lane));
194
195 float difference = offset_measure[cluster_idx] - gain_measure[cluster_idx];
196 float gain_correction = (signal_upscaled ? 0.8f : 1.0f) / difference;
197
198 if (gain_correction < 0.7f || 1.2f < gain_correction) {
199 bool carrier_idx = redac.routing.self_carrier();
200 return UnitResult::err_fmt("Gain correction far beyond normal %f at %d/%d/%d", gain_correction, carrier_idx, cluster_idx, lane);
201 }
202
203 if (gain_correction > 1.1f) {
204 bool carrier_idx = redac.routing.self_carrier();
205 LOGV("Gain correction exceeding 1.1 at %s(%d)/%d/%d which is %f at", redac.get_entity_id().c_str(), carrier_idx, cluster_idx, lane, gain_correction);
206 gain_correction = 1.1f;
207 gain_correction_exceeding = true;
208 }
209
210 measured_gain_correction[cluster_idx][lane] = gain_correction;
211 }
212
213 if (gain_correction_exceeding) {
214 LOG_ERROR("Gain correction exceeding 1.1.");
215 }
216
217 return UnitResult::ok();
218}
219
220UnitResult CalibrationBase::collect_gain_corrections(uint8_t cluster_idx, uint8_t lane, float gain_correction) {
221 if (!TRY(redac.routing.is_sink_present(cluster_idx, lane))) {
222 return UnitResult::ok();
223 }
224
225 if (lane < 8) {
226 agg_gain_corrs[cluster_idx + 1][lane].add(gain_correction);
227 return UnitResult::ok();
228 }
229
230 auto dst_sector = static_cast<blocks::TBlock::Sector>(cluster_idx + 1);
231 auto src_sector = redac.carrier_t_block->src_of_signal(dst_sector, lane - 8);
232 auto& agg_gain_corr = agg_gain_corrs[src_sector][lane];
233
234 if (src_sector == 0) {
235 auto dst_lane = TRY(redac.routing.cluster2lane(cluster_idx, lane));
236 auto src_lane = TRY(redac.routing.source(dst_lane));
237 if (agg_gain_corr.get_n() == 0) {
238 bpl_srcs[lane] = src_lane;
239 }else if (bpl_srcs[lane] != src_lane) {
240 return UnitResult::err_fmt("Expected source lane be unique %s:%d:%d from %d:%d:%d - %d:%d:%d", redac.get_entity_id().c_str(), cluster_idx, lane, src_lane.carrier(), src_lane.cluster(), src_lane.lane(), bpl_srcs[lane].carrier(), bpl_srcs[lane].cluster(), bpl_srcs[lane].lane());
241 }
242 }
243
244 agg_gain_corr.add(gain_correction);
245
246
247 /*if (src_sector != blocks::TBlock::Sector::BPL) {
248 // Signal is local, no need to send data over network
249 aggregate_gain_correction[src_sector][lane].add(gain_correction);
250 return UnitResult::ok();
251 }*/
252
253 // Send signal to other redac
254 // Package can be very simplistic, containing only:
255 // [lane_idx, gain_correction]
256 // The receiving side must determine which cluster it came from.
257 // Send every measurement as separate package. Allows for easy error correction when we get to that point
258
259
260 //auto src_node_path = TRY(redac.routing.node2path(src_lane.node()));
261 //stage.add(src_node_path, lane, gain_correction);
262
263 return UnitResult::ok();
264}
265
266BoolResult CalibrationBase::receive_gain_corrections(uint32_t timeout) {
267 pb_Envelope envelope;
268 if (!TRY(broadcast_input->read(envelope, timeout)))
269 return BoolResult::ok(false);
270
271 if (envelope.which_kind != pb_Envelope_message_v1_tag)
272 return UnitResult::err("Expected message v1 tag");
273
274 auto& msg_in = envelope.kind.message_v1;
275 if (msg_in.which_kind != pb_MessageV1_calibrate_data_command_tag)
276 return UnitResult::err_fmt("Expected calibration data but message is of different kind: %d", msg_in.which_kind);
277
278 auto& cmd = msg_in.kind.calibrate_data_command;
279 for (auto data_idx = 0; data_idx < cmd.data_count; ++data_idx) {
280 auto& data_in = cmd.data[data_idx];
281 // We must determine which cluster generates the signal
282
283 if (data_in.carrier != redac.routing.self_carrier())
284 continue;
285
286 if (data_in.lane < 8)
287 return UnitResult::err("Not expected calibration data for first 8 lanes via network!");
288
289 auto source = redac.carrier_t_block->src_of_signal(blocks::TBlock::Sector::BPL, data_in.lane - 8);
290 if (source == blocks::TBlock::Sector::BPL)
291 return UnitResult::err_fmt("Not expected calibration data for not connected backplane! %d", static_cast<int>(redac.carrier_t_block->get_connections()[0]));
292
293 agg_gain_corrs[source][data_in.lane].add(data_in.gain_correction, data_in.weight);
294 }
295
296 return BoolResult::ok(true);
297}
298
299UnitResult CalibrationBase::exchange_gain_corrections() {
300 // Entry into this function is synchronized with +-10μs jitter
301
302 Sync timer;
303 for (uint8_t cluster_idx = 0; cluster_idx < 3; ++cluster_idx) {
304 for (uint8_t lane_idx = 0; lane_idx < 32; ++lane_idx) {
305 // sending gain corrections others wait for
306 auto gain_correction = measured_gain_correction[cluster_idx][lane_idx];
307 TRY(collect_gain_corrections(cluster_idx, lane_idx, gain_correction));
308 }
309 }
310
311 CalibrationStaging staging;
312 for (uint8_t lane_idx = 0; lane_idx < 32; ++lane_idx) {
313 auto src = bpl_srcs[lane_idx];
314 if (!src.valid()) continue;
315 auto& correction = agg_gain_corrs[0][lane_idx];
316 TRY(staging.add(src, correction.get_average(), correction.get_n()));
317 }
318
319 TRY_TRUE(timer.sync_at_ms(10));
320 for (auto idx = 0; idx < redac.routing.num_carrier(); ++idx) {
321 if (idx == redac.routing.self_carrier()) {
322 TRY(broadcast_output->write(staging.envelope));
323 }else{
324 TRY(receive_gain_corrections(9));
325 }
326
327 TRY_TRUE(timer.sync_at_ms(10));
328 }
329
330 bool gain_correction_exceeding = false;
331 // Actually apply the gathered values
332 for (auto cluster_idx = 0; cluster_idx < 3 ; ++cluster_idx) {
333 for (auto lane_idx = 0; lane_idx < 32 ; ++lane_idx) {
334 auto& gain_correction = agg_gain_corrs[cluster_idx + 1][lane_idx];
335 auto expected_count = TRY(redac.routing.use_count(cluster_idx, lane_idx));
336
337 if(expected_count != gain_correction.get_n())
338 return UnitResult::err_fmt(
339 "Mismatch signal use and gain correction count of carrier %s/%d on lane %d. Received %d but expected %d.",
340 redac.get_entity_id().c_str(),
341 cluster_idx,
342 lane_idx,
343 gain_correction.get_n(),
344 expected_count);
345
346 if (gain_correction.get_n() == 0)
347 continue;
348
349 auto gain_correction_avg = gain_correction.get_average();
350 if (gain_correction_avg > 1.1f) {
351 LOGV("Received gain correction exceeding 1.1 which is %f", gain_correction_avg);
352 gain_correction_avg = 1.1f;
353 gain_correction_exceeding = true;
354 }
355
356 TRY (redac.clusters[cluster_idx].cblock->set_gain_correction(lane_idx, gain_correction_avg));
357 }
358 }
359
360 if (gain_correction_exceeding)
361 LOG_ERROR("Gain correction exceeding 1.1.");
362
363 TRY_TRUE(redac.write_to_hardware());
364 return UnitResult::ok();
365}
366
367UnitResult CalibrationBase::do_final_offset_correction() {
368 // Entry into this function is synchronized with +-10μs jitter
369 Sync timer;
370 constexpr auto ground = blocks::UBlock::Transmission_Mode::GROUND;
371 constexpr auto one = blocks::UBlock::Reference_Magnitude::ONE;
372 for (auto &cluster : redac.clusters)
373 TRY_TRUE(cluster.ublock->hardware->write_transmission_modes_and_ref({ground, ground}, one));
374
375 // Account for timing variances on other boards
376 // This could most likely be reduced further, especially considering track_time below
377 TRY_TRUE(timer.sync_at_us(1000));
378
379 // Do SH-block offset correction for each cluster in parallel
380 // Takes track_delay + inject_delay + 100μs for write_to_hardware
381 // TODO: Refactor into carrier convenience function
382 for (auto &cluster : redac.clusters)
383 cluster.shblock->hardware->set_state(blocks::SHState::TRACK);
384 TRY_TRUE(timer.sync_at_us(10000));
385 for (auto &cluster : redac.clusters)
386 cluster.shblock->hardware->set_state(blocks::SHState::INJECT);
387
388 TRY_TRUE(timer.sync_at_us(5000));
389
390 for (auto &cluster : redac.clusters)
391 TRY_TRUE(cluster.ublock->write_to_hardware());
392 return UnitResult::ok();
393}
394
395UnitResult CalibrationLeader::do_() {
396 Sync timer;
397 LOG_ALWAYS("Init calibration routes for run as leader...")
398 TRY_TRUE(timer.sync_at_ms(500));
399 LOG_ALWAYS("Starting calibration routes for run as leader...")
400 TRY(do_initial());
401 LOG_ALWAYS("Started calibration routes for run as leader...")
402 for (uint8_t lane = 0u; lane < 32; lane++) {
403 TRY_TRUE(timer.sync_at_us(20000));
404 TRY(do_lane(lane));
405 }
406 TRY_TRUE(timer.sync_at_us(1000));
407 TRY(exchange_gain_corrections());
408 TRY_TRUE(timer.sync_at_us(1000));
409 LOG_ALWAYS("Finishing calibration routes for run as leader...")
410 TRY_TRUE(redac.write_to_hardware());
411 TRY_TRUE(timer.sync_at_us(10000));
412 TRY(do_final_offset_correction());
413 return UnitResult::ok();
414}
415
416UnitResult CalibrationLeader::do_initial() {
417 pb_Envelope envelope;
418 auto& msg = transport::init_v1_message(envelope, pb_MessageV1_calibrate_init_command_tag);
419 msg.kind.calibrate_init_command = pb_CalibrateInitCommand_init_default;
420 TRY(send(envelope));
421 delayMicroseconds(19);
422 return CalibrationBase::do_initial();
423}
424
425UnitResult CalibrationLeader::do_lane(uint8_t lane) {
426 pb_Envelope envelope;
427 auto& msg = transport::init_v1_message(envelope, pb_MessageV1_calibrate_lane_command_tag);
428 auto& lane_cmd = msg.kind.calibrate_lane_command = pb_CalibrateLaneCommand_init_default;
429 lane_cmd.lane = lane;
430
431 TRY(send(envelope));
432 delayMicroseconds(19);
433 return CalibrationBase::do_lane(lane);
434}
435
436UnitResult CalibrationLeader::exchange_gain_corrections() {
437 pb_Envelope envelope;
438 transport::init_v1_message(envelope, pb_MessageV1_calibrate_finalize_command_tag);
439 TRY(send(envelope));
440 delayMicroseconds(19);
441 return CalibrationBase::exchange_gain_corrections();
442}
443
444UnitResult CalibrationLeader::do_final_offset_correction() {
445 pb_Envelope envelope;
446 transport::init_v1_message(envelope, pb_MessageV1_calibrate_offset_command_tag);
447
448 TRY(send(envelope));
449 delayMicroseconds(19);
450 return CalibrationBase::do_final_offset_correction();
451}
452
453CalibrationFollower::CalibrationFollower(REDAC &redac,
454 unsigned int timeout_ms)
455 : CalibrationBase(redac), timeout_ms(timeout_ms) {}
456
457UnitResult CalibrationFollower::receive_command(pb_Envelope &envelope) const {
458 if (TRY(broadcast_input->read(envelope, timeout_ms)))
459 return UnitResult::ok();
460
461 return UnitResult::err("Timeout while waiting for incoming calibration command");
462}
463
464UnitResult CalibrationFollower::do_() {
465 LOG_ALWAYS("Init calibrating routes for run as follower...")
466 TRY(do_initial());
467 LOG_ALWAYS("Started calibrating routes for run as follower...")
468 while (true) {
469 TRY(do_lane_as_told());
470 if (is_done())
471 break;
472 }
473 TRY(exchange_gain_corrections());
474 TRY_TRUE(redac.write_to_hardware());
475 TRY(do_final_offset_correction());
476 return UnitResult::ok();
477}
478
479UnitResult CalibrationFollower::wait_for(pb_Envelope& envelope, int v1_tag) const {
480 Sync timer;
481 while (true){
482 if (timer.elapsed_ms() > timeout_ms)
483 return UnitResult::err("Error initializing calibration");
484 TRY(receive_command(envelope));
485 if (envelope.which_kind != pb_Envelope_message_v1_tag) continue;
486 if (envelope.kind.message_v1.which_kind != v1_tag) continue;
487 break;
488 }
489
490 return UnitResult::ok();
491}
492
493UnitResult CalibrationFollower::do_initial() {
494 pb_Envelope envelope;
495 TRY(wait_for(envelope, pb_MessageV1_calibrate_init_command_tag));
496 return CalibrationBase::do_initial();
497}
498
499UnitResult CalibrationFollower::do_lane_as_told() {
500 pb_Envelope envelope = pb_Envelope_init_default;
501 TRY(wait_for(envelope, pb_MessageV1_calibrate_lane_command_tag));
502
503 auto& msg = envelope.kind.message_v1;
504 auto& lane_cmd = msg.kind.calibrate_lane_command;
505 uint8_t lane = lane_cmd.lane;
506 if (lane >= 32)
507 return UnitResult::err("Error: Told to calibrate a lane >= 32.");
508 done |= 1 << lane;
509 return CalibrationBase::do_lane(lane);
510}
511
512UnitResult CalibrationFollower::exchange_gain_corrections() {
513 pb_Envelope envelope;
514 TRY(wait_for(envelope, pb_MessageV1_calibrate_finalize_command_tag));
515 return CalibrationBase::exchange_gain_corrections();
516}
517
518UnitResult CalibrationFollower::do_final_offset_correction() {
519 pb_Envelope envelope;
520 TRY(wait_for(envelope, pb_MessageV1_calibrate_offset_command_tag));
521 return CalibrationBase::do_final_offset_correction();
522}
523
524} // namespace platform
525
526msg::handlers::HandleResult msg::handlers::RegisterExternalEntitiesRequestHandler::handle(const pb_Envelope &envelope_in, pb_Envelope &envelope_out) {
527 transport::init_v1_message(envelope_out, pb_MessageV1_success_message_tag);
528 auto& msg_in = envelope_in.kind.message_v1;
529 auto redac = dynamic_cast<platform::REDAC*>(&platform::REDAC::get());
530
531 auto& register_external_entities_cmd = msg_in.kind.register_external_entities_command;
532 for (size_t idx = 0; idx < register_external_entities_cmd.entities_count; ++idx) {
533 auto& entity = register_external_entities_cmd.entities[idx];
534 if (!entity.has_value) continue;
535 auto& data = entity.value.data.bytes;
536 IPAddress address(data[0], data[1], data[2], data[3]);
537 if (redac == nullptr) continue;
538 redac->routing.set_path_address(std::string_view(entity.key), address);
539 }
540 return HandleResult::ok();
541}
Definition mode.h:14
pb_MessageV1 & init_v1_message(pb_Envelope &envelope, pb_size_t which_kind)
Definition transport.cpp:32