DDS Sine Wave on Teensy 2.0

Status
Not open for further replies.

wb8nbs

Active member
I have Direct Digital Synthesis working on the 32u4 now in both Phase Correct and Fast PWM modes. Fast works better as the clock rate is twice as fast as Phase Correct.

1000Hz.jpg

My notes and a code link are at https://wb8nbs.wordpress.com/2015/12/08/more-fun-with-direct-digital-synthesis-32u4-and-fast-pwm/
 
That 10k + 10nF capacitor has single pole at 1.6 kHz. You might consider a 2-pole filter.

If you had a Teensy 3.2, you could use the DAC for true analog output. You'd still want to filter, but there's a lot less high frequency junk to filter away. The DAC also has levels defined by the stable on-chip voltage reference, which is a lot better than having the single amplitude defined by the digital power supply voltage.
 
That 10k + 10nF capacitor has single pole at 1.6 kHz. You might consider a 2-pole filter.

If you had a Teensy 3.2, you could use the DAC for true analog output. You'd still want to filter, but there's a lot less high frequency junk to filter away. The DAC also has levels defined by the stable on-chip voltage reference, which is a lot better than having the single amplitude defined by the digital power supply voltage.

I'll work my way up to that. i bought a Teensy LC to learn a bit about ARM chips but have not used it yet.

The application is morse code, I expect to limit the tone frequencies to 100-1500 Hz into a 2 inch speaker so the distortion will probably not be noticed. My "vintage" ears prefer lower tones anyway.
 
You should definitely give it a try on that Teensy-LC with the true analog DAC. :)

For the timer interrupt, you can use Teensy's IntervalTimer object to easily replace the timer3 code, without any need to dig into the complexities of accessing Teensy's timer hardware.

http://www.pjrc.com/teensy/td_timing_IntervalTimer.html

It's extremely simple. You can also just use analogWrite(A12, sineTable[sineTableindex]) to update the DAC from within your interrupt function.

On 32 bit Teensy, there's no need to use PROGMEM. Just use "const" on the array and it goes into flash only. You can access it as a normal array, rather than pgm_read_byte_near().

On top of all that, the DAC is 12 bits true analog resolution. Use analogWriteResolution(12) in setup(), and then you can write 12 bit numbers. Because Teensy-LC is a true 32 bit processor, 8, 16 and 32 bit numbers are all natively the same fast speed.

It's so much better, and less expensive too than those older boards too!
 
Thanks Paul. I'll set that up. I also came across This URL describing how to use the USB clock on the 10 bit timer. I will try that also. I suspect at some clock speed the processor will not have time to do anything but the ISR though.
 
Yes, the same is true on AVR and ARM and any other microcontroller. If you process a single sample per ISR, eventually as you increase the sample rate the ISR takes too much CPU time. ARM will probably allow you to go faster.

For efficiency at higher sample rates, you need to use DMA, like the Teensy Audio Library does. Then you rapidly generate a buffer of data and use 1 interrupt per buffer. The DMA hardware takes care of actually moving the individual samples from the buffer to the DAC, PWM, I2S, etc.

So far, the Teensy Audio Library only supports Teensy 3.x. Teensy-LC is planned, but not working yet.
 
I'm testing on the Teensy LC. So far so good and the wave is, as you said, much cleaner. Sketch link is https://dl.dropboxusercontent.com/u/40929640/ArduinoSineSynth/TeensyLCFullTable.zip

Is there a way to change the DAC reference voltage on the fly? I want to amplitude modulate the sine wave, ramp up when it starts and ramp down when it stops. I've been doing this by switching in tables with lower amplitude but would be more elegant just to change the full scale amplitude.
 
Sure, just use analogReference(INTERNAL), if you're using analogWrite().

But do understand the reference may have very different settling time.
 
I'll try that. Looking at the data sheet, it's not at all clear how the references work for the DAC.

The frequency granularity is pretty large with this method. Like if I change the sample period by 1 microsecond with the 256 sample table, I have changed the tone wave period by 256 microseconds. That's a big tradeoff between frequency resolution and table size, I can see it in the sweep - gets very jerky at the high end.

By The Way, that Teensy LC sketch compiles and loads OK from my 32 bit Athlon machine, but will not compile on my 64 bit laptop. Both running arduino 1.6.1. It's not blocking my test but do you have any idea where I can look to resolve that?

Code:
/home/jbh/arduino-1.6.1/hardware/tools/avr/bin/avr-g++ -c -g -Os -w -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -MMD -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=10601 -DARDUINO_AVR_UNO -DARDUINO_ARCH_AVR -I/home/jbh/arduino-1.6.1/hardware/arduino/avr/cores/arduino -I/home/jbh/arduino-1.6.1/hardware/arduino/avr/variants/standard /tmp/build7251633030617973201.tmp/KO7MDDSCodeIntegerFullTableTeensyLC_V2.cpp -o /tmp/build7251633030617973201.tmp/KO7MDDSCodeIntegerFullTableTeensyLC_V2.cpp.o 
KO7MDDSCodeIntegerFullTableTeensyLC_V2.ino:138:1: error: 'IntervalTimer' does not name a type
KO7MDDSCodeIntegerFullTableTeensyLC_V2.ino: In function 'void setupPeriod()':
KO7MDDSCodeIntegerFullTableTeensyLC_V2.ino:212:3: error: 'waveTimer' was not declared in this scope
KO7MDDSCodeIntegerFullTableTeensyLC_V2.ino: In function 'void MakeSineISR()':
KO7MDDSCodeIntegerFullTableTeensyLC_V2.ino:227:15: error: 'A12' was not declared in this scope
Error compiling.

DAC440.jpg
 
The Teensy-LC version is working. My notes and some photos are on my Wordpress page. It makes beautiful sine waves but there are some reservations. I'm coming to the conclusion that DDS is best left to the FPGA people.

I could not get IntervalTimer to go below five microseconds. Maybe because the ISR was using 100% of the CPU at that point.

64 step table:

LCQTR064.JPG

Bottom trace raw from the DAC, top trace thru simple filter.
 
Hi

I've been lurking in this thread .... hehehe ... I have a need for sine waves, spaced 44100 / 128 apart .... 344.53125 Hz apart ....:rolleyes:

Because its a power of two thing, I can get away from using a "fixed point" accumulator to tune the frequency of the dds table 'stepthrough'. So ..... I can just plug in magic whole numbers, rely on a uint8 wrapping around, and get the sine wave(s) I want.

Here is the code, and a plot of the relevant sine on ReaFIR frequency analyser .... there is a lot of noise N jitter and all sorts probably because I have only used a dc blocking cap, and no lowpass filter ... maybe the way I step through the table is suspect too?? I'm pretty sure that 255 + 1 is zero, though ...so the crap on the plot (and the low frequency sounds I can here on my monitors) are probably because of my minimalist analog circuitry ..Anyway ...

Code:
const int sineTable[] = {
  2048, 2098, 2148, 2198, 2248, 2298, 2348, 2398,
  2447, 2496, 2545, 2594, 2642, 2690, 2737, 2784,
  2831, 2877, 2923, 2968, 3013, 3057, 3100, 3143,
  3185, 3226, 3267, 3307, 3346, 3385, 3423, 3459,
  3495, 3530, 3565, 3598, 3630, 3662, 3692, 3722,
  3750, 3777, 3804, 3829, 3853, 3876, 3898, 3919,
  3939, 3958, 3975, 3992, 4007, 4021, 4034, 4045,
  4056, 4065, 4073, 4080, 4085, 4089, 4093, 4094,
  4095, 4094, 4093, 4089, 4085, 4080, 4073, 4065,
  4056, 4045, 4034, 4021, 4007, 3992, 3975, 3958,
  3939, 3919, 3898, 3876, 3853, 3829, 3804, 3777,
  3750, 3722, 3692, 3662, 3630, 3598, 3565, 3530,
  3495, 3459, 3423, 3385, 3346, 3307, 3267, 3226,
  3185, 3143, 3100, 3057, 3013, 2968, 2923, 2877,
  2831, 2784, 2737, 2690, 2642, 2594, 2545, 2496,
  2447, 2398, 2348, 2298, 2248, 2198, 2148, 2098,
  2048, 1997, 1947, 1897, 1847, 1797, 1747, 1697,
  1648, 1599, 1550, 1501, 1453, 1405, 1358, 1311,
  1264, 1218, 1172, 1127, 1082, 1038, 995, 952,
  910, 869, 828, 788, 749, 710, 672, 636,
  600, 565, 530, 497, 465, 433, 403, 373,
  345, 318, 291, 266, 242, 219, 197, 176,
  156, 137, 120, 103, 88, 74, 61, 50,
  39, 30, 22, 15, 10, 6, 2, 1,
  0, 1, 2, 6, 10, 15, 22, 30,
  39, 50, 61, 74, 88, 103, 120, 137,
  156, 176, 197, 219, 242, 266, 291, 318,
  345, 373, 403, 433, 465, 497, 530, 565,
  600, 636, 672, 710, 749, 788, 828, 869,
  910, 952, 995, 1038, 1082, 1127, 1172, 1218,
  1264, 1311, 1358, 1405, 1453, 1501, 1550, 1599,
  1648, 1697, 1747, 1797, 1847, 1897, 1947, 1997
};

IntervalTimer sampleRate;      // create an interval timer

void sineISR()
{
  static uint8_t freqIndex;
  analogWrite(A12, sineTable[freqIndex]);
  freqIndex += 64;  //    64 / 256 * 44100 = 11,025Khz  
}

void setup()
{
  analogWriteResolution(12);
  sampleRate.begin (sineISR, 1 / 0.044100); //micros @ 44.1kHz
}

void loop() 
{
  while (1);
}

Plot.png

I used a teensy LC. Thankyou PaulStoffregen.
 
Last edited:
I don't think you can get that kind of precision with IntervalTimer and a 48 Mhz processsor.

You are incrementing the table index by 64 at each interrupt so it goes through the entire table in 4 steps. This is the actual waveform on my scope. The frequency is about 21,740 Hz:

DSFC0493.JPG

I change the increment in your ISR to 1 and get a nice sine wave but at only 170 Hz.

DSFC0494.JPG

It's a bit flat topped. Maybe the DAC doesn't like being driven all the way to the top.

11.025 Khz has a period of 90 microseconds so to use the full 256 step table would require a step time of 90/256 = 0.354 microseconds but IntervalTimer only has a resolution of 1 microsecond plus I find that four microseconds is as low as it will go. If I make your freqIndex step size 16 and sampleRate.begin(sineISR,4) I can get 14,700 hz wave out of it. If I chage to sampleRate.begin(sineISR,5) it goes all the way down to 12,200. That's the effect of the 1 microsecond granularity at the high end.

And of course with a step of 16 the waveform isn't as smooth and I see some timing jitter (probably because of Integer arithmetic roundoffs)

DSCF0495.JPG
 
thankyou for this... As you can tell, I am very new to this ... I will rethink things...
 
You are about where I was a year ago. It took me a couple of months to get something that actually worked. I've concluded that with the tradeoffs, serious DDS is best done in dedicated hardware. With the precision I think you are asking for, you could look at the Analog Devices chips AD9835, AD9850, AD9851 and many others, and just write control software in the Teensy. Chinese modules are available on Amazon or Ebay for about the price of the TeensyLC.
 
Using the search function for these forums or looking at the code of the audio library, you could easily find out how to use the DAC in combination with its ring buffer and the PDB (the audio library uses even DMA) to output audio data at high rates without jitter.

Just for info: The teensy 3.2 is quick enough to take signed 16bit data from a table at a 48kHz rate, process it with a 33 step half band FIR oversampling filter and output audio at 192kHz to the DAC which greatly improves the audio quality of that 12bit DAC and minimized analogue filter requirements behind.

@wb8nbs: I just read your blog which you linked in your last post. You could have greatly improved the audio quality by replacing that cheap RC-filter by an active second order Sallen-Key Butterworth low pass around an op amp.
 
Last edited:
Theremingenieur: Thankyou for your post ...I have come to the same conclusion as you ... teensy 3.x is well quick enough especially using dma .... I found this code ..... your suggestion of using an oversampling filter and then into the dac at a high rate is also very good, and is similar to the way I found on a Texas Instruments datasheet ...as you say, analog filtering is then 'relaxed'.

I am using a teensyLC, and my knowledge of digital filters is more hypothetical than theoretical :eek: so I will see how far I can up the sample rate (without dma) and look at an active lowpass filter .... From reading, one of the possible constraints on the DAC is capacitive loading, so maybe a voltage follower and then an active low pass (I am thinking very simple maybe 2poles?) ... I need some amplification anyways for the project,so I was always going down the active filter route.

I have a lot to learn.
 
Last edited:
I saw that example. Went as far as getting it to compile. Both adrian and I have LC's though and it does not have a PDB.

And I thought about a switched capacitor filter but decided it was overkill for the average Ham operator's ears.
 
You can have all in one, active 2nd order low pass and voltage follower by choosing the resistor values to remain >= 10kOhm here: http://www.daycounter.com/Filters/Sallen-Key-LP-Calculator.phtml

No need to reinvent the powder (as we say in France) or the wheel (as we say in Germany) ;)
Working at a sampling rate of 32kHz, the highest possible audio frequency would be 16kHz (thank you, Mr Nyquist), but I think that morse tones are ways lower, so that you might choose a lower 3dB frequency (i.e. 4kHz) to make the filter more effective.

I will publish my PDB-DAC with 4x oversampling code soon here, it needs still a few refinements. I'm always reluctant to use external libraries without exactly understanding what the code written by other people does, so I most times finish up studying the libraries and then rewriting the functions and objects I need, adapted and optimized for my purposes.
 
Last edited:
Theremingeniuer: that lowpassfilter is perfect ... I was looking at this (single pole /RC?) one a couple of days ago, but I guess the point is that it is doable with a bit of thinking on my part ...

154SV.png

since I need some amplification, I will adapt the Sallen-Key Butterworth low pass to give me some gain .... the maths looks a bit tricky, and I am feeling anti mathmatical right at the moment

I look forward to your PDB-DAC code ... I think that because it uses a programmable delay block, it won't work on the LC, as wb8nbs says, but luckily I have some teensy 3.2s on the way...
 
Last edited:
Sallen-Key LPF or Passive RC-RC and Amplifier configured for single-supply operation

Theremingeniuer: that lowpassfilter is perfect ... I was looking at this (single pole /RC?) one a couple of days ago, ... since I need some amplification, I will adapt the Sallen-Key Butterworth low pass to give me some gain .... the maths looks a bit tricky .

The previous circuit is indeed a single-pole LPF with an amplifier (Av = 1 + Rf/Rg). For a 2nd order filter, the equal-component Sallen-Key is the form I generally use. The frequency response, Q and gain are not independent, and changing the gain significantly affects everything else. A simpler and more versatile approach is to design the filter Fc & Q, and then adjust gain in the next stage. Since a dual opamp is also 8 pins and ~same price, you get it "for free." For a single supply design, almost any rail-to-rail amp with a BW of a few MHz will work fine. I show the schematic below. Its response is between Bessel and Butterworth, with no peaking.

For an even simpler approach, one can get an approximate 2nd order response with just 2R & 2C if 2nd R is >> 1st R. Output impedance is high. If it is going into a high-impedance input, then it's fine. In the schematic, I also show this solution, both with Fc ~= 2.5kHz. Values have lots of latitude. You can see the frequency response and the time response for each filter, plus a similar final output with their respective amplifiers afterword, each with a net gain of ~3. (AC-coupled input may be used or omitted as required for the application.)

S-K,RC_schem.PNG
Schematic showing an RC-RC filter + Amp, and an Active filter + Amp

S-K,RC_freq_response.PNG
Frequency response is similar

S-K,RC_time_response.PNG
The time response with 200Hz and 20kHz inputs shows the higher frequency substantially attenuated as desired
 
thanks for this David. I had come to the same conclusion that a separate gain stage is needed for a sallen key filter, and as you point out, dual op amps are 'dime a dozen' ... I constructed a sallen key low pass filter, and it seemed to work ok ...things improved markedly when I added a passive hi pass filter, to get rid of some low frequency junk! ...With the sallen key filter I still got significant frequencies at about2x the 19khz 'doublepole I calculated ... does this indicate I got something wrong (I used an LM 358N dual op amp in single supply configuration, but the single supply configuration is somewhat different to the one in your schematic ...)?? Anyway, a two pole linear passive filter looks worth a try too. I have some time hobby time coming up so, I will get back into it. I am thinking a buffer, passive two pole, and then some gain ...I'll have another look at the sallen key as well...
 
Status
Not open for further replies.
Back
Top