Go Down

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

#### raron

##### Feb 02, 2011, 09:22 amLast 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 test2 buttons, to select mode and function:- 2 modes: binary or trinary (requires different resistor circuits)- 2 functions: ramp-up and sine waveTo test a 2,3 and 4-bit tristate DACNOTE: Proof-of-concept kind of thing      Also, this is not optimized code at all!     (C) 2011 raron2011.01.07-082011.01.27*/const int outputs = 4;     // nr of tristate outputs for the resistor DACint base = 3;const int binStates = pow(2,outputs);const int triStates = pow(3,outputs);int waitTime = 1;        // in millisconst int buttonPin2 = 2;  // A button to select generator modeconst int buttonPin1 = 3;  // DAC mode (binary / trinary)//const int potPin = 0;      // frequency adjustconst int ledPin1 = 13;    // sine wave indicatorconst int ledPin2 = 10;    // tristate DAC indicatorint genMode = 0;int genModes = 1; // nr. of signal generator modes-1int button1 = 0;int oldButton1 = button1;int button2 = 0;int oldButton2 = button2;char outputString[outputs];// non-PWM outputs for the slightly faster digitalWriteint outputPin[outputs] = { 4, 7, 8, 12 }; // MSB to LSBint 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 baseYvoid 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 DACvoid writeBinaryDAC(int value){  int temp;  for (int i=0; i<outputs; i++)  {    temp = (value >> (outputs-i-1)) & 0x0001;    digitalWrite(outputPin[i],temp);  }}`

#### macegr

#1
##### Feb 02, 2011, 11:13 pm
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

#2
##### Feb 03, 2011, 12:44 am
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

#3
##### Feb 03, 2011, 02:12 am
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 pmLast 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