/* Example for using DMA with ADC
    This example uses DMA object to do the sampling.  It does not use a timer so it runs
    at whatever speed the ADC will run at with current settings.

    It should work for Teensy LC, 3.x and T4

  DMA: using AnalogBufferDMA with two buffers, this runs in continuous mode and when one buffer fills
    an interrupt is signaled, which sets flag saying it has data, which this test application
    scans the data, and computes things like a minimum, maximum, average values and an RMS value.
    For the RMS it keeps the average from the previous set of data.
*/

#include <ADC.h>
#include <AnalogBufferDMA.h>

#ifdef ADC_USE_DMA

// This version uses both ADC1 and ADC2
#if defined(KINETISL)
const int readPin_adc_0 = A0;
#elif defined(KINETISK)
const int readPin_adc_0 = A0;
const int readPin_adc_1 = A2;
#else
const int readPin_adc_0 = A0;
const int readPin_adc_1 = 26;
#endif

ADC *adc = new ADC(); // adc object
const uint32_t initial_average_value = 2048;

// Going to try two buffers here  using 2 dmaSettings and a DMAChannel
#ifdef KINETISL
const uint32_t buffer_size = 500;
#else
const uint32_t buffer_size = 1600;
#endif

DMAMEM static volatile uint16_t __attribute__((aligned(32))) dma_adc_buff1[buffer_size];
DMAMEM static volatile uint16_t __attribute__((aligned(32))) dma_adc_buff2[buffer_size];
AnalogBufferDMA abdma1(dma_adc_buff1, buffer_size, dma_adc_buff2, buffer_size);

#ifdef ADC_DUAL_ADCS
DMAMEM static volatile uint16_t __attribute__((aligned(32))) dma_adc2_buff1[buffer_size];
DMAMEM static volatile uint16_t __attribute__((aligned(32))) dma_adc2_buff2[buffer_size];
AnalogBufferDMA abdma2(dma_adc2_buff1, buffer_size, dma_adc2_buff2, buffer_size);
#endif

void setup() {
    while (!Serial && millis() < 5000) ;

    pinMode(LED_BUILTIN, OUTPUT);
    pinMode(readPin_adc_0, INPUT); //pin 23 single ended
#ifdef ADC_DUAL_ADCS
    pinMode(readPin_adc_1, INPUT);
#endif

    Serial.begin(9600);
    Serial.println("Setup ADC_0");
    // reference can be ADC_REFERENCE::REF_3V3, ADC_REFERENCE::REF_1V2 (not for Teensy LC) or ADC_REF_EXT.
    //adc->setReference(ADC_REFERENCE::REF_1V2, ADC_0); // change all 3.3 to 1.2 if you change the reference to 1V2

    adc->adc0->setAveraging(8); // set number of averages
    adc->adc0->setResolution(12); // set bits of resolution


    // always call the compare functions after changing the resolution!
    //adc->enableCompare(1.0/3.3*adc->getMaxValue(ADC_0), 0, ADC_0); // measurement will be ready if value < 1.0V
    //adc->enableCompareRange(1.0*adc->getMaxValue(ADC_1)/3.3, 2.0*adc->getMaxValue(ADC_1)/3.3, 0, 1, ADC_1); // ready if value lies out of [1.0,2.0] V

    // enable DMA and interrupts
    Serial.println("before enableDMA"); Serial.flush();


    // setup a DMA Channel.
    // Now lets see the different things that RingbufferDMA setup for us before
    abdma1.init(adc, ADC_0);
    abdma1.userData(initial_average_value); // save away initial starting average
#ifdef ADC_DUAL_ADCS
    Serial.println("Setup ADC_1");
    adc->adc1->setAveraging(8); // set number of averages
    adc->adc1->setResolution(12); // set bits of resolution
    abdma2.init(adc, ADC_1);
    abdma2.userData(initial_average_value); // save away initial starting average
    adc->adc1->startContinuous(readPin_adc_1);
#endif

    // Start the dma operation..
    adc->adc0->startContinuous(readPin_adc_0);

    Serial.println("End Setup");
}

char c = 0;


void loop() {

    // Maybe only when both have triggered?
#ifdef ADC_DUAL_ADCS
    if ( abdma1.interrupted() && abdma2.interrupted()) {
        if (abdma1.interrupted()) ProcessAnalogData(&abdma1, 0);
        if (abdma2.interrupted()) ProcessAnalogData(&abdma2, 1);
        Serial.println();
    }
#else
    if ( abdma1.interrupted()) {
        ProcessAnalogData(&abdma1, 0);
        Serial.println();
    }
#endif

}

void ProcessAnalogData(AnalogBufferDMA *pabdma, int8_t adc_num) {
  uint32_t sum_values = 0;
  uint16_t min_val = 0xffff;
  uint16_t max_val = 0;

  uint32_t average_value = pabdma->userData();

  volatile uint16_t *pbuffer = pabdma->bufferLastISRFilled();
  volatile uint16_t *end_pbuffer = pbuffer + pabdma->bufferCountLastISRFilled();

  float sum_delta_sq = 0.0;
  if ((uint32_t)pbuffer >= 0x20200000u)  arm_dcache_delete((void*)pbuffer, sizeof(dma_adc_buff1));
  while (pbuffer < end_pbuffer) {
    if (*pbuffer < min_val) min_val = *pbuffer;
    if (*pbuffer > max_val) max_val = *pbuffer;
    sum_values += *pbuffer;
    int delta_from_center = (int) * pbuffer - average_value;
    sum_delta_sq += delta_from_center * delta_from_center;

    pbuffer++;
  }

  int rms = sqrt(sum_delta_sq / buffer_size);
  average_value = sum_values / buffer_size;
  Serial.printf(" %d - %u(%u): %u <= %u <= %u %d ", adc_num, pabdma->interruptCount(), pabdma->interruptDeltaTime(), min_val,
                average_value, max_val, rms);
  pabdma->clearInterrupt();

  pabdma->userData(average_value);
}

#else // make sure the example can run for any boards (automated testing)
void setup() {}
void loop() {}
#endif // ADC_USE_DMA
