/* 
Example code for Physics 398 DLP to read the four microphones in 
the group 1 project.

filename is read_4_ADCsv2.ino

 v1.0 by Ivan Velkovsky
 v2.0 includes mods from George Gollin, March 2021
 University of Illinois
  
 See the 2021 Microchip "SAM D5x/E5x Family Data Sheet" DS60001507G
 */

// the group 1 project has microphones attaced to analog inputs A1 - A4. The
// multiplexer numers associated with these channels are not simply related
// to the M4 analog pin numbers. Here's the correspondence:
// for ADC0:
//   A1 => 0x05
//   A2 => 0x02
//   A3 => 0x03
//   A4 => 0x04
// for ADC1:
//   A1 unavailable
//   A2 => 0x0
//   A3 => 0x01
//   A4 unavailable

// we will read A1 and A4 with ADC0; A2 and A3 with ADC1

//  See "SAM D5x/E5x Family Data Sheet" p. 1455, about input control. The MUXPOS bits are 
// in INPUTCTRL bits 0 - 4. See the data sheet, p. 1431 for information about the ADCs. 
// There are two-- ADC0 and ADC1.

#include <math.h>

// array for the ADC values
uint16_t ADC_reads[5];

// which MUX pins go with which ADC channels that we'll actually be reading...
// I note that ADC1 only can see two of these pins, both of which are in the
// M4's port B.
#define A0_ADC0_MUX ADC_INPUTCTRL_MUXPOS_AIN0
#define A1_ADC0_MUX ADC_INPUTCTRL_MUXPOS_AIN5
#define A2_ADC0_MUX ADC_INPUTCTRL_MUXPOS_AIN2
#define A3_ADC0_MUX ADC_INPUTCTRL_MUXPOS_AIN3
#define A4_ADC0_MUX ADC_INPUTCTRL_MUXPOS_AIN4

#define A2_ADC1_MUX ADC_INPUTCTRL_MUXPOS_AIN0
#define A3_ADC1_MUX ADC_INPUTCTRL_MUXPOS_AIN1

uint16_t ADC0_report, ADC1_report;

// how many times to read the ADCs during a speed test
uint32_t number_of = 100000;

// how many times to read the ADCs during an RMS and mean test
uint32_t number_of_RMS = 1000;

// other globals related to the mean and RMS calculations
double ADC_val_sum[5];
double ADC_valsq_sum[5];
double ADC_mean[5];
double ADC_meansq[5];
double ADC_RMS[5];

// here are arrays to hold some ADC values for use in determining time offsets.
#define ADC_ARRAY_LENGTH  10000
// double A1_signal[ADC_ARRAY_LENGTH];
// double A2_signal[ADC_ARRAY_LENGTH];
// double A3_signal[ADC_ARRAY_LENGTH];
// double A4_signal[ADC_ARRAY_LENGTH];
float A1_signal[ADC_ARRAY_LENGTH];
float A2_signal[ADC_ARRAY_LENGTH];
float A3_signal[ADC_ARRAY_LENGTH];
float A4_signal[ADC_ARRAY_LENGTH];

double microsec_per_bin;
uint32_t elapsed_time;

///////////////////////////////////////////////////////////////////////////

void setup() {
  // start up the serial monitor port...
  Serial.begin(115200);

  // wait for serial port to open.
  while(!Serial);

  Serial.println("\n************ Microphone/ADC investigation ************");

  // now initialize the M4 ADCs: there are two of them!
  ADC_init();
}

///////////////////////////////////////////////////////////////////////////

void loop() {

  // read ADC0 and ADC1 more or less simultaneously. some terminology: 
  // Atmel/Microchip defines bitfields (different from individual bits)
  // in their register structure definitions. For example, the INPUTCTRL register has
  // MUXPOS in the low-order five bits (INPUTCTRL[4:0]). These bits can be set using the 
  // .bit function, which acts on BITFIELDS as opposed to individual bits. Thus,
  //    ADC0->INPUTCTRL.bit.MUXPOS = 0xf; 
  // will set bits 0 - 3 to 1, while setting bit 4 to 0.

  Serial.println("\nJust entered loop."); 
  
  // Serial.println("Here is a dump of some ADC registers.");
  // dump_registers();

  // Serial.println("\n========================================");
  Serial.print("Now do a speed test: read all ADCS ");
  Serial.print(number_of);
  Serial.println(" times.");

  // enable both ADCs... we need to do this before calling read_four_ADCs inside the 
  // following loop.
  ADC0->CTRLA.bit.ENABLE = 1;
  ADC1->CTRLA.bit.ENABLE = 1;

  // keep track of when we start
  uint32_t t_start = millis();

  for(int how_many_reads = 0; how_many_reads < number_of; how_many_reads++)
  {
    // each time we call read_four_ADCs, the results are loaded in the (global) 
    // array ADC_reads.
    read_four_ADCs();
  } 

  // elapsed time, etc.
  elapsed_time = millis() - t_start;
  microsec_per_bin = float(1000 * elapsed_time)  / number_of;

  Serial.print("elapsed time (ms) was "); Serial.println(elapsed_time); 
  Serial.print("ADC read speed, in microseconds per 4-ADC read: "); 
  Serial.println(microsec_per_bin, 3);
  Serial.print("last readings for A1 - A4 stored in ADC_reads were ");

  for(int which_A_pin = 1; which_A_pin < 5; which_A_pin++)
  {
    Serial.print(ADC_reads[which_A_pin]); Serial.print("  ");
  } 

  Serial.println("\n========================================\n");

  Serial.println("Now calculate the RMS and means of the ADC channels.");

  Serial.print("ADC clock prescale value parameter N is ");
  Serial.print(ADC0->CTRLA.bit.PRESCALER);
  Serial.println(" [prescale factor is 2**(N+1)]");

  // Serial.println("\nNow re-enable ADC0 and ADC1 and wait for not-busy...");
  ADC0->CTRLA.bit.ENABLE = 1;
  ADC1->CTRLA.bit.ENABLE = 1;
  while(ADC0->STATUS.bit.ADCBUSY == 1 || ADC1->STATUS.bit.ADCBUSY == 1); 

  // keep looping to do the following stuff over and over and over and...
  while(1)
  {
    // calculate averages, etc. now.
    int trial_mean = 2050;

    Serial.println("\nCalculate average, average square, and RMS for each ADC");
    Serial.print("Sutract a trial mean of ");
    Serial.println(trial_mean);

    // initialize stuff used in calculating averages and chisq
    for(int which_A_pin = 1; which_A_pin < 5; which_A_pin++)
    {
      ADC_val_sum[which_A_pin] = 0;
      ADC_valsq_sum[which_A_pin] = 0;
    }

    // now read the ADCs a lot of times. 
    for(uint32_t ijk = 0; ijk < ADC_ARRAY_LENGTH; ijk++)
    {
      // read the four ADC values and store what we got.
      read_four_ADCs();

      // now accumulate into sums, subtracting a trial mean to keep the numbers small.
      for(int which_A_pin = 1; which_A_pin < 5; which_A_pin++)
      {
        ADC_val_sum[which_A_pin] = ADC_val_sum[which_A_pin] + 
          (ADC_reads[which_A_pin] - trial_mean);
        ADC_valsq_sum[which_A_pin] = ADC_valsq_sum[which_A_pin] + 
          (ADC_reads[which_A_pin] - trial_mean) * (ADC_reads[which_A_pin] - trial_mean);
      }
      A1_signal[ijk] = ADC_reads[1] - trial_mean;
      A2_signal[ijk] = ADC_reads[2] - trial_mean;
      A3_signal[ijk] = ADC_reads[3] - trial_mean;
      A4_signal[ijk] = ADC_reads[4] - trial_mean;
    }

    // Calculate the (shifted) means and means of squares. and also the RMS.
    for(int which_A_pin = 1; which_A_pin < 5; which_A_pin++)
    {
      ADC_mean[which_A_pin] = ADC_val_sum[which_A_pin] / ADC_ARRAY_LENGTH;
      ADC_meansq[which_A_pin] = ADC_valsq_sum[which_A_pin] / ADC_ARRAY_LENGTH;
      ADC_RMS[which_A_pin] = pow((ADC_meansq[which_A_pin] - 
        ADC_mean[which_A_pin] * ADC_mean[which_A_pin]), 0.5);

      Serial.print("ADC pin A"); Serial.print(which_A_pin);
      Serial.print(":  shifted mean = "); Serial.print(ADC_mean[which_A_pin]);
      Serial.print("  shifted mean sq = "); Serial.print(ADC_meansq[which_A_pin]);
      Serial.print("  RMS = "); Serial.println(ADC_RMS[which_A_pin]);
    }

    // now normalize...
    int chan;
    for(int ijk = 0; ijk < ADC_ARRAY_LENGTH; ijk++)
    {
      // Normalize things by the RMS values.
      chan = 1;
      A1_signal[ijk] = A1_signal[ijk] / ADC_RMS[chan];
      chan = 2;
      A2_signal[ijk] = A2_signal[ijk] / ADC_RMS[chan];
      chan = 3;
      A3_signal[ijk] = A3_signal[ijk] / ADC_RMS[chan];
      chan = 4;
      A4_signal[ijk] = A4_signal[ijk] / ADC_RMS[chan];
    }

    // now calculate a chisq of sorts, by trying to align A1 and A2 in time. the denominator
    // is just a guess.

    Serial.println("\n\n*************************************************");
    Serial.println("*************************************************");
    Serial.println("\nNow do a chisq scan by comparing Ai and Aj with different offsets. ");
    Serial.println("Negative index offset means Aj index is moved closer to the start of the Aj array.");
    Serial.println("A good match with negative offset means signal arrives at Aj LATER than Ai.");
    Serial.print("A sample comprises "); Serial.print(ADC_ARRAY_LENGTH);
    Serial.println(" reads per ADC channel\n");

    float t_off;
    int index1;
    int index2;

    index1 = 1;
    index2 = 2;
    Serial.print("A"); Serial.print(index1); Serial.print(", A"); Serial.println(index2);
    t_off = calculate_signal_shift(A1_signal, A2_signal);
    Serial.print("offset = ");
    Serial.print(t_off, 3); Serial.println(" microseconds"); 
    if(t_off < 0.)
    {
      Serial.print("A"); Serial.print(index1); Serial.print(" is EARLIER than A"); 
      Serial.println(index2);
    } else {
      Serial.print("A"); Serial.print(index1); Serial.print(" is LATER than A"); 
      Serial.println(index2);
    }
    Serial.println();

    index1 = 1;
    index2 = 3;
    Serial.print("A"); Serial.print(index1); Serial.print(", A"); Serial.println(index2);
    t_off = calculate_signal_shift(A1_signal, A3_signal);
    Serial.print("offset = ");
    Serial.print(t_off, 3); Serial.println(" microseconds"); 
    if(t_off < 0.)
    {
      Serial.print("A"); Serial.print(index1); Serial.print(" is EARLIER than A"); 
      Serial.println(index2);
    } else {
      Serial.print("A"); Serial.print(index1); Serial.print(" is LATER than A"); 
      Serial.println(index2);
    }
    Serial.println();

    index1 = 1;
    index2 = 4;
    Serial.print("A"); Serial.print(index1); Serial.print(", A"); Serial.println(index2);
    t_off = calculate_signal_shift(A1_signal, A4_signal);
    Serial.print("offset = ");
    Serial.print(t_off, 3); Serial.println(" microseconds"); 
    if(t_off < 0.)
    {
      Serial.print("A"); Serial.print(index1); Serial.print(" is EARLIER than A"); 
      Serial.println(index2);
    } else {
      Serial.print("A"); Serial.print(index1); Serial.print(" is LATER than A"); 
      Serial.println(index2);
    }
    Serial.println();

    index1 = 2;
    index2 = 3;
    Serial.print("A"); Serial.print(index1); Serial.print(", A"); Serial.println(index2);
    t_off = calculate_signal_shift(A2_signal, A3_signal);
    Serial.print("offset = ");
    Serial.print(t_off, 3); Serial.println(" microseconds"); 
    if(t_off < 0.)
    {
      Serial.print("A"); Serial.print(index1); Serial.print(" is EARLIER than A"); 
      Serial.println(index2);
    } else {
      Serial.print("A"); Serial.print(index1); Serial.print(" is LATER than A"); 
      Serial.println(index2);
    }
    Serial.println();

    index1 = 2;
    index2 = 4;
    Serial.print("A"); Serial.print(index1); Serial.print(", A"); Serial.println(index2);
    t_off = calculate_signal_shift(A2_signal, A4_signal);
    Serial.print("offset = ");
    Serial.print(t_off, 3); Serial.println(" microseconds"); 
    if(t_off < 0.)
    {
      Serial.print("A"); Serial.print(index1); Serial.print(" is EARLIER than A"); 
      Serial.println(index2);
    } else {
      Serial.print("A"); Serial.print(index1); Serial.print(" is LATER than A"); 
      Serial.println(index2);
    }
    Serial.println();

    index1 = 3;
    index2 = 4;
    Serial.print("A"); Serial.print(index1); Serial.print(", A"); Serial.println(index2);
    t_off = calculate_signal_shift(A3_signal, A4_signal);
    Serial.print("offset = ");
    Serial.print(t_off, 3); Serial.println(" microseconds"); 
    if(t_off < 0.)
    {
      Serial.print("A"); Serial.print(index1); Serial.print(" is EARLIER than A"); 
      Serial.println(index2);
    } else {
      Serial.print("A"); Serial.print(index1); Serial.print(" is LATER than A"); 
      Serial.println(index2);
    }
    Serial.println();

    Serial.println("*************************************************");
    Serial.println("*************************************************\n\n");
  }

  Serial.println("\n+++++++++++ time to fall into a black hole. ++++++++++++");
  while(1);
}


void ADC_init() {
  // ADC setup code. Initializes both ADC0 and ADC1 to sample at a reasonably fast rate,
  //set precision to 12 bits, and turn off any averaging, bit shifting, or increased sampling
  // time.

  // see SAMD51 data sheet, p. 1451. Disable the ADCs so that we can set some
  // configuration register bits. see data sheet p. 1450.
  ADC0->CTRLA.bit.ENABLE = 0;    
  ADC1->CTRLA.bit.ENABLE = 0;

  // Clock ADC GCLK prescaler of 128 (120 MHz / 128 = 937.5 kHz): see data sheet p. 1451 
  // For other possible values. (...DIV64, for example)
  // ADC0->CTRLA.reg = ADC_CTRLA_PRESCALER_DIV4;
  // ADC1->CTRLA.reg = ADC_CTRLA_PRESCALER_DIV4;   
  ADC0->CTRLA.reg = ADC_CTRLA_PRESCALER_DIV16;
  ADC1->CTRLA.reg = ADC_CTRLA_PRESCALER_DIV16;   
  // ADC0->CTRLA.reg = ADC_CTRLA_PRESCALER_DIV32;
  // ADC1->CTRLA.reg = ADC_CTRLA_PRESCALER_DIV32;   
  // ADC0->CTRLA.reg = ADC_CTRLA_PRESCALER_DIV64;
  // ADC1->CTRLA.reg = ADC_CTRLA_PRESCALER_DIV64;   
  // ADC0->CTRLA.reg = ADC_CTRLA_PRESCALER_DIV128;
  // ADC1->CTRLA.reg = ADC_CTRLA_PRESCALER_DIV128;   
  
  // Set ADC resolution to 12 bits
  ADC0->CTRLB.reg =  ADC_CTRLB_RESSEL_12BIT;
  ADC1->CTRLB.reg =  ADC_CTRLB_RESSEL_12BIT;

  // set INPUTCTRL register to make sure that differential mode if OFF. (this is the default.)
  ADC0->INPUTCTRL.bit.DIFFMODE = 0;
  ADC1->INPUTCTRL.bit.DIFFMODE = 0;
  
  // Take a single sample per measurement, with no bit shifting of the sample. The single
  // vertical bar is a bitwise OR. 
  ADC0->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_1 | ADC_AVGCTRL_ADJRES(0x00ul);
  ADC1->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_1 | ADC_AVGCTRL_ADJRES(0x00ul);
                     
  // Set max Sampling Time Length to half divided ADC clock pulse. see p. 1461.
  ADC0->SAMPCTRL.reg = 0x00;                       
  ADC1->SAMPCTRL.reg = 0x00;                       
  
  // now enable the ADCs.
  ADC0->CTRLA.bit.ENABLE = 1;    
  ADC1->CTRLA.bit.ENABLE = 1;

  // now wait for the ADCs to be ready.
  while(ADC0->STATUS.bit.ADCBUSY == 1 || ADC1->STATUS.bit.ADCBUSY == 1 || 
        ADC0->SYNCBUSY.reg > 0        || ADC1->SYNCBUSY.reg > 0); 

  // Serial.println("********* In adc_init; about to dump ADC registers. *********");
  // dump_registers();

}

///////////////////////////////////////////////////////////////////////////

void dump_registers()
{
  // dump some of the ADC-related registers to the serial monitor.
  // see the SAMD51 "data sheet": DS60001507G, in particular pp 1449-1450.

  uint32_t dump32;
  uint16_t dump16;
  uint8_t dump8;
  Serial.println("\n************************ ADC0 registers (HEX) *************************");

  // data sheet p. 1451
  dump16 = ADC0->CTRLA.reg;
  Serial.print("CTRLA:     0x");    
  if(dump16 < 0x0010) Serial.print("0");
  if(dump16 < 0x0100) Serial.print("0");
  if(dump16 < 0x1000) Serial.print("0");
  Serial.println(dump16, HEX);

  // data sheet p. 1453
  dump8 = ADC0->EVCTRL.reg;
  Serial.print("EVCTRL:    0x");
  if(dump8 < 0x10) Serial.print("0");
  Serial.println(dump8, HEX);

  // data sheet p. 1454
  dump8 = ADC0->DBGCTRL.reg;
  Serial.print("DBGCTRL:   0x");
  if(dump8 < 0x10) Serial.print("0");
  Serial.println(dump8, HEX);
  
  dump16 = ADC0->INPUTCTRL.reg;
  Serial.print("INPUTCTRL: 0x");
  if(dump16 < 0x0010) Serial.print("0");
  if(dump16 < 0x0100) Serial.print("0");
  if(dump16 < 0x1000) Serial.print("0");
  Serial.println(dump16, HEX);
  
  dump16 = ADC0->CTRLB.reg;
  Serial.print("CTRLB:     0x");    
  if(dump16 < 0x0010) Serial.print("0");
  if(dump16 < 0x0100) Serial.print("0");
  if(dump16 < 0x1000) Serial.print("0");
  Serial.println(dump16, HEX);

  dump8 = ADC0->REFCTRL.reg;
  Serial.print("REFCTRL:   0x");
  if(dump8 < 0x10) Serial.print("0");
  Serial.println(dump8, HEX);
 
  dump8 = ADC0->AVGCTRL.reg;
  Serial.print("AVGCTRL:   0x");  
  if(dump8 < 0x10) Serial.print("0");
  Serial.println(dump8, HEX);

  dump8 = ADC0->SAMPCTRL.reg;
  Serial.print("SAMPCTRL:  0x");  
  if(dump8 < 0x10) Serial.print("0");
  Serial.println(dump8, HEX);
 
  dump8 = ADC0->SWTRIG.reg;
  Serial.print("SWTRIG:    0x");  
  if(dump8 < 0x10) Serial.print("0");
  Serial.println(dump8, HEX);
 
  dump8 = ADC0->STATUS.reg;
  Serial.print("STATUS:    0x");  
  if(dump8 < 0x10) Serial.print("0");
  Serial.println(dump8, HEX);
 
  dump32 = ADC0->SYNCBUSY.reg;
  Serial.print("SYNCBUSY:  0x");  
  if(dump32 < 0x0010) Serial.print("0");
  if(dump32 < 0x0100) Serial.print("0");
  if(dump32 < 0x1000) Serial.print("0");
  Serial.println(dump32, HEX);
 
  dump16 = ADC0->RESULT.reg;
  Serial.print("RESULT:    0x");  
  if(dump16 < 0x0010) Serial.print("0");
  if(dump16 < 0x0100) Serial.print("0");
  if(dump16 < 0x1000) Serial.print("0");
  Serial.println(dump16, HEX);
 
  dump16 = ADC0->RESS.reg;
  Serial.print("RESS:      0x");  
  if(dump16 < 0x0010) Serial.print("0");
  if(dump16 < 0x0100) Serial.print("0");
  if(dump16 < 0x1000) Serial.print("0");
  Serial.println(dump16, HEX);

  Serial.println("\n************************ ADC1 registers (HEX) *************************");

  dump16 = ADC1->CTRLA.reg;
  Serial.print("CTRLA:     0x");    
  if(dump16 < 0x0010) Serial.print("0");
  if(dump16 < 0x0100) Serial.print("0");
  if(dump16 < 0x1000) Serial.print("0");
  Serial.println(dump16, HEX);

  dump8 = ADC1->EVCTRL.reg;
  Serial.print("EVCTRL:    0x");
  if(dump8 < 0x10) Serial.print("0");
  Serial.println(dump8, HEX);

  dump8 = ADC1->DBGCTRL.reg;
  Serial.print("DBGCTRL:   0x");
  if(dump8 < 0x10) Serial.print("0");
  Serial.println(dump8, HEX);
  
  dump16 = ADC1->INPUTCTRL.reg;
  Serial.print("INPUTCTRL: 0x");
  if(dump16 < 0x0010) Serial.print("0");
  if(dump16 < 0x0100) Serial.print("0");
  if(dump16 < 0x1000) Serial.print("0");
  Serial.println(dump16, HEX);
  
  dump16 = ADC1->CTRLB.reg;
  Serial.print("CTRLB:     0x");    
  if(dump16 < 0x0010) Serial.print("0");
  if(dump16 < 0x0100) Serial.print("0");
  if(dump16 < 0x1000) Serial.print("0");
  Serial.println(dump16, HEX);

  dump8 = ADC1->REFCTRL.reg;
  Serial.print("REFCTRL:   0x");
  if(dump8 < 0x10) Serial.print("0");
  Serial.println(dump8, HEX);
 
  dump8 = ADC1->AVGCTRL.reg;
  Serial.print("AVGCTRL:   0x");  
  if(dump8 < 0x10) Serial.print("0");
  Serial.println(dump8, HEX);

  dump8 = ADC1->SAMPCTRL.reg;
  Serial.print("SAMPCTRL:  0x");  
  if(dump8 < 0x10) Serial.print("0");
  Serial.println(dump8, HEX);
 
  dump8 = ADC1->SWTRIG.reg;
  Serial.print("SWTRIG:    0x");  
  if(dump8 < 0x10) Serial.print("0");
  Serial.println(dump8, HEX);
 
  dump8 = ADC1->STATUS.reg;
  Serial.print("STATUS:    0x");  
  if(dump8 < 0x10) Serial.print("0");
  Serial.println(dump8, HEX);
 
  dump16 = ADC1->SYNCBUSY.reg;
  Serial.print("SYNCBUSY:  0x");  
  if(dump16 < 0x0010) Serial.print("0");
  if(dump16 < 0x0100) Serial.print("0");
  if(dump16 < 0x1000) Serial.print("0");
  Serial.println(dump16, HEX);
 
  dump16 = ADC1->RESULT.reg;
  Serial.print("RESULT:    0x");  
  if(dump16 < 0x0010) Serial.print("0");
  if(dump16 < 0x0100) Serial.print("0");
  if(dump16 < 0x1000) Serial.print("0");
  Serial.println(dump16, HEX);
 
  dump16 = ADC1->RESS.reg;
  Serial.print("RESS:      0x");  
  if(dump16 < 0x0010) Serial.print("0");
  if(dump16 < 0x0100) Serial.print("0");
  if(dump16 < 0x1000) Serial.print("0");
  Serial.println(dump16, HEX);
}

///////////////////////////////////////////////////////////////////////////

void read_four_ADCs()
{
  // read the four ADC channels A1, A2, A3, and A4 and put the values into a 
  // (pre-defined) global array variable.

  // make sure you have already enabled the ADCs.

  // set the MUX channels for A1 and A2...
  ADC0->INPUTCTRL.bit.MUXPOS = A1_ADC0_MUX;
  ADC1->INPUTCTRL.bit.MUXPOS = A2_ADC1_MUX;

  // wait for sync...
  while(ADC0->SYNCBUSY.bit.INPUTCTRL == 1 || ADC1->SYNCBUSY.bit.INPUTCTRL == 1)

  // tell ADCs to digitize A1 and A2...
  ADC0->SWTRIG.bit.START = 1; 
  ADC1->SWTRIG.bit.START = 1; 

  // now wait until both ADCs are finished.
  while(ADC0->STATUS.bit.ADCBUSY == 1  || ADC1->STATUS.bit.ADCBUSY == 1) 

  // now fetch (and store) the results. ADC_reads is a global array, defined before setup().
  ADC_reads[1] = ADC0->RESULT.reg;
  ADC_reads[2] = ADC1->RESULT.reg;

  // now set the MUX channels for A4 and A3...
  ADC0->INPUTCTRL.bit.MUXPOS = A4_ADC0_MUX;
  ADC1->INPUTCTRL.bit.MUXPOS = A3_ADC1_MUX;

  // wait for sync...
  while(ADC0->SYNCBUSY.bit.INPUTCTRL == 1 || ADC1->SYNCBUSY.bit.INPUTCTRL == 1)

  // digitize...
  ADC0->SWTRIG.bit.START = 1; 
  ADC1->SWTRIG.bit.START = 1; 

  // now wait until both ADCs are finished.
  while(ADC0->STATUS.bit.ADCBUSY == 1  || ADC1->STATUS.bit.ADCBUSY == 1) 

  // now fetch and store the results. (note that ADC0 is reading A4, not A3.)
  ADC_reads[4] = ADC0->RESULT.reg;
  ADC_reads[3] = ADC1->RESULT.reg;
}

///////////////////////////////////////////////////////////////////////////

float calculate_signal_shift(float *array1, float *array2)
{
  // calculate a chisq for shifted arrays: A_index1 vs shifted A_index2.
  // return the apparent time offset between the two arrays, in microseconds,
  // for the best shift value.

  bool debug1 = false;
  bool debug2 = true;

  // now calculate a chisq of sorts, by trying to align A1 and A2 in time. the denominator
  // is just a guess.

  double denominatorsq = 1.;
  // double denominatorsq = 20.;
  int index_offset;
  int clearance = 10;
  int left_bin = -10;
  int right_bin = 10;
  int test_bins = right_bin - left_bin + 1;

  double chisq[test_bins];
  int which_bin = 0;

  for(index_offset = left_bin; index_offset <= right_bin; index_offset++)
  {
    chisq[which_bin] = 0.;
    for(int ijk = clearance; ijk < ADC_ARRAY_LENGTH - clearance; ijk++)
    {
      chisq[which_bin] = chisq[which_bin] + 
        pow((array1[ijk] - array2[ijk + index_offset]), 2.) / denominatorsq;
    }

    if(debug1)
    {
      Serial.print("index offset  = "); Serial.print(index_offset);
      Serial.print("    chisq = "); Serial.println(chisq[which_bin]);
    }
    which_bin++;
  }

  // now find the minimum chisq.
  double chisq_min = 10000000.;
  int best_index;

  for(int ijk = 0; ijk < test_bins-1; ijk++)
  {
    if(chisq[ijk] < chisq_min)
    {
      chisq_min = chisq[ijk];
      best_index = ijk;
    }
  }
  if(debug1)
  {
    Serial.print("\nBest chisq is in bin "); Serial.print(best_index);
    Serial.print(" (array index offset = "); Serial.print(best_index + left_bin);
    Serial.println(")");
  }

  // fit a parabola to the chisq fuction using chisq.
  // see 
  // https://stackoverflow.com/questions/717762/how-to-calculate-the-vertex-of-a-parabola-given-three-points

  if(debug1)
  {
    Serial.println("\nNow generate the parabola defined by the three best chisq bins.");
  }

  float x1 = float(best_index - 1);
  float x2 = float(best_index);
  float x3 = float(best_index + 1);

  float denom = (x1 - x2) * (x1 - x3) * (x2 - x3);

  float y1 = chisq[best_index - 1];
  float y2 = chisq[best_index];
  float y3 = chisq[best_index + 1];

  if(debug2)
  {
    Serial.print("points/bins on the parabola: x1, x2, x3, y1, y2, y2 are ");
    Serial.print(x1); Serial.print(", ");
    Serial.print(x2); Serial.print(", ");
    Serial.print(x3); Serial.print(";   ");
    Serial.print(y1); Serial.print(", ");
    Serial.print(y2); Serial.print(", ");
    Serial.print(y3); Serial.println(". ");
    // Serial.println("Note that x values are bins, starting at zero for the first index offest value");
  }

  float A = (x3 * (y2 - y1) + x2 * (y1 - y3) + x1 * (y3 - y2)) / denom;
  float B = (x3*x3 * (y1 - y2) + x2*x2 * (y3 - y1) + x1*x1 * (y2 - y3)) / denom;
  float C = (x2 * x3 * (x2 - x3) * y1 + x3 * x1 * (x3 - x1) * y2 + x1 * x2 * (x1 - x2) * y3) / denom;
  if(debug1)
  {
    Serial.print("parabola coefficients A, B, C are ");
    Serial.print(A); Serial.print("  ");
    Serial.print(B); Serial.print("  ");
    Serial.print(C); Serial.println("  ");
  }

  float x_vertex = -B / (2 * A);
  float y_vertex = C - B * B / (4. * A);

  if(debug2)
  {
    Serial.print("chisq parabola x_vertex, y_vertex = ");
    Serial.print(x_vertex); Serial.print("  ");
    Serial.print(y_vertex); Serial.println("  ");
  }

  if(debug1)
  {
    Serial.println("(Again, note that x values are bins, starting at zero for the first index offest value)");
    Serial.print("In terms of bins offset from zero shift, parabola x_vertex is at ");
    Serial.println(x_vertex + left_bin);
    Serial.print("this corresponds to ");
    Serial.print(microsec_per_bin * (x_vertex + left_bin), 3);
    Serial.println(" microseconds.");
    Serial.println("\n+++++++++++ All done! ++++++++++++");
  }
  return microsec_per_bin * (x_vertex + left_bin);
}

