Although I cant post my whole project, here is a little twist that might help some folks. I needed a pair of 0 - 5 V analog outs with 10 bit precision that could drive a 10k load within 1% of either rail. Even finding a buffer amplifier operating that close to the rail with Zout <100 ohm output is not easy. Before resorting to new supply voltages, an idea struck using only resistors and capacitors.
In the Old Skewl we sometimes used a binary weighted divider to turn binary into analog, like this:
Its an alternative to the R/2R ladder more commonly used. Just stick in four bits and a voltage comes out. An Arduino PWM output can be expanded to 10 bit like this:
Arduino analogWrite using bits[7…0] plus digitalWrite to 2 pins for bit and bit does the trick. Precision resistors for accuracy and/or linearity may be required for your application.
My project turns out to only use the top and bottom quarter of the voltage range, so bits 9 and 8 could be set with a single pin. Optionally, in software, the digital output could be set to Hi-Z and allow the PWM to cover the full voltage range with 8 bits of precision, and enable the digital output only to increase the precision to 10 bits in the top and bottom quarter, but my project doesnt need the middle of the range.
So thats the grand design. An interesting detail was the resistors.
I needed a stiff output so I chose some very low values, to use as much “grunt” as an ATmega pin can safely source or sink. ATmega outputs are not ideal at high currents, which creates increasing errors as the resistors get lower in value. Referring to figs 29-160 and 29-162 in data sheet for the ATmega328P used in the Arduino Uno, one can derive from the slope that the pins have an effective internal series resistance of 20 to 30 ohms over temperature.
The binary divider resistors on the output pins must be reduced by a nominal 25 ohms to compensate for this.
Each channel ended up being this:
I was nicely surprised by the low values possible, only 33 ohms and 150 ohms, (or 58 ohms and 175 ohms with internal resistance) although this did require rather large capacitors in the 2-pole filter needed to get the quiet output required. The 43 ohm divider impedance plus a 47 ohm filter resistor keeps the total under 100 ohms required to drive a 10k load to within 1% of the opposite rail.
But do the math, including the 25 ohms internal, and see that in worst case with the outputs in opposite states the current is just over HALF of what can be carried continuously over the entire temperature range. With 2 pairs both fighting themselves the ATmega is barely warm, about the same as when not doing something this crazy.
You can overstress this is with an external short circuit; set the pins to opposite states and then short the output to which ever rail the PWM pin is stuck at. In this case the digital pin will carry about 1/3 more than its ratings, but at room temperature it could probably do this for years.
Two of these channels stimulate an external device, and both are monitored by normal Arduino 10-bit analog inputs. This closes the loop for accuracy during operation, and allows the project to run an exhaustive self-test at start up, including filter step response. Hopefully upon detection of a shorted output I remembered to leave the pins set to the low-stress state.
This is my first Arduino project and its going well, a tribute to the Arduino team and the IDE. Hope some folks find other uses for my 10 bit analog output.