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 return {run, data_handler};
234}
235
236FLASHMEM unsigned int daq::stream::details::get_number_of_data_vectors_in_buffer() {
237 // Note that this does not consider whether these have been streamed out yet
238 return channel.TCD->BITER - channel.TCD->CITER;
239}
240
241FLASHMEM std::array<volatile uint32_t, daq::stream::details::BUFFER_SIZE> daq::stream::details::get_buffer() {
242 return buffer;
243}
244
245// NOT FLASHMEM
246status daq::stream::process(const run::Run &run, run::RunDataHandler *const data_handler, bool partial) {
247 // Pointer to the part of the buffer which (may) contain partial data at end of acquisition time
248 static auto partial_buffer_part = details::buffer.data();
249
250 if (!run.daq_config)
251 return status("Invalid DAQ configuration.");
252 if (details::overflow_data)
253 return status("DMA overflow.");
254
255 // Default values for streaming
256 volatile uint32_t *active_buffer_part;
257 size_t outer_count = details::BUFFER_SIZE / run.daq_config.get_num_channels() / 2;
258
259 // Change streaming parameters depending on whether the first or second half of buffer is streamed
260 if (details::first_data) {
261 active_buffer_part = details::buffer.data();
262 partial_buffer_part = details::buffer.data() + details::BUFFER_SIZE / 2;
263 details::first_data = false;
264 } else if (details::last_data) {
265 active_buffer_part = details::buffer.data() + details::BUFFER_SIZE / 2;
266 partial_buffer_part = details::buffer.data();
267 details::last_data = false;
268 } else if (partial) {
269 // Stream the remaining partially filled part of the buffer.
270 // This should be done exactly once, after the data acquisition stopped.
271 active_buffer_part = partial_buffer_part;
272 if (partial_buffer_part == details::buffer.data())
273 // If we have streamed out the second part the last time,
274 // the partial data is in the first part of the buffer
275 // and get_number_of_data_vectors_in_buffer returns only
276 // the number of partial data vectors to stream.
277 outer_count = details::get_number_of_data_vectors_in_buffer();
278 else
279 // If we have streamed out the first part the last time,
280 // the partial data is in the second half,
281 // and get_number_of_data_vectors_in_buffer does not consider this
282 // and returns number of vectors in first_half plus number of partials
283 // in second half, and we have to subtract the number in the first half.
284 outer_count = details::get_number_of_data_vectors_in_buffer() - outer_count;
285 // In case of multi-cycle OP computations (e.g. quantum circuits), we want to
286 // do a partial stream after each OP cycle. When acquisition continues in the next cycle,
287 // it is easiest to fill the buffer from the beginning again and reset all relevant counters.
288 details::channel.TCD->CITER = details::channel.TCD->BITER;
289 details::channel.TCD->DADDR = details::buffer.data();
290 details::first_data = details::last_data = details::overflow_data = false;
291 if (!outer_count) {
292 return status::success();
293 }
294 } else
295 return status::success();
296
297 data_handler->handle(active_buffer_part, outer_count, run.daq_config.get_num_channels(), run);
298 return status::success();
299}
300
301FLASHMEM status daq::stream::stop(const run::Run &run) {
302 details::channel.disable();
303
304 // TODO: In case of streams not synchronized with OP state,
305 // this function should probably actually stop the timers.
306
307 auto flexio = FlexIOHandler::flexIOHandler_list[1];
308 // Disable DMA trigger generation
309 flexio->port().SHIFTSDEN = 0;
310 // Clear global data for ::daq::dma::interrupt
311 details::run_data_handler = nullptr;
312 details::run = nullptr;
313 details::first_data = details::last_data = details::overflow_data = false;
314
315 // Check shifter errors
316 // Unused channels (when get_num_channels() < 8) always go into SHIFTERR,
317 // since they are written to due to always being active, but not read from by the DMA.
318 uint32_t _shifterr_mask =
319 (run.daq_config.get_num_channels() > 1) ? ((1u << run.daq_config.get_num_channels()) - 1) : 1u;
320 if (flexio->port().SHIFTERR & _shifterr_mask)
321 return status("SHIFTERR was asserted during stream.");
322 // Clear all SHIFTERR
323 flexio->port().SHIFTERR = 0xFF;
324
325 return status::success();
326}
327
328FLASHMEM status daq::stream::start(const run::Run &run, run::RunDataHandler *const data_handler) {
329 using namespace daq::details::flexio;
330 // Care: FlexIO clock is enabled in _init_once() and this function will get stuck without it
331
332 disable();
333 // Reset is necessary before each continuous acquisition,
334 // otherwise internal counter values are not reset and result in unwanted behaviour
335 reset();
336 _init_timers();
337 _init_shifters();
338
339 auto _flexio_pin_gate = get_flexio()->mapIOPinToFlexPin(daq::details::PIN_GATE);
340 get_flexio()->port().TIMCTL[_gated_timer_idx] = FLEXIO_TIMCTL_TRGSEL(2 * _flexio_pin_gate) |
341 FLEXIO_TIMCTL_TRGPOL | FLEXIO_TIMCTL_TRGSRC |
342 FLEXIO_TIMCTL_TIMOD(0b11);
343 get_flexio()->port().TIMCFG[_gated_timer_idx] = FLEXIO_TIMCFG_TIMDIS(0b011) | FLEXIO_TIMCFG_TIMENA(0b110);
344 get_flexio()->port().TIMCMP[_gated_timer_idx] = 239;
345
346 get_flexio()->port().TIMCTL[_sample_timer_idx] =
347 FLEXIO_TIMCTL_TRGSEL(4 * _gated_timer_idx + 3) | FLEXIO_TIMCTL_TRGSRC | FLEXIO_TIMCTL_TIMOD(0b11);
348 get_flexio()->port().TIMCFG[_sample_timer_idx] = FLEXIO_TIMCFG_TIMDEC(0b01) | FLEXIO_TIMCFG_TIMENA(0b110);
349 get_flexio()->port().TIMCMP[_sample_timer_idx] = (1'000'000 / run.daq_config.get_sample_rate()) * 2 - 1;
350
351 enable();
352
353 // Update global pointers for ::daq::dma::interrupt
354 details::run_data_handler = data_handler;
355 details::run = &run;
356
357 /*
358 * Configure DMA
359 */
360
361 // Select shifter zero to generate DMA events.
362 // Which shifter is selected should not matter, as long as it is used.
363 uint8_t shifter_dma_idx = 0;
364 // Set shifter to generate DMA events.
365 get_flexio()->port().SHIFTSDEN = 1 << shifter_dma_idx;
366 // Configure DMA channel
367 details::channel.begin();
368
369 // Configure minor and major loop of DMA process.
370 // One DMA request (SHIFTBUF store) triggers one major loop.
371 // A major loop consists of several minor loops.
372 // For each loop, source address and destination address is adjusted and data is copied.
373 // The number of minor loops is implicitly defined by how much data they should copy.
374 // The approach here is to use the minor loops to copy all shift registers when one triggers the DMA.
375 // That means each major loop copies over NUM_CHANNELS data points.
376 // Triggering multiple major loops fills up the ring buffer.
377 // Once all major loops are done, the ring buffer is full and an interrupt is triggered to handle the data.
378 // For such configurations DMAChannel provides sourceCircular() and destinationCircular() functions,
379 // which are not quite able to handle our case (BITER/CITER and ATTR_DST must be different).
380 // That's why we do it by hand :)
381
382 // BITER "beginning iteration count" is the number of major loops.
383 // Each major loop fills part (see TCD->NBYTES) of the ring buffer.
384 details::channel.TCD->BITER = details::buffer.size() / run.daq_config.get_num_channels();
385 // CITER "current major loop iteration count" is the current number of major loops left to perform.
386 // It can be used to check progress of the process. It's reset to BITER whe we filled the buffer once.
387 details::channel.TCD->CITER = details::buffer.size() / run.daq_config.get_num_channels();
388
389 // Configure source address and its adjustments.
390 // We want to circularly copy from SHIFTBUFBIS.
391 // In principle, we are only interested in the last 16 bits of each SHIFTBUFBIS.
392 // Unfortunately, configuring it in such a way was not successful -- maybe in the future.
393 // Instead, we copy the full 32 bit of data.
394 // SADDR "source address start" is where the DMA process starts.
395 // Set it to the beginning of the SHIFTBUFBIS array.
396 details::channel.TCD->SADDR = get_flexio()->port().SHIFTBUFBIS;
397 // NBYTES "minor byte transfer count" is the number of bytes transferred in one minor loop.
398 // Set it such that the whole SHIFTBUFBIS array is copied, which is 4 bytes * NUM_CHANNELS
399 details::channel.TCD->NBYTES = 4 * run.daq_config.get_num_channels();
400 // SOFF "source address offset" is the offset added onto SADDR for each minor loop.
401 // Set it to 4 bytes, equaling the 32 bits each shift register has.
402 details::channel.TCD->SOFF = 4;
403 // ATTR_SRC "source address attribute" is an attribute setting the circularity and the transfer size.
404 // The format is [5bit MOD][3bit SIZE].
405 // The 5bit MOD is the number of lower address bites allowed to change, effectively circling back to SADDR.
406 // The 3bit SIZE defines the source data transfer size.
407 // Set MOD to 5, allowing the address bits to change until the end of the SHIFTBUFBIS array and cycling.
408 // MOD = 5 because 2^5 = 32, meaning address cycles after 32 bytes, which are 8*32 bits.
409 // Set SIZE to 0b010 for 32 bit transfer size.
410 uint8_t MOD_SRC = __builtin_ctz(run.daq_config.get_num_channels() * 4);
411 details::channel.TCD->ATTR_SRC = ((MOD_SRC & 0b11111) << 3) | 0b010;
412 // SLAST "last source address adjustment" is an adjustment applied to SADDR after all major iterations.
413 // We don't want any adjustments, since we already use ATTR_SRC to implement a circular buffer.
414 details::channel.TCD->SLAST = 0;
415
416 // Configure destination address and its adjustments.
417 // We want to circularly copy into a memory ring buffer.
418 // Since we always copy 32 bit from SADDR, we will have the memory buffer in a 32 bit layout as well,
419 // even though we are again only interested in the lower 16 bits.
420 // DADDR "destination address start" is the start of the destination ring buffer.
421 // Set to address of ring buffer.
422 details::channel.TCD->DADDR = details::buffer.data();
423 // DOOFF "destination address offset" is the offset added to DADDR for each minor loop.
424 // Set to 4 bytes, equaling the 32 bits each shift register has.
425 details::channel.TCD->DOFF = 4;
426 // ATTR_SRC "destination address attribute" is analogous to ATTR_SRC
427 // Set first 5 bit MOD according to size of ring buffer.
428 // Since only power-of-two number of channels N<=8 is allowed, N*NUM_CHANNELS will always fit for each N.
429 // Set last 3 bits to 0b010 for 32 bit transfer size.
430 uint8_t MOD = __builtin_ctz(details::BUFFER_SIZE * 4);
431 // Check if memory buffer address is aligned such that MOD lower bits are zero (maybe this is too pedantic?)
432 if (reinterpret_cast<uintptr_t>(details::buffer.data()) & ~(~static_cast<uintptr_t>(0) << MOD)) {
433 return ("DMA buffer memory range is not sufficiently aligned.");
434 }
435 details::channel.TCD->ATTR_DST = ((MOD & 0b11111) << 3) | 0b010;
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
Definition daq.h:14
Routines for data acquisition (DAQ) using the internal analog-to-digital converters (ADC).
Definition daq.cpp:15
uint32_t
Definition flasher.cpp:195