Pages: [1]   Go Down
Author Topic: The 2ct DAC  (Read 4234 times)
0 Members and 1 Guest are viewing this topic.
0
Offline Offline
Full Member
***
Karma: 2
Posts: 109
ArduiYES!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hi all,


remember those software selectable weak (20k) pullups on the input pins ?

It occurred to me that those can be used as a programmable voltage divider:
configuring a few ports with pullup in parallel lowers the overall value of that resistance.
if you want less pins (and thus 20k resistors) parallel, put the surplus of input pins into high-impedance mode.

So, you take a few unused ports, and set them to INPUT.
connect all those INPUT pins together.
then locate the GND pin and insert a 20k resistor between that GND pin and the tied-together pins.



now you can create voltages between 0 and "a bit under your board's
supply voltage". These voltages are measured over the external resistor.

here is the general table of values, assuming 20k pullups and a 20k
resistor to ground
0 pins pull up0/1volts
1 pin pullup1/2 of Vcc
2 pin pullup2/3 of Vcc
3 pin pullup3/4 of Vcc
4 pin pullup4/5 of Vcc
I'm sure you seethe pattern

when increasing and then decreasing the number of pull ups you
get a nice positive half-sinus with these values.

You can play with the value of the external resistor to get the voltages you might want.
 
Naturally you can also use these pullups as part of an ordinary R-2R resistor ladder, but check if all your pullup resistors have equal value.
The data sheet says minimum = 20kOhm and maximum = 50kOhm, so there is quite a bit of spread...

you can use your ADC in order to measure the actual voltages.
since there are several ways to (e.g.) pick 2 pullups from 4 pins you can select the pins that give the most accurate/desirable value.

using a scope I saw a bit of capacitive behaviour at high frequencies.
meaning that it took time to reach a lower voltage from a higher voltage, with the typical capacitor discharge curve.

I have cobbled together a concept sketch and a sketch to try out maximum attainable frequency. I reached 500 kHz
with the fast version. However, the amplitude of that signal was only half a volt.
the shape of the sinus was very smooth and symmetrical.

The slower concept sketch did about 110 kHz, having an amplitude of 2 volts on my 16 MHz Duamilanuove. it was a bit more jagged.
this is the view at 5 microseconds per division

When lowering the frequency the signal slowly becomes
the sequence of stepped half-sines again.
this is with a delay(5) inserted


concept sketch :
Code:
// Ronald van Aalst
// Dec 22 2010
// 2ct DAC (concept)
// creates a crude sinus of about 110 kHz
// utilizing the input pullups (20k to 50k) of the arduino ports,
// using them as a programmable voltage divider
// this program uses pin 4,5,6, and 7
// tie these pins together and connect them to GND
// via a separate 1/4 watt 22k resistor
//
// further ideas :
// -- tie the signal pin to the ADC in order to determine actual voltages generated.
// -- use ADC measurement to select the sets of pins to pullup
// -- put the SetVal functionality in a timed interrupt for background operation
// -- create a R/2R ladder using the pullups and some separate resistors (measure actual voltages with the adc)

void setup() {    
}

void loop() {

  asm volatile ("cli \n" : : : ); // disable interrupts
  
  for (int i = 0; i<1;) { // use a for because the loop() is too much overhead and creates timing issues on the scope
  
  SetVal(B00000000); // no pullups, signal = 0 volts
 
  SetVal(B00000001); // one pullup active (20k) : signal about 1/2 of 5 volts = 2,5 volts

  SetVal(B00000011); // two parallel pullups = 10k : signal =  2/3 of 5 volts = 3,33 volts

  SetVal(B00000111); // three par. pullups : signal = 3/4 of 5 volts = 3,75
 
  SetVal(B00001111); // four pullups active : signal = 4/5 of 5 volts = 4 volts

  SetVal(B00000111); // and down again

  SetVal(B00000011);

  SetVal(B00000001); // and ready for next cyclus
  
  }
}

void SetVal (byte pat) {
  pat = pat << 4;
  DDRD |= B00001111; // pin 7654 are input; rest unchanged
  PORTD = ((pat & B11110000) | (PORTD & B00001111)) ; // set pins in "pat" pins on pullup
  // insert delay here
}


and the fast version
Code:
// Ronald van Aalst
// Dec 22 2010
// 2ct DAC (concept; fast version)
// on a 5 volt duamilanove it generates a 0,5 volt top-top 500 khz signal
// utilizing the input pullups (20k to 50k) as a DAC,
// using them as a programmable voltage divider
// this program uses pin 4,5,6, and 7
// tie these pins together and connect them to GND via a separate 1/4 watt 22k resistor
//

void setup() {    
}

void loop() {

  asm volatile ("cli \n" : : : ); // disable interrupts
  
  DDRD |= B00001111; // pin 7654 are input; rest unchanged
  
  for (int i = 0; i<1;) { // use a for because the loop() is too much overhead and creates timing issues on the scope
  
//  SetVal(B00000000); // no pullups, signal = 0 volts
  PORTD = (B00000000 | (PORTD & B00001111)) ; // set pins in "pat" pins on pullup
//  SetVal(B00000001); // one pullup active (20k) : signal about 1/2 of 5 volts = 2,5 volts
  PORTD = (B00010000 | (PORTD & B00001111)) ; // set pins in "pat" pins on pullup
//  SetVal(B00000011); // two parallel pullups = 10k : signal =  2/3 of 5 volts = 3,33 volts
  PORTD = (B00110000 | (PORTD & B00001111)) ; // set pins in "pat" pins on pullup
//  SetVal(B00000111); // three par. pullups : signal = 3/4 of 5 volts = 3,75
  PORTD = (B01110000 | (PORTD & B00001111)) ; // set pins in "pat" pins on pullup
//  SetVal(B00001111); // four pullups active : signal = 4/5 of 5 volts = 4 volts
  PORTD = (B11110000 | (PORTD & B00001111)) ; // set pins in "pat" pins on pullup
//  SetVal(B00000111); // and down again
  PORTD = (B01110000 | (PORTD & B00001111)) ; // set pins in "pat" pins on pullup
//  SetVal(B00000011);
  PORTD = (B00110000 | (PORTD & B00001111)) ; // set pins in "pat" pins on pullup
//  SetVal(B00000001); // and ready for next cyclus
  PORTD = (B00010000 | (PORTD & B00001111)) ; // set pins in "pat" pins on pullup  
  }
}



so, this is a nice way to implement high frequency sine generator.
at lower frequency you will have to smooth (filter)
the sequence of stepped half-sines into something more resembling a true and smooth sine, I guess.

if only ATMEL had provided a choice between pull-up and pull-down;
then a full sine could be generated.
If anybody knows a trick to flip the signal upside down in order to complete the negative part of the sine,
I'm most interested. I tried hooking the ground of the scope onto an OUTPUT pin, hoping to create a negative voltage that way, but that did not do the trick.

This signal generator depends on the current the INPUT pins will PROVIDE. that is not a lot, since input pins expect to sink current.
So, the signal should not be loaded by whatever you send the signal into. You WILL need some amplifier or at least something to
deliver the signal at less output impedance. a FET or OpAmp should do the trick.

Still, not bad for a single resistor...

Ps, this might be old hat to you, but I have never seen the input pullups used this way.
On the other hand, most things have already been tried, so let
me know if you saw a comparable trick somewhere.



 
« Last Edit: December 22, 2010, 05:31:21 pm by raalst » Logged

0
Offline Offline
Faraday Member
**
Karma: 16
Posts: 2857
ruggedcircuits.com
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

This is the most interesting thing I've read today. Nice job smiley

--
The Gadget Shield: accelerometer, RGB LED, IR transmit/receive, light sensor, potentiometers, pushbuttons
Logged

Boston Suburbs
Offline Offline
God Member
*****
Karma: 16
Posts: 955
I am above your silly so-called "Laws", Mister Ohm.
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Dear lord, that's nothing short of brilliant.

I've also considered using PWM to drive a capacitor to supply external aRef voltage or to control the gain of an external amplifier.  I've also been sniffing around at the comparator built into the 328.  Thought projects at the moment only, but workable, I think.

This however is so damn elegant and simple.. bravo.
Logged

When the testing is complete there will be... cake.

Left Coast, CA (USA)
Offline Offline
Brattain Member
*****
Karma: 361
Posts: 17262
Measurement changes behavior
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Well if you wire the external summing junction to a series capacitor with a high value resistor on the output side of the cap to ground, won't that block the DC bias from the signal?

 Lefty

Logged

0
Offline Offline
Full Member
***
Karma: 2
Posts: 109
ArduiYES!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

The lacking pulldowns I complained about can be implemented by upgrading the 2 cent DAC to a few cent DAC.

instead of a single resistor to ground, use a few OUTPUT LOW ports
and for each port a corresponding 22k resistor per port.

now you have a programmable pulldown as well as the programmable pullup.

some bitshuffling resulted in this code
Code:
// Ronald van Aalst
// Dec 22 2010
// Few cent DAC (concept)
// creates a stepped sinus-like analog voltage.
// utilizing the input pullups (20k to 50k) of the arduino ports,
// using them as a programmable voltage divider
// this program uses pin 4,5,6, and 7
// tie these pins together and connect them
// a bunch of tied-together OUTPUT-LOW ports
// (I used port 8,9,10 and 11) as a programmable pull-down
// Now the voltage can be set below 1/2Vcc as well by enabling
// more and more pulldown ports.
// I have not tried it yet but combining other numbers of pull ups and pulldowns
// will give you a few more voltages you can synthesize.
//


void setup() {    
}

void loop() {

  asm volatile ("cli \n" : : : ); // disable interrupts
  
  for (int i = 0; i<1;) { // use a for because the loop() is too much overhead and creates timing issues on the scope
  
  //SetVal(B00000000); // no pullups, signal = 0 volts
 
  SetVal(B10000001); // one pullup active (20k), one pulldown active : signal about 1/2 of 5 volts = 2,5 volts

  SetVal(B10000011); // two parallel pullups = 10k : signal =  2/3 of 5 volts = 3,33 volts

  SetVal(B10000111); // three par. pullups : signal = 3/4 of 5 volts = 3,75
 
  SetVal(B10001111); // four pullups active : signal = 4/5 of 5 volts = 4 volts
  
  //SetVal(B00001111); // circuit broken; only pullups : 5v

  SetVal(B10000111); // and down again

  SetVal(B10000011);

  SetVal(B10000001); // and ready for lower part
  
  SetVal(B11000001);
  
  SetVal(B11100001);
  
  SetVal(B11110001);
  
  SetVal(B11100001);
  
  SetVal(B11000001);
  
  // SetVal(B10000001); // same as asrt value
  
  //SetVal(B10000000); // circuit broken : 0v
  }
}

void SetVal (byte pat) {
  int pat1 = pat & B00001111; //pattern for portB
  int pat2 = pat & B11110000; //pattern for portD
  DDRD  = DDRD & B00001111; // pin 7/6/5/4 are input; rest unchanged
  PORTD = ((PORTD & B00001111) | pat2); // set pins in "pat" pins on pullup
  DDRB  = ((DDRB & B11110000) | pat1);
  PORTB = PORTB & B11110000; // B pins are OUTPUT LOW or INPUT hi-Z so PORTB value is always 0
  delay(5);
}

with this result


when removing the delay and moving the code in SetVal to the main loop (in order to get minimum amount of instructions per sine) the circuit reaches a maximum frequency of 166 kHz (2 volts top/top)


Ps I have trouble creating a nice schematic, have a look at the fritzing site for the wiring plan http://fritzing.org/projects/2-cent-dac/
« Last Edit: December 23, 2010, 07:16:12 am by raalst » Logged

0
Offline Offline
Full Member
***
Karma: 2
Posts: 109
ArduiYES!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

@lefty :

Sorry I was not specific. I was talking about the possibility to synthesize voltages lower than 0.5 Vcc, i.e. to generate something looking like the negative swing of a sine. I could only create a bump above  0,5 Vcc.

anyway, solved now  smiley

Logged

Norway
Offline Offline
Sr. Member
****
Karma: 4
Posts: 423
microscopic quantum convulsions of space-time
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Nice job and writeup!

Quote
instead of a single resistor to ground, use a few OUTPUT LOW ports
and for each port a corresponding 22k resistor per port.
But then you don't really have to use any internal pullups? Just set outputs to 0 or 1. Perhaps even weigh the resistors according to LSB - MSB of the outputs. It should work for a few bits at least. 5 outputs = 32 values? Or combine it somehow with the high-Z state of an input (but without the internal pullup) to get higher resolution (I think - haven't really thought it through).
Logged

0
Offline Offline
Full Member
***
Karma: 2
Posts: 109
ArduiYES!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I think that you will find you will arrive at a standard R-2R ladder if you follow that train of thought.

nothing wrong with that. what I did was just another approach, not necessarily a better one. although <boast mode=on>I think I have not yet read about an arduino synthesizing a 166khz signal. The signal generators I have seen are limited to audio range <boast mode=off>

at AVR freaks it was noted that the sequence of values generated does not constitute a sine but more likely an exp() or log() function. that is true. And the consequences of this are not clear to me.
I have the feeling , however, that the error in using a table lookup
will be close to the errors my approach introduces.
I don't have a Fourier analysis tool to check the error (nor the math skills to calculate it..) unfortunately.
Logged

Norway
Offline Offline
Sr. Member
****
Karma: 4
Posts: 423
microscopic quantum convulsions of space-time
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
I think that you will find you will arrive at a standard R-2R ladder if you follow that train of thought.
That might well be, but I have to say I'm not entirely sure about it. I would think using the high-Z state as a third option should give more, well, options/resolution (as well as some useless ones).

for 4 outputs:

binary: 2^4 = 16 values
trinary: 3^4 = 81 values

But, all outputs set to high-Z are not really useable. Also any combination of more than one output set to the same value, not counting the high-Z states (IE two or more set to the same 0 or 1, the rest high-Z), is useless. Should be 2^4-2 useless combinations pr. non-high-Z value, that is times two for both 0 and 1. Plus the all high-Z state.

Sorry, probably a messy explanation. IE 29 useless values off the top of my head. Probably somewhat tricky to figure out the 81-29 = 52 uniformly increasing values and their corresconding "trinary" value.

Just tossing out an idea here. It would be pretty cool to have a 52-step DAC from 4 outputs. Or 182 steps from 5.

But as you say, your approach is a bit different, and it was a cool way of utilizing one extra resistor with the internal pull-ups! (Or a couple of extra as of now) smiley
Logged

0
Offline Offline
Full Member
***
Karma: 2
Posts: 109
ArduiYES!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

it occurred to me, thanks to your remarks, that we can make a
combined pull-up / pull-down pin.
simply put a 22k resistor and a diode on the pin.
now putting the pin in OUTPUT-LOW will create a
pulldown using the external resistor, but the pullup of
INPUT-HIGH will bypass the resistor via the diode.

there will be a forward diode drop though.

using 4 pullup/pulldown pins you should be able to synthesize
0, 0.25, 0.33, 0.5, 0.66, 0.75 and 1 times Vcc
when I ignore the forward diode drop.

we are leaving the 2ct territory though  ;D
« Last Edit: December 24, 2010, 05:49:48 pm by raalst » Logged

Pages: [1]   Go Up
Jump to: