Pages: [1]   Go Down
Author Topic: DDS using external spi Dac  (Read 1021 times)
0 Members and 1 Guest are viewing this topic.
Offline Offline
Newbie
*
Karma: 0
Posts: 7
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hello,

I'm currently working on a project to build a DDS system using the external MCP4921 DAC (12 bits).
I've found some example code online for creating waves using DDS and for sending information via
spi. I have tried to manipulated the code to output 12 bit values but there is still some issues that I have yet to figure out.
I have tested my code by connecting the output of the dac to a scope. There is an output
signal but not the one expected. Currently, I've been testing only sine wave.

Some questions I have:
How do I determine the speed of the reference clock I want to use?
How do I determine the maximum output frequency? I found an example that used Max output freq = Max sample rate / # of samples
I was unable to use this equation because I was unsure of the max sample rate. I checked the data sheet for the MCP4921 but I was unable to
find it.

Any help would be greatly appreciated.

Please find below my current code:

Code:
#include "SPI.h"
#include "Math.h"

#define ss 53

void writeData(word val) ;
void setTimer() ;


    double sineLookup[1024] =
    {
      2048,2060,2073,2085,2098,2110,2123,2135,2148,2161,
      2173,2186,2198,2211,2223,2236,2248,2261,2273,2286,
      2298,2311,2323,2335,2348,2360,2373,2385,2398,2410,
      2422,2435,2447,2459,2472,2484,2496,2508,2521,2533,
      2545,2557,2569,2581,2594,2606,2618,2630,2642,2654,
      2666,2678,2690,2702,2714,2725,2737,2749,2761,2773,
      2784,2796,2808,2819,2831,2843,2854,2866,2877,2889,
      2900,2912,2923,2934,2946,2957,2968,2979,2990,3002,
      3013,3024,3035,3046,3057,3068,3078,3089,3100,3111,
      3122,3132,3143,3154,3164,3175,3185,3195,3206,3216,
      3226,3237,3247,3257,3267,3277,3287,3297,3307,3317,
      3327,3337,3346,3356,3366,3375,3385,3394,3404,3413,
      3423,3432,3441,3450,3459,3468,3477,3486,3495,3504,
      3513,3522,3530,3539,3548,3556,3565,3573,3581,3590,
      3598,3606,3614,3622,3630,3638,3646,3654,3662,3669,
      3677,3685,3692,3700,3707,3714,3722,3729,3736,3743,
      3750,3757,3764,3771,3777,3784,3791,3797,3804,3810,
      3816,3823,3829,3835,3841,3847,3853,3859,3865,3871,
      3876,3882,3888,3893,3898,3904,3909,3914,3919,3924,
      3929,3934,3939,3944,3949,3953,3958,3962,3967,3971,
      3975,3980,3984,3988,3992,3996,3999,4003,4007,4010,
      4014,4017,4021,4024,4027,4031,4034,4037,4040,4042,
      4045,4048,4051,4053,4056,4058,4060,4063,4065,4067,
      4069,4071,4073,4075,4076,4078,4080,4081,4083,4084,
      4085,4086,4087,4088,4089,4090,4091,4092,4093,4093,
      4094,4094,4094,4095,4095,4095,4095,4095,4095,4095,
      4094,4094,4094,4093,4093,4092,4091,4090,4089,4088,
      4087,4086,4085,4084,4083,4081,4080,4078,4076,4075,
      4073,4071,4069,4067,4065,4063,4060,4058,4056,4053,
      4051,4048,4045,4042,4040,4037,4034,4031,4027,4024,
      4021,4017,4014,4010,4007,4003,3999,3996,3992,3988,
      3984,3980,3975,3971,3967,3962,3958,3953,3949,3944,
      3939,3934,3929,3924,3919,3914,3909,3904,3898,3893,
      3888,3882,3876,3871,3865,3859,3853,3847,3841,3835,
      3829,3823,3816,3810,3804,3797,3791,3784,3777,3771,
      3764,3757,3750,3743,3736,3729,3722,3714,3707,3700,
      3692,3685,3677,3669,3662,3654,3646,3638,3630,3622,
      3614,3606,3598,3590,3581,3573,3565,3556,3548,3539,
      3530,3522,3513,3504,3495,3486,3477,3468,3459,3450,
      3441,3432,3423,3413,3404,3394,3385,3375,3366,3356,
      3346,3337,3327,3317,3307,3297,3287,3277,3267,3257,
      3247,3237,3226,3216,3206,3195,3185,3175,3164,3154,
      3143,3132,3122,3111,3100,3089,3078,3068,3057,3046,
      3035,3024,3013,3002,2990,2979,2968,2957,2946,2934,
      2923,2912,2900,2889,2877,2866,2854,2843,2831,2819,
      2808,2796,2784,2773,2761,2749,2737,2725,2714,2702,
      2690,2678,2666,2654,2642,2630,2618,2606,2594,2581,
      2569,2557,2545,2533,2521,2508,2496,2484,2472,2459,
      2447,2435,2422,2410,2398,2385,2373,2360,2348,2335,
      2323,2311,2298,2286,2273,2261,2248,2236,2223,2211,
      2198,2186,2173,2161,2148,2135,2123,2110,2098,2085,
      2073,2060,2048,2035,2022,2010,1997,1985,1972,1960,
      1947,1934,1922,1909,1897,1884,1872,1859,1847,1834,
      1822,1809,1797,1784,1772,1760,1747,1735,1722,1710,
      1697,1685,1673,1660,1648,1636,1623,1611,1599,1587,
      1574,1562,1550,1538,1526,1514,1501,1489,1477,1465,
      1453,1441,1429,1417,1405,1393,1381,1370,1358,1346,
      1334,1322,1311,1299,1287,1276,1264,1252,1241,1229,
      1218,1206,1195,1183,1172,1161,1149,1138,1127,1116,
      1105,1093,1082,1071,1060,1049,1038,1027,1017,1006,
      995,984,973,963,952,941,931,920,910,900,
      889,879,869,858,848,838,828,818,808,798,
      788,778,768,758,749,739,729,720,710,701,
      691,682,672,663,654,645,636,627,618,609,
      600,591,582,573,565,556,547,539,530,522,
      514,505,497,489,481,473,465,457,449,441,
      433,426,418,410,403,395,388,381,373,366,
      359,352,345,338,331,324,318,311,304,298,
      291,285,279,272,266,260,254,248,242,236,
      230,224,219,213,207,202,197,191,186,181,
      176,171,166,161,156,151,146,142,137,133,
      128,124,120,115,111,107,103,99,96,92,
      88,85,81,78,74,71,68,64,61,58,
      55,53,50,47,44,42,39,37,35,32,
      30,28,26,24,22,20,19,17,15,14,
      12,11,10,9,8,7,6,5,4,3,
      2,2,1,1,1,0,0,0,0,0,
      0,0,1,1,1,2,2,3,4,5,
      6,7,8,9,10,11,12,14,15,17,
      19,20,22,24,26,28,30,32,35,37,
      39,42,44,47,50,53,55,58,61,64,
      68,71,74,78,81,85,88,92,96,99,
      103,107,111,115,120,124,128,133,137,142,
      146,151,156,161,166,171,176,181,186,191,
      197,202,207,213,219,224,230,236,242,248,
      254,260,266,272,279,285,291,298,304,311,
      318,324,331,338,345,352,359,366,373,381,
      388,395,403,410,418,426,433,441,449,457,
      465,473,481,489,497,505,514,522,530,539,
      547,556,565,573,582,591,600,609,618,627,
      636,645,654,663,672,682,691,701,710,720,
      729,739,749,758,768,778,788,798,808,818,
      828,838,848,858,869,879,889,900,910,920,
      931,941,952,963,973,984,995,1006,1017,1027,
      1038,1049,1060,1071,1082,1093,1105,1116,1127,1138,
      1149,1161,1172,1183,1195,1206,1218,1229,1241,1252,
      1264,1276,1287,1299,1311,1322,1334,1346,1358,1370,
      1381,1393,1405,1417,1429,1441,1453,1465,1477,1489,
      1501,1514,1526,1538,1550,1562,1574,1587,1599,1611,
      1623,1636,1648,1660,1673,1685,1697,1710,1722,1735,
      1747,1760,1772,1784,1797,1809,1822,1834,1847,1859,
      1872,1884,1897,1909,1922,1934,1947,1960,1972,1985,
      1997,2010,2022,2035
       };
    word outVal = 0;
    int initialFreq = 100 ;
    double refFrequency = 0 ;
    unsigned long int tuningWordM;
    long int phaseAccumulator ;
    double waveData[1024] ;

void setup()
  {
   
    Serial.begin(115200);
    pinMode(ss, OUTPUT); //cs flag
    SPI.begin() ; //wake up spi bus
    digitalWrite(ss, HIGH) ; //Set to master mode
   
    setTimer() ; //Set up timer 2
   
    refFrequency = 16000000/(199+1);
    tuningWordM = ((pow(2, 32) * initialFreq) / refFrequency); //Calculate initial tuning word
  }
 void writeData(word val)
   {
     digitalWrite(ss, LOW);
     byte holder = highByte(val) ;
     holder = 0b00001111 & holder ; //Clear 4 MSB for config data
     holder = 0b00110000 | holder ; //Add config data and 4 bits data
     SPI.transfer(holder) ;
     holder = lowByte(val) ;
     SPI.transfer(holder) ;
     digitalWrite(ss, HIGH)  ; 
     delay(25) ;
   } 
   

void setTimer() {
  // Disable interrupts while setting registers
          noInterrupts() ; //disable all interrupts
  TCCR2A = 0;
  TCCR2B = 0;
 
          TCCR2B|= (1 << WGM22);//CTC Mode -Clear Timer on Compare Match (CTC) Mode-
  TCCR2B |= (1 << CS20);  // Prescaler x1
  OCR2A = 199;// Set compared value

  ASSR &= ~(1 << AS2);// Use system clock for Timer/Counter2

  TIMSK2 = 0; // Reset Timer/Counter2 Interrupt Mask Register
  TIMSK2 |= (1 << OCIE2A);// Enable Output Compare Match A Interrupt
  interrupts(); //enable all interrupts
}
 
     ISR(TIMER2_COMPA_vect){
  word lastPhaseAccumulator = phaseAccumulator;
  // Update phase accumulator counter, the phase accumulator will automatically overflow at 2^32
  phaseAccumulator += tuningWordM;
  // Use the 10 MSBs from phase accumulator as frequency information
  word phaseAccumulatorMSBs = (phaseAccumulator >> 22);
  // Set DAC using lookup table (2^10 bits in length)
  outVal = sineLookup[phaseAccumulatorMSBs];
          writeData(outVal) ;

}   
Logged

Montreal
Offline Offline
Faraday Member
**
Karma: 27
Posts: 2569
Per aspera ad astra.
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Looking at
Quote
Fast Settling Time of 4.5 μs
I'd expect 1/4.5 = 222 kHz max for this DAC. May be more, with less accuracy -- higher distortion. I don't think arduino can drive with this speed, depends on the code in interrupt subroutine. Even update with 200 kHz, sine wave max - 100 kHz.  Don't call writedata from ISR, better do it inside. Look up the size of "word" and "double", I don't use them for a while, probably, word is 16-bit on arduino,  too small for phase accumulator. Unsigned long, or long long .
Logged

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

The first issue I figured out was in the interrupt
By adding the following line
 phaseAccumulatorMSBs  = phaseAccumulatorMSBs & 0x03FF ; //Clear bits 13-16
I was successfully able to output a fairly clean 1000 Hz signal using 12 bits of resolution.
I'm guessing since phaseAccumulatorMSBs was defined as a word (16 bits) the excess bits not
used either had random information or were undefined therefore altering the correct value.

Code:
ISR(TIMER2_COMPA_vect){
  word lastPhaseAccumulator = phaseAccumulator;
  // Update phase accumulator counter, the phase accumulator will automatically overflow at 2^32
  phaseAccumulator += tuningWordM;
  // Use the 10 MSBs from phase accumulator as frequency information
  word phaseAccumulatorMSBs = (phaseAccumulator >> 22); //word is 16 bits but I'm only filling 10
        phaseAccumulatorMSBs  = phaseAccumulatorMSBs & 0x03FF ; //Clear bits 13-16
  // Set DAC using lookup table (2^10 bits in length)
  outVal = sineLookup[phaseAccumulatorMSBs];
          writeData(outVal) ;


Magician I unsure what you mean when you said " Don't call writedata from ISR, better do it inside."
Where do you mean by inside?

Now that I have the maximum output frequency how do I determine the proper reference clock to reach 100 kHz?
I referenced the equation fo = m*fc / 2n
Assuming my maximum output is 100,000 = m*fc / 232?
But I'm unsure how to incorporate M to correctly determine the fc.
In my code as well I'm unsure how to use M.
Previously I was using this as my M but after some tests I realized this actually had no effect on the signal output
Code:
OCR2A = 199;// Set compared value
Logged

Offline Offline
Edison Member
*
Karma: 116
Posts: 2205
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

It takes about 100us to transmitt a byte with software spi @ 1MIPS. At 16MIPS, you can cut that down to about 10us. For your spi, you need to transmit two bytes -> 20us per point. You have 1024 points -> 20ms per cycle, or 50hz.

You can then figure out how to speed it up / down, or to compromise it to get to what you want, as the math is basically the same.
Logged

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

After some extra reading I was able to understand how the tuning word (m) works.
I'm still having trouble understanding how to choose the proper reference clock.

I also moved on to implementing some other features such as buttons to increase and decrease voltage,
as well as changing the waveform. I've stumbled upon some new issues I'm currently stuck on.

When I'm running my code for some reason the code does not enter into the main loop, and
the cause of this is the interrupt where the output is generated. I'm not sure if there is something
I'm missing or I'm utilizing the interrupt improperly. I updated my old code to work with 8 bits instead,
for the increased accuracy I was receiving (until I can figure out the ref clock).

Here is my updated code (sorry for it being so long)

Code:
 //Main Program Function Generator
 
  #define ss 47


  /*Slave Select Definitions*/
  //ss - on pin 47 - Sending Waveforms

  #define SignalSelection 2
  #define FreqInc10 3
  #define FreqInc100 4
  #define FreqDec10 5
  #define FreqDec100 6
  
  /*Button Definitions*/
  //button on pin 2
  //button on pin 3
  //button on pin 4`
  //button on pin 5
  //button on pin 6

  LiquidCrystal lcd(24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34);// Initialize number of LCD data pins
  
  Bounce bouncer = Bounce(SignalSelection, 10); //Create Debounce on button with 10ns
  Bounce bouncer1 = Bounce(FreqInc10, 10); //Create Debounce on button with 10ns
  Bounce bouncer2 = Bounce(FreqInc100, 10); //Create Debounce on button with 10ns
  Bounce bouncer3 = Bounce(FreqDec10, 10); //Create Debounce on button with 10ns
  Bounce bouncer4 = Bounce(FreqDec10, 10); //Create Debounce on button with 10ns

  
  int SignalSelect ; //Store Pin Value
  int IncFreq10 ;//Store Pin Value
  int IncFreq100 ;//Store Pin Value
  int DecFreq10 ;//Store Pin Value
  int DecFreq100 ;//Store Pin Value

  void writeDataFG(word val) ; //Used to write to Data -waveforms-
  void setTimer() ; //Time for checking for overflow

  
    word outVal = 0;
    int freq = 250  ;  
    double refFrequency = 31376.6 ; //8 bit
    unsigned long int tuningWordM;
    long int phaseAccumulator ;
    byte phaseAccumulatorMSBs ;
    int signal = 0 ;
    

  void setup()
    {
        Serial.begin(115200);
        
        pinMode(ss, OUTPUT); //Slave Select -Sending Waveforms-
        pinMode(ss2, OUTPUT); //Slave Select -DC Offset-
        pinMode(ss3, OUTPUT); //Slave Select -Digi Pot-
        
        pinMode(SignalSelection, INPUT) ; //Set pin as input
        pinMode(FreqInc10, INPUT) ; //Set pin as input
        pinMode(FreqInc100, INPUT) ; //Set pin as input
        pinMode(FreqDec10, INPUT) ; //Set pin as input
        pinMode(FreqDec100, INPUT) ; //Set pin as input

        
        pinMode(SignalSelection, HIGH) ; //enable pullup
        pinMode(FreqInc10, HIGH) ; //enable pullup
        pinMode(FreqInc100, HIGH) ; //enable pullup
        pinMode(FreqDec10, HIGH) ; //enable pullup
        pinMode(FreqDec100, HIGH) ; //enable pullup
    
        SPI.begin() ; //wake up spi bus
        digitalWrite(ss, HIGH) ; //Set to master mode
                
        //setTimer() ; //Initialize Timer

        tuningWordM = ((pow(2, 32) * freq) / refFrequency); //Calculate initial tuning word

    }
 void writeDataFG(word val)
   {
     digitalWrite(ss, LOW);
     byte holder = highByte(val) ;
     holder = 0b00001111 & holder ; //Clear 4 MSB for config data
     holder = 0b00110000 | holder ; //Add config data and 4 bits data
     SPI.transfer(holder) ;
     holder = lowByte(val) ;
     SPI.transfer(holder) ;
     digitalWrite(ss, HIGH)  ;  
   }  

  void setTimer()
        {
 // Disable interrupts while setting registers
          noInterrupts() ; //disable all interrupts
          
 TCCR2A = 0;//Initialize Register A to 0
 TCCR2B = 0;//Initialize Register B to 0
 
          TCCR2B|= (1 << WGM22);//CTC Mode -Clear Timer on Compare Match (CTC) Mode-
 TCCR2B |= (1 << CS20);  // Prescaler x1
 
 ASSR &= ~(1 << AS2);// Use system clock for Timer/Counter2

 TIMSK2 = 0; // Reset Timer/Counter2 Interrupt Mask Register
 TIMSK2 |= (1 << OCIE2A);// Enable Output Compare Match A Interrupt

 interrupts(); //enable all interrupts
          //OCR2A = 4;// Set compared value
}
 
     ISR(TIMER2_COMPA_vect){
  phaseAccumulator += tuningWordM; // Update phase accumulator counter, the phase accumulator will automatically overflow at 2^32
  phaseAccumulatorMSBs = (phaseAccumulator >> 24); // Use the 8 MSBs from phase accumulator as frequency information
           //If you want 10 bit shift by 22 and change BYTE to WORD
           //phaseAccumulatorMSBs = phaseAccumulatorMSBs & 0x03FF ;
           //The un-used 6 upper bits must be clear to ensure no random information
           if(signal == 0)
               {
                 outVal = sawtoothLookup[phaseAccumulatorMSBs];
                 outVal = outVal << 4 ;
                 writeDataFG(outVal) ;
               }
            else if(signal == 1)
               {
               outVal = squareLookup[phaseAccumulatorMSBs];
               outVal = outVal << 4 ;
               writeDataFG(outVal) ;
               }
              else if(signal == 2)
               {
               outVal = triangleLookup[phaseAccumulatorMSBs];
               outVal = outVal << 4 ;
               writeDataFG(outVal) ;
               }
              else if(signal == 3)
               {
               outVal = sawtoothLookup[phaseAccumulatorMSBs];
               outVal = outVal << 4 ;
               writeDataFG(outVal) ;
               }
              else
               {
               outVal = revsawtoothLookup[phaseAccumulatorMSBs];
               outVal = outVal << 4 ;
               writeDataFG(outVal) ;
               }    
}  
  void loop()
    {
      if((bouncer.update() == true) || ( bouncer1.update() == true) ||
         (bouncer2.update() == true) || (bouncer3.update() == true) ||
         (bouncer4.update() == true))
           {  //check to see if any button changed state
  
             SignalSelect = bouncer.read() ;//read debounced pin
             IncFreq10 = bouncer1.read() ;//read debounced pin
             IncFreq100 = bouncer2.read() ;//read debounced pin
             DecFreq10 = bouncer3.read() ;//read debounced pin
             DecFreq100 = bouncer4.read() ;//read debounced pin
  
              if(SignalSelect == HIGH)
                {
                    if(signal == 4)
                      {
                         signal = 0 ;//Only 5 total waves
                      }
                    else
                      {
                         signal = signal + 1 ;
                      }
                            if(signal == 0)
                               {
                                  lcd.setCursor(15,0) ; //Set cursor to position 15 row 1
                                  lcd.print("SineWave");
                               }
                            else if(signal == 1)
                               {
                                  lcd.setCursor(15,0) ; //Set cursor to position 15 row 1
                                  lcd.print(" Square ");
                               }
                            else if(signal == 2)
                               {
                                  lcd.setCursor(15,0) ; //Set cursor to position 15 row 1
                                  lcd.print("Triangle");
                               }
                            else if(signal == 3)
                               {
                                  lcd.setCursor(15,0) ; //Set cursor to position 15 row 1
                                  lcd.print("SawTooth");
                               }
                            else
                               {
                                  lcd.setCursor(15,0) ; //Set cursor to position 15 row 1
                                  lcd.print("RevSawTh");
                               }
                }
              else if(IncFreq10 == HIGH)
                {
                   freq = freq + 10 ;
                   tuningWordM = ((pow(2, 32) * freq) / refFrequency); //calculate new tuning word      
                }
              else if(IncFreq100 == HIGH)
                {
                   freq = freq + 100 ;
                   tuningWordM = ((pow(2, 32) * freq) / refFrequency); //calculate new tuning word      
                }
              else if(DecFreq10 == HIGH)
                {
                   freq = freq - 10 ;
                   tuningWordM = ((pow(2, 32) * freq) / refFrequency); //calculate new tuning word      
                }
              else if(DecFreq100 == HIGH)
                {
                   freq = freq - 100 ;
                   tuningWordM = ((pow(2, 32) * freq) / refFrequency); //calculate new tuning word      
                }  
             else
                {
                     //do nothing
                }
        
    }
  
  

    
Logged

United Kingdom
Offline Offline
Tesla Member
***
Karma: 224
Posts: 6593
Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law.
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Code:
double sineLookup[1024] =
    {
      2048,2060,2073,2085,2098,2110,2123,2135,2148,2161,
      ...

Why have you declared sineLookup with type double, since you are only going to convert it back to integer to send to the DAC? Declare it with type uint16_t instead.

If you use many of those lookup tables, you'll run out of RAM (you must be using a mega or similar, otherwise you would have already). Look up PROGMEM in the reference section of the main Arduino site to see how to avoid this.
Logged

Formal verification of safety-critical software, software development, and electronic design and prototyping. See http://www.eschertech.com. Please do not ask for unpaid help via PM, use the forum.

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

Originally, I was generating the values in that table which were then double values.

I have already changed from  double to bytes instead of 16 bit values.
Currently, I'm using 8 bit resolution because I was able to produce
my "expected" input frequency. Or at least get a closer output frequency to
my expect. Once I determine how to correctly select the reference clock
I will try to switch back to 12 bit resolution.
Logged

Pages: [1]   Go Up
Jump to: