Sunday, May 17, 2015

A simple parallel DAC

In previous posts, I have described some experiments with a serial DAC - one that uses a SPI interface to load the values. I noted that the speed of the serial communication put a limit on how fast you can drive it. How fast could we go with a parallel DAC? There are plenty of parallel DAC ICs available, but there is also a simple alternative that can be used for doing speed tests, and that is a resistor ladder. Wikipedia has a page about them and there is another tutorial here. In the resistor ladder, you need two values, one twice the other (call these R and 2R). I used R=10k. I didn't have enough resistors lying around to use 10K throughout, so for the 2R resistors, I used a 12k and a 8.2k in series. This gave me enough for a 9 bit DAC.

For a first, experiment, I connected it up to an Arduino Uno, with the top 6 bits on pins 8-13 (port B) and the bottom three on pins 5-7 (port D). It's fastest to drive the ports directly, rather than using digitalWrite. The code is below. It has multiple options in it: sampled triangle and sine waves, and a square wave, using a delay to get the frequency approximately right; and a version which drives the ports as fast as possible.

The results are remarkably good. Here are example triangle and sine waves at about 10kHz:


For square waves, they still look OK at 100kHz:




The fastest square (-ish) wave I could get was about 250kHz:

In the speed test version, I could get it up to around 2.63MHz, though of course the signal is nothing like a square wave at the point:

By ignoring the lower order bits on port D, I pushed it up to 4.17MHz, with the waveform looking not much different to above.

Note that this isn't a practical design for a real DAC. It works in these experiments because the scope doesn't put much load on it. In a real design, you would put some buffering such as an opamp on the output, and also use resistors with tighter tolerances and more exact values

Here's the code. XMOS version to follow in another post.


#define NSAMPLES 64
short samples[NSAMPLES];
long max_value = 511;
double freq = 400000; // Approximate, due to rounding and overheads
#define PI 3.14156

void setup() {
  // Use pins 5 to 13 (low to high bit)
  DDRD = 0xe0;  // Pins 5-7
  DDRB = 0x3f;  // Pins 8-13
  for (int i = 0; i < NSAMPLES; ++i) {
    // Triangle
    //samples[i] = (max_value * i)/NSAMPLES;
    // Sine
    samples[i] = (short)(max_value * (1 + sin((float)i * 2 * PI / NSAMPLES))/2);
  }
}

void set(unsigned short n) {
  PORTD = (n & 0x07) << 5;
  PORTB = (n & 0x1f8) >> 3;
}

// True for a sampled waveform, false for a square wave.
bool sampled = false;

// Set for max speed square wave.
#define MAXSPEED

void loop() {
#ifdef MAXSPEED
    while (1) {
      PORTD = 0;
      PORTB = 0;
      PORTD = 0xe0;
      PORTB = 0x3f;
    }
#else
  if (sampled) {
    int delay_us = (int)((1000000.0/freq)/NSAMPLES);
    while (1) {
      for (int i = 0; i < NSAMPLES; ++i) {
        set(samples[i]);
        delayMicroseconds(delay_us);
      }
    }
  } else {
    int delay_us = (int)((1000000.0/freq)/2);
    while (1) {
      set(0);
      delayMicroseconds(delay_us);
      set(max_value);
      delayMicroseconds(delay_us);
    }
  }
#endif
}

No comments: