DAC and timed ADC using DMA

Hi everyone,

I am quite new to Arduino DUE and microC programming in general and my question may seem dumb. Yhe task I have to accomplish is to generate one single sine wave at 4kHz and do two adc at 1 kHz, exactly when the sine wave has its peak, and then send them via Serial.

I have do that with interupts or DMA, I have searched on this forum and on SAM3X8E datasheet but I cannot get how to solve my specific case. I have seen a post with the code I attach below but I can't understand where ADCs are stored (how to "extract" and use them), how to use just one DAC and what does this cycle (in ADC_Handler) do:

  boolean chsel;
  for (uint8_t i = 0; i < bufsize; i++) {
    buf[Oldbufn][i] |= chsel << 12;                    // Select alternatively DAC0 and DAC1
    chsel = !chsel;
  }

Thank you for your time and I apologize for these noob doubts.

Luigi

Here is the code,by ard_newbie, I mentioned before:

/*********************************************************************************************************/
/*  44.1 KHz ADC conversions of A0 , idem for A1, triggered by Timer Counter 0 channel 1 TIOA1           */
/*  44.1 KHz DAC output on DAC0, idem for DAC1, triggered by Timer Counter 0 channel 2 TIOA2             */
/*********************************************************************************************************/

volatile uint8_t bufn, Oldbufn, bufn_dac;
const uint16_t bufsize = 128;            
const uint8_t bufnumber = 4;             
const uint8_t _bufnumber = bufnumber - 1;
volatile uint16_t buf[bufnumber][bufsize];        
void setup()
{

  pinMode(LED_BUILTIN, OUTPUT);  // For ADC debugging
  pinMode(12, OUTPUT);           // For DACC debugging

  adc_setup();
  dac_setup();
  tc_adc_setup();
  tc_dac_setup();
}

void loop()
{

}

/*************  Configure adc_setup function  *******************/
void adc_setup() {

  PMC->PMC_PCER1 |= PMC_PCER1_PID37;                    // ADC power ON

  ADC->ADC_CR = ADC_CR_SWRST;                           // Reset ADC
  ADC->ADC_MR |=  ADC_MR_TRGEN_EN                       // Hardware trigger select
                  | ADC_MR_TRGSEL_ADC_TRIG2             // Trigger by TIOA1
                  | ADC_MR_PRESCAL(1);

  // ADC->ADC_ACR = ADC_ACR_IBCTL(0b01);                 // For frequencies > 500 KHz

  ADC->ADC_IDR = ~ADC_IDR_ENDRX;
  ADC->ADC_IER = ADC_IER_ENDRX;                         // End Of Conversion interrupt enable for channel 7
  //NVIC_SetPriority(ADC_IRQn, 0xFF);
  NVIC_EnableIRQ(ADC_IRQn);                             // Enable ADC interrupt
  ADC->ADC_CHER = ADC_CHER_CH6 | ADC_CHER_CH7;          // Enable Channels 7 = A0 and 6 = A1; Trigger frequency is multiplied by 2
                                                        // The sampling frequency for 1 channel times the number of channels !!

  /*********  PDC/DMA  buffer filling sequence **********/
  ADC->ADC_RPR = (uint32_t)buf[2];                      // DMA buffer - First one will be buf[1]
  ADC->ADC_RCR = bufsize;
  ADC->ADC_RNPR = (uint32_t)buf[3];                     // next DMA buffer
  ADC->ADC_RNCR = bufsize;
  bufn = 3;
  ADC->ADC_PTCR |= ADC_PTCR_RXTEN;                      // Enable PDC Receiver channel request
  ADC->ADC_CR = ADC_CR_START;
}

/*********  Call back function for ADC PDC/DMA **************/
void ADC_Handler () {

  //if ( ADC->ADC_ISR & ADC_ISR_ENDRX) {               // Useless because the only one
  Oldbufn = bufn;

  bufn = (bufn + 1) & _bufnumber;
  ADC->ADC_RNPR = (uint32_t)buf[bufn];
  ADC->ADC_RNCR = bufsize;

  /*******************************************/
  boolean chsel;
  for (uint8_t i = 0; i < bufsize; i++) {
    buf[Oldbufn][i] |= chsel << 12;                    // Select alternatively DAC0 and DAC1
    chsel = !chsel;
  }

  // Todo : digital filtering before DAC output
  
 /*************************************************/
  // For debugging only
  static uint32_t Count;
  if (Count++ == 689) { //84000000/8/238/2/128  = ~689
    Count = 0;
    PIOB->PIO_ODSR ^= PIO_ODSR_P27;  // Toggle LED_BUILTIN every 1 Hz
  }
  /*************************************************/
  // }
}

/*************  Configure adc_setup function  *******************/
void dac_setup ()
{

  PMC->PMC_PCER1 = PMC_PCER1_PID38;                   // DACC power ON

  DACC->DACC_CR = DACC_CR_SWRST ;                     // Reset DACC
  DACC->DACC_MR = DACC_MR_TRGEN_EN                    // Hardware trigger select
                  | DACC_MR_TRGSEL(0b011)             // Trigger by TIOA2
                  | DACC_MR_TAG_EN                    // Output on DAC0 and DAC1
                  | DACC_MR_WORD_HALF                 
                  | DACC_MR_REFRESH (1)
                  | DACC_MR_STARTUP_8
                  | DACC_MR_MAXS;

  DACC->DACC_ACR = DACC_ACR_IBCTLCH0(0b11) //0b10
                   | DACC_ACR_IBCTLCH1(0b11) // 0b10
                   | DACC_ACR_IBCTLDACCORE(0b01);

  DACC->DACC_IDR = ~DACC_IDR_ENDTX;
  DACC->DACC_IER = DACC_IER_ENDTX;                    // TXBUFE works too !!!
  //NVIC_SetPriority(DACC_IRQn, 0xFF);
  NVIC_EnableIRQ(DACC_IRQn);
  DACC->DACC_CHER = DACC_CHER_CH0 | DACC_CHER_CH1;    // enable channels 1 = DAC1 and 0 = DAC0

  /*************   configure PDC/DMA  for DAC *******************/
  DACC->DACC_TPR  = (uint32_t)buf[0];                 // DMA buffer
  DACC->DACC_TCR  = bufsize;
  DACC->DACC_TNPR = (uint32_t)buf[1];                 // next DMA buffer
  DACC->DACC_TNCR =  bufsize;
  bufn_dac = 1;
  DACC->DACC_PTCR = DACC_PTCR_TXTEN;                  // Enable PDC Transmit channel request

}

/*********  Call back function for DAC PDC/DMA **************/
void DACC_Handler() {   // DACC_ISR_HANDLER()         // move Sinus/PDC/DMA pointers to next buffer

  //if ( DACC->DACC_ISR & DACC_ISR_ENDTX) {           // Useless because the only one

  bufn_dac = (bufn_dac + 1) & _bufnumber;
  DACC->DACC_TNPR = (uint32_t)buf[bufn_dac];
  DACC->DACC_TNCR = bufsize;
  
  /*****************************************/
  // For debugging only
  static uint32_t Count;
  if (Count++ == 689) { //84000000/8/119/128 = ~689
    Count = 0;
    PIOD->PIO_ODSR ^= PIO_ODSR_P8;  // Toggle LED_BUILTIN every 1 Hz
  }
  /****************************************/
  //}
}

/*******  Timer Counter 0 Channel 1 to generate PWM pulses thru TIOA1  for ADC ********/
void tc_adc_setup() { 

  PMC->PMC_PCER0 |= PMC_PCER0_PID28;                      // TC1 power ON : Timer Counter 0 channel 1 IS TC1
  TC0->TC_CHANNEL[1].TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK2  // MCK/8, clk on rising edge
                              | TC_CMR_WAVE               // Waveform mode
                              | TC_CMR_WAVSEL_UP_RC       // UP mode with automatic trigger on RC Compare
                              | TC_CMR_ACPA_CLEAR         // Clear TIOA1 on RA compare match
                              | TC_CMR_ACPC_SET;          // Set TIOA1 on RC compare match

  TC0->TC_CHANNEL[1].TC_RC = 238;  //<*********************  Frequency = (Mck/8)/TC_RC  Hz = 22.05 Hz
  TC0->TC_CHANNEL[1].TC_RA = 40;  //<********************   Any Duty cycle in between 1 and TC_RC

  TC0->TC_CHANNEL[1].TC_CCR = TC_CCR_CLKEN;               // TC1 enable

}

/*******  Timer Counter 0 Channel 2 to generate PWM pulses thru TIOA2  for DACC ********/
void tc_dac_setup() { 

  PMC->PMC_PCER0 |= PMC_PCER0_PID29;                      // TC2 power ON : Timer Counter 0 channel 2 IS TC2
  TC0->TC_CHANNEL[2].TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK2  // MCK/8, clk on rising edge
                              | TC_CMR_WAVE               // Waveform mode
                              | TC_CMR_WAVSEL_UP_RC       // UP mode with automatic trigger on RC Compare
                              | TC_CMR_ACPA_CLEAR         // Clear TIOA2 on RA compare match
                              | TC_CMR_ACPC_SET;          // Set TIOA2 on RC compare match

  TC0->TC_CHANNEL[2].TC_RC = 119;  //<*********************  Frequency = (Mck/8)/TC_RC  Hz = 44.1 Hz
  TC0->TC_CHANNEL[2].TC_RA = 20;  //<********************   Any Duty cycle in between 1 and TC_RC

  TC0->TC_CHANNEL[2].TC_CCR = TC_CCR_CLKEN;               // TC2 enable
  TC0->TC_BCR = TC_BCR_SYNC;                              // Synchro TC1 and TC2
}

Tyarel:
Yhe task I have to accomplish is to generate one single sine wave at 4kHz and do two adc at 1 kHz, exactly when the sine wave has its peak, and then send them via Serial.

What does that mean ??

I apologize that I have not been very clear but I meant that I have to do three different tasks:

  • Generate one sine wave on the output (so one I need one DAC pin activated) with a frequency of 4 KHz
  • Get two readings from two different sensors (thus two different ADC channels are involved) each millisecond (frequency: 1 KHz). In particular, these two readings must be synchronized (with a degree of approximation, because I know that I cannot achieve parallel conversion) with the peak of the sine wave that I have previously generated thanks to the DAC
  • After the acquisition of ADC I have to send data via Serial

Thanks for your reply.

Luigi

A uni assignment ?

IMO you could do that this way:

Enable 3 ADC channels, e.g. A0 (for sensor 0),A1 (for sensor 1) and A2 (to receive DAC1). Remember that A0 = ADC channel 7, A1 = ADC channel 6 and A2 is ADC channel 5.

If you consider that a sin wave is accurately drawn with e.g. 64 points for 1 period, declare a buffer of 64 uint16_t that you fill with one period of a sin wave between 0 and 4095.

With a PDC DMA, you continuously output this sin wave on one DAC, e.g. DAC1 by triggering a DAC output at the frequency of 256 KHz (= 64 * 4 KHz). A timer counter programed at this frequency will trigger DAC1. Add at least 1.5 K Ohm resistor in serie between DAC1 and A3 or your DAC will burn!

The ADC peripheral has a window feature with which you can trigger the ADC Handler whenever an ADC conversion (select ADC channel 5 in this case) exceeds a threshold (5/6 * 3.3V  3400). This interrupt will fire with a 4 KHz frequency. Inside the ADC Handler, declare a static variable as an interruption counter. This variable reaches 3 (0, 1, 2, 3) with a frequency of 1 KHz so that you can save ADC_CDR[7] and ADC_CDR[6] in 2 volatile variables, set a Flag inside the ADC Handler to indicate the loop() that there is something to Serial print. In loop(), Serial print accordingly and clear the Flag. If you need an RC filter to avoid EMI, select an RC constant at least 100 times the input frequency.

ADC conversions can be triggered by the same Timer Counter than the one used by DAC1 at a frequency of 256 KHz. The ADC peripheral will automatically multiply this frequency by the number of enabled channels (3 in this case) to process conversions. You can check that 3 * 256KHz is under the maximum conversion frequency allowed (1 MHz).

Select Serial.begin(250000) to speed up printings in serial monitor.

You will find example sketches in the DUE forum for each part of this script and SAM3X datasheet is your best friend.

Perfect, thank you very much! I will try to implement it, and yes, I have a uni assignment about this.
Just to know, is there a function for Arduino like SysTick ?

Thank you for your time.
Luigi

Hi, I am back here because I have some problem with ADC.

I tried what you suggested and I also had to decrease frequency of sine wave (now it is 350Hz, instead of 4kHz) and now ADC must be fired every peaks of the wave because of other stuff in the project .
However, my problem is that the ADC blocks all my board. When the adc_setup is commented the DAC goes well (I see it on an oscilloscope) but when ADC is "active" neither DAC nor ADC work, so Serial neither (because it is triggered by AD conversion).

I really cannot figure out why there is this problem (maybe interrupts take all my cpu cycles?).
If anyone can help me, I will be very grateful.

Thank you in advance!
Luigi

Here is the code:

#define BaudRate 460800

uint16_t volt[2];
uint8_t flag = 0;
volatile uint8_t bufn, Oldbufn, bufn_dac;
const uint16_t bufsize = 150;            
const uint8_t bufnumber = 1;             
const uint8_t _bufnumber = bufnumber - 1;

volatile uint16_t buf[bufnumber][bufsize] =
{
    { 2048,2133,2219,2304,2389,2473,2557,2639,
2721,2801,2880,2958,3034,3108,3181,3251,
3319,3385,3449,3510,3569,3625,3678,3729,
3776,3821,3862,3900,3935,3967,3995,4020,
4041,4059,4073,4084,4091,4095,4095,4091,
4084,4073,4059,4041,4020,3995,3967,3935,
3900,3862,3821,3776,3729,3678,3625,3569,
3510,3449,3385,3319,3251,3181,3108,3034,
2958,2880,2801,2721,2639,2557,2473,2389,
2304,2219,2133,2048,1962,1876,1791,1706,
1622,1538,1456,1374,1294,1215,1137,1061,
987,914,844,776,710,646,585,526,
470,417,366,319,274,233,195,160,
128,100,75,54,36,22,11,4,
0,0,4,11,22,36,54,75,
100,128,160,195,233,274,319,366,
417,470,526,585,646,710,776,844,
914,987,1061,1137,1215,1294,1374,1456,
1538,1622,1706,1791,1876,1962}};
       
void setup()
{
  
  Serial.begin(BaudRate);
  while(!Serial);  // Wait for connection
  
  pinMode(LED_BUILTIN, OUTPUT);  // For ADC debugging
  pinMode(12, OUTPUT);           // For DACC debugging
  
  
  adc_setup();
  dac_setup();
  tc_adc_setup();
  tc_dac_setup();
}

void loop()
{
  
  uint8_t checksum; 
  if(flag == 1){    //Protocol via Serial
  
  checksum = (uint8_t)(volt[0] >> 8) + (uint8_t)(volt[0]) + (uint8_t)(volt[1] >> 8) + (uint8_t)(volt[1]);

  Serial.write(0x02); // Start of text

  Serial.write((uint8_t) (volt[0] >> 8));
  Serial.write((uint8_t) volt[0]);
  Serial.write((uint8_t) (volt[1] >> 8));
  Serial.write((uint8_t) volt[1]);
  
  Serial.write(checksum);
  
  flag = 0 ;
  }
}

/*************  Configure adc_setup function  *******************/
void adc_setup() {

  PMC->PMC_PCER1 |= PMC_PCER1_PID37;                    // ADC power ON

  ADC->ADC_CR = ADC_CR_SWRST;                           // Reset ADC
  ADC->ADC_MR |=  ADC_MR_TRGEN_EN                       // Hardware trigger select
                  | ADC_MR_TRGSEL_ADC_TRIG2             // Trigger by TIOA1
                  | ADC_MR_PRESCAL(1);

  // ADC->ADC_ACR = ADC_ACR_IBCTL(0b01);                 // For frequencies > 500 KHz

  ADC->ADC_IDR = ~ADC_IDR_ENDRX;
  ADC->ADC_IER = ADC_IER_ENDRX;                         // End Of Conversion interrupt enable for channel 7
  //NVIC_SetPriority(ADC_IRQn, 0xFF);
  NVIC_EnableIRQ(ADC_IRQn);                                                // Enable ADC interrupt
  ADC->ADC_CHER = ADC_CHER_CH5 | ADC_CHER_CH6 | ADC_CHER_CH7;          // Enable Channels 7 = A0 and 6 = A1; Trigger frequency is multiplied by 2
                                                                           // The sampling frequency for 1 channel times the number of channels !!
}

/*********  Call back function for ADC PDC/DMA **************/
void ADC_Handler () {
  

  if((ADC -> ADC_CDR[7]) > 2970){ // DAC range from 0.58 to 2.4V - > peak when ADC measures 2978 (= 2.4V)
                  
      volt[0] = ADC -> ADC_CDR[6]; //max value after filter: 3.3V ok
      volt[1] = ADC -> ADC_CDR[5];
      flag = 1;
  }
}


/*************  Configure adc_setup function  *******************/
void dac_setup ()
{

  PMC->PMC_PCER1 = PMC_PCER1_PID38;                   // DACC power ON

  DACC->DACC_CR = DACC_CR_SWRST ;                     // Reset DACC
  DACC->DACC_MR = DACC_MR_TRGEN_EN                    // Hardware trigger select
                  | DACC_MR_TRGSEL(0b011)             // Trigger by TIOA2
                  | DACC_MR_TAG_EN                    // Output on DAC0 and DAC1
                  | DACC_MR_WORD_HALF                 
                  | DACC_MR_REFRESH (1)
                  | DACC_MR_STARTUP_8
                  | DACC_MR_MAXS;

  DACC->DACC_ACR = DACC_ACR_IBCTLCH0(0b11) //0b10
                   | DACC_ACR_IBCTLCH1(0b11) // 0b10
                   | DACC_ACR_IBCTLDACCORE(0b01);

  DACC->DACC_IDR = ~DACC_IDR_ENDTX;
  DACC->DACC_IER = DACC_IER_ENDTX;                    // TXBUFE works too !!!
  NVIC_SetPriority(DACC_IRQn, 0xFF);
  NVIC_EnableIRQ(DACC_IRQn);
  DACC->DACC_CHER = DACC_CHER_CH0;    // enable channels 1 = DAC1 and 0 = DAC0

  /*************   configure PDC/DMA  for DAC *******************/
  DACC->DACC_TPR  = (uint32_t)buf[0];                 // DMA buffer
  DACC->DACC_TCR  = bufsize;
  DACC->DACC_TNPR = (uint32_t)buf[1];                 // next DMA buffer
  DACC->DACC_TNCR = bufsize;
  bufn_dac = 1;
  DACC->DACC_PTCR = DACC_PTCR_TXTEN;                  // Enable PDC Transmit channel request

}

/*********  Call back function for DAC PDC/DMA **************/
void DACC_Handler() {   // DACC_ISR_HANDLER()         // move Sinus/PDC/DMA pointers to next buffer

  //if ( DACC->DACC_ISR & DACC_ISR_ENDTX) {           // Useless because the only one

  bufn_dac = (bufn_dac + 1) & _bufnumber;
  DACC->DACC_TNPR = (uint32_t)buf[bufn_dac];
  DACC->DACC_TNCR = bufsize;
  
}

/*******  Timer Counter 0 Channel 1 to generate PWM pulses thru TIOA1  for ADC ********/
void tc_adc_setup() { 

  PMC->PMC_PCER0 |= PMC_PCER0_PID28;                      // TC1 power ON : Timer Counter 0 channel 1 IS TC1
  TC0->TC_CHANNEL[1].TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK2  // MCK/8, clk on rising edge
                              | TC_CMR_WAVE               // Waveform mode
                              | TC_CMR_WAVSEL_UP_RC       // UP mode with automatic trigger on RC Compare
                              | TC_CMR_ACPA_CLEAR         // Clear TIOA1 on RA compare match
                              | TC_CMR_ACPC_SET;          // Set TIOA1 on RC compare match

  TC0->TC_CHANNEL[1].TC_RC = 800;  //<*********************  Frequency = (Mck/8)/TC_RC  Hz = 13125 Hz -> Adc can be sampled less frequently because  a range of 4 values verify the if clause in adchandler
  TC0->TC_CHANNEL[1].TC_RA = 20;  //<********************   Any Duty cycle in between 1 and TC_RC

  TC0->TC_CHANNEL[1].TC_CCR = TC_CCR_CLKEN;               // TC1 enable

}

/*******  Timer Counter 0 Channel 2 to generate PWM pulses thru TIOA2  for DACC ********/
void tc_dac_setup() { 

  PMC->PMC_PCER0 |= PMC_PCER0_PID29;                      // TC2 power ON : Timer Counter 0 channel 2 IS TC2
  TC0->TC_CHANNEL[2].TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK2  // MCK/8, clk on rising edge
                              | TC_CMR_WAVE               // Waveform mode
                              | TC_CMR_WAVSEL_UP_RC       // UP mode with automatic trigger on RC Compare
                              | TC_CMR_ACPA_CLEAR         // Clear TIOA2 on RA compare match
                              | TC_CMR_ACPC_SET;          // Set TIOA2 on RC compare match

  TC0->TC_CHANNEL[2].TC_RC = 200;  //<*********************  Frequency = (Mck/8)/TC_RC  Hz = 52500 -> 150 samples per wave -> 350 hz full sine wave
  TC0->TC_CHANNEL[2].TC_RA = 20;  //<********************   Any Duty cycle in between 1 and TC_RC

  TC0->TC_CHANNEL[2].TC_CCR = TC_CCR_CLKEN;               // TC2 enable
  TC0->TC_BCR = TC_BCR_SYNC;                              // Synchro TC1 and TC2
}