How to turn four Arduino 8 bit PWM outputs into four independent D/A DC outputs with over 24 bits of monotonic resolution.
I am very close to a low cost power supply design I am really happy with. Different from the previous two, but I think the one I have now is the design I have been aiming at. Better then the Mark I and Mark II designs in many ways.
I have to thank AcHmed99. The discussions with him actually resolved some issue for me.
Anyway, I took a break from that to try out another idea. Going for the low cost solution, I wanted to drive the design from a PWM on something like an Arduino. Pwm is great in that it is totally monotonic which is something I really want in my design, but the resolution is lousy. There is one 16 bit PWM, but that is still something like 0.5mV increments, if you are really careful about the scaling of the PWM output. The scaling will probably mean that full voltage out from the supply is only about 50% of full scale for the PWM. I want software calibration, and I want to be able to finely adjust the output voltage so that I can get a 6 1/6 digit meter to show 10.00000 volts. That needs 20 bits of resolution or more.
I used the 8 bit PWM's in FAST mode, and correct for accumulated error every PWM cycle. I ran the Arduino's PWM at maximum speed of 32KHz. The filter was a 3 stage 10k/0.1uf RC chain. Two stages are not enough to clean out the sub mV ripple. I am pretty happy with the results.
It means with some better coding, I should be able to get 4 independent PWM outputs with sub microvolt resolution.
Here is the code I used to test it, if anyone is interested. Pretty rough - I only wanted to get it to work, and I would probably go to assembler in the interrupt routine to implement it properly.
unsigned long value ;
unsigned long pwm_accum ;
volatile int Sec2 = 0;
// Interrupt every PWM cycle - this is where all the magic happens
ISR(TIMER2_COMPA_vect) {
digitalWrite(13, HIGH); // set the LED on - just toggling this so I can see when the interrupt routine is running
OCR2B = (byte) (pwm_accum >> 24) ; // Send the top byte of the PWM error accumulator to the PWM for the next cycle.
pwm_accum = pwm_accum - (((unsigned long) OCR2B) << 24 ) ; // Subtract the byte sent to the PWM from the top byte of the PWM error accumulator.
pwm_accum = pwm_accum + value; //Add the intended value for the output to the error from the last cycle
digitalWrite(13, LOW); // set the LED off
}
void setup() {
pinMode(3, OUTPUT); // PWM OCR2B
pinMode(11, OUTPUT); // PWM OCR2A
pinMode(13,OUTPUT) ;
TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM21) | _BV(WGM20);
// TCCR2B = _BV(CS22);
TCCR2B = 0x01; //Full 16MHz click for the PWM
// OCR2A = 180;
// OCR2B = 255;
value = 0L ;
pwm_accum = value ;
TIMSK2 = (1 << OCIE2A); // Enable interrupt when Timer reaches OCRA
sei();
}
void loop() {
value = 0x08980000L; // Equals 0.1678466 volts )
}
The PWM is run in FAST mode at a 16MHz clock rate. There are some things I will need to discover like what can go wrong with a current PWM cycle when you change the PWM register. Even if every PWM cycle is not perfect, if I get a monotonic control output with no evident digitizing steps, no evident voltage fluctuations and as much resolution as the power supply regulator allows, I will be extremely happy.
It is much slower of course then a DAC. Small changes settle quickly, but a large change like from 0V to maximum would take about 1 second to settle properly.
To implement it properly, you cannot use the PWM straight out of the Atmel. I would probably use a 74HC family non inverting buffer powered from a voltage reference to generate an accurate PWM output.
It may be that running the PWM at full speed adds to many errors due to hardware delay variations, and in that case, I will just slow the PWM down by a factor of 8. A better idea would be to use a high speed 74HC family synchronous flip-flop to reconstitute an accurately times PWM.
Back to the voltage regulator design. I will post it soon.
Richard.