Go Down

Topic: The tri-state trinary DAC (Read 6080 times) previous topic - next topic

raron

Feb 02, 2011, 09:22 am Last Edit: Feb 02, 2011, 05:11 pm by raron Reason: 1
As this is not strictly Arduino-related (though I used one in my tests), I post this here and not in the exhibition section.

Inspired (once again) by a thread in this (the old) forum, "The 2ct DAC" (http://arduino.cc/forum/index.php/topic,8715.0.html), I wanted to see if a trinary DAC was possible by utilizing the high-Z state of a digital output pin, thus getting 3 states total.

The solution (as far as I know it) is extremely simple, yet maybe not that usable if many bits are desired, nor high speed. My test code for the Arduino is also very simple, no direct port access nor interrupts, it was just for a proof-of-concept three-state DAC. It is also extremely slow.


In short, a binary weighted resistor DAC (not R-2R) looks like this:


Where each resistor doubles in value.

A corresponding (except 4 bit instead of 3) trinary ladder would look like this (at least I'm pretty sure it will):

Where each resistor triples in value compared to the one before it.

But a digital output is not a trinary one. What I found was that by using a resistor divider on the digital outputs, as well as a trinary weighted resistor the output is fairly linear. With more bits comes the usual culprits like the required low resistor tolerances, especially for the MSB resistors.
I only tested with 5% resistors, up to 4 bits. And even that showed some irregularities (for 3^4 = 81 steps). This is also due to the fact I wouldn't load the digital outputs too much, as a lower resistor divider on the outputs also improves this. I mostly used two 180 ohm resistors, which translates to about 28 mA on each output that are either 0 or 2 (as I call the trinary equivalent to a binary 1 - Full Vcc). In addition to the trinary weighted resistors, but these were set to be 3.33 k ohm, 10 k ohm, 30 k ohm and finally 90 k ohm.



A 3-bit test, with a to-scale overlay from a processing program I made:


A 4-bit test. Not entirely even as can be seen (+ noisy):


And a 4-bit sine wave test, also with a simple RC filter on channel 2:



More details in my blog for the specially interested: http://raronoff.wordpress.com/2011/02/02/the-three-state-trinary-ternary-resistor-ladder-dac/


The Arduino test code (No schematics as of now, also this is not cleaned up):
Code: [Select]
/*  4-bit tristate resistor DAC test

2 buttons, to select mode and function:
- 2 modes: binary or trinary (requires different resistor circuits)
- 2 functions: ramp-up and sine wave

To test a 2,3 and 4-bit tristate DAC

NOTE: Proof-of-concept kind of thing
     Also, this is not optimized code at all!
   
(C) 2011 raron



2011.01.07-08
2011.01.27
*/

const int outputs = 4;     // nr of tristate outputs for the resistor DAC
int base = 3;

const int binStates = pow(2,outputs);
const int triStates = pow(3,outputs);

int waitTime = 1;        // in millis


const int buttonPin2 = 2;  // A button to select generator mode
const int buttonPin1 = 3;  // DAC mode (binary / trinary)
//const int potPin = 0;      // frequency adjust
const int ledPin1 = 13;    // sine wave indicator
const int ledPin2 = 10;    // tristate DAC indicator

int genMode = 0;
int genModes = 1; // nr. of signal generator modes-1


int button1 = 0;
int oldButton1 = button1;

int button2 = 0;
int oldButton2 = button2;

char outputString[outputs];

// non-PWM outputs for the slightly faster digitalWrite
int outputPin[outputs] = { 4, 7, 8, 12 }; // MSB to LSB

int countVal = 0;
int outValue = 0;
float value;


unsigned long time;

void setup()
{
 pinMode(buttonPin1, INPUT);
 digitalWrite (buttonPin1, HIGH); // pullup on the button input
 pinMode(buttonPin2, INPUT);
 digitalWrite (buttonPin2, HIGH); // pullup on the button input
 pinMode(ledPin1, OUTPUT);
 pinMode(ledPin2, OUTPUT);
 
 for (int i=0; i<outputs; i++)
 {
   pinMode(outputPin[i],INPUT);    // starts (output) pins as inputs (high-Z state)
   digitalWrite(outputPin[i],LOW); // No internal pullups
 }
}



void loop()
{
 button1 = !digitalRead(buttonPin1);
 button2 = !digitalRead(buttonPin2);
 //waitTime = analogRead(potPin)/4;
 
 //time = micros();
 //time = millis();
 
 // crude, non-debounced buttons
 if (button1 && oldButton1 != button1) genMode++;
 if (button2 && oldButton2 != button2) base++;

 if (genMode > genModes) genMode = 0;
 
 if (base > 3)
 {
    base = 2;
    for (int i=0; i<outputs; i++) pinMode(outputPin[i],OUTPUT);
 }

 if (base == 2) digitalWrite(ledPin2,LOW);
 if (base == 3) digitalWrite(ledPin2,HIGH);


 countVal++;

 // ramp up
 if (genMode == 0)
 {
   digitalWrite(ledPin1,LOW);

   // Binary resistor DAC
   if (base == 2)
   {
     //outValue = (time*binStates/period)%binStates;
     if (countVal >= binStates) countVal = 0;
     writeBinaryDAC(countVal);
   }

   // Tristate (ternary / trinary resistor DAC)
   if (base == 3)
   {
     if (countVal >= triStates) countVal = 0;
     // convert output to string
     value2baseM(countVal, outputs, base);
     writeTristateDAC();
   }
 }
 

 // sine wave
 if (genMode == 1)
 {
   digitalWrite(ledPin1,HIGH);

   // Binary resistor DAC
   if (base == 2)
   {
     if (countVal >= binStates) countVal = 0;
     outValue = (int)(binStates/2.0 * (1.0 + sin( 2 * PI * ((double)countVal/binStates))));
     if (outValue < 0) outValue = 0;
     if (outValue >= binStates) outValue = binStates-1;
     writeBinaryDAC(outValue);
   }

   // Tristate (ternary / trinary resistor DAC)
   if (base == 3)
   {
     if (countVal >= triStates) countVal = 0;
     outValue = (int)(triStates/2.0 * (1.0 + sin( 2 * PI * ((double)countVal/triStates))));
     if (outValue < 0) outValue = 0;
     if (outValue >= triStates) outValue = triStates-1;
     value2baseM(outValue, outputs, base);
     writeTristateDAC();
   }
 }


 oldButton1 = button1;
 oldButton2 = button2;
 
//  delay(waitTime);
}


// Convert and make a string with the value in baseY

void value2baseM(int value, int digits, int baseM)
{
 int temp;
 int remainder;
 int digit;
 for (int i=digits-1; i>=0; i--)
 {
   temp = value / baseM;
   remainder = value % baseM;
   value = temp;
   // Make the ASCII code
   digit = remainder + '0';
   outputString[i] = (char)digit; // build string
 }
 //outputString[digits]='\0';
}


// Tristate (ternary / trinary resistor DAC)
// Note: Very ineffecient (slow!)
void writeTristateDAC()
{
 for (int i=0; i<outputs; i++)
 {
   switch(outputString[i])
   {
     case '0':
       pinMode(outputPin[i],OUTPUT);
       digitalWrite(outputPin[i], LOW);
       break;
     case '1':
       pinMode(outputPin[i],INPUT);
       digitalWrite(outputPin[i],LOW); // no internal pullup!
       break;
     case '2':
       pinMode(outputPin[i],OUTPUT);
       digitalWrite(outputPin[i],HIGH);
   }
 }
}


// Binary resistor DAC
void writeBinaryDAC(int value)
{
 int temp;
 for (int i=0; i<outputs; i++)
 {
   temp = (value >> (outputs-i-1)) & 0x0001;
   digitalWrite(outputPin[i],temp);
 }
}


macegr

I like it. This is the DAC equivalent of Charlieplexing. Let's call it a RaronDAC!
Unique RGB LED Modules and Arduino shields: http://www.macetech.com/store

raron

Hi thanks for that! :)

Btw pretty cool and unexpected comment, but I think I'll leave that for others to decide. I didn't think about the charlieplexing analogy, I guess it kind of is.

macegr

No, I seriously think it's a great idea. The 3-bit and 4-bit methods are especially useful, compared to the resolution available from their binary weighted counterparts, and considering resistor tolerance. You could actually simplify the circuit a lot by making a 1/2Vcc reference that isn't affected much by output current. Either an divider + op-amp voltage follower based reference or a zener reference if you're confident of your VCC regulation. If you use a dual op-amp to buffer the DAC output, then you have a spare amplifier for your reference anyway.
Unique RGB LED Modules and Arduino shields: http://www.macetech.com/store

raron

#4
Feb 03, 2011, 05:40 pm Last Edit: Feb 06, 2011, 12:06 pm by raron Reason: 1
Quote
The 3-bit and 4-bit methods are especially useful, compared to the resolution available from their binary weighted counterparts, and considering resistor tolerance.

My thought as well. There is a speed penalty though, as each state requires (most likely) two writes - output mode and output state.

As for the Vcc/2 reference and op-amps, I was thinking about something similar, but I think one would need one op-amp pr. output (needs to separate the outputs). Thus  adding a bit of complexity but avoiding the high load situation with low resistor dividers on the outputs. One could then use some higher-resistance voltage dividers on the output, and have as you say voltage-follower op-amps on the output. Which is then fed to the trinary weighted resistors.I think it would be more linear (also assuming a regulated Vcc ofc, and with resistor tolerances in mind).

But as said adding a bit of complexity, and dependent on usage might not be needed.

Something like this:



In the schematics above I just have 1k, 3k, 9k and 27k for the trinary weighted resistors.

A slight correction to my first post, two of the resistors was I believe 2% (last ring red), which I happened to have; the 90 K ohm ones. I used three 10k (5%) in parallel for the 3.33 k ohm, 10k, and then 3 90 k ohm in parallel for 30k, and then 90 k ohm for the last.


Edit: typo.

Go Up