Pages: [1]   Go Down
Author Topic: Sinewave 100k generator, DAC + PDC + Timer.  (Read 487 times)
0 Members and 1 Guest are viewing this topic.
Montreal
Offline Offline
Edison Member
*
Karma: 23
Posts: 2487
Per aspera ad astra.
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Atmel has an example of sinewave source , integrating DAC and Timer, max. freq. 3 kHz with LUT 100 samples. It doesn't use PDC.
So here is my research, build with different snippets of the code posted on this forum and some atmel's example code.
Max freq. 10k if "high fidelity" LUT in use , 160 samples per period of sine. To get freq. up to 100k, code is switching for another, "low resolution" LUT, only 16 samples per one period. CLI expecting nnnnnf command line, default 1k.

Code:
#define    NWAVE               80

uint16_t  Sinewave[2][NWAVE] = {
  {
   +4095,   +4093,   +4089,   +4081,   +4070,   +4056,   +4038,   +4018,   +3995,   +3968,   +3939,   +3907,   +3872,   +3834,   +3793,   +3750,
   +3704,   +3656,   +3605,   +3551,   +3495,   +3438,   +3377,   +3315,   +3251,   +3185,   +3118,   +3048,   +2977,   +2905,   +2831,   +2757,
   +2681,   +2604,   +2526,   +2447,   +2368,   +2289,   +2209,   +2128,   +2048,   +1968,   +1887,   +1807,   +1728,   +1649,   +1570,   +1492,
   +1415,   +1339,   +1265,   +1191,   +1119,   +1048,    +978,    +911,    +845,    +781,    +719,    +658,    +601,    +545,    +491,    +440,
    +392,    +346,    +303,    +262,    +224,    +189,    +157,    +128,    +101,     +78,     +58,     +40,     +26,     +15,      +7,      +3,
  },
  {
      +1,      +3,      +7,     +15,     +26,     +40,     +58,     +78,    +101,    +128,    +157,    +189,    +224,    +262,    +303,    +346,
    +392,    +440,    +491,    +545,    +601,    +658,    +719,    +781,    +845,    +911,    +978,   +1048,   +1119,   +1191,   +1265,   +1339,
   +1415,   +1492,   +1570,   +1649,   +1728,   +1807,   +1887,   +1968,   +2048,   +2128,   +2209,   +2289,   +2368,   +2447,   +2526,   +2604,
   +2681,   +2757,   +2831,   +2905,   +2977,   +3048,   +3118,   +3185,   +3251,   +3315,   +3377,   +3438,   +3495,   +3551,   +3605,   +3656,
   +3704,   +3750,   +3793,   +3834,   +3872,   +3907,   +3939,   +3968,   +3995,   +4018,   +4038,   +4056,   +4070,   +4081,   +4089,   +4093
  }
};

uint16_t  SineFast[2][NWAVE] = {
  {
   +4095,   +3939,   +3495,   +2831,   +2048,   +1265,    +601,    +157,      +1,    +157,    +601,   +1265,   +2048,   +2831,   +3495,   +3939,
   +4095,   +3939,   +3495,   +2831,   +2048,   +1265,    +601,    +157,      +1,    +157,    +601,   +1265,   +2048,   +2831,   +3495,   +3939,
   +4095,   +3939,   +3495,   +2831,   +2048,   +1265,    +601,    +157,      +1,    +157,    +601,   +1265,   +2048,   +2831,   +3495,   +3939,
   +4095,   +3939,   +3495,   +2831,   +2048,   +1265,    +601,    +157,      +1,    +157,    +601,   +1265,   +2048,   +2831,   +3495,   +3939,
   +4095,   +3939,   +3495,   +2831,   +2048,   +1265,    +601,    +157,      +1,    +157,    +601,   +1265,   +2048,   +2831,   +3495,   +3939
  },
  {
   +4095,   +3939,   +3495,   +2831,   +2048,   +1265,    +601,    +157,      +1,    +157,    +601,   +1265,   +2048,   +2831,   +3495,   +3939,
   +4095,   +3939,   +3495,   +2831,   +2048,   +1265,    +601,    +157,      +1,    +157,    +601,   +1265,   +2048,   +2831,   +3495,   +3939,
   +4095,   +3939,   +3495,   +2831,   +2048,   +1265,    +601,    +157,      +1,    +157,    +601,   +1265,   +2048,   +2831,   +3495,   +3939,
   +4095,   +3939,   +3495,   +2831,   +2048,   +1265,    +601,    +157,      +1,    +157,    +601,   +1265,   +2048,   +2831,   +3495,   +3939,
   +4095,   +3939,   +3495,   +2831,   +2048,   +1265,    +601,    +157,      +1,    +157,    +601,   +1265,   +2048,   +2831,   +3495,   +3939
  }
};

            int       fast_mode  =    0;
            int       freq_inhz  = 1000; // Default Hz
            int       freq_intc  =    0;
            int       user_intf  =    0;

volatile   uint16_t   sptr       =    0;

void setup()
{
  Serial.begin (115200);
  dac_setup();       
  freq_intc = freqToTc(freq_inhz);
  TC_setup();       
  setup_pio_TIOA0();
}

int tcToFreq( int tc_cntr)
{
  int freq_hz;     

  if( tc_cntr == 0 ) return 1000;
  if( fast_mode ) freq_hz = (420000000UL / tc_cntr) / (2 * NWAVE);
  else            freq_hz = ( 42000000UL / tc_cntr) / (2 * NWAVE);
  return freq_hz;   
}

int freqToTc( int freq_hz)
{
  int tc_cntr = 0;

  if( freq_hz == 0 ) return 25;
  if( fast_mode ) tc_cntr = (420000000UL / freq_hz) / (2 * NWAVE);
  else            tc_cntr = ( 42000000UL / freq_hz) / (2 * NWAVE);
  return tc_cntr;
}

void switch_mode( int mode)
{
  if( mode == 0 )
  {
    DACC->DACC_TPR  =  (uint32_t)  Sinewave[0];      // DMA buffer
    DACC->DACC_TCR  =  NWAVE;
    DACC->DACC_TNPR =  (uint32_t)  Sinewave[1];      // next DMA buffer
    DACC->DACC_TNCR =  NWAVE;
  }
  else
  {
    DACC->DACC_TPR  =  (uint32_t)  SineFast[0];      // DMA buffer
    DACC->DACC_TCR  =  NWAVE;
    DACC->DACC_TNPR =  (uint32_t)  SineFast[1];      // next DMA buffer
    DACC->DACC_TNCR =  NWAVE;
  }
}

void loop()
{
  char in_Byte;
  int     temp;
         
  if (Serial.available() > 0) {
    in_Byte = Serial.read();
    // Message Format To Set  Frequency:  1000f + "send".
    if((in_Byte >= '0') && (in_Byte <= '9'))
    {
      user_intf = (user_intf * 10) + (in_Byte - '0');
    }
    else
    {
      if (in_Byte == 'f') // end delimiter
      {
        if ((user_intf > 20) && (user_intf < 100000))
        {
          freq_inhz = user_intf;
            if( freq_inhz > 10000 ) temp = 1;
            else                    temp = 0;
            if (temp != fast_mode)
            {
              fast_mode = temp;
              switch_mode(fast_mode);
            }
         
          freq_intc = freqToTc(freq_inhz);
          TC_setup();       
          Serial.print("Fast Mode = ");
          Serial.println(fast_mode, DEC);     
          Serial.print("freq_inhz = ");
          Serial.println(freq_inhz, DEC);     
          Serial.print("freq_intc = ");
          Serial.println(freq_intc, DEC);     
          Serial.print("approximation = ");
          temp = tcToFreq(freq_intc);
          Serial.println(temp, DEC);     
        }
      user_intf = 0; // reset to 0 ready for the next sequence of digits
      }   
    }
  }
}

void DACC_Handler(void)
{
  if((dacc_get_interrupt_status(DACC) & DACC_ISR_ENDTX) == DACC_ISR_ENDTX) {
    ++sptr;
    sptr &=  0x01;
    if(fast_mode == 0)
    {
      DACC->DACC_TNPR =  (uint32_t)  Sinewave[sptr];      // next DMA buffer
      DACC->DACC_TNCR =  NWAVE;
    }
  else
    {
      DACC->DACC_TNPR =  (uint32_t)  SineFast[sptr];      // next DMA buffer
      DACC->DACC_TNCR =  NWAVE;
    }
  }
}

void setup_pio_TIOA0() 
{
  PIOB->PIO_PDR = PIO_PB25B_TIOA0; 
  PIOB->PIO_IDR = PIO_PB25B_TIOA0; 
  PIOB->PIO_ABSR |= PIO_PB25B_TIOA0;
}


void TC_setup ()
{
  pmc_enable_periph_clk(TC_INTERFACE_ID + 0 *3 + 0);

  TcChannel * t = &(TC0->TC_CHANNEL)[0];           
  t->TC_CCR = TC_CCR_CLKDIS;                       
  t->TC_IDR = 0xFFFFFFFF;                           
  t->TC_SR;                                         
  t->TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK1 |         
              TC_CMR_WAVE |                         
              TC_CMR_WAVSEL_UP_RC |                 
              TC_CMR_EEVT_XC0 |     
              TC_CMR_ACPA_CLEAR | TC_CMR_ACPC_CLEAR |
              TC_CMR_BCPB_CLEAR | TC_CMR_BCPC_CLEAR;
 
  t->TC_RC = freq_intc;
  t->TC_RA = freq_intc /2;       
  t->TC_CMR = (t->TC_CMR & 0xFFF0FFFF) | TC_CMR_ACPA_CLEAR | TC_CMR_ACPC_SET;
  t->TC_CCR = TC_CCR_CLKEN | TC_CCR_SWTRG;   
}

void dac_setup ()
{
  pmc_enable_periph_clk (DACC_INTERFACE_ID) ; // start clocking DAC
  dacc_reset(DACC);
  dacc_set_transfer_mode(DACC, 0);
  dacc_set_power_save(DACC, 0, 1);            // sleep = 0, fastwkup = 1
  dacc_set_analog_control(DACC, DACC_ACR_IBCTLCH0(0x02) | DACC_ACR_IBCTLCH1(0x02) | DACC_ACR_IBCTLDACCORE(0x01));
  dacc_set_trigger(DACC, 1);
 
//  dacc_set_channel_selection(DACC, 1);
  //dacc_enable_channel(DACC, 1);
  dacc_set_channel_selection(DACC, 0);
  dacc_enable_channel(DACC, 0);

  NVIC_DisableIRQ(DACC_IRQn);
  NVIC_ClearPendingIRQ(DACC_IRQn);
  NVIC_EnableIRQ(DACC_IRQn);
  dacc_enable_interrupt(DACC, DACC_IER_ENDTX);

  DACC->DACC_TPR  =  (uint32_t)  Sinewave[0];      // DMA buffer
  DACC->DACC_TCR  =  NWAVE;
  DACC->DACC_TNPR =  (uint32_t)  Sinewave[1];      // next DMA buffer
  DACC->DACC_TNCR =  NWAVE;
  DACC->DACC_PTCR =  0x00000100;  //TXTEN - 8, RXTEN - 1.
}

« Last Edit: March 25, 2014, 07:09:34 pm by Magician » Logged

Offline Offline
Newbie
*
Karma: 0
Posts: 9
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Thanks for your example. It is hard to find PDC + DAC example for SAM in internet. smiley-lol
Logged

Montreal
Offline Offline
Edison Member
*
Karma: 23
Posts: 2487
Per aspera ad astra.
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Than find and show for everyone.
The problem, that not every example you find would compile with arduino IDE.  Example:
Code:
dacc_disable_interrupt(DACC, DACC_IER_ENDTX);
pdc_enable_transfer(PDC_DACC, PERIPH_PTCR_TXTEN);
First line is o'key, but second generate an error message, because for some reason IDE doesn't include PDC support.
In this topic I demonstrate workaround, (but of course it's not missile technology) to get direct access to a few registers:
Code:
DACC->DACC_TPR  =  (uint32_t)  Sinewave[0];      // DMA buffer
  DACC->DACC_TCR  =  NWAVE;
  DACC->DACC_TNPR =  (uint32_t)  Sinewave[1];      // next DMA buffer
  DACC->DACC_TNCR =  NWAVE;
  DACC->DACC_PTCR =  0x00000100;  //TXTEN - 8, RXTEN - 1.
I also get my DUE 2 weeks ago, and already "impressed" by informativity of the atmel data sheet, where registers TCR and TNPR listed in PDC section, and you have to guess that DACC->DACC_TCR would address to right place!
Logged

Offline Offline
Newbie
*
Karma: 0
Posts: 3
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Any particular reason to use a 2D array for a LUT? 1D array LUT will just work, right?

Also, shouldn't it be
if((dacc_get_interrupt_status(DACC_BASE) & DACC_ISR_ENDTX) ==  DACC_ISR_ENDTX)
instead of using "DACC_IER_ENDTX"

Please check!
« Last Edit: March 25, 2014, 06:26:15 pm by xubuli » Logged

Montreal
Offline Offline
Edison Member
*
Karma: 23
Posts: 2487
Per aspera ad astra.
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
Any particular reason to use a 2D array for a LUT? 1D array LUT will just work, right?
No, plz. read comments in OP. There are 2 array, for freq. <10k, and >10k. DAC sampling conversion limited to ~1.6 MHz, so only 16 values is used to compose sine wave at freq. 10 - 100 k. Of course, distortion level is quite high, I have no means to measure it's directly, but math estimation tells ~1 % THD.

Correction:   I just realized, that you mean something else, why each array is split in 2-subarrays?  This is specific of the DMA (PDC). To make it run continuously, I have to supply two pointers to the data, current one and NEXT. If there is only one, than DMA stops after transmission completed , and I have to jump inside ISR to restart all process again, and there is no warranty that this could be done in time, especially if slot is 84/1.6 = 52 clock cycles CPU. This is why 160-points sinewave array divided in two half. Time slot extended more than 80x, reload pointers timeframe equals all the time till one half is feed-ed to DAC

Quote
Also, shouldn't it be
if((dacc_get_interrupt_status(DACC_BASE) & DACC_ISR_ENDTX) ==  DACC_ISR_ENDTX)
instead of using "DACC_IER_ENDTX"
Here you are right, of course ISR_ENDTX. I changed code to fix a bug, thanks.
« Last Edit: March 26, 2014, 01:10:10 am by Magician » Logged

0
Online Online
Shannon Member
****
Karma: 162
Posts: 10523
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

If you use DDS you use a full size sine-table whatever frequency since the phase value is
always in the correct range.

In short:
Code:
  analogWrite (sine_table [(phase += freq) >> shift]) ;
Logged

[ I won't respond to messages, use the forum please ]

Montreal
Offline Offline
Edison Member
*
Karma: 23
Posts: 2487
Per aspera ad astra.
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
If you use DDS you use a full size sine-table whatever frequency since the phase value is
always in the correct range.

Correct if there is no PDC. DMA controller always address memory sequentially, so phase accumulator technics, that is exploiting jumping along sinewave table is not possible. Don't forget, there is  1.6 MHz transfer rate, and is only way to get 100k sine
Simple DDS, is exactly what Atmel already did, works great,  below 3k...
 
Logged

Pages: [1]   Go Up
Jump to: