Managed to get a more decent analog channels sampler working for Teensy4.
It allows continuous streaming, dual channel synchronous sampling, at rates up to 1 Ms/s, 12 bits, into buffers as big as what fits in DMAMEM.
Either 1, 2, 4 or 8 analog input channels, sampled at max rates of 1 Ms/s, 1 Ms/s, 500 ks/s or 250 ks/s.
With nanoseconds sharp deterministic sampling - because hardware triggered.
Could not get it working for >8 analog input channels. And also have a weird issue when DMOD is 16 - that DMOD value in DMA channel's
TCD->ATTR somehow does not work as expected. Other than that it seems to work.
Maybe it's an idea to convert this one-ino-example into a library file? (I lack the skills for that).
It allows continuous streaming, dual channel synchronous sampling, at rates up to 1 Ms/s, 12 bits, into buffers as big as what fits in DMAMEM.
Either 1, 2, 4 or 8 analog input channels, sampled at max rates of 1 Ms/s, 1 Ms/s, 500 ks/s or 250 ks/s.
With nanoseconds sharp deterministic sampling - because hardware triggered.
Could not get it working for >8 analog input channels. And also have a weird issue when DMOD is 16 - that DMOD value in DMA channel's
TCD->ATTR somehow does not work as expected. Other than that it seems to work.
Maybe it's an idea to convert this one-ino-example into a library file? (I lack the skills for that).
Code:
/************************************************************************************************************* */
// Teensy4 analog inputs with i.MX_RT1062 ADC1, ADC2, ADC_ETC and DMA
// deterministic, isochronous, synchronous sampling
// read analog inputs, two channels synchronously, sequencing 1,2,4 or 8 analog input channels per ADC
// hardware timer (QUAD_TIMER4) triggered
// using DMA channels, large buffers in DMAMEM, continuously cycling or one-shot
//
#include "ADC.h"
#include "DMAChannel.h"
// uncomment or comment out these useADCx #defines as needed
#define useADC1
#define useADC2
// CHANNELS_PER_ADC is either 1, 2, or 4. It specifies how many analog input pins successively connect to the ADC multiplexer that's sequentially controlled by the ADC_ETC
#define CHANNELS_PER_ADC 4
// N_SAMPLES_PER_INPUT - specifies how many samples (from each analog input pin) to collect in one iteration - must be a power of 2 number, therfore specified here as log2
#define L2_N_SAMPLES_PER_INPUT 4
#define N_SAMPLES_PER_INPUT (1<<L2_N_SAMPLES_PER_INPUT)
#define DMA_BUFFER_SIZE (CHANNELS_PER_ADC * N_SAMPLES_PER_INPUT)
#define DMA_BUFFER_SIZE_inBytes (DMA_BUFFER_SIZE * 2)
#define OneIterationBytesCount (2*256*CHANNELS_PER_ADC)
// adc_averaging - specifies how many ADC samples in a row are averaged before declaring it one new sample - either 1, 4, 8, 16 or 32 (reduced noise, but slows down the max effective samplerate)
#define adc_averaging 1
// adc_nbits - ADC resolution, either 8, 10 or 12
#define adc_nbits 12
// Circular_super_major_loop_DMA_buffer - 0 for one shot, 1 for continuous rolling back mode (filling large circular DMA buffers all the time)
volatile bool Circular_super_major_loop_DMA_buffer = 1;
volatile double max_f_sample = 0;
volatile double min_T_sample = 0;
volatile double f_sample = 0.0; // in Hz, will be populated after initialising QUADTIMER TMR4
volatile double T_sample = 0.0; // in seconds, will be populated after initalising QUADTIMER TMR4
static DMAChannel etc_adc1_dmachannel = DMAChannel();
static DMAChannel etc_adc2_dmachannel = DMAChannel();
ADC *adc = new ADC();
#ifdef useADC1
DMAMEM __attribute__((aligned(DMA_BUFFER_SIZE_inBytes))) static uint16_t dma_buffer_adc1[DMA_BUFFER_SIZE];
#endif
#ifdef useADC2
DMAMEM __attribute__((aligned(DMA_BUFFER_SIZE_inBytes))) static uint16_t dma_buffer_adc2[DMA_BUFFER_SIZE];
#endif
volatile uint32_t N_super_major_iterations = 1;
volatile uint32_t super_major_iterations_counter = 0;
// Flags to indicate new data availability
volatile bool flag_entire_DMA_buffer_is_filled_with_new_data = 0;
volatile bool flag_one_major_DMA_buffer_is_filled_with_new_data = 0;
volatile int32_t index_last_filled_super_major_iteration_buffer = -1;
volatile uint32_t prev_timestamp_all_super_major_iterations_captured = 0;
volatile uint32_t timestamp_all_super_major_iterations_captured = 0;
volatile uint32_t delta_timestamp_all_super_major_iterations_captured = 0;
static DMAChannel* prevDMA_TriggerChannel = nullptr;
static DMAChannel* primary_adc_dmachannel = nullptr;
extern "C" {
extern void xbar_connect(unsigned int input, unsigned int output);
extern const uint8_t pin_to_channel[];
}
// trigger for ADC conversions (the sampling rate for all analog input channels) comes from QUADTIMER4
void start_timer4 ()
{
/*
uncomment these 5 lines for setting trigger ferquency as 1 MHz / M, with M as 16 uint_16
double target_frequency = 250000.0; // 250 kHz
uint32_t clk_div_factor = F_BUS_ACTUAL / (target_frequency * 2);
uint16_t N = 75;
uint16_t M = clk_div_factor / N;
start_timer4 (N, M, 0); // this gives target_frequency as trigger rate - but only works for M=4..65535, so only for 250 kHz down to 15.3 Hz, and not very accurately
return;
*/
// uncomment only one of the many options below to select a trigger rate
// start_timer4 (1500, 25000, 1); // this gives 1 Hz
// start_timer4 (1500, 2500, 1); // this gives 10 Hz
// start_timer4 (1500, 250, 1); // this gives 100 Hz
// start_timer4 (1500, 50, 0); // this gives 1 kHz
// start_timer4 (1500, 5, 0); // this gives 10 kHz
// start_timer4 (750, 5, 0); // this gives 20 kHz
// start_timer4 (375, 5, 0); // this gives 40 kHz
// start_timer4 (243, 7, 0); // this gives 44.1 kHz - ish (44091.711 Hz it will be)
// start_timer4 (313, 5, 0); // this gives 48 kHz - ish (47923.323 Hz it will be)
// start_timer4 (87, 9, 0); // this gives 96 kHz - ish (95785.441 Hz it will be)
// start_timer4 (150, 5, 0); // this gives 100 kHz
start_timer4 (75, 5, 0); // this gives 200 kHz
// start_timer4 (15, 10, 0); // this gives 500 kHz
// start_timer4 (15, 5, 0); // this gives 1 MHz
}
void start_timer4(uint16_t N, uint16_t M, uint8_t L2prescale)
{
// Configure the TMR4 Quad Timer directly, using two 16 bits timers, cascaded, to make a trigger pulse for the ADC_ETC.
// repetition rate will be F_BUS_ACTUAL/(N*M*2^L2prescale). Example: with N=150, M=100, L2prescale=0, the rate will be 150E6/(150*100*1) = 10.000 kHz.
// but that's divided by 2 because we toggle the OFLAG after having counted N*M timer ticks
TMR4_ENBL &= ~(1 << 0); // Disable Timer
TMR4_SCTRL1 = TMR_SCTRL_OEN | TMR_SCTRL_FORCE;
TMR4_CSCTRL1 = TMR_CSCTRL_CL1(1) | TMR_CSCTRL_TCF1EN;
TMR4_CNTR0 = 0;
TMR4_LOAD0 = 0;
TMR4_CNTR1 = 0;
TMR4_LOAD1 = 0;
TMR4_COMP10 = N - 1;
TMR4_CMPLD10 = TMR4_COMP10; // Set compare value
TMR4_COMP11 = M - 1;
TMR4_CMPLD11 = TMR4_COMP11; // Set compare value
int pcs = 0x8 + L2prescale;
int prescale = 1 << L2prescale;
TMR4_CTRL0 = TMR_CTRL_CM(1) | TMR_CTRL_PCS(pcs) | TMR_CTRL_LENGTH; // TMR4.0 configuration: ticks on "IP bus clock divide by prescaler"
TMR4_CTRL1 = TMR_CTRL_CM(7) | TMR_CTRL_PCS(4+0) | TMR_CTRL_LENGTH | TMR_CTRL_OUTMODE(3); // TMR4.1: ticks on TMR4.0 output
TMR4_ENBL |= (1 << 0); // Enable Timer
f_sample = (double)F_BUS_ACTUAL/(N*M*prescale) / 2;
T_sample = 1.0 / f_sample;
// Debug output to verify timer setup
Serial.printf("F_BUS=%ld Hz\n", F_BUS_ACTUAL);
Serial.printf("Timer4 Prescaler is 2^%d=%d\n", L2prescale, prescale);
Serial.printf("Timer4 started with calculated period: %lu ticks (%d x %d)\n", N*M, N, M);
Serial.printf("The reload rate thus is %1.3f Hz\n", (double)F_BUS_ACTUAL/(N*M*prescale));
Serial.printf("The ADC_ETC trigger rate (f_sample) for all analog input pins thus is %1.3f Hz, which is once per %1.3f us\n", f_sample, T_sample*1E6);
Serial.flush();
}
/*
void timer4_isr ()
{
TMR4_CSCTRL1 &= ~TMR_CSCTRL_TCF1;
asm("dsb");
// Serial.print("T");
}
*/
void dma_etc_adc_isr()
{
// Serial.print("d");
uint32_t timestamp_super_major_iteration_done = micros();
flag_one_major_DMA_buffer_is_filled_with_new_data = 1;
index_last_filled_super_major_iteration_buffer = super_major_iterations_counter;
super_major_iterations_counter++;
if (super_major_iterations_counter == (N_super_major_iterations-1))
if (!Circular_super_major_loop_DMA_buffer)
if (primary_adc_dmachannel != nullptr)
primary_adc_dmachannel->disableOnCompletion();
if (super_major_iterations_counter >= N_super_major_iterations)
{
timestamp_all_super_major_iterations_captured = timestamp_super_major_iteration_done;
delta_timestamp_all_super_major_iterations_captured = timestamp_all_super_major_iterations_captured - prev_timestamp_all_super_major_iterations_captured;
prev_timestamp_all_super_major_iterations_captured = timestamp_all_super_major_iterations_captured;
#ifdef useADC1
#ifdef useADC2
if (!Circular_super_major_loop_DMA_buffer)
etc_adc2_dmachannel.TCD->CSR |= 1; // extra final manual start for last DMA iteration on second (linked) DMA channel
#endif
#endif
super_major_iterations_counter = 0;
flag_entire_DMA_buffer_is_filled_with_new_data = 1;
}
DMA_CINT = 64; // CAIR
asm("dsb");
}
void adc_init()
{
#ifdef useADC1
ADC1_CFG |= ADC_CFG_ADTRG; // Hardware trigger
ADC1_HC0 = 16; // ADC_ETC channel, 144 int enabled, 16 int disabled
ADC1_GC &= ~ADC_GC_ADCO; // Continuous conversion disabled, same as adc->adcX->singleMode()
#endif
#ifdef useADC2
ADC2_CFG |= ADC_CFG_ADTRG; // Hardware trigger
ADC2_HC0 = 16; // ADC_ETC channel
ADC2_GC &= ~ADC_GC_ADCO; // Continuous conversion disabled
#endif
}
void adc_etc_reset()
{
IMXRT_ADC_ETC.CTRL = ADC_ETC_CTRL_SOFTRST; // Perform a software reset on ADC_ETC
asm volatile("dsb"); // Ensure SOFTRST operation is completed
IMXRT_ADC_ETC.CTRL &= ~ADC_ETC_CTRL_SOFTRST; // Clear SOFTRST
delay(5);
}
int disable_keeper_on_analog_input_pin (uint8_t pin)
{
if (pin > 41)
return 0;
uint8_t ch = pin_to_channel[pin];
if (ch == 255)
return 0;
// check if pin has input "keeper"
volatile uint32_t *pad = portControlRegister(pin);
uint32_t padval = *pad;
if ((padval & (IOMUXC_PAD_PUE | IOMUXC_PAD_PKE)) == IOMUXC_PAD_PKE)
*pad = padval & ~IOMUXC_PAD_PKE;
// disable keeper, as it messes up analog with higher source impedance
// but don't touch user's setting if ordinary pullup, which some
// use together with capacitors or other circuitry
return 1;
}
void adc_etc_setup()
{
// Clear the TSC_BYPASS bit to ensure ADC2 is not occupied by the Touch Screen Controller
IMXRT_ADC_ETC.CTRL &= ~ADC_ETC_CTRL_TSC_BYPASS; // Clear TSC_BYPASS (bit 30)
#ifdef useADC1
// Enable TRIG[0] for xbar triggering
IMXRT_ADC_ETC.CTRL |= ADC_ETC_CTRL_TRIG_ENABLE((1 << 0));
// Configure TRIG[0] for ADC1
IMXRT_ADC_ETC.TRIG[0].CTRL = ADC_ETC_TRIG_CTRL_TRIG_CHAIN(CHANNELS_PER_ADC - 1);
// Configure chain for TRIG[0]
IMXRT_ADC_ETC.TRIG[0].CHAIN_1_0 =
ADC_ETC_TRIG_CHAIN_B2B0 |
ADC_ETC_TRIG_CHAIN_CSEL0(pin_to_channel[A0]) |
ADC_ETC_TRIG_CHAIN_IE0(0) |
ADC_ETC_TRIG_CHAIN_HWTS0(1) |
ADC_ETC_TRIG_CHAIN_B2B1 |
ADC_ETC_TRIG_CHAIN_CSEL1(pin_to_channel[A2]) |
ADC_ETC_TRIG_CHAIN_IE1(0) |
ADC_ETC_TRIG_CHAIN_HWTS1(1);
IMXRT_ADC_ETC.TRIG[0].CHAIN_3_2 =
ADC_ETC_TRIG_CHAIN_B2B0 |
ADC_ETC_TRIG_CHAIN_CSEL0(pin_to_channel[A4]) |
ADC_ETC_TRIG_CHAIN_IE0(0) |
ADC_ETC_TRIG_CHAIN_HWTS0(1) |
ADC_ETC_TRIG_CHAIN_B2B1 |
ADC_ETC_TRIG_CHAIN_CSEL1(pin_to_channel[A6]) |
ADC_ETC_TRIG_CHAIN_IE1(0) |
ADC_ETC_TRIG_CHAIN_HWTS1(1);
/*
commented out because cannot get 128 bit DMA minor iterations working ok
IMXRT_ADC_ETC.TRIG[0].CHAIN_5_4 =
ADC_ETC_TRIG_CHAIN_B2B0 |
ADC_ETC_TRIG_CHAIN_IE0(0) |
ADC_ETC_TRIG_CHAIN_CSEL0(pin_to_channel[A8]) |
ADC_ETC_TRIG_CHAIN_HWTS0(1) |
ADC_ETC_TRIG_CHAIN_B2B1 |
ADC_ETC_TRIG_CHAIN_CSEL1(pin_to_channel[A9]) |
ADC_ETC_TRIG_CHAIN_IE1(0) |
ADC_ETC_TRIG_CHAIN_HWTS1(1);
IMXRT_ADC_ETC.TRIG[0].CHAIN_7_6 =
ADC_ETC_TRIG_CHAIN_B2B0 |
ADC_ETC_TRIG_CHAIN_IE0(0) |
ADC_ETC_TRIG_CHAIN_CSEL0(pin_to_channel[A10]) |
ADC_ETC_TRIG_CHAIN_HWTS0(1) |
ADC_ETC_TRIG_CHAIN_B2B1 |
ADC_ETC_TRIG_CHAIN_CSEL1(pin_to_channel[A11]) |
ADC_ETC_TRIG_CHAIN_IE1(0) |
ADC_ETC_TRIG_CHAIN_HWTS1(1);
*/
#endif
#ifdef useADC2
#ifdef useADC1
// Configure TRIG[4] for ADC2 in sync mode with TRIG[0]
IMXRT_ADC_ETC.TRIG[4].CTRL =
ADC_ETC_TRIG_CTRL_TRIG_CHAIN(CHANNELS_PER_ADC - 1) |
ADC_ETC_TRIG_CTRL_SYNC_MODE;
#else
IMXRT_ADC_ETC.CTRL |= ADC_ETC_CTRL_TRIG_ENABLE((1 << 4));
IMXRT_ADC_ETC.TRIG[4].CTRL = ADC_ETC_TRIG_CTRL_TRIG_CHAIN(CHANNELS_PER_ADC - 1);
#endif
// Configure chain for TRIG[4]
IMXRT_ADC_ETC.TRIG[4].CHAIN_1_0 =
ADC_ETC_TRIG_CHAIN_B2B0 |
ADC_ETC_TRIG_CHAIN_CSEL0(pin_to_channel[A1] & 0xf) |
ADC_ETC_TRIG_CHAIN_IE0(0) |
ADC_ETC_TRIG_CHAIN_HWTS0(1) |
ADC_ETC_TRIG_CHAIN_B2B1 |
ADC_ETC_TRIG_CHAIN_CSEL1(pin_to_channel[A3] & 0xf) |
ADC_ETC_TRIG_CHAIN_IE1(0) |
ADC_ETC_TRIG_CHAIN_HWTS1(1);
IMXRT_ADC_ETC.TRIG[4].CHAIN_3_2 =
ADC_ETC_TRIG_CHAIN_B2B0 |
ADC_ETC_TRIG_CHAIN_CSEL0(pin_to_channel[A5] & 0xf) |
ADC_ETC_TRIG_CHAIN_IE0(0) |
ADC_ETC_TRIG_CHAIN_HWTS0(1) |
ADC_ETC_TRIG_CHAIN_B2B1 |
ADC_ETC_TRIG_CHAIN_CSEL1(pin_to_channel[A7] & 0xf) |
ADC_ETC_TRIG_CHAIN_IE1(0) |
ADC_ETC_TRIG_CHAIN_HWTS1(1);
/*
commented out because cannot get 128 bit DMA minor iterations working ok
IMXRT_ADC_ETC.TRIG[4].CHAIN_5_4 =
ADC_ETC_TRIG_CHAIN_B2B0 |
ADC_ETC_TRIG_CHAIN_IE0(0) |
ADC_ETC_TRIG_CHAIN_CSEL0(pin_to_channel[A12] & 0xf) |
ADC_ETC_TRIG_CHAIN_HWTS0(1) |
ADC_ETC_TRIG_CHAIN_B2B1 |
ADC_ETC_TRIG_CHAIN_CSEL1(pin_to_channel[A13] & 0xf) |
ADC_ETC_TRIG_CHAIN_IE1(0) |
ADC_ETC_TRIG_CHAIN_HWTS1(1);
IMXRT_ADC_ETC.TRIG[4].CHAIN_7_6 =
ADC_ETC_TRIG_CHAIN_B2B0 |
ADC_ETC_TRIG_CHAIN_IE0(0) |
ADC_ETC_TRIG_CHAIN_CSEL0(pin_to_channel[A14] & 0xf) |
ADC_ETC_TRIG_CHAIN_HWTS0(1) |
ADC_ETC_TRIG_CHAIN_B2B1 |
ADC_ETC_TRIG_CHAIN_CSEL1(pin_to_channel[A15] & 0xf) |
ADC_ETC_TRIG_CHAIN_IE1(0) |
ADC_ETC_TRIG_CHAIN_HWTS1(1);
*/
#endif
disable_keeper_on_analog_input_pin (A0);
disable_keeper_on_analog_input_pin (A1);
disable_keeper_on_analog_input_pin (A2);
disable_keeper_on_analog_input_pin (A3);
disable_keeper_on_analog_input_pin (A4);
disable_keeper_on_analog_input_pin (A5);
disable_keeper_on_analog_input_pin (A6);
disable_keeper_on_analog_input_pin (A7);
/*
commented out because cannot get 128 bit DMA minor iterations working ok
disable_keeper_on_analog_input_pin (A8);
disable_keeper_on_analog_input_pin (A9);
disable_keeper_on_analog_input_pin (A10);
disable_keeper_on_analog_input_pin (A11);
disable_keeper_on_analog_input_pin (A12);
disable_keeper_on_analog_input_pin (A13);
disable_keeper_on_analog_input_pin (A14);
disable_keeper_on_analog_input_pin (A15);
*/
// disable_keeper_on_analog_input_pin (A16);
// disable_keeper_on_analog_input_pin (A17);
}
void setup_xbar()
{
CCM_CCGR2 |= CCM_CCGR2_XBAR1(CCM_CCGR_ON); // Enable clock for XBAR
// Connect TMR4 Timer output to ADC_ETC Trigger input
#ifdef useADC1
xbar_connect(XBARA1_IN_QTIMER4_TIMER1, XBARA1_OUT_ADC_ETC_TRIG00); // Connect Timer to trigger TRIG[0]
#else
#ifdef useADC2
xbar_connect(XBARA1_IN_QTIMER4_TIMER1, XBARA1_OUT_ADC_ETC_TRIG10); // Connect Timer to trigger TRIG[4]
#endif
#endif
}
void adc_dma_setup ()
{
#ifdef useADC1
adc_dma_setup_channel (&etc_adc1_dmachannel, 0, dma_buffer_adc1);
#endif
#ifdef useADC2
adc_dma_setup_channel (&etc_adc2_dmachannel, 1, dma_buffer_adc2);
#endif
#ifdef useADC1
ADC_ETC_DMA_CTRL = 1<<0;
#else
ADC_ETC_DMA_CTRL = 1<<4;
#endif
IMXRT_ADC_ETC.CTRL |= ADC_ETC_CTRL_DMA_MODE_SEL;
}
void adc_dma_setup_channel (DMAChannel *adc_dmachannel, int ch, uint16_t *dma_buffer)
{
adc_dmachannel->begin();
switch (ch)
{
#ifdef useADC1
case 0: adc_dmachannel->source(ADC_ETC_TRIG0_RESULT_1_0); break;
#endif
#ifdef useADC2
case 1: adc_dmachannel->source(ADC_ETC_TRIG4_RESULT_1_0); break;
#endif
}
adc_dmachannel->destinationCircular(dma_buffer, DMA_BUFFER_SIZE_inBytes);
uint32_t SMOD = (31 - __builtin_clz(2 * CHANNELS_PER_ADC));;
uint32_t DMOD = (31 - __builtin_clz(DMA_BUFFER_SIZE_inBytes));
if (DMOD == 16)
{
Serial.printf ("DMOD is 16 because DMA_BUFFER_SIZE_inBytes is %d - that does not work - likely a hardware bug\n", DMA_BUFFER_SIZE_inBytes);
Serial.printf ("workaround: chose a buffer space that is 2x larger\n");
while (1);
}
//Serial.printf ("DMOD=%d SMOD=%d \n", DMOD, SMOD);
uint32_t SSIZE;
uint32_t DSIZE;
adc_dmachannel->TCD->NBYTES = 2 * CHANNELS_PER_ADC;
adc_dmachannel->TCD->DOFF = 2 * CHANNELS_PER_ADC;
adc_dmachannel->TCD->SOFF = 0;
switch (CHANNELS_PER_ADC)
{
case 1:
DSIZE = 1;
SSIZE = 1;
break;
case 2:
DSIZE = 2;
SSIZE = 2;
break;
case 4:
DSIZE = 3;
SSIZE = 3;
break;
// case 8:
// DSIZE = 3;
// SSIZE = 3;
// break;
default:
Serial.printf ("unsupported SENSORS per ADCx CHANNEL\n");
while(1);
}
//Serial.printf ("DMOD=%d SMOD=%d \n", DMOD, SMOD);
adc_dmachannel->TCD->ATTR = DMA_TCD_ATTR_SMOD(SMOD) | DMA_TCD_ATTR_SSIZE(SSIZE) | DMA_TCD_ATTR_DMOD(DMOD) | DMA_TCD_ATTR_DSIZE(DSIZE);
//Serial.printf ("ATTR=%04x %x %x %x %x\n", adc_dmachannel->TCD->ATTR,(adc_dmachannel->TCD->ATTR>>11) & 0x1f,(adc_dmachannel->TCD->ATTR >> 8)&7,(adc_dmachannel->TCD->ATTR>>3)&0x1f,adc_dmachannel->TCD->ATTR & 7 );
if (N_SAMPLES_PER_INPUT <= 256)
{
adc_dmachannel->transferCount(N_SAMPLES_PER_INPUT);
N_super_major_iterations = 1;
if (!Circular_super_major_loop_DMA_buffer)
adc_dmachannel->disableOnCompletion();
}
else
{
adc_dmachannel->transferCount(256);
N_super_major_iterations = DMA_BUFFER_SIZE / (256*CHANNELS_PER_ADC);
adc_dmachannel->TCD->DLASTSGA = 0;
}
if (prevDMA_TriggerChannel == nullptr)
{
adc_dmachannel->triggerAtHardwareEvent(DMAMUX_SOURCE_ADC_ETC);
adc_dmachannel->interruptAtCompletion();
adc_dmachannel->attachInterrupt(dma_etc_adc_isr, 16);
primary_adc_dmachannel = adc_dmachannel;
}
else
{
prevDMA_TriggerChannel->TCD->CITER |= DMA_TCD_CITER_ELINKYES_ELINK;
prevDMA_TriggerChannel->TCD->CITER &= ~DMA_TCD_CITER_ELINKYES_LINKCH_MASK;
prevDMA_TriggerChannel->TCD->CITER |= DMA_TCD_CITER_ELINKYES_LINKCH(adc_dmachannel->channel);
prevDMA_TriggerChannel->TCD->BITER = prevDMA_TriggerChannel->TCD->CITER;
prevDMA_TriggerChannel->TCD->CSR &= ~(DMA_TCD_CSR_MAJORLINKCH_MASK);
prevDMA_TriggerChannel->TCD->CSR |= DMA_TCD_CSR_MAJORLINKCH(adc_dmachannel->channel) | DMA_TCD_CSR_MAJORELINK;
}
prevDMA_TriggerChannel = adc_dmachannel;
}
void start_filling_dma_channels()
{
if (primary_adc_dmachannel != nullptr)
primary_adc_dmachannel->enable();
}
void stop_filling_dma_channels()
{
if (primary_adc_dmachannel != nullptr)
primary_adc_dmachannel->disable();
}
double adc_calculate_conversion_time ()
{
#ifdef useADC1
uint32_t ADC_CFG = ADC1_CFG;
#else
uint32_t ADC_CFG = ADC2_CFG;
#endif
uint32_t ADC_GC = ADC1_GC;
uint32_t AVGE = (ADC_GC >> 5) & 1;
uint32_t AVGS = (ADC_CFG >> 14) & 3;
uint32_t ADSTS = (ADC_CFG >> 8) & 3;
uint32_t ADIV = (ADC_CFG >> 5) & 3;
uint32_t ADLSMP = (ADC_CFG >> 4) & 1;
uint32_t MODE = (ADC_CFG >> 2) & 3;
uint32_t ADICLK = (ADC_CFG >> 0) & 3;
uint32_t AverageNum = 1;
uint32_t BaseConversionTime = 0;
uint32_t LongSampleTimeAdder = 0;
uint32_t SFCAdder = 0; // contiuous mode is all we want
uint32_t Clock_Divide_Select;
double ConversionTimeInMicroSeconds;
if (AVGE)
switch (AVGS)
{
case 0 : AverageNum = 4; break;
case 1 : AverageNum = 8; break;
case 2 : AverageNum = 16; break;
case 3 : AverageNum = 32; break;
}
switch (MODE)
{
case 0: BaseConversionTime = 17; break;
case 1: BaseConversionTime = 21; break;
case 2: BaseConversionTime = 25; break;
}
if (!ADLSMP)
switch (ADSTS)
{
case 0: LongSampleTimeAdder = 3; break;
case 1: LongSampleTimeAdder = 5; break;
case 2: LongSampleTimeAdder = 7; break;
case 3: LongSampleTimeAdder = 9; break;
}
else
switch (ADSTS)
{
case 0: LongSampleTimeAdder = 13; break;
case 1: LongSampleTimeAdder = 17; break;
case 2: LongSampleTimeAdder = 21; break;
case 3: LongSampleTimeAdder = 25; break;
}
uint32_t ConversionTimeInADCCLKs = SFCAdder + LongSampleTimeAdder + BaseConversionTime;
double T_IPG = 1E6 / (double)F_BUS_ACTUAL;
double two_bus_cycles = T_IPG * 2;
double ADICLK_in_us = 0.0;
switch (ADICLK)
{
case 0: ADICLK_in_us = T_IPG; break;
case 1: ADICLK_in_us = 2*T_IPG; break;
// case 2: ADICLK_in_us = T_Reserved; break;
// case 3: ADICLK_in_us = T_Asynchronous clock; break;
}
switch (ADIV)
{
case 0: Clock_Divide_Select = 1; break;
case 1: Clock_Divide_Select = 2; break;
case 2: Clock_Divide_Select = 4; break;
case 3: Clock_Divide_Select = 8; break;
}
double T_ADCCLK = ADICLK_in_us * Clock_Divide_Select;
ConversionTimeInMicroSeconds = two_bus_cycles + ConversionTimeInADCCLKs * T_ADCCLK;
ConversionTimeInMicroSeconds *= AverageNum;
return ConversionTimeInMicroSeconds;
}
void setup()
{
while (!Serial && (millis() < 5000));
Serial.println("Setup ADC with ETC, Timer Trigger and DMA");
#ifdef useADC1
Serial.println("using ADC1");
#endif
#ifdef useADC2
Serial.println("using ADC2");
#endif
Serial.printf("scanned analog inputs per ADCx is %d, ", CHANNELS_PER_ADC);
Serial.printf("#samples is %d\n", N_SAMPLES_PER_INPUT);
// Configure ADC1 and ADC2
#ifdef useADC1
adc->adc0->setAveraging(adc_averaging);
adc->adc0->setResolution(adc_nbits);
adc->adc0->setConversionSpeed(ADC_CONVERSION_SPEED::VERY_HIGH_SPEED);
adc->adc0->setSamplingSpeed(ADC_SAMPLING_SPEED::VERY_HIGH_SPEED);
adc->adc0->singleMode();
adc->adc0->disableInterrupts();
#endif
#ifdef useADC2
adc->adc1->setAveraging(adc_averaging);
adc->adc1->setResolution(adc_nbits);
adc->adc1->setConversionSpeed(ADC_CONVERSION_SPEED::VERY_HIGH_SPEED);
adc->adc1->setSamplingSpeed(ADC_SAMPLING_SPEED::VERY_HIGH_SPEED);
adc->adc1->singleMode();
adc->adc1->disableInterrupts();
#endif
adc_init();
adc_etc_reset(); // Reset the ADC_ETC module
adc_etc_setup(); // Set up the ADC_ETC for hardwired triggering
min_T_sample = (CHANNELS_PER_ADC * adc_calculate_conversion_time ());
max_f_sample = 1E6 / min_T_sample;
Serial.printf ("Estimated all %d per ADC converions time is %1.3f us, f_sample_max = %1.1f Hz\n", CHANNELS_PER_ADC, min_T_sample, max_f_sample);
/*
fill buffers with 111 - handy when debugging, just to check that DMA does or does not overwrite properly with ADC samples
#ifdef useADC1
for (int i=0; i<(CHANNELS_PER_ADC*N_SAMPLES_PER_INPUT); i++)
dma_buffer_adc1[i]=111;
arm_dcache_flush(dma_buffer_adc1, DMA_BUFFER_SIZE_inBytes);
#endif
#ifdef useADC2
for (int i=0; i<(CHANNELS_PER_ADC*N_SAMPLES_PER_INPUT); i++)
dma_buffer_adc2[i]=111;
arm_dcache_flush(dma_buffer_adc2, DMA_BUFFER_SIZE_inBytes);
#endif
*/
adc_dma_setup();
setup_xbar ();
start_timer4();
if (f_sample >= (max_f_sample / 1.10)) // apply a 10% margin just to be sure
{
Serial.printf ("oops - looks like we're exceeding the ADC max bandwidth - consider either fewer channels, lower averaging, or some of the other ADC settings\n");
while (1);
}
// attachInterruptVector(IRQ_QTIMER4, timer4_isr);
// NVIC_SET_PRIORITY(IRQ_QTIMER4, 16); // Example priority level
// NVIC_ENABLE_IRQ(IRQ_QTIMER4);
start_filling_dma_channels();
timestamp_all_super_major_iterations_captured = micros();
prev_timestamp_all_super_major_iterations_captured = timestamp_all_super_major_iterations_captured;
}
void dumpDMA_TCD(DMAChannel *dmabc)
{
Serial.printf("DMAch%d %x %x:", dmabc->channel, (uint32_t)dmabc, (uint32_t)dmabc->TCD);
Serial.printf("SA:%x SO:%d AT:%x NB:%x SL:%d DA:%x DO: %d CI:%x DL:%x CS:%x BI:%x\n",
(uint32_t)dmabc->TCD->SADDR, dmabc->TCD->SOFF, dmabc->TCD->ATTR, dmabc->TCD->NBYTES, dmabc->TCD->SLAST,
(uint32_t)dmabc->TCD->DADDR, dmabc->TCD->DOFF, dmabc->TCD->CITER, dmabc->TCD->DLASTSGA, dmabc->TCD->CSR, dmabc->TCD->BITER);
Serial.printf ("ATTR=%04x %x %x %x %x\n", dmabc->TCD->ATTR,(dmabc->TCD->ATTR>>11) & 0x1f,(dmabc->TCD->ATTR >> 8)&7,(dmabc->TCD->ATTR>>3)&0x1f,dmabc->TCD->ATTR & 7 );
// Serial.printf("ADC_ETC_DMA_CTRL:%x\n", ADC_ETC_DMA_CTRL);
Serial.printf("DMA_ES:%x\n", DMA_ES);
// Serial.printf("test_data:%x\n", &test_data);
}
void loop()
{
if (flag_entire_DMA_buffer_is_filled_with_new_data)
{
flag_entire_DMA_buffer_is_filled_with_new_data = 0;
#ifdef useADC1
arm_dcache_delete(dma_buffer_adc1, DMA_BUFFER_SIZE_inBytes);
#endif
#ifdef useADC2
arm_dcache_delete(dma_buffer_adc2, DMA_BUFFER_SIZE_inBytes);
#endif
Serial.printf ("index: ");
for (int k=0; k<CHANNELS_PER_ADC; k++)
{
#ifdef useADC1
Serial.printf ("A%d ", 2*k);
#endif
#ifdef useADC2
Serial.printf ("A%d ", 2*k+1);
#endif
}
Serial.printf ("\n");
for (int i=0; i<N_SAMPLES_PER_INPUT; i++)
{
Serial.printf ("%d: ", i);
for (int k=0; k<CHANNELS_PER_ADC; k++)
{
#ifdef useADC1
Serial.printf ("%d ", dma_buffer_adc1[CHANNELS_PER_ADC*i + k]);
#endif
#ifdef useADC2
Serial.printf ("%d ", dma_buffer_adc2[CHANNELS_PER_ADC*i + k]);
#endif
}
Serial.printf ("\n");
}
Serial.printf ("t=%d dt=%d us\n", timestamp_all_super_major_iterations_captured, delta_timestamp_all_super_major_iterations_captured);
Serial.flush();
}
}