REDAC HybridController
Firmware for LUCIDAC/REDAC Teensy
Loading...
Searching...
No Matches
daq.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 <Arduino.h>
6#include <DMAChannel.h>
7#include <algorithm>
8#include <bitset>
9
10#include <carrier/carrier.h>
11#include <daq/teensy/daq.h>
12#include <utils/logging.h>
13#include <utils/running_avg.h>
14
15namespace daq {
16
17namespace stream {
18
19namespace details {
20
21// We need a memory segment to implement a ring buffer.
22// Its size should be a power-of-2-multiple of NUM_CHANNELS.
23// The DMA implements ring buffer by restricting the N lower bits of the destination address to change.
24// That means the memory segment must start at a memory address with enough lower bits being zero,
25// see manual "data queues [with] power-of-2 size bytes, [...] should start at a 0-modulo-size address".
26// One can put this into DMAMEM for increased performance, but that did not work for me right away.
27// *MUST* be at least 2*NUM_CHANNELS, otherwise some math later does not work.
28__attribute__((aligned(BUFFER_SIZE * 4))) std::array<volatile uint16_t, BUFFER_SIZE> buffer = {0};
29
30DMAChannel channel(false);
31const run::Run *run = nullptr;
32run::RunDataHandler *run_data_handler = nullptr;
33//volatile bool first_data = false;
34//volatile bool last_data = false;
35volatile bool overflow = false;
36// Pointer to the part of the buffer which (may) contain partial data at end of acquisition time
37//auto partial_buffer_part = buffer.data();
38
39volatile uint16_t head = 0;
40volatile uint16_t tail = 0;
41
42// NOT
43FASTRUN void interrupt() {
44 head = channel.TCD->BITER - channel.TCD->CITER;
45 overflow |= head == tail;
46
47 // Clear interrupt
48 channel.clearInterrupt();
49 // Memory barrier
50#ifdef ARDUINO
51 asm("DSB");
52#endif
53}
54
55} // namespace details
56
57} // namespace stream
58
59} // namespace daq
60
61void daq::enable() { details::flexio::get_flexio()->port().CTRL |= FLEXIO_CTRL_FLEXEN; }
62
63void daq::disable() { details::flexio::get_flexio()->port().CTRL &= ~FLEXIO_CTRL_FLEXEN; }
64
65void daq::reset() {
66 disable();
67 details::flexio::get_flexio()->port().CTRL |= FLEXIO_CTRL_SWRST;
68 delayNanoseconds(100);
69 details::flexio::get_flexio()->port().CTRL &= ~FLEXIO_CTRL_SWRST;
70 delayNanoseconds(100);
71}
72
73status daq::details::flexio::_init_once() {
74 // Configure and enable clock
75 get_flexio()->setClockSettings(3, 1, 1);
76 get_flexio()->hardware().clock_gate_register |= get_flexio()->hardware().clock_gate_mask;
77
78 // Put MOSI pins into flexio mode
79 uint8_t _flexio_pin_cnvst = get_flexio()->mapIOPinToFlexPin(PIN_CNVST);
80 uint8_t _flexio_pin_clk = get_flexio()->mapIOPinToFlexPin(PIN_CLK);
81 uint8_t _flexio_pin_gate = get_flexio()->mapIOPinToFlexPin(PIN_GATE);
82 // Check if any of the pins could not be mapped to the same FlexIO module
83 if (_flexio_pin_cnvst == 0xff or _flexio_pin_clk == 0xff or _flexio_pin_gate == 0xff)
84 return status("Could not map pins to FlexIO module.");
85 get_flexio()->setIOPinToFlexMode(PIN_CNVST);
86 get_flexio()->setIOPinToFlexMode(PIN_CLK);
87 get_flexio()->setIOPinToFlexMode(PIN_GATE);
88
89 // Put MISO pins into flexio mode
90 for (auto pin_miso : PINS_MISO) {
91 if (get_flexio()->mapIOPinToFlexPin(pin_miso) == 0xff)
92 return status("Could not map pins to FlexIO module.");
93 get_flexio()->setIOPinToFlexMode(pin_miso);
94 }
95
96 return status::success();
97}
98
99void daq::details::flexio::_init_timers() {
100 get_flexio()->port().TIMCTL[_gated_timer_idx] = 0;
101 get_flexio()->port().TIMCFG[_gated_timer_idx] = 0;
102 get_flexio()->port().TIMCMP[_gated_timer_idx] = 0;
103
104 get_flexio()->port().TIMCTL[_sample_timer_idx] = 0;
105 get_flexio()->port().TIMCFG[_sample_timer_idx] = 0;
106 get_flexio()->port().TIMCMP[_sample_timer_idx] = 0;
107
108 get_flexio()->port().TIMCTL[_cnvst_timer_idx] =
109 FLEXIO_TIMCTL_PINSEL(get_flexio()->mapIOPinToFlexPin(PIN_CNVST)) | FLEXIO_TIMCTL_PINCFG(0b11) |
110 FLEXIO_TIMCTL_TRGSRC | FLEXIO_TIMCTL_TRGSEL(4 * _sample_timer_idx + 3) | FLEXIO_TIMCTL_TIMOD(0b10);
111 get_flexio()->port().TIMCFG[_cnvst_timer_idx] = FLEXIO_TIMCFG_TIMDIS(0b010) | FLEXIO_TIMCFG_TIMENA(0b111);
112 get_flexio()->port().TIMCMP[_cnvst_timer_idx] = 0x0000'10'60;
113
114 // Add a delay timer to control delay between CNVST and first CLK.
115 // Basically a second CNVST timer which is slightly longer and used to trigger first CLK.
116 get_flexio()->port().TIMCTL[_delay_timer_idx] =
117 FLEXIO_TIMCTL_TRGSRC | FLEXIO_TIMCTL_TRGSEL(4 * _sample_timer_idx + 3) | FLEXIO_TIMCTL_TIMOD(0b11);
118 get_flexio()->port().TIMCFG[_delay_timer_idx] = FLEXIO_TIMCFG_TIMDIS(0b010) | FLEXIO_TIMCFG_TIMENA(0b111);
119 get_flexio()->port().TIMCMP[_delay_timer_idx] = 105;
120
121 get_flexio()->port().TIMCTL[_clk_timer_idx] =
122 FLEXIO_TIMCTL_PINSEL(get_flexio()->mapIOPinToFlexPin(PIN_CLK)) | FLEXIO_TIMCTL_PINCFG(0b11) |
123 FLEXIO_TIMCTL_TRGSRC | FLEXIO_TIMCTL_TRGSEL(4 * _delay_timer_idx + 3) | FLEXIO_TIMCTL_TRGPOL |
124 FLEXIO_TIMCTL_TIMOD(0b01);
125 get_flexio()->port().TIMCFG[_clk_timer_idx] = FLEXIO_TIMCFG_TIMDIS(0b010) | FLEXIO_TIMCFG_TIMENA(0b110);
126 get_flexio()->port().TIMCMP[_clk_timer_idx] = 0x0000'1B'03;
127}
128
129void daq::details::flexio::_init_shifters() {
130 for (auto _pin_miso_idx = 0u; _pin_miso_idx < PINS_MISO.size(); _pin_miso_idx++) {
131 // Trigger all shifters from CLK
132 get_flexio()->port().SHIFTCTL[_pin_miso_idx] =
133 FLEXIO_SHIFTCTL_TIMSEL(_clk_timer_idx) | FLEXIO_SHIFTCTL_TIMPOL |
134 FLEXIO_SHIFTCTL_PINSEL(get_flexio()->mapIOPinToFlexPin(PINS_MISO[_pin_miso_idx])) |
135 FLEXIO_SHIFTCTL_SMOD(1);
136 get_flexio()->port().SHIFTCFG[_pin_miso_idx] = 0;
137 }
138}
139
140status daq::init() {
141 RETURN_IF_FAILED(details::flexio::_init_once());
142 details::flexio::_init_timers();
143 details::flexio::_init_shifters();
144 enable();
145
146 pinMode(details::PIN_EN, OUTPUT);
147 digitalWriteFast(details::PIN_EN, HIGH);
148
149 delay(1000);
150
151 return status::success();
152}
153
154void daq::reboot() {
155 digitalWriteFast(details::PIN_EN, LOW);
156 delay(100);
157 digitalWriteFast(details::PIN_EN, HIGH);
158 delay(1000);
159
160 // First thing contains some more or less junk data
161 for (auto _ = 0; _ < 10; _++)
162 sample_raw();
163}
164
165status daq::calibrate(carrier::Carrier &carrier) {
166 LOG(ANABRID_DEBUG_CALIBRATION, __PRETTY_FUNCTION__);
167
168 reboot();
169
170 LOG(ANABRID_DEBUG_CALIBRATION, "Resetting current ADC connections...");
171 // Disconnect all signal connections in ADC switching matrix to force them to a zero
172 auto old_adc_channels = carrier.get_adc_channels();
173 carrier.reset_adc_channels();
174 // Ensure the ADC bus is actually the one connected to the ADCs (and not the SH gain measurement)
175 auto old_adc_bus = carrier.ctrl_block->get_adc_bus();
176 carrier.ctrl_block->reset_adc_bus();
177 // Write to hardware
178 if (!carrier.write_to_hardware())
179 return status("Error writing disconnect of ADCs to hardware.");
180
181 // Read zero offsets and save them
182 LOG(ANABRID_DEBUG_CALIBRATION, "Reading ADC zero offsets:");
183 auto zeros_raw = daq::average(daq::sample_raw);
184 std::transform(zeros_raw.begin(), zeros_raw.end(), details::raw_zero_offsets.begin(),
185 [](auto zero_raw) { return details::RAW_ZERO_IDEAL - zero_raw; });
186 for (auto raw_zero_offset : details::raw_zero_offsets)
187 LOG(ANABRID_DEBUG_CALIBRATION, raw_zero_offset);
188
189 // Reset previous changes
190 LOG(ANABRID_DEBUG_CALIBRATION, "Restoring previous ADC connections...");
191 static_cast<void>(carrier.set_adc_channels(old_adc_channels));
192 carrier.ctrl_block->set_adc_bus(old_adc_bus);
193 // Write to hardware
194 if (!carrier.write_to_hardware())
195 return status("Error writing previous connections of ADCs to hardware.");
196
197 // Sanity check, otherwise return successful
198 if (std::any_of(details::raw_zero_offsets.begin(), details::raw_zero_offsets.end(),
199 [](auto value) { return abs(value) > 100; }))
200 return status("Zero offsets of ADCs are suspiciously large.");
201
202 LOG(ANABRID_DEBUG_CALIBRATION, __PRETTY_FUNCTION__);
203
204 return status::success();
205}
206
207std::array<uint16_t, daq::NUM_CHANNELS> daq::sample_raw() {
208 using namespace details::flexio;
209
210 // Clear SHIFTSTAT (by writing 0xFF (sic!)), which might be partially set by a previous acquisition
211 get_flexio()->port().SHIFTSTAT = 0xFF;
212
213 // Disable and reset _sample_timer_idx
214 get_flexio()->port().TIMCTL[_sample_timer_idx] = 0;
215 get_flexio()->port().TIMCFG[_sample_timer_idx] = 0;
216 delayNanoseconds(42);
217 // Generate just one sample pulse
218 get_flexio()->port().TIMCMP[_sample_timer_idx] = 1;
219 get_flexio()->port().TIMCTL[_sample_timer_idx] = FLEXIO_TIMCTL_TIMOD(0b11);
220 get_flexio()->port().TIMCFG[_sample_timer_idx] = FLEXIO_TIMCFG_TIMDIS(0b010);
221
222 // Wait until shift buffer is filled or timeout in case of hardware errors
223 for (auto _ = 0u; _ < 50; _++) {
224 if (get_flexio()->port().SHIFTSTAT & 0xFF)
225 return {static_cast<uint16_t>(get_flexio()->port().SHIFTBUFBIS[0]),
226 static_cast<uint16_t>(get_flexio()->port().SHIFTBUFBIS[1]),
227 static_cast<uint16_t>(get_flexio()->port().SHIFTBUFBIS[2]),
228 static_cast<uint16_t>(get_flexio()->port().SHIFTBUFBIS[3]),
229 static_cast<uint16_t>(get_flexio()->port().SHIFTBUFBIS[4]),
230 static_cast<uint16_t>(get_flexio()->port().SHIFTBUFBIS[5]),
231 static_cast<uint16_t>(get_flexio()->port().SHIFTBUFBIS[6]),
232 static_cast<uint16_t>(get_flexio()->port().SHIFTBUFBIS[7])};
233 delayNanoseconds(100);
234 }
235 // Returning zero in error case may hide the error
236 // Of course, returning RAW_SUSPICIOUS_VALUE is only slightly better :)
237 return {details::RAW_SUSPICIOUS_VALUE, details::RAW_SUSPICIOUS_VALUE, details::RAW_SUSPICIOUS_VALUE,
238 details::RAW_SUSPICIOUS_VALUE, details::RAW_SUSPICIOUS_VALUE, details::RAW_SUSPICIOUS_VALUE,
239 details::RAW_SUSPICIOUS_VALUE, details::RAW_SUSPICIOUS_VALUE};
240}
241
242std::array<float, daq::NUM_CHANNELS> daq::sample() { return raw_to_float(sample_raw()); }
243
244std::array<float, daq::NUM_CHANNELS> daq::average(size_t samples, unsigned int delay_us) {
245 return average(daq::sample, samples, delay_us);
246}
247
248daq::stream::Scope daq::stream::get(const run::Run &run, run::RunDataHandler *const data_handler,
249 bool start) {
250 return {run, data_handler, start};
251}
252
253unsigned int daq::stream::details::remaining_data_count() {
254 uint16_t citer = channel.TCD->CITER;
255 uint16_t biter = channel.TCD->BITER;
256
257 uint16_t halt_biter = biter >> 1;
258 uint16_t count = biter - citer;
259 if (count > halt_biter) {
260 count -= halt_biter;
261 }
262 return count;
263}
264
265std::array<volatile uint16_t, daq::stream::details::BUFFER_SIZE> daq::stream::details::get_buffer() {
266 return buffer;
267}
268
269// NOT
270FASTRUN run::RunHandleResult daq::stream::process(const run::Run &run, run::RunDataHandler *const data_handler, bool partial) {
271 if (!run.daq_config)
272 return run::RunHandleResult::err("Invalid DAQ configuration.");
273 if (details::overflow)
274 return run::RunHandleResult::err("DMA overflow.");
275
276 auto alignment = run.daq_config.get_num_channels_min_power_of_two();
277
278 if (details::head != details::tail){
279 size_t sample_count = details::HALF_BUFFER_SIZE / alignment;
280 auto data_ptr = const_cast<uint16_t*>(details::buffer.data() + alignment * details::tail);
281 auto result = data_handler->handle(run, data_ptr, sample_count);
282 details::tail = details::head;
283 if (result.is_err())
284 return result;
285 }
286
287 if (partial) {
288 size_t sample_count = details::remaining_data_count();
289 auto data_ptr = const_cast<uint16_t*>(details::buffer.data() + alignment * details::tail);
290 auto result = data_handler->handle(run, data_ptr, sample_count);
291 if (result.is_err())
292 return result;
293 }
294
295 return run::RunHandleResult::ok({});
296}
297
298FASTRUN status daq::stream::stop(const run::Run &run) {
299 details::channel.disable();
300
301 // TODO: In case of streams not synchronized with OP state,
302 // this function should probably actually stop the timers.
303
304 auto flexio = FlexIOHandler::flexIOHandler_list[1];
305 // Disable DMA trigger generation
306 flexio->port().SHIFTSDEN = 0;
307 // Clear global data for ::daq::dma::interrupt
308 details::run_data_handler = nullptr;
309 details::run = nullptr;
310 details::head = details::tail = 0;
311 details::overflow = false;
312
313 // Check shifter errors
314 // Unused channels (when get_num_channels() < 8) always go into SHIFTERR,
315 // since they are written to due to always being active, but not read from by the DMA.
316 uint32_t _shifterr_mask = (run.daq_config.get_num_channels_min_power_of_two() > 1)
317 ? ((1u << run.daq_config.get_num_channels_min_power_of_two()) - 1)
318 : 1u;
319 if (flexio->port().SHIFTERR & _shifterr_mask)
320 return status("SHIFTERR was asserted during stream (%d).", flexio->port().SHIFTERR);
321 // Clear all SHIFTERR
322 flexio->port().SHIFTERR = 0xFF;
323
324 return status::success();
325}
326
327FASTRUN status daq::stream::start(const run::Run &run, run::RunDataHandler *const data_handler) {
328 using namespace daq::details::flexio;
329 // Care: FlexIO clock is enabled in _init_once() and this function will get stuck without it
330
331 disable();
332 // Reset is necessary before each continuous acquisition,
333 // otherwise internal counter values are not reset and result in unwanted behaviour
334 reset();
335 _init_timers();
336 _init_shifters();
337
338 auto& port = get_flexio()->port();
339
340 auto _flexio_pin_gate = get_flexio()->mapIOPinToFlexPin(daq::details::PIN_GATE);
341 port.TIMCTL[_gated_timer_idx] = FLEXIO_TIMCTL_TRGSEL(2 * _flexio_pin_gate) |
342 FLEXIO_TIMCTL_TRGPOL | FLEXIO_TIMCTL_TRGSRC |
343 FLEXIO_TIMCTL_TIMOD(0b11);
344 port.TIMCFG[_gated_timer_idx] = FLEXIO_TIMCFG_TIMDIS(0b011) | FLEXIO_TIMCFG_TIMENA(0b110);
345 port.TIMCMP[_gated_timer_idx] = 59;
346
347 port.TIMCTL[_sample_timer_idx] =
348 FLEXIO_TIMCTL_TRGSEL(4 * _gated_timer_idx + 3) | FLEXIO_TIMCTL_TRGSRC | FLEXIO_TIMCTL_TIMOD(0b11);
349 port.TIMCFG[_sample_timer_idx] = FLEXIO_TIMCFG_TIMDEC(0b01) | FLEXIO_TIMCFG_TIMENA(0b110);
350 port.TIMCMP[_sample_timer_idx] = (1'000'000 / run.daq_config.get_sample_rate()) * 2 - 1;
351
352 enable();
353
354 // Update global pointers for ::daq::dma::interrupt
355 details::run_data_handler = data_handler;
356 details::run = &run;
357 details::head = details::tail = 0;
358 details::overflow = false;
359
360 /*
361 * Configure DMA
362 */
363
364 // Select shifter zero to generate DMA events.
365 // Which shifter is selected should not matter, as long as it is used.
366 uint8_t shifter_dma_idx = 0;
367 // Set shifter to generate DMA events.
368 port.SHIFTSDEN = 1 << shifter_dma_idx;
369 // Configure DMA channel
370 details::channel.begin();
371
372 // Configure minor and major loop of DMA process.
373 // One DMA request (SHIFTBUF store) triggers one major loop.
374 // A major loop consists of several minor loops.
375 // For each loop, source address and destination address is adjusted and data is copied.
376 // The number of minor loops is implicitly defined by how much data they should copy.
377 // The approach here is to use the minor loops to copy all shift registers when one triggers the DMA.
378 // That means each major loop copies over NUM_CHANNELS data points.
379 // Triggering multiple major loops fills up the ring buffer.
380 // Once all major loops are done, the ring buffer is full and an interrupt is triggered to handle the data.
381 // For such configurations DMAChannel provides sourceCircular() and destinationCircular() functions,
382 // which are not quite able to handle our case (BITER/CITER and ATTR_DST must be different).
383 // That's why we do it by hand :)
384
385
386 // BITER "beginning iteration count" is the number of major loops.
387 // Each major loop fills part (see TCD->NBYTES) of the ring buffer.
388 details::channel.TCD->BITER = details::buffer.size() / run.daq_config.get_num_channels_min_power_of_two();
389 // CITER "current major loop iteration count" is the current number of major loops left to perform.
390 // It can be used to check progress of the process. It's reset to BITER whe we filled the buffer once.
391 details::channel.TCD->CITER = details::channel.TCD->BITER;
392
393 // Configure source address and its adjustments.
394 // We want to circularly copy from SHIFTBUFBIS.
395 // In principle, we are only interested in the last 16 bits of each SHIFTBUFBIS.
396 // Unfortunately, configuring it in such a way was not successful -- maybe in the future.
397 // Instead, we copy the full 32 bit of data.
398 // SADDR "source address start" is where the DMA process starts.
399 // Set it to the beginning of the SHIFTBUFBIS array.
400 details::channel.TCD->SADDR = port.SHIFTBUFBIS;
401 // NBYTES "minor byte transfer count" is the number of bytes transferred in one minor loop.
402 // Set it such that the whole SHIFTBUFBIS array is copied, which is 2 bytes * NUM_CHANNELS
403 details::channel.TCD->NBYTES = 2 * run.daq_config.get_num_channels_min_power_of_two();
404 // SOFF "source address offset" is the offset added onto SADDR for each minor loop.
405 // Set it to 4 bytes, equaling the 32 bits each shift register has.
406 details::channel.TCD->SOFF = 4;
407 // ATTR_SRC "source address attribute" is an attribute setting the circularity and the transfer size.
408 // The format is [5bit MOD][3bit SIZE].
409 // The 5bit MOD is the number of lower address bites allowed to change, effectively circling back to SADDR.
410 // The 3bit SIZE defines the source data transfer size.
411 // Set MOD to 5, allowing the address bits to change until the end of the SHIFTBUFBIS array and cycling.
412 // MOD = 5 because 2^5 = 32, meaning address cycles after 32 bytes, which are 8*32 bits.
413 // Set SIZE to 0b010 for 32 bit transfer size.
414 uint8_t MOD_SRC = __builtin_ctz(run.daq_config.get_num_channels_min_power_of_two() * 4);
415 details::channel.TCD->ATTR_SRC = (MOD_SRC << 3) | 0b001;
416 // SLAST "last source address adjustment" is an adjustment applied to SADDR after all major iterations.
417 // We don't want any adjustments, since we already use ATTR_SRC to implement a circular buffer.
418 details::channel.TCD->SLAST = 0;
419
420 // Configure destination address and its adjustments.
421 // We want to circularly copy into a memory ring buffer.
422 // Since we always copy 32 bit from SADDR, we will have the memory buffer in a 32 bit layout as well,
423 // even though we are again only interested in the lower 16 bits.
424 // DADDR "destination address start" is the start of the destination ring buffer.
425 // Set to address of ring buffer.
426 details::channel.TCD->DADDR = details::buffer.data();
427 // DOOFF "destination address offset" is the offset added to DADDR for each minor loop.
428 // Set to 2 bytes, equaling the 16 bits each shift register has.
429 details::channel.TCD->DOFF = 2;
430 // ATTR_SRC "destination address attribute" is analogous to ATTR_SRC
431 // Set first 5 bit MOD according to size of ring buffer.
432 // Since only power-of-two number of channels N<=8 is allowed, N*NUM_CHANNELS will always fit for each N.
433 // Set last 3 bits to 0b001 for 16 bit transfer size.
434 uint8_t MOD = __builtin_ctz(details::BUFFER_SIZE * 2);
435 details::channel.TCD->ATTR_DST = (MOD << 3) | 0b001;
436 // DLASTSGA "last destination address adjustment or next TCD" is similar to SLAST.
437 // We don't want any adjustments, since we already use ATTR_DST to implement a circular buffer.
438 details::channel.TCD->SLAST = 0;
439
440 // Call an interrupt when done
441 details::channel.attachInterrupt(details::interrupt);
442 details::channel.interruptAtCompletion();
443 details::channel.interruptAtHalf();
444 // Trigger from "shifter full" DMA event
445 details::channel.triggerAtHardwareEvent(get_flexio()->shiftersDMAChannel(shifter_dma_idx));
446 // Enable dma channel
447 details::channel.enable();
448 if (details::channel.error()) {
449 return ("dma::channel.error");
450 }
451
452 return status::success();
453}
utils::status status
Definition daq.h:21
uint32_t
Definition flasher.cpp:195
__attribute__((section(".fastrun"), noinline, noclone, optimize("Os"))) int flash_sector_not_erased(uint32_t address)
Definition flasher.cpp:114
Definition daq.h:14
Routines for data acquisition (DAQ) using the internal analog-to-digital converters (ADC).
Definition daq.cpp:15
void reboot()
Just a little generic Teensy reboot routine.
Definition reboot.cpp:7