Direct Digital Synthesizer (DDS) (Part 2)
Part 1, 2, 3, 4, 5, 6, 7, 8, 9
Now that we have the hardware ready, let’s go for the firmware! Firstly, we need a reference table containing wave data points. In our case, we will use a 256 steps table. In the context of this project, the content of this table will change depending on a wave type option: apart from the standard wave profiles (sine, square, triangle) and some more exotic ones (saw teeth, random), you will be able to create your own wave profiles. Custom data shall be recorded in the non volatile memory (EEPROM) using the read() and write() functions from the EEPROM library in order to avoid boring uploads.
// Generate wave data in global vector void generateWaveData(int style){ switch(style){ case OPT_WAV_TYP_FLAT: // Flat for (int i=0; i < stepsPerCycle; i++) vWaveData[i] = standByValue; break; case OPT_WAV_TYP_SINE: // Sine for (int i=0; i < stepsPerCycle; i++) vWaveData[i] = (maxDACValue*(sin((i*6.2831)/stepsPerCycle)+1))/2; break; case OPT_WAV_TYP_SQUARE: // Square for (int i=0; i < stepsPerCycle/2; i++) vWaveData[i] = 0; for (int i=stepsPerCycle/2; i < stepsPerCycle; i++) vWaveData[i] = maxDACValue; break; case OPT_WAV_TYP_TRIANGLE: // Triangle for (int i=0; i < stepsPerCycle/2; i++) vWaveData[i] = (i*maxDACValue*2)/stepsPerCycle; for (int i=stepsPerCycle/2; i < stepsPerCycle; i++) vWaveData[i] = ((stepsPerCycle-i)*maxDACValue*2)/stepsPerCycle; break; case OPT_WAV_TYP_SAW_TOO_POS_SLP: // Saw tooth positive slope for (int i=0; i < stepsPerCycle; i++) vWaveData[i] = (i*maxDACValue)/stepsPerCycle; break; case OPT_WAV_TYP_SAW_TOO_NEG_SLP: // Saw tooth negative slope for (int i=0; i < stepsPerCycle; i++) vWaveData[i] = ((stepsPerCycle-i)*maxDACValue)/stepsPerCycle; break; case OPT_WAV_TYP_RANDOM: // Random (Noise) // randomSeed(millis()); for (int i=0; i < stepsPerCycle; i++) vWaveData[i] = random(0, maxDACValue); break; case OPT_WAV_TYP_CUSTOM: // Custom wave for (int i=0; i < stepsPerCycle; i++) vWaveData[i] = EEPROM.read(i); break; } }
Note: OPT_WAV_TYP_xxx are global constants. Steps per cycle is 256 (table length) and max dac counts are 2^6 – 1 = 63, where 6 is the number of bits of the R2R ladder DAC.
Then we need a time base. It has to be fast and accurate. We will use timer/counter2 to do the job and perform conversions inside the Interrupt Service Routine (ISR). The number of instructions within the ISR shall be kept as short as possible in order to prevent time overlaps.
// Interrupt service run when Timer/Counter2 reaches OCR2A ISR(TIMER2_COMPA_vect){ long lastPhaseAccumulator = phaseAccumulator; // Update phase accumulator counter, the phase accumulator will automatically overflow at 2^32 phaseAccumulator += tuningWordM; // Use the 8 MSBs from phase accumulator as frequency information byte phaseAccumulatorMSBs = (phaseAccumulator >> 24); // Set R2R DAC using lookup table (2^8 bits in length) PORTB = vWaveData[phaseAccumulatorMSBs]; // For diag purpose: warning, execution of these lines take time! PORTD ^= (1 << PIND6); if (lastPhaseAccumulator >= phaseAccumulator) { PORTD ^= (1 << PIND5); } }
Note: From the code, you may understand that the time base ticks can be monitored on PIND6, while cycle ticks can be monitored on PIND5. These lines can be commented in order to save a little extra time.
Here is the initialization routine for timer/counter2:
// Initialize Timer/Counter2 parameters void setTimer() { // Disable interrupts while setting registers cli(); // Reset control registers TCCR2A = 0; TCCR2B = 0; // Clear Timer on Compare Match (CTC) Mode TCCR2A |= (1 << WGM21); // Prescaler x1 TCCR2B |= (1 << CS20); // Set compared value OCR2A = upperCountingValue; // Use system clock for Timer/Counter2 ASSR &= ~(1 << AS2); // Reset Timer/Counter2 Interrupt Mask Register TIMSK2 = 0; // Enable Output Compare Match A Interrupt TIMSK2 |= (1 << OCIE2A); // Enable interrupts once registers have been updated sei(); }
Note: Timer/counters 0 and 1 are disabled at the se up level in order to prevent timer/counter2 unstability. Delays have been replaced by NOP (No OPeration) instructions.
// Reset timer/counters 0 and 1 in order to reduce noise on timer/counter2 TIMSK0 = 0; TIMSK1 = 0;
The reference frequency is derived from the MCU frequency (16 Mhz in Arduino’s case) and from the timer/counter2 settings. For an upper counting value of 199, a prescaler of 1, the reference frequency is 80 KHz.
const int upperCountingValue = 199; // Change value for setting different reference frequency. Minimum is 127, for no synch pins configuration const double refFrequency = 16000000/(upperCountingValue+1); // CPU_frequency/upperCountingValue/prescaler
Setting the cycle frequency is as simple as computing the magic tuningWord!
// Set frequency; int setFrequency(int frequency){ // Calculate tuning word value tuningWordM = (unsigned long) ((pow(2, 32) * frequency) / refFrequency); }
Dear All;
Dear Friends;
I would like to produce different analog Waveforms (Sinewave, Triangle, Squarewave …) with different frequencies through a Arduino Duemilanove. I would like to have the waveforms on three different ports.
I have used the “DIRECT DIGITAL SYNTHESIZER (DDS)” method.
In order to have three different waveforms on three ports I need to use “Compare interrupts” of TIMER0, TIMER1 and TIMER2.
To test the performance of this method, I first wrote a simple code which is similar to the code above. I have included my code below for your review. Now, Here is the problem:
I can successfully generate signals with desired frequencies on the ports which are being updated by TIMER1 and TIMER2 Interrupts, but the frequnecy of the signal which is being generated by TIMER0 interrupt is 1/3 (One third) of the two other signals (generated by the TIMER1 and TIMER2 interrupts). For example when I have 250Hz signal on PortB and PortD, There is a 80Hz signal on PortC!!!
The strange thing here is when I remove TIMER1 Interrupt from this code, the signal frequency on PortC which is being updated by TIMER0 becomes 250Hz!!!!
Can anyone please tell me what is the probelm?
———————————————————-
int vWaveData[256];
long int i;
unsigned long int tuningWordM;
int upperCountingValue, desiredfreq;
double refFrequency;
long int phaseAccumulator0,phaseAccumulator1,phaseAccumulator2;
void generateWaveData(); // Generate wave data in global vector
void setup()
{
cli(); // Disable interrupts while setting registers
TCCR2A = 0; // Reset control registers
TCCR2B = 0; // Reset control registers
TCCR2A |= (1 << WGM21); // Clear Timer2 on Compare Match (CTC) Mode
TCCR2B |= (1 << CS20); // Prescaler x1 for Timer2
OCR2A = 255; // 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
TCCR1A = 0; // Reset control registers
TCCR1B = 0; // Reset control registers
TCCR1B |= (1 << WGM12); // Clear Timer on Compare Match (CTC) Mode
TCCR1B |= (1 << CS10); // Prescaler x1
OCR1A = 255; // Set compared value
TIMSK1 = 0; // Reset Timer/Counter2 Interrupt Mask Register
TIMSK1 |= (1 << OCIE1A); // Enable Output Compare Match A Interrupt */
TCCR0A = 0; // Reset control registers
TCCR0B = 0; // Reset control registers
TCCR0A |= (1 << WGM01); // Clear Timer on Compare Match (CTC) Mode
TCCR0B |= (1 << CS00); // Prescaler x1
OCR0A = 255; // Set compared value
TIMSK0 = 0; // Reset Timer/Counter2 Interrupt Mask Register
TIMSK0 |= (1 << OCIE0A); // Enable Output Compare Match A Interrupt
generateWaveData(); // Create Sinewave look-up table
upperCountingValue = 255;
desiredfreq=250;
refFrequency = 16000000/(upperCountingValue+1);
tuningWordM = ((pow(2, 32) * desiredfreq) / refFrequency);
DDRB = B11111111; //Set PORTB as output
DDRC = B11111111; //Set PORTC as output
DDRD = B11111111; //Set PORTD as output
sei(); // Enable interrupts once registers have been update
}
void loop()
{
}
void generateWaveData() //Generate a Sawtooth Wave
{
for (int i=0; i > 24);
PORTB = vWaveData[phaseAccumulatorMSB2];
}
ISR(TIMER1_COMPA_vect) // Interrupt service run when Timer/Counter1 reaches OCR1A
{
phaseAccumulator1 += tuningWordM;
byte phaseAccumulatorMSB1 = (phaseAccumulator1 >> 24);
PORTD = vWaveData[phaseAccumulatorMSB1];
}
ISR(TIMER0_COMPA_vect) // Interrupt service run when Timer/Counter1 reaches OCR1A
{
phaseAccumulator0 += tuningWordM;
byte phaseAccumulatorMSB0 = (phaseAccumulator0 >> 24);
PORTC = vWaveData[phaseAccumulatorMSB0];
}
Generally speaking, the only idea that crosses my mind is that the interrupt generated by timer0 has the lowest priority (Check Table 11-2. Reset and Interrupt Vectors in ATmega88) versus Timer 2 (8 th position) and Timer 1 (12 th position). May be are you experiencing an interrupts conflict, which is canceled when removing one timer with higher priority?
Q1: The generateWaveData routine and its use look strange tome! Can you explain?
I am new to the Arduino and do not understand what to do with your code. Do I put it all in one file and upload to the Arduino? Or, do I load part of it from Processing (assuming that you are using the Processing interface).
Thanks for the project!!
Mark
Please check this thread
Thanks, admin, but the link you provided appears to be to a basic overview of Arduino. I am familiar enough with Arduino to have several projects running. I’m just confused about what to do with the various code fragments above.
Mark
OK, I am with you now (BTW admin==Didier)
I am making public portions of code that I specifically worked on and which bring significant informationto programmers. However, I plan to open a download area to registered users…