T4 ADC real-time, timer triggered, with DMA, using ADC_ETC

sicco

Well-known member
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).

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();
    }
}
 
Back
Top