dds sine wave frequency changing

hello everybody,

i'm working on this sketch :
http://interface.khm.de/index.php/lab/experiments/arduino-dds-sinewave-generator/

i just change some lines in order to change the frequency according my sensor's value. it works well up to
1000hz then i can hear on the background some strange noise like randomly falling notes. it's quiet.
i think it comes from the fact that current frequency value is sent at a different clock (loop clock) than the one of the interrupt function (sampling rate clock). i believe that i can't put analogRead in the interrupt function because of the process cost.
maybe one of the solution is to ask the time interrupt function to wait the end of a cycle before
updating the frequency value. So if the interrupt function reach the end of a cycle (end of the sine array)
it updates the frequency according to my sensor value. i don't know how to make it...
so how can i synchronize my sensor value with the time interrupt ?

i just change this part :

while(1) {
if (c4ms > 10) { // timer / wait for 40ms
c4ms=0;
//dfreq=analogRead(0); // read Poti on analog pin 0 to adjust output frequency from 0..1023 Hz
dfreq=440+(analogRead(0)); //my sensor value

cbi (TIMSK2,TOIE2); // disble Timer2 Interrupt
sbi (TIMSK2,TOIE2); // enable Timer2 Interrupt

/* Serial.print(dfreq);
Serial.print(" ");
Serial.println(tword_m);*/
}
// calulate DDS new tuning word
tword_m=pow(2,32)*dfreq/refclk;

sbi(PORTD,6); // Test / set PORTD,7 high to observe timing with a scope
cbi(PORTD,6); // Test /reset PORTD,7 high to observe timing with a scope
}

the complete sketch is attached

thank you !

KHMDDSsignalReq.pde (4.56 KB)

I don't pretend to understand all the intricacies of that sketch, but I follow the general idea that you are using a timer to increment the index into your sine256 table and the timer value is being scaled to produce your required output frequency.

When you change the frequency you change the offset into the sine wave which introduces a discontinuity into the shape of the wave. The shorter the wave length the great the impact of a given frequency shift would be. I guess this is what is causing your problem and this would explain why it seems acceptable at lower frequencies.

I can see two ways to tackle the problem.

Option 1 is to put the results of your frequency calculation into a temporary variable and only apply them to the 'live' working variable tword_m when your index into sine256 is at zero. This means the change does not cause a phase shift in the output so there is no discontinuity.

Option 2 is to change the code that calculates tword_m so that instead of just setting it to the value corresponding to the new frequency, it increments (or decrements) to move the frequency towards the new value. Since this is being re-evaluated very frequently the frequency shift would not take very long to complete, but it would occur gradually rather than as a step change. There would still be some distortion of the output signal, but hopefully a lot less.

I think I prefer the first approach but you could try either and see which works best.

By the way, is there any reason for disabling and them immediately re-enabling Timer2 interrupts within loop()? That might also cause occasional glitches.

yes your option 1 is exactly what i plan to do but i don't manage to make it right. i 've tried this but it doesn't change anything... i add another tword variable, so tword_m stay in the time interrupt function and tword (current frequency calculated with the sensor value) is in the loop()

in the loop() :

while(1) {
if (c4ms > 10) { // timer / wait for 40ms
c4ms=0;
//dfreq=analogRead(0); // read Poti on analog pin 0 to adjust output frequency from 0..1023 Hz
dfreq=2440+(analogRead(0));

cbi (TIMSK2,TOIE2); // disble Timer2 Interrupt
sbi (TIMSK2,TOIE2); // enable Timer2 Interrupt

/* Serial.print(dfreq);
Serial.print(" ");
Serial.println(tword_m);*/
}
// calulate DDS new tuning word
tword = pow(2,32)*dfreq/refclk;

sbi(PORTD,6); // Test / set PORTD,7 high to observe timing with a scope
cbi(PORTD,6); // Test /reset PORTD,7 high to observe timing with a scope
}

in the interrupt function :

ISR(TIMER2_OVF_vect) {

sbi(PORTD,7); // Test / set PORTD,7 high to observe timing with a oscope

phaccu=phaccu+tword_m; // soft DDS, phase accu with 32 bits
icnt=phaccu >> 24; // use upper 8 bits for phase accu as frequency information
// read value fron ROM sine table and send to PWM DAC
OCR2A=pgm_read_byte_near(sine256 + icnt);

//when index is at 0 update tword_m
if(icnt==0){
tword_m = tword;
//Serial.println(icnt);
}

if(icnt1++ == 125) { // increment variable c4ms all 4 milliseconds
c4ms++;
icnt1=0;
}

cbi(PORTD,7); // reset PORTD,7
}

i 've tried the sketch without the timer2 enable/disable it seems to work as before... it doesn't change.

The tword and twordm variables are bigger than a byte which means that updates to them are not atomic. It's just barely possible that the interrupt handler is being called while the main loop is in the middle of updating the value, and seeing an inconsistent value. (It doesn't feel very likely, but it's possible.)

I think the code to disable/enable interrupts is pointless where you have it, but if you put that around the code to update tword then that would ensure the interrupt handler saw the correct value.

when i put tword between disable/enable i hear some dirty noise because the clock of the loop() is too slow... that's why i use to separate them. i don't know what to do...

//when index is at 0 update tword_m
if(icnt==0){
tword_m = tword;
Serial.println(icnt);
}

moreover serial.println doesn't output anything, the serial monitor is scrolling but nothing appears...

polish:
when i put tword between disable/enable i hear some dirty noise because the clock of the loop() is too slow... that's why i use to separate them. i don't know what to do...

As far as I understand it, that code in loop() is only updating the frequency, it does not have to be executed at any particular interval and does not directly affect the output. I can't interpret your comment about 'the clock of the loop' in any way that makes sense. The interrupts should only need to be disabled for long enough for you to assign one long value - I'd be astonished if that affected the output enough to be audible. Can you post the code with that change so I can check whether you did what I expected?

Also, I noticed a commented-out Serial.print in your interrupt handler. You should not ever write to the serial port from within an interrupt handler.

believe that i can't put analogRead in the interrupt function because of the process cost.

That's certain. analogRead() takes around 100 us, and your interrupt is coming every 32 us. If you do an analogRead() inside the ISR, two or three more interrupts will occur, and be missed, before the analogRead() completes.

[W]hen i put tword between disable/enable i hear some dirty noise because the clock of the loop() is too slow

That's not particularly surprising, either. Calculating tword_m takes a floating point division and multiplication, and conversion from float to long. I'd be surprised if it could complete in 32 us, or about 500 CPU cycles. A quick test suggests that it might take about 44 us to make the calculation - too long to avoid missing an interrupt.

The guys at the Academy of Media Arts in Cologne may have overlooked this problem when they wrote their program, since they only recalculated once per second, instead of 25 times like your program does. There's no point in suspending interrupts to do an interruptible calculation. To avoid the problem, you can move the calculation out of the sensitive zone - calculate the tuning word and store it in a temporary variable, disable the interrupt, copy the tuning word to the active variable, and reenable the interrupt. Like this:

unsigned long tword_t;  // temporary variable for tuning word

      tword_t = pow(2,32) * dfreq / refclk;
      cbi (TIMSK2,TOIE2);              // disble Timer2 Interrupt
      tword_m = tword_t;
      sbi (TIMSK2,TOIE2);              // enable Timer2 Interrupt

That leaves the slow calculation where it can be interrupted, and only requires a few cycles while interrupts are disabled to complete the assignment. If the calculation happens while the interrupt is disabled, that makes for substantial phase jitter happening 25 times per second - likely to be audible.

Serial.println doesn't output anything, the serial monitor is scrolling but nothing appears

Again, no surprise. Your baud rate is 9600; at that speed it takes nearly a millisecond to print a single character. Your tuning word is quite long. If you try to print it every 40 ms, as your code indicates, the printing operation won't be able to keep up. If you try to print from the ISR, you'll be printing something every 32 us, and that's way too fast. Pick a much higher baud rate, and print less often. And, as has already been said, it's a lousy idea to print from an ISR.

Finally, consider how quickly your input value may be changing. if it can move very far in 40 ms, you might consider sampling less often, or mayby you'd prefer to digitally filter the analog reading.

of course it's audible ! it's simple if i put this i can hear clic for each c4ms condition :

while(1) {
if (c4ms > 10) { // timer / wait for 40ms
c4ms=0;
//dfreq=analogRead(0); // read Poti on analog pin 0 to adjust output frequency from 0..1023 Hz
dfreq=2440+(analogRead(0));

cbi (TIMSK2,TOIE2); // disble Timer2 Interrupt
tword = pow(2,32)*dfreq/refclk;

sbi (TIMSK2,TOIE2); // enable Timer2 Interrupt

/* Serial.print(dfreq);
Serial.print(" ");
Serial.println(tword_m);*/
}
// calulate DDS new tuning word

sbi(PORTD,6); // Test / set PORTD,7 high to observe timing with a scope
cbi(PORTD,6); // Test /reset PORTD,7 high to observe timing with a scope
}

now if i change the place of tword in the loop like this, there is no clicks and pops but i have some
strange noise in the background (freq>1000hz), like a quiet feedback of notes :

while(1) {
if (c4ms > 10) { // timer / wait for 40ms
c4ms=0;
//dfreq=analogRead(0); // read Poti on analog pin 0 to adjust output frequency from 0..1023 Hz
dfreq=2440+(analogRead(0));

cbi (TIMSK2,TOIE2); // disble Timer2 Interrupt
sbi (TIMSK2,TOIE2); // enable Timer2 Interrupt

/* Serial.print(dfreq);
Serial.print(" ");
Serial.println(tword_m);*/
}
// calulate DDS new tuning word
tword = pow(2,32)*dfreq/refclk;

sbi(PORTD,6); // Test / set PORTD,7 high to observe timing with a scope
cbi(PORTD,6); // Test /reset PORTD,7 high to observe timing with a scope
}

so i've got to choosee between clicking sounds or strange feedback :slight_smile:

i forgot to precise my output, i use a rc filter with a resistor 330 ohm and a capacitor of 0.01uf. normally i should use a 300ohm resistor (for 50khz corner frequency) but i make this rc filter with what i have :slight_smile: maybe this strange feedback comes from there ? from the 30ohms difference ?
then after the low pass i use a LM386 ship...
on the website where i take the code they use a chebyshef low pass circuit, i should use the same but i don't have all the components.

tmd3 your solution works (in term of sounds) as what i have done before (don't ask me why :slight_smile: ) :
while(1) {
if (c4ms > 10) { // timer / wait for 40ms
c4ms=0;
//dfreq=analogRead(0); // read Poti on analog pin 0 to adjust output frequency from 0..1023 Hz
dfreq=2440+(analogRead(0));

cbi (TIMSK2,TOIE2); // disble Timer2 Interrupt
sbi (TIMSK2,TOIE2); // enable Timer2 Interrupt

}
//i just put this here
// calulate DDS new tuning word
tword = pow(2,32)*dfreq/refclk;

sbi(PORTD,6); // Test / set PORTD,7 high to observe timing with a scope
cbi(PORTD,6); // Test /reset PORTD,7 high to observe timing with a scope
}

your solution is of course better because you understand all te process, but at final the sound is the same.
i've got the same strange feedback in the background. in fact i have two sounds, the sinewave in the foreground and some randomly quiet notes in the back.
maybe the problem comes from my lowpass RC filter and more precisely its inaccurate precision (i talk about it at the end of the previous post).

Two things you must do:

  1. Disable the Timer2 interrupt while you change the tuning word, and
  2. Calculate the new value of the tuning word while the Timer2 interrupt is enabled.
    See the code, posted earlier, explaining exactly how to accomplish both things.

With regard to your output filter, and selection of components:
Your output signal will vary between 440 Hz and (440 + 1023) = 1463 Hz. Your signals are close to pure sine waves, so you aren't interested in preserving any higher harmonics. The pulse-width modulation frequency is about 32 kHz. The frequency that you want to filter out is 32 kHz, and the frequency that you want to keep is 1463 Hz. There's no point in filtering at 50 kHz. I'd recommend filtering at about 2500 Hz, corresponding to a resistor of about 6K in place of your 330, and see how that works.

If you want pure tones, then you just might have to go get the components for a multi-pole Chebyshev filter. The single-pole resistor-capacitor filter that you're using may not be adequate to meet your expectations.

I'll note that it seems that you don't have a clear understanding of what you're trying to accomplish. I don't think that you understand very well how the program works, how interrupts work, or how the hardware works. If that's so, then I'll recommend that you start with the wikipedia article on pulse width modulation.

no i understand how dds works, the website about it made by the KHM is great for learning the basis.
now you're right about the code implementation, i am lost with some part of the code but the general idea about interrupt and DDS is clear for me. i understand more the theory side than the code process.
when i said to you that your code suggestion works as my previous attempt, it's real. i tried both your code and mine, same result in the speakers, same strange quiet noise. obviously i prefer your code because i know that my version comes from some hazardous trial and errors testing (as a layman :)).
now even if don't understand all the code it should work as its is without this noise.
if i can make this working correctly i will purchase the understanding of the whole code. maybe you want to hear the result, i attach a record, you can hear clearly these strange notes in the background. maybe
this problem comes more from aliasing than from the code (i use a RC filter but maybe it's not enough) ? i don't know how aliasing does sounds like...

thanks for your help.

strangeNotes.wav (3.67 MB)

The noise you're referring to - is it that warbling that starts at about three seconds into the recording?

I wouldn't call that noise - it is the output frequency changing. That implies that you have calculated a new value for the frequency, presumably because analogRead() has returned a new value.

did you hear two distinct sounds in this audio file ? on the foreground we can hear the sinewave sweeping (it's what we want) but if you listen carefully you will notice that there are some others notes in the background. these random notes are audibles (quiet at first) since the begining of the recording but there are louder at 3sec until the end. maybe you have to increase the volume a bit. it sounds like a quiet echo of randomly triggered notes.

i tryed to decrease the cut off frequency of the RC filter but it doesn't change anything.

Are you referring to that really, really quiet noise that is almost impossible to make out (on my speakers) in the background? As if there were brief bursts of pink noise being introduced.

yes !!! it's not noise you can hear quickly triggered but distinct tone. the more the sensor is excited the more you will hear this strange echo in the background.
maybe it comes from the response time of the rc filter ? i don't have the components to test the chebyshev
filter...

If you look at the WAV file with an audio editor (I use GoldWave) you can see that your sketch isn't generating a clean sine wave and the spectrum of the wave has harmonics all over the place.
The peaks of the waveform are almost flat which I think is caused by the low resolution of the sine wave table. Note, for example, that there are six consecutive zeroes in the table which correspond to the negative peak of the waveform. There are seven consecutive occurrences of 254 which correspond to the positive peak.

Pete

yes you're right i checked this into audacity... but the sketch comes from this website and they seems to obtain a great sinewave with a clear peak at 1000hz (frequency test) :
http://interface.khm.de/index.php/lab/experiments/arduino-dds-sinewave-generator/

so maybe the problem is less the resolution than the RC filter smoothing, i need to try with their chebyshev filter... maybe my RC filter is too slow in response time ?
i added another pole, so i've got two poles in series but it doesn't make a big change...

i just have tried this another code from adam freed :
http://adrianfreed.com/content/arduino-sketch-high-frequency-precision-sine-wave-tone-sound-synthesis

this code is a good example because there are variation in frequency. the sound result is the same we can hear the same noise in the output...

That code uses essentially the same sinewave table and DDS technique so it's going to produce the same audio. I think you'll have to work on your audio filter to improve the sound.

Pete

yes it's exactly what i plan to do. i have to order some inductors for the chebyshev lowpass filter.
so i've got to wait... i give up the RC filter.