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