Example of driving ADC and DAC from timer for regular sampling

Hi,

I have used this code to successfully acquire the audio from a 2-wire intercom system and played it back using a audio amplifier.

Now on the same 2-wire system there is data present too, which again acquired( on A0 using ADC) using this code and shown on the oscilloscope at DAC output.

Now I would like to show the received data onto serial port ?

MarkT:

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 refresh not needed at 48kHz
    (24 << DACC_MR_STARTUP_Pos) ;  // 24 = 1536 cycles which I think is in range 23..45us since DAC clock = 42MHz

DACC->DACC_IDR = 0xFFFFFFFF ; // no interrupts
  DACC->DACC_CHER = DACC_CHER_CH0 << 0 ; // enable chan0
}

void dac_write (int val)
{
  DACC->DACC_CDR = val & 0xFFF ;
}

Hi, This might be long shot since this hasn't been posted on in a while, but I had a question regarding the "dac_write" function:

Section 44.6.4 of the SAM3A-Atmel documentation says: "Warning: Writing in the DACC_CDR register while TXRDY flag is inactive will corrupt FIFO data."

I was wondering why it's not necessary to implement some sort of check that prevents writing to the CDR when the the TXRDY flag is inactive.

Regards,
-H

The DAC peripheral is much faster than the ADC peripheral:

Sam3x datasheet, Page 1403: Max ADC sampling frequency = 1 MHz

Sam3x datasheet, Page 1411: Max DAC sampling frequency = 2 MHz

BTW, to sample from one of the builtin ADC channels and output from one of the builtin DAC channels, the best option is to make use of the PDC DMA to divide by the buffer size the total number of interruptions :slight_smile:

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 (this setup TIOB as output)
              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
  t->TC_RA =  440 ;                                             //why 440???
  t->TC_CMR = (t->TC_CMR & 0xFFF0FFFF) | TC_CMR_ACPA_CLEAR | TC_CMR_ACPC_SET ;  // set clear and set from RA and RC compares
  
  t->TC_CCR = TC_CCR_CLKEN | TC_CCR_SWTRG ;  // re-enable local clocking and switch to hardware trigger source.

I am trying to output a 60Hz sinewave via a DMA. Would anyone be able to explain why TC_RA is set to 440?

In the attached code I have a 2000 point sinewave (I know it could have less points) I set TC_RC to 350 because the dac should output at a frequency of n*"desired hz"

However, when I ran the code with RA at 440 the dac did not output anything, when I changed RA to 110 the DAC began to output.

DMADAC.ino.ino (14 KB)

TC_RA = TC_RC/2 to output a 50% duty cycle. And TC_RA < TC_RC !!

I am trying to use the sketch number 1, but actually I cannot have what there is on the pictures of the oscilloscope, can someone help me ?

Post the sketch you are actually using (between code tags).

Thank you for your answer, actually I can have it but the signals seems to be in opposite phase
this is my code

void setup()

//generation du Timer sampling a 1kHz
{
  adc_setup () ;         // setup ADC
  
  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 (this setup TIOB as output)
              TC_CMR_ACPA_CLEAR | TC_CMR_ACPC_CLEAR |
              TC_CMR_BCPB_CLEAR | TC_CMR_BCPC_CLEAR ;
  
  t->TC_RC =  42000 ;     // counter resets on RC, so sets period in terms of 42MHz clock
  t->TC_RA =  21000 ;     // roughly square wave
  t->TC_CMR = (t->TC_CMR & 0xFFF0FFFF) | TC_CMR_ACPA_CLEAR | TC_CMR_ACPC_SET ;  // set clear and set from 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
  dac_setup () ;        // setup up DAC auto-triggered at 48kHz
}

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 dac_write (int val)
{
  DACC->DACC_CDR = val & 0xFFF ;
}


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 refresh not needed at 48kHz
    (24 << DACC_MR_STARTUP_Pos);                       // 24 = 1536 cycles which I think is in range 23..45us since DAC clock = 42MHz
  DACC->DACC_IDR = 0xFFFFFFFF;                         // no interrupts
  DACC->DACC_CHER = DACC_CHER_CH0 << 0;                // enable chan0
 
}

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\
 
}

// 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

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

#ifdef __cplusplus
}
#endif


void loop() 
{
}

this is exactly the same, just modifying the sampling rate...

TUCTUC77:
I can have it but the signals seems to be in opposite phase

dac_write (0xFFF & ~val) ; // copy inverted to DAC output FIFO

BTW, there is an interrupt after each and every conversion ( 1000 times per second). A more rational sampling method would be to fill a sampling buffer (e.g. 128 values), output this buffer thru a DAC while continuously sampling to fill another buffer with a PDC DMA thus dividing interrupts by a 128 scale.

https://forum.arduino.cc/index.php?topic=224672.0
reply 10

thank you for your reply, i did not saw the inversion, think i was tired.

About the other sketch, do you think it is really better, because this one works good and i only need a sampling frequency of 1k and the input is a sinewave of 100hz, i have the feeling that i can works with that sketch, no ?

Sampling a 100 Hz sinwave at a frequency of 1000 Hz is not a good idea (10 points per period is a bit weak). You won't see a nice sinewave output from the DAC.

I would go for at least 60 points per period.

Thank you for your reply, actually this is for an experiment so i need to see the steps ! but for sure this is not a good sinewave fron the DAC

I have one additional question, in that case (the timer drives the ADC), does it mean that the ADC_clock = sampling frequency ?

If not how can i know the ADC_clock ?

Thank you