Arduino ADC timing

While the ADC code shown previously works, I would like to understand it better; to understand how to correctly setup the Arduino ADC. Having looked at the datasheet for the Arduino processor (Atmel SAM3X8e) I am confused as the information given there seems inconsistent.

In Section 43.6.10 titled “ADC Timings” it states that:

“A minimal Tracking Time is necessary for the ADC to guarantee the best converted final value between two channel selections. This time has to be programmed through the TRACKTIM bit field in the Mode Register, ADC_MR.”

The tracking time is given by:

\displaystyle \frac{\rm{TRACKTIM}+1}{\rm{ADC\ Clock\ Frequency}} \mbox{\quad i.e.\quad } (\rm{TRACKTIM}+1)t_{CP\_ADC}

where {t_{CP\_ADC}} is the period of the ADC Clock {(1/(\rm{ADC\ Clock\ Frequency})}).

The TRACKTIM bit field has 4 bits and so takes values between 0 and 15. The tracking time therefore takes values between 1 and 16 times the ADC clock period, {t_{CP\_ADC}}.\newline

Then, in Section 45.7.2.1 titled “Track and Hold Time versus Source Output Impedance” it says:

“During the tracking phase the ADC needs to track the input signal during the tracking times shown below:

  • 10 bit mode, {t_{track}=0.042 Z_{source}+160}
  • 12 bit mode, {t_{track}=0.054 Z_{source}+205}

with {t_{track}} in ns and {Z_{source}} in ohms.

Two cases must be considered:

  1. The calculated tracking time {t_{track}} is lower than {15 t_{CP\_ADC}}. Set {\rm{TRANSFER}=1} and {\rm{TRACKTIM}=0} in ADC\_MR.\newline In this case the allow {Z_{source}} can be computed versus the ADC frequency with the hypothesis that {t_{track}=15 t_{CP\_ADC}}.
  2. The calculated tracking time {t_{track}} is higher than {15 t_{CP\_ADC}}. Set {\rm{TRANSFER}=1} and {\rm{TRACKTIM}=0} in ADC_MR. In this case a timer will trigger the ADC in order to set the correct sampling rate according to the Track time.”

In the first case it says to set {\rm{TRACKTIM}=0} and the tracking time according to 43.6.10 is then one period of the ADC clock, {t_{CP\_ADC}}. How is this reconciled with the fact that for case 1 the tracking time can be up to {15 t_{CP\_ADC}}, while in case 2 it will be even higher still?

Looking at notes on the ADC implementation on a similar Atmel microcontroller (SAM3S) suggests the following:

  • In free-run mode the TRACKTIM and TRANSFER fields of the Mode Register are used to set the tracking and transfer timings. The tracking time needs to be set depending on the source impedance, but I still don’t understand what factors affect the transfer time.
  • In triggered mode set {\rm{TRACKTIM}=0} and {\rm{TRANSFER}=1}. Depending on the source impedance the details in the electrical characteristics section allow a tracking time to be calculated and this can then be used to calculate the sampling frequency. The sampling frequency should be between 0.05 and 1 MHz.

I will try to confirm this and report back.

 

Advertisements
Posted in Arduino | Leave a comment

Testing the Arduino ADC and DAC

I am presently living away from home as my cottage is being renovated. This makes experimentation difficult as most of my belongings are in storage. I decided to try to instrument what I was doing using my laptop.

I can use my laptop to generate and/or record audio signals and also use it to process and plot data received from the serial port. For my initial experiments I am using a tone generator program to provide sine wave inputs (ToneGen) and Matlab for recording audio and also receiving and processing data from the serial port. The speed of the serial port limits the amount of data that can be sent and therefore the frequencies that can be processed in this way.

The AC coupling used in the original the input circuit attenuates the low frequencies. A better, DC coupled, circuit uses an op-amp as a level shifter and can be used at the same time to adjust the input level to suit the ADC. The circuit that I used is

levelShiftCircuit

The idea for the circuit was suggested here. Assuming an ideal op-amp gives, for the {+} input

\displaystyle \frac{V_s-V_+}{R_2}+\frac{V_{in}-V_+}{R_1}=0

and, for the {-} input

\displaystyle \frac{V_-}{R_3}=\frac{V_{out}-V_-}{R_4}.

Rearranging gives

\displaystyle R_1 V_s+R_2 V_{in}=(R_1+R_2)V_+

and

\displaystyle R_3 V_{out}=(R_3+R_4)V_-.

Ideally {V_+=V_-} and choosing {R_1+R_2=R_3+R_4} gives

\displaystyle R_3 V_{out}=R_1 V_s+R_2 V_{in}.

Adjusting levels so that when {V_{in}=0, V_{out}=V_s/2} gives {R_3=2R_1} and

\displaystyle V_{out}=\frac{V_s}{2}+\left(\frac{R_2}{2R_1}\right)V_{in}.

The voltage gain {g=R_2/2R_1}. Choosing, for the moment, {g=1} gives

\displaystyle R_4=R_1\mbox{\quad and\quad}R_3=R_2=2R_1.

The recommendations here say that the resistors should all be at least {10k} and so I have taken

\displaystyle R_4=R_1=10k\mbox{\quad and\quad}R_3=R_2=20k.

For my circuit I used a TI OPA2340 low voltage rail-to-rail op-amp which comes in an 8 pin DIP package making it easy to use on a breadboard.

 

Posted in Arduino | Leave a comment

ADC to DAC revisited

The simple program shown previously got me started on the Arduino, but no real processing is done on the input. To do any useful processing the system will need to be better organised. The time critical processes are reading the ADC and writing the DAC, these can be done under interrupts with the rest of the processing done in the loop function. Some form of co-operative scheduing would seem to be appropriate.

As a first step, here is the same program reorganised with buffers for input from the ADC and for ouput to the DAC. Values are added and removed from these buffers under interrupt while transfer from one buffer to the other is done inside the loop function.


/**********************************************************************
* Sketch:      audioTest.ino
* Description: Audio filtering test - read ADC, process, write to DAC
*              This first test just transfers values from input to
*              output
*
* Author:      Chris Newton
*
* (C) Copyright 2015, Chris Newton.
*
**********************************************************************/

// Define the circular buffers used in these tests
#include "circularBuffer.h"

#include "DAC_routines.h"
#include "ADC_routines.h"
#include "timer_routines.h"

volatile int16_t DACMissCount; // number of times there is no data for the DAC 

void setup()
{
  Serial.begin (115200) ;
  double frequencySet = timer_setup(48000);        // setup timer
  Serial.print("Frequency set is: ");
  Serial.println(frequencySet);
  adc_setup () ;        // setup ADC
  dac_setup () ;        // setup up DAC auto-triggered at 48kHz
}

void dac_write (int val)
{
  DACC->DACC_CDR = val & 0xFFF ;
}
 
#ifdef __cplusplus
extern "C" 
{
#endif

void ADC_Handler (void)  // Read the ADC and write to the DAC,
{                        //  all done her for now
  int16_t val;
  if (ADC->ADC_ISR & ADC_ISR_EOC7)   
  {  // ensure there was an End-of-Conversion and we read the ISR reg
    val=*(ADC->ADC_CDR+7);  // get conversion result
    inputBuffer[ibFront]=(val-2048); // put in the input buffer
    ibFront=(ibFront+1)&BUFMASK;     // move pointer
    ibCount++;
  }
  if (obCount != 0)  // Check to see if there is anything in the
  {                  // output buffer 
	  val = outputBuffer[obBack] + 2048;
	  obBack = (obBack + 1)&BUFMASK;
	  obCount--;
	  dac_write(val);    // send the value to the DAC output FIFO
  }
  else
	  DACMissCount++;
}

#ifdef __cplusplus
}
#endif

void loop() 
{
  Serial.print("Starting the processing loop ...\n");
  DACMissCount = 0;
  enableADCTrigger();
  TC0ClockEnable(0);
  while(1)
  {
    int16_t val;
    int32_t imr;
    if (ibCount!=0)  // Check to see if there is input waiting
    {                // If so copy one value to the output buffer
	  imr = ADC->ADC_IMR;
	  ADC->ADC_IDR = 0x1F00FFFF;   // disable all interrupts
      val=outputBuffer[obFront]=inputBuffer[ibBack];
      ibBack=(ibBack+1)&BUFMASK;
      ibCount--;
      obFront=(obFront+1)&BUFMASK;
      obCount++;
	  ADC->ADC_IER = imr;   // re-enable interrupts
    }
	if (DACMissCount > 0)
	{
		Serial.println(DACMissCount);
		imr = ADC->ADC_IMR;
		ADC->ADC_IDR = 0x1F00FFFF;   // disable all interrupts
		DACMissCount = 0;
		ADC->ADC_IER = imr;   // re-enable interrupts
	}
  }
}

The setup routines have been moved to a separate file, they are the same as before and so are not repeated here. circularBuffer.h just defines the variables used for the circular buffers. There should be a circular buffer class, but for these first tests I wanted to avoid any overhead. I added routines to enable and disable the ADC trigger allowing the ADC to be started and stopped as required. These are in the ADC_routines file.


void enableADCTrigger(void)
{
	ADC->ADC_MR |= (ADC_MR_TRGEN);	// Set the trigger enable bit
}

void disableADCTrigger(void)
{
	ADC->ADC_MR &= (~ADC_MR_TRGEN);	// Clear the trigger enable bit
}

Update: M0XPD has built an SDR receiver using the Arduino Due (see his blog) so there is hope.

Posted in Arduino, SDR | Leave a comment

IMT Exercise 1.1.4

Let {d_1,d_2\ge1}, and let {E_1\subset\mathbb{R}^{d_1}}, {E_2\subset\mathbb{R}^{d_2}} be elementary sets. Show that {E_1\times E_2\subset\mathbb{R}^{d_1+d_2}} is elementary , and {m^{d_1+d_2}(E_1\times E_2)=m^{d_1}(E_1)\times m^{d_2}(E_2)}.
Let partitions of {E_1} and {E_2} into finite sets of disjoint boxes be {B^1_1\cup\ldots\cup B^1_n}, and {B^2_1\cup\ldots\cup B^2_m}. Then

\displaystyle E_1\times E_2=\bigcup_{\substack{i=1\ldots n\\ j=1\ldots m}} B^1_i\times B^2_j

Consider {B^1_i=I^1_{i,1}\times\ldots\times I^1_{i,d_1}} and {B^2_j= I^2_{j,1}\times\ldots\times I^2_{j,d_2}}, then
\displaystyle B^1_i\times B^2_j=I^1_{i,1}\times\ldots\times I^1_{i,d_1}\times I^2_{j,1}\times\ldots\times I^2_{j,d_2}

It is clear that {B^1_i\times B^2_j\subset\mathbb{R}^{d_1+d_2}} and as {d_1} and {d_2} are both finite so therefore is {d_1+d_2}. Furthermore
\displaystyle m^{d_1+d_2}(B^1_i\times B^2_j)=|I^1_{i,1}|\times\ldots\times |I^1_{i,d_1}|\times |I^2_{j,1}|\times\ldots\times |I^2_{j,d_2}|=m^{d_1}(B^1_i)\times m^{d_2}(B^2_j).

As {E_1} and {E_2} are partitioned into disjoint boxes the new boxes in the Cartesian product {E_1\times E_2} are also disjoint. This gives
\displaystyle m^{d_1+d_2}(E_1\times E_2)=\sum_{\substack{i=1\ldots n\\ j=1\ldots m}} m^{d_1}(B^1_i)\times m^{d_2}(B^2_j)=m^{d_1}(E_1)\times m^{d_2}(E_2)

{\Box}

Posted in IMT Exercises, Measure Theory | Leave a comment

IMT Exercise 1.1.3

(Uniqueness of elementary measure). Let {d\ge1}. Let {m':\mathcal{E}(\mathbb{R}^d)\rightarrow \mathbb{R}^+} be a map from the collection {\mathcal{E}(\mathbb{R}^d)} of elementary subsets of {\mathbb{R}^d} to the non-negative reals that obeys the non-negativity, finite additivity and translation invariance properties. Show that there exists a constant {c\i\mathbb{R}^+} such that {m'(E)=c m(E)} for all elementary sets {E}. In particular, if we impose the additional normalisation {m'([0,1)^d)=1}, then {m'\equiv m}. (Hint: Set {c:=m([0,1)^d)}, then compute {m'([0,\frac{1}{n})^d)} for any positive integer {n}.)

For a box {B} we know that {m(B)\in\mathbb{R}^+} and we are given that {m'(B)\in\mathbb{R}^+} so provided {B\neq\emptyset} we can always write {m'(B)=c m(B)} with {c\in\mathbb{R}^+}.

Consider two disjoint boxes {B_1} and {B_2} with {B_1\neq B_2\neq\emptyset} and let

\displaystyle m'(B_1)=c_1 m(B_1)\mbox{, } m'(B_2)=c_2 m(B_2)\mbox{ and } m(B_1\cup B_2)=c_U m(B_1\cup B_2)

As {m'} and {m} both obey the finite additivity property we have

\displaystyle c_U(m(B_1)+m(B_2))=c_1 m(B_1)+c_2 m(B_2)

Rearranging gives

\displaystyle (c_U-c_1)m(B_1)=(c_2-c_U)m(B_2)

This will only be true for all boxes {B_1} and {B_2} if

\displaystyle c_U=c_1\mbox{ and }c_2=c_U

and so for all boxes {B\neq\emptyset} we have a constant {c} such that

\displaystyle m'(B)=c m(B).

If we consider the box {[0,\frac{1}{n})^d} with {n} a positive integer we have

\displaystyle m'([0,\frac{1}{n})^d)=cm([0,\frac{1}{n})^d)

and considering the limit as {n\rightarrow\infty} we obtain the result that {m'(\emptyset)=0}.

Using these results and the results of the previous two exercises we can see that for any elementary set

\displaystyle m'(E)=c m(E).

If we normalise {m'} so that {m'([0,\frac{1}{n})^d)=1} then {c=1} and we have {m'\equiv m}.

{\Box}

Posted in IMT Exercises, Measure Theory | Leave a comment

IMT Exercise 1.1.2

Give an alternate proof of Lemma 1.1.2(ii) by showing that any two partitions of {E} into boxes admit a mutual refinement into boxes that arise from taking Cartesian products from elements from finite collections of disjoint intervals.

Lemma 1.1.2(ii) If {E} is partitioned as the finite union {B_1\cup\ldots\cup B_k} of disjoint boxes, then the quantity {m(E)=|B_1|+\ldots +|B_k|} is independent of the partition. In other words, given any other partition {B'_1\cup\ldots\cup B'_{k'}} of {E}, one has {|B_1|+\ldots +|B_k|=|B'_1|+\ldots +|B'_{k'}|}.

The diagram below shows an example with two overlapping boxes, the second column show two different partitions and these can be refined further giving the result shown in the last column.

boxes1

One way of refining these partitions is suggested below, the lighter boxes are compared and their intersection saved. The remainder(s) are retained for comparison at a later stage although in the illustration there is only on and it is used straight away.

boxes2

It is clear that the elementary measure is the same for both of these partitions.

Let two partitions of {E} as the finite union of disjoint boxes be {B_1\cup\ldots\cup B_k} and {B'_1\cup\ldots\cup B'_{k'}}. We aim to refine these partitions so that at the end the partitions are the same. This will give us our result.

Consider two intersecting boxes, {B_i} and {B'_{j}}, if {B_i=B'_j} then no refinement is necessary. Now we focus on the case when {B_i\supset B'_{j}}. For ease of notation set {X=B_i} and {Y=B'_{j}} and consider the intervals {I_{X,1}\ldots I_{X,d}} corresponding to box {X} and how they are partitioned by the intersection with {Y}. Consider {I_{X,k}} the intersection with {I_{Y,k}} partitions this interval into

  • {I_{X,k}^M} the part of the interval {I_{X,k}} which intersects with the corresponding interval of {Y}, {I_{Y,k}}.
  • {I_{X,k}^L} the lower interval, this is the part of the interval {I_{X,k}} which is less than the interval {I_{Y,k}}, or just the lower endpoint of {I_{X,k}}.
  • {I_{X,k}^U} the upper interval, this is the part of the interval {I_{X,k}} which is greater than the interval {I_{Y,k}}, or just the upper endpoint of {I_{X,k}}.

The boxes formed from the Cartesian products of these intervals form a refinement of {B_i} (which includes {B'_j}).

When {B'_j\supset B_i} we use a similar procedure to find a refinement of {B'_j} which contains {B_i}.

We can repeat this process, comparing boxes, until no more refinement is possible. As the refinement of a box does not alter its elementary measure we obtain our result, i.e. that {m(E)=|B_1|+\ldots +|B_k|=|B'_1|+\ldots +|B'_{k'}|}.

{\Box}

Posted in IMT Exercises, Measure Theory | Leave a comment

ADC to DAC on the Arduino

The code below can be used to read values from an ADC (A0) and then just copy them to a DAC (DAC0) for output. The code has been adapted from the post by MarkT (see link below). Details of the timers on the Arduino Due can be found in the SAM3X/A datasheet, section 37, pg. 869.


/********************************************************************************
* Sketch:      ADCtoDAC.ino
* Description: Read ADC and then write to DAC
*
*  Example of driving ADC and DAC from timer for regular sampling adapted from
*  the post by MarkT:
*
*    http://forum.arduino.cc/index.php?topic=205096.0  
*
*********************************************************************************/

void setup()
{
  Serial.begin (9600) ;  // was for debugging
  adc_setup () ;         // setup ADC
  timer_setup();         // setup timer
  dac_setup () ;         // setup up DAC auto-triggered at 48kHz
}

// Circular buffer, power of two.
#define BUFSIZE 0x400
#define BUFMASK 0x3FF
volatile int samples [BUFSIZE] ;
volatile int sptr = 0 ;
volatile int isr_count = 0 ;   // this was for debugging

#ifdef __cplusplus
extern "C" 
{
#endif

int val;

void ADC_Handler (void)
{
  if (ADC->ADC_ISR & ADC_ISR_EOC7)   
  {    // ensure there was an End-of-Conversion and read the ISR reg
    val = *(ADC->ADC_CDR+7) ;    // get conversion result
    samples [sptr] = val ;       // stick in circular buffer
    sptr = (sptr+1) & BUFMASK ; // move pointer
    dac_write (val) ;           // copy value to DAC output FIFO
  }
  isr_count ++ ;
}

#ifdef __cplusplus
}
#endif


void setup_pio_TIOA0 ()   // Configure Ard pin 2 as output from TC0 channel
                          // A (copy of trigger event)
{
  PIOB->PIO_PDR = PIO_PB25B_TIOA0 ;  // disable PIO control
  PIOB->PIO_IDR = PIO_PB25B_TIOA0 ;   // disable PIO interrupts
  PIOB->PIO_ABSR |= PIO_PB25B_TIOA0 ;  // switch to B peripheral
}

void timer_setup()
{
  pmc_enable_periph_clk (TC_INTERFACE_ID + 0*3+0) ;  // clock the TC0 channel 0

  TcChannel * t = &(TC0->TC_CHANNEL)[0] ;    // pointer to TC0 registers
                                             // for its channel 0
  t->TC_CCR = TC_CCR_CLKDIS ;  // disable internal clocking while setup regs
  t->TC_IDR = 0xFFFFFFFF ;     // disable interrupts
  t->TC_SR ;                   // read int status reg to clear pending
  t->TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK1 |   // use TCLK1 (prescale by 2, = 42MHz)
              TC_CMR_WAVE |         // waveform mode
              TC_CMR_WAVSEL_UP_RC | // count-up PWM using RC as threshold
              TC_CMR_EEVT_XC0 |     // Set external events from XC0
              TC_CMR_ACPA_CLEAR | TC_CMR_ACPC_CLEAR |
              TC_CMR_BCPB_CLEAR | TC_CMR_BCPC_CLEAR ;
  
  t->TC_RC =  875 ;     // counter resets on RC, so sets period 
                        // in terms of 42MHz clock - 48khz sample rate
  t->TC_RA =  438;     // roughly square wave
  t->TC_CMR = (t->TC_CMR & 0xFFF0FFFF) | TC_CMR_ACPA_CLEAR | TC_CMR_ACPC_SET ; 
                        // set and clear flags for RA and RC compares
  t->TC_CCR = TC_CCR_CLKEN | TC_CCR_SWTRG ;  // re-enable local clocking and switch
                                             // to hardware trigger source.

  setup_pio_TIOA0 () ;  // drive Arduino pin 2 at 48kHz to bring clock out
}  

void dac_setup ()
{
  pmc_enable_periph_clk (DACC_INTERFACE_ID) ; // start clocking DAC
  DACC->DACC_CR = DACC_CR_SWRST ;  // reset DAC
 
  DACC->DACC_MR = 
    DACC_MR_TRGEN_EN | DACC_MR_TRGSEL (1) |  // trigger 1 = TIO output of TC0
    (0 << DACC_MR_USER_SEL_Pos) |  // select channel 0
    DACC_MR_REFRESH (0x0F) |       // bit of a guess... I'm assuming no refresh
    (24 <DACC_IDR = 0xFFFFFFFF ; // no interrupts
  DACC->DACC_CHER = DACC_CHER_CH0 <DACC_CDR = val & 0xFFF ;
}
 
void adc_setup ()
{
  NVIC_EnableIRQ (ADC_IRQn) ;   // enable ADC interrupt vector
  ADC->ADC_IDR = 0xFFFFFFFF ;   // disable interrupts
  ADC->ADC_IER = 0x80 ;         // enable AD7 End-Of-Conv interrupt
                                // (Arduino pin A0)
  ADC->ADC_CHDR = 0xFFFF ;      // disable all channels
  ADC->ADC_CHER = 0x80 ;        // enable just A0
  ADC->ADC_CGR = 0x15555555 ;   // All gains set to x1
  ADC->ADC_COR = 0x00000000 ;   // All offsets off
  
  ADC->ADC_MR = (ADC->ADC_MR & 0xFFFFFFF0) | (1 << 1) | ADC_MR_TRGEN ;
                                // 1 = trig source TIO from TC0
}

void loop() 
{
}

The loop is empty as all of the work is done in the interrupt routine.

The test circuit used with this code is shown here.

The code runs and, given audio input from the PC, generates slightly noisy output and there is also an occasional glitch. The Arduino is currently powered from the PC, I need to power the Arduino independently and see how it performs then. It certainly works well enough to encourage further experimentation.

Posted in Arduino, SDR | Leave a comment