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