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;
18net::EthernetUDP CalibrationBase::net_receive;
19
20Box<UdpMessageOutputStream> CalibrationBase::broadcast_output;
21Box<UdpMessageInputStream> CalibrationBase::broadcast_input;
22Box<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_receive);
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: Instead of saving adc channels, switch to direct hardware access as well
47 _orig_adc_channels = redac.get_adc_channels();
48
49 // TODO: Check block null pointer
50 // TODO: Check assumptions of calibration (e.g. no signal is used locally and externally)
51}
52
53UnitResult CalibrationBase::send(const pb_Envelope &envelope) {
54 if (!broadcast_output->write(envelope))
55 return UnitResult::err("Unable to send UDP data");
56 return UnitResult::ok();
57}
58
59// searches where lane i_in_lane gets used in summation and prepares this summation node to be routed to an
60// M-Block ID lane. This does not preserve any connections. It also automatically connects the ADCs to the
61// chosen output. i.e. the signal in cluster 0 gets routed to ADC 0 and so on.
62UnitResult CalibrationBase::separate_lane(uint8_t i_in_lane) {
63 for (const auto &cluster : redac.clusters) {
64 // Find used output lane
65 auto original_i_config = cluster.iblock->get_outputs();
66 uint8_t original_output_lane = 0xff;
67
68 for (uint8_t lane = 0; lane < original_i_config.size(); lane++) {
69 if (original_i_config[lane] & cluster.iblock->hardware->INPUT_BITMASK(i_in_lane)) {
70 original_output_lane = lane;
71 break;
72 }
73 }
74
75 // i_in_lane doesn't get used in this cluster so do not connect anything
76 if (original_output_lane == 0xff) {
77 cluster.iblock->hardware->write_outputs({});
78 continue;
79 }
80 // Write to new sum directly to hardware
81 std::array<uint32_t, 16> new_outputs = {};
82 uint8_t id_in_lane = used_id_lanes[cluster.get_cluster_idx()].first;
83 new_outputs[id_in_lane] = original_i_config[original_output_lane];
84
85 cluster.iblock->hardware->write_outputs(new_outputs);
86 }
87
88 return UnitResult::ok();
89}
90
91UnitResult CalibrationBase::do_initial() {
92 // Entry into this function is synchronized with +-10μs jitter
93
94 // Set all signals to zero
95 // Takes ~2ms for all clusters
96 for (auto &cluster : redac.clusters) {
97 for (auto lane : blocks::CBlock::OUTPUT_IDX_RANGE())
98 if (!cluster.cblock->hardware->write_factor(lane, 0.0))
99 return UnitResult::err("Error setting factor on C-block");
100
101 if (!cluster.ublock->hardware->write_transmission_modes_and_ref(
102 {blocks::UBlock::Transmission_Mode::POS_REF, blocks::UBlock::Transmission_Mode::POS_REF},
103 blocks::UBlock::Reference_Magnitude::ONE))
104 return UnitResult::err("Error setting constant on U-block");
105 }
106
107 // Account for timing variances on other boards
108 // This could most likely be reduced further, especially considering track_time below
109 delayMicroseconds(100);
110
111 // Do SH-block offset correction for each cluster in parallel
112 // Takes track_delay + inject_delay + 100μs for write_to_hardware
113 // TODO: Refactor into carrier convenience function
114 for (auto &cluster : redac.clusters) {
115 cluster.shblock->hardware->set_state(blocks::SHState::TRACK);
116 }
117 delayMicroseconds(10000);
118 for (auto &cluster : redac.clusters) {
119 cluster.shblock->hardware->set_state(blocks::SHState::INJECT);
120 }
121 delayMicroseconds(5000);
122
123 // TODO: Move into prepare function
124 redac.reset_adc_channels();
125
126 for (auto &cluster : redac.clusters) {
127 // TODO: reset cblock gain correction factor
128
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 uint8_t id_in_lane = 0, id_out_lane = 0;
140
141 auto id_connections = id_lane_block->ID_OUTPUT_CONNECTIONS();
142 for (uint8_t lane = 0; lane < id_connections.size(); lane++) {
143 if (id_connections[lane] != -1) {
144 id_in_lane = id_lane_block->slot_to_global_io_index(id_connections[lane]);
145 id_out_lane = id_lane_block->slot_to_global_io_index(lane);
146 break;
147 }
148 }
149
150 used_id_lanes.emplace_back(id_in_lane, id_out_lane);
151
152 TRY(redac.set_adc_channel(cluster.get_cluster_idx(), static_cast<int8_t>(id_out_lane),
153 cluster.get_cluster_idx()));
154 }
155 if (!redac.write_adcs_to_hardware())
156 return UnitResult::err("Error writing ADCs to hardware");
157
158 return UnitResult::ok();
159}
160
161UnitResult CalibrationBase::do_lane(uint8_t lane) {
162 GateTimer timer;
163 // Entry into this function is synchronized with +-10μs jitter
164 // Set this signal to 1 or 0.1
165 // Takes ~100μs for all clusters, mostly in write_to_hardware
166 for (auto &cluster : redac.clusters) {
167 // Find out whether any of the targets of the signal is up-scaling
168 // TODO: We can only calibrate signals where all targets are either up-scaling or not.
169 // So it would be nice to check this here.
170 // But of course, we could only check the local usages.
171 bool signal_upscaled = TRY(redac.routing.is_upscaled(cluster.get_cluster_idx(), lane));
172 // Set lane to constant 0.1 or 1
173 if (!cluster.ublock->hardware->write_transmission_modes_and_ref(
174 {blocks::UBlock::Transmission_Mode::POS_REF, blocks::UBlock::Transmission_Mode::POS_REF},
175 signal_upscaled ? blocks::UBlock::Reference_Magnitude::ONE_TENTH
176 : blocks::UBlock::Reference_Magnitude::ONE
177
178 ))
179 return UnitResult::err("Error setting constant on U-block");
180 if (!cluster.cblock->hardware->write_factor(lane, 1.0))
181 return UnitResult::err("Error setting factor on C-block");
182 }
183
184 // TODO: Maybe move pre-computation into prepare() function
185 TRY(separate_lane(lane));
186
187 // Account for timing variances on other boards and possible signal transients
188 // This could most likely be reduced further
189 timer.release_after_us(5000);
190
191 // Measure signal here
192 auto gain_measure = daq::average(daq::sample, 4, 10);
193
194 // Wait before resetting signal (important to reduce problems caused by jitter)
195 timer.release_after_us(1000);
196 // Reset signal to zero and measure offset
197 // Takes ~100μs
198 for (auto &cluster : redac.clusters) {
199 if (!cluster.cblock->hardware->write_factor(lane, 0.0))
200 return UnitResult::err("Error setting factor on C-block");
201 }
202
203 // Account for timing variances on other boards and possible signal transients
204 // This could most likely be reduced further
205 timer.release_after_us(5000);
206
207 auto offset_measure = daq::average(daq::sample, 4, 10);
208
209 // Analyse data
210 bool gain_correction_exceeding = false;
211 for (size_t cluster_idx = 0; cluster_idx < redac.clusters.size(); cluster_idx++) {
212 if (!TRY(redac.routing.is_sink_present(cluster_idx, lane))) continue;
213
214 bool signal_upscaled = TRY(redac.routing.is_upscaled(cluster_idx, lane));
215 float difference = offset_measure[cluster_idx] - gain_measure[cluster_idx];
216 float gain_correction = (signal_upscaled ? 0.1f : 1.0f) / difference;
217
218 if (gain_correction > 1.1f) {
219 gain_correction = 1.1f;
220 gain_correction_exceeding = true;
221 }
222
223 measured_gain_correction[cluster_idx][lane] = gain_correction;
224 }
225
226 if (gain_correction_exceeding) {
227 LOG_ERROR("Gain correction exceeding 1.1.");
228 }
229
230 return UnitResult::ok();
231}
232
233UnitResult CalibrationBase::distribute_gain_corrections(uint8_t cluster_idx, uint8_t lane, float gain_correction) {
234 if (!TRY(redac.routing.is_sink_present(cluster_idx, lane))) {
235 return UnitResult::ok();
236 }
237
238 if (lane < 8) {
239 received_gain_correction[cluster_idx][lane].add(gain_correction);
240 return UnitResult::ok();
241 }
242
243 auto dst_sector = static_cast<blocks::TBlock::Sector>(cluster_idx + 1);
244 auto src_sector = redac.carrier_t_block->src_of_signal(dst_sector, lane - 8);
245 if (src_sector != blocks::TBlock::Sector::BPL) {
246 // Signal is local, no need to send data over network
247 auto src_cluster_idx = static_cast<uint8_t>(src_sector) - 1;
248 received_gain_correction[src_cluster_idx][lane].add(gain_correction);
249 return UnitResult::ok();
250 }
251
252 // Send signal to other redac
253 // Package can be very simplistic, containing only:
254 // [lane_idx, gain_correction]
255 // The receiving side must determine which cluster it came from.
256 // Send every measurement as separate package. Allows for easy error correction when we get to that point
257
258 pb_Envelope envelope;
259 auto& msg_out = transport::init_v1_message(envelope, pb_MessageV1_calibrate_data_command_tag);
260 auto& data_cmd = msg_out.kind.calibrate_data_command;
261 data_cmd.has_data = true;
262
263 auto src_entity_path = TRY(redac.routing.source_path(cluster_idx, lane));
264 auto signal_source = TRY(redac.routing.ip_address(src_entity_path.segment(0)));
265
266 data_cmd.data = pb_CalibrationData{
267 .lane = lane,
268 .gain_correction = gain_correction
269 };
270
271 transport::DatagramMessageOutputStream<UDP*> data_output(&net_receive, signal_source, net_receive_port);
272 data_output.write(envelope);
273 return UnitResult::ok();
274}
275
276UnitResult CalibrationBase::receive_gain_corrections(uint32_t timeout) {
277 pb_Envelope envelope;
278 while (true) {
279 if (!TRY(data_input->read(envelope, timeout)))
280 return UnitResult::ok();
281
282 if (envelope.which_kind != pb_Envelope_message_v1_tag)
283 return UnitResult::err("Expected message v1 tag");
284
285 auto& msg_in = envelope.kind.message_v1;
286 if (msg_in.which_kind != pb_MessageV1_calibrate_data_command_tag) {
287 LOG_ERROR("Expected calibration data but message is of different kind.")
288 return UnitResult::err("Expected calibration data but message is of different kind");
289 }
290
291 auto& data_in = msg_in.kind.calibrate_data_command.data;
292 // We must determine which cluster generates the signal
293 if (data_in.lane < 8)
294 return UnitResult::err("Not expected calibration data for first 8 lanes via network!");
295
296 auto source = redac.carrier_t_block->src_of_signal(blocks::TBlock::Sector::BPL, data_in.lane - 8);
297 if (source == blocks::TBlock::Sector::BPL)
298 return UnitResult::err("Not expected calibration data for not connected backplane!");
299
300 auto cluster_idx = static_cast<uint8_t>(source) - 1;
301 received_gain_correction[cluster_idx][data_in.lane].add(data_in.gain_correction);
302 }
303}
304
305UnitResult CalibrationBase::send_receive_apply_gain_corrections() {
306 // Entry into this function is synchronized with +-10μs jitter
307
308 GateTimer timer;
309 for (uint8_t cluster_idx = 0; cluster_idx < 3; ++cluster_idx) {
310 for (uint8_t lane_idx = 0; lane_idx < 32; ++lane_idx) {
311 // sending gain corrections others wait for
312 auto gain_correction = measured_gain_correction[cluster_idx][lane_idx];
313 TRY(distribute_gain_corrections(cluster_idx, lane_idx, gain_correction));
314
315 TRY(receive_gain_corrections(5));
316
317 timer.release_after_us(10);
318 }
319 }
320
321 TRY(receive_gain_corrections(5000));
322
323
324 bool gain_correction_exceeding = false;
325 // Actually apply the gathered values
326 for (auto cluster_idx = 0; cluster_idx < 3 ; ++cluster_idx) {
327 for (auto lane_idx = 0; lane_idx < 32 ; ++lane_idx) {
328 auto& gain_correction = received_gain_correction[cluster_idx][lane_idx];
329 auto count = TRY(redac.routing.use_count(cluster_idx, lane_idx));
330
331 if (count != gain_correction.get_n()) {
332 auto node = TRY(redac.routing.cluster2node(cluster_idx));
333
334 return UnitResult::err_fmt(
335 "Mismatch signal use and gain correction count of carrier %s/%d (%d) on lane %d. Received %d but expected %d.",
336 redac.get_entity_id().c_str(),
337 cluster_idx,
338 node,
339 lane_idx,
340 gain_correction.get_n(),
341 count);
342 }
343
344 if (!gain_correction.get_n()) continue;
345
346 auto gain_correction_avg = gain_correction.get_average();
347 if (gain_correction_avg > 1.1f) {
348 gain_correction_avg = 1.1f;
349 gain_correction_exceeding = true;
350 }
351
352 TRY (redac.clusters[cluster_idx].cblock->set_gain_correction(lane_idx, gain_correction_avg));
353 }
354 }
355
356 if (gain_correction_exceeding)
357 LOG_ERROR("Gain correction exceeding 1.1.");
358
359 // Restore original configuration
360 TRY(redac.set_adc_channels(_orig_adc_channels));
361 TRY(redac.write_to_hardware());
362
363 return UnitResult::ok();
364}
365
366UnitResult CalibrationBase::do_final_offset_correction() {
367 // Entry into this function is synchronized with +-10μs jitter
368 for (auto &cluster : redac.clusters) {
369 if (!cluster.ublock->hardware->write_transmission_modes_and_ref(
370 {blocks::UBlock::Transmission_Mode::GROUND, blocks::UBlock::Transmission_Mode::GROUND},
371 blocks::UBlock::Reference_Magnitude::ONE))
372 return UnitResult::err("Error setting ground on U-block");
373 }
374
375 // Account for timing variances on other boards
376 // This could most likely be reduced further, especially considering track_time below
377 delayMicroseconds(100);
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 }
385 delayMicroseconds(10000);
386 for (auto &cluster : redac.clusters) {
387 cluster.shblock->hardware->set_state(blocks::SHState::INJECT);
388 }
389 // TODO: This delay could probably be removed
390 delayMicroseconds(5000);
391
392 // Restore U-block
393 for (auto &cluster : redac.clusters) {
394 // TODO: Could be optimized by only writing transmission modes
395 TRY(cluster.ublock->write_to_hardware());
396 }
397
398 return UnitResult::ok();
399}
400
401UnitResult CalibrationLeader::do_() {
402 GateTimer timer;
403 LOG_ALWAYS("Init calibration routes for run as leader...")
404 timer.release_after_ms(500);
405 TRY(do_initial());
406 LOG_ALWAYS("Started calibration routes for run as leader...")
407
408 for (uint8_t lane = 0u; lane < 32; lane++) {
409 timer.release_after_us(1000);
410 TRY(do_lane(lane));
411 }
412 timer.release_after_us(1000);
413 TRY(send_receive_apply_gain_corrections());
414 // TODO: Determine how long we should actually wait here.
415 // There can be a lot of variance in the previous send_receive_apply_gain_corrections(),
416 // so for now we stick with a large -- and safe -- delay.
417 timer.release_after_us(10000);
418 TRY(do_final_offset_correction());
419 return UnitResult::ok();
420}
421
422UnitResult CalibrationLeader::do_initial() {
423 pb_Envelope envelope;
424 auto& msg = transport::init_v1_message(envelope, pb_MessageV1_calibrate_init_command_tag);
425 msg.kind.calibrate_init_command = pb_CalibrateInitCommand_init_default;
426 TRY(send(envelope));
427 delayMicroseconds(19);
428 return CalibrationBase::do_initial();
429}
430
431UnitResult CalibrationLeader::do_lane(uint8_t lane) {
432 pb_Envelope envelope;
433 auto& msg = transport::init_v1_message(envelope, pb_MessageV1_calibrate_lane_command_tag);
434 auto& lane_cmd = msg.kind.calibrate_lane_command = pb_CalibrateLaneCommand_init_default;
435 lane_cmd.lane = lane;
436
437 TRY(send(envelope));
438 delayMicroseconds(19);
439 return CalibrationBase::do_lane(lane);
440}
441
442UnitResult CalibrationLeader::send_receive_apply_gain_corrections() {
443 pb_Envelope envelope;
444 transport::init_v1_message(envelope, pb_MessageV1_calibrate_finalize_command_tag);
445 TRY(send(envelope));
446 delayMicroseconds(19);
447 return CalibrationBase::send_receive_apply_gain_corrections();
448}
449
450UnitResult CalibrationLeader::do_final_offset_correction() {
451 pb_Envelope envelope;
452 transport::init_v1_message(envelope, pb_MessageV1_calibrate_offset_command_tag);
453
454 TRY(send(envelope));
455 delayMicroseconds(19);
456 return CalibrationBase::do_final_offset_correction();
457}
458
459CalibrationFollower::CalibrationFollower(REDAC &redac,
460 unsigned int timeout_ms)
461 : CalibrationBase(redac), timeout_ms(timeout_ms) {}
462
463UnitResult CalibrationFollower::receive_command(pb_Envelope &envelope) {
464 if (TRY(broadcast_input->read(envelope, timeout_ms)))
465 return UnitResult::ok();
466
467 return UnitResult::err("Timout while waiting for incoming calibration command");
468}
469
470UnitResult CalibrationFollower::do_() {
471 LOG_ALWAYS("Init calibrating routes for run as follower...")
472 TRY(do_initial());
473 LOG_ALWAYS("Starting calibrating routes for run as follower...")
474 while (true) {
475 TRY(do_lane_as_told());
476 if (is_done())
477 break;
478 }
479 TRY(send_receive_apply_gain_corrections());
480 TRY(do_final_offset_correction());
481 return UnitResult::ok();
482}
483
484UnitResult CalibrationFollower::do_initial() {
485 pb_Envelope envelope;
486 TRY(receive_command(envelope));
487 return CalibrationBase::do_initial();
488}
489
490UnitResult CalibrationFollower::do_lane_as_told() {
491 pb_Envelope envelope = pb_Envelope_init_default;
492 TRY(receive_command(envelope));
493 auto& msg = envelope.kind.message_v1;
494 if (msg.which_kind != pb_MessageV1_calibrate_lane_command_tag) {
495 return UnitResult::err_fmt(
496 "Expected calibration lane command but message is of different kind: %d",
497 msg.which_kind
498 );
499 }
500 auto& lane_cmd = msg.kind.calibrate_lane_command;
501 uint8_t lane = lane_cmd.lane;
502 if (lane >= 32)
503 return UnitResult::err("Error: Told to calibrate a lane >= 32.");
504 done |= 1 << lane;
505 return CalibrationBase::do_lane(lane);
506}
507
508UnitResult CalibrationFollower::send_receive_apply_gain_corrections() {
509 pb_Envelope envelope;
510 TRY(receive_command(envelope));
511 return CalibrationBase::send_receive_apply_gain_corrections();
512}
513
514UnitResult CalibrationFollower::do_final_offset_correction() {
515 pb_Envelope envelope;
516 TRY(receive_command(envelope));
517 return CalibrationBase::do_final_offset_correction();
518}
519
520} // namespace platform
521
522msg::handlers::HandleResult msg::handlers::RegisterExternalEntitiesRequestHandler:: handle(const pb_Envelope &envelope_in, pb_Envelope &envelope_out) {
523 transport::init_v1_message(envelope_out, pb_MessageV1_success_message_tag);
524 auto& msg_in = envelope_in.kind.message_v1;
525 auto redac = dynamic_cast<platform::REDAC*>(&platform::REDAC::get());
526
527
528 auto& register_external_entities_cmd = msg_in.kind.register_external_entities_command;
529 for (size_t idx = 0; idx < register_external_entities_cmd.entities_count; ++idx) {
530 auto& entity = register_external_entities_cmd.entities[idx];
531 if (!entity.has_value) continue;
532 auto& data = entity.value.data.bytes;
533 IPAddress address(data[0], data[1], data[2], data[3]);
534 if (redac == nullptr) continue;
535 redac->routing.set_path_address(std::string_view(entity.key), address);
536 }
537 return HandleResult::ok();
538}
uint32_t
Definition flasher.cpp:195
pb_MessageV1 & init_v1_message(pb_Envelope &envelope, pb_size_t which_kind)
Definition transport.cpp:32