As a follow up to one of my earlier projects, the Variable regulated power supply, initially designed to deliver 0..30Volts at up to 10Amps, I have decided it was time for an upgrade. I kept the enclosure and the nice 250W toroidal transformer, and am reshaping everything as a new digital power supply with regulated voltage and current, short circuit protection, lcd display, buzzer for various alarms, temperature sensor on the heat sink and automated protection. Optional bluetooth (HC-05 module) connectivity has been added to the PCB, to allow programming the supply or reading its parameters. For the core of this design I've chosen the well known ATMega8 microcontroller.

I've built this from scratch, so I wanted to explain all steps properly. The article got rather large in length, so you might want to use the following table of content to jump directly to the section of interest:
01. Introductory details
02. The working principle
03. The Digital to Analog Converter (DAC)
04. Voltage amplifier using an OP-Amp
05. Controlling the output
06. The user interface
07. Pictures and comments
08. Source code and PCB
09. Final notes

01. Introductory details

The enclosure has been brushed using a rotary tool, than painted black with a nice opaque colour. The heat sink has been cleaned and the power transistors mounted using wood screws that work great with aluminium as well.
digital_power_supply_1 digital_power_supply_2 digital_power_supply_3
A DS18B20 temperature sensor is also added to the heat-sink and pressed tightly using a metal piece for optimum contact. It's purpose is to start the fan or cut the power off depending on how hot it gets.
digital_power_supply_4 digital_power_supply_5
The resistors in the pictures have been changed to 0.1Ohm 5W, in the final circuit diagram. They are connected one to each transistor's emitter, to equalise the current trough this parallel assembly.

02. The working principle

So we have the transformer, a rectifier bridge and a few filter capacitors. The output goes trough several power transistors mounted in parallel on the heatsink. All is left to do is to linearly control the base of the power transistors in order to get variable output. In my previous design I used the LM317 for that. Now, with the digital design, I designed a digital to analog converter (DAC) to serve the purpose.
power_transistors_variable_output
A representative block diagram would be the following:
digital_bench_power_supply_diagram
The transformer I am using puts out 24VAC and has a centre tap for 12VAC and is rated for 250W. After the rectifier and the filter caps, for the full 24VAC, I can measure an output of 33.8Volts. Ideally, the supply should give us a variable output from 0V to 33.8V at a maximum current of 10Amps, or at least as close to that as possible. For practical reasons, let's set the target for the maximum output voltage to 30V.

03. The Digital to Analog Converter (DAC)

To command the base of the power transistors we'll need a signal that goes from 0 to 30V. The Atmega8 has plenty of Analog to Digital converters, but not the opposite. So we need to build one ourselves. There are several options here. One is to use a dedicated IC. I had none in my toolbox. So to build everything from scratch, I could either go for a R-2R network or for using a PWM output with a low pass filter.
OPTION-1: DAC with a R-2R network
While being fast and stable, this requires lots of resistors and a high number of IO pins, depending on the maximum resolution we want to achieve. A solution to reduce the pins is to use a shift register, like I did on my serial digital to analog converter, but I simply didn't have enough space on the PCB. So I went with the second option:
OPTION-2: DAC with a PWM generator and a low pass filter
Using ATmega's PWM on port PB1, I can generate a variable duty cycle PWM signal. A low pass filter consisting of a resistor and a small capacitor is added.
dac_pwm_lowpass
When the PWM signal goes high, it starts charging the capacitor, so the output voltage starts rising. When the PWM signal goes low again, it starts discharging the capacitor and the output voltage starts falling. The DC voltage at the output isn't smooth, it keeps going up and down slightly around the average voltage, and effect called ripple. You can find more details on this in the Serial digital to analog converter article, under DAC Method 1.
By changing the duty cycle from 0 to 100%, we get a variable voltage on the filter's capacitor which goes from 0V to Vcc, which for our circuit is 5V. To use a small size capacitor and save space on the PCB, the PWM's frequency must be high. To get from 5V to 30V, an OP Amp is used between the DAC and the power transistors. The first half of the OP Amp works as a buffer, allowing the DAC signal in on its high impedance input, while the output is a low impedance one, perfect for controlling the bases directly. Before we get there, the second half of the OP Amp is used as a non inverting amplifier with a gain of 6 selected using resistors, to go from 5V to 6x5V = 30V, exactly what we need.
But to get back to the DAC, we need to keep an eye on the ripple, as that will get into our output line and affect the stability. Using the oscilloscope, I connected CH1 (yellow) on the filter capacitor and the second channel CH2 (cyan) on the base of the power transistors, right after the OPAmp output.
ripple_on_PWM_DAC
It's not much, but it's there. The question is what frequency works best for the smallest ripple? As the RC filter capacitor is 100nF, the answer is a higher frequency. Here is a comparison on the same setup, using 1KHz, 10KHz, and 100KHz. The first two oscillographs have identical bases (you can see all parameters at the bottom):
PWM_DAC_variable_frequency
Going with 100KHz reduces the ripple on the output to less than 0.05V. The DAC is ready.

04. Voltage Amplifier using an OP-Amp

If you want to learn what an op-amp is, see Dave's video tutorial - by far one of the best resources on this topic:

Having the DAC output 0 to 5V, an OP-Amp was the perfect choice for boosting the command signal to 30V. There are certain limitations and if you've watched the video you know the OPAmp output can't really reach the rails input, unless it was designed to do so. Regular OPAmps will have a few volts difference. If I use the TL082, this digital bench power supply will output 26V and a maximum current of 5Amps. If I use the LM384, the maximum output goes up to 31.5V, while the maximum current will only be little below 4Amps. I also tried the TL062 and the LF357 but the results where inadequate for this application.
opamp_low_pass_filter_dac
The diagram shows the way the OpAmp is used. The first half as a buffer, taking the DAC output on its nice high impedance input, and setting a gain of almost six (68K/10K = 6.8) to amplify the low DAC voltage up to the interval we want. The output is a low impedance one, perfect for driving the power transistor base directly. So this block will set certain limits on the design. I opted for the TL082 for the final variant.
The second half of the OPAmp receives feedback on its negative input via the 68K / 10K resistors. The gain will be A = 68K / 10K = 6.8 . The 100 Ohm output resistors has little effect on the limits the supply can reach, it is there just to protect the base of the transistors from over-current.
Note: the opamp's rails are connected to the maximum voltage provided by the power supply, immediately after the rectifier block and to GND. This means two things: the opamp used must be able to withstand the relatively high input voltage, which in this case goes up to 33.8V . Second, as the negative rail is connected to ground, the voltage output is shifted to positive values only, but that is exactly what we want here. As Dave explained, even those opamps rated for dual positive/negative supplies will work in this configuration, assuming the difference from V+ to V- is greater then our requirement of 33.8V for this application. The TL082 fits ok, while the LM384 is pushed to the limit, but still works. Other Opamps can be used as well.

05. Controlling the output

This supply needs to be regulated (controlled voltage and current) but also offer shortcircuit protection. Finally we want the output parameters displayed on the LCD (voltage and drawn current). To do so, we'll need a way of measuring the output voltage and current. For the voltage, a simple resistive divider connected to one of the microcontroller's ADC ports does the job:
voltage_measurement_ADC
For the current, I used three high power resistors in parallel, with a low total resistance (0.18Ohm), in series with the power supply output. Each resistor is rated for 5W, so we get a total of 15W when in parallel. Using P = I^2 x R , we get I = 9.12Amps max current capacity.
When a consumer is connected, it is practically put in series with our shunt resistor, so the current flows through the consumer, but the same current also flows through our shunt, causing a voltage drop that validates Ohm's law: I = V / R. We know the resistance (0.18Ohm) and we can measure the voltage using another ADC port, allowing us to find out what the current is.
current_measurement_ADC
This means we'll use one ADC channel for measuring the voltage and another ADC channel for measuring the current via the voltage drop on the shunt. Knowing the current is critical, so we can take action in case of shortcircuit. The ADC conversion must be done as quickly as possible and we need to react without any delays. This means the current control code must be done separately from the main program loop. The solution is to do the ADC conversions using interrupts, and set the prescaler to a convenient value. Each time the ADC conversion is complete, we change the channel (if it was voltage we go for current and vice versa) and we restart the ADC. By doing so we alternatively measure both the voltage and the current .

  1.  
  2. float vref = 5.0;
  3. #define MODE_MEASURE_CURRENT 0
  4. #define MODE_MEASURE_VOLTAGE 1
  5. volatile bool adc_mode = MODE_MEASURE_CURRENT;
  6. volatile float current = 0, voltage = 0;
  7. ...
  8. // setup ADC to measure as interrupt: go for current first
  9. adc_mode = MODE_MEASURE_CURRENT;
  10. ADMUX = PC5;
  11. ADCSRA = (1<<ADEN) | (1<<ADIE) | (1<<ADIF) | (1<<ADSC) |
  12. (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0); // set prescaler to 128 (16M/128=125kHz, above 200KHz 10bit results are not reliable)
  13. sei();
  14. ...
  15. // Interrupt service routine for the ADC completion
  16. // we measure the current (PC5) and the voltage (PC4) alternatively
  17. ISR(ADC_vect){
  18. uint16_t analogVal = ADCL | (ADCH << 8);
  19. if (adc_mode == MODE_MEASURE_CURRENT) {
  20. current = (analogVal / 1024.0 * vref) / 0.186666; // the output power resistor measures 0.55Ohm
  21. // alternate mode
  22. adc_mode = MODE_MEASURE_VOLTAGE;
  23. ADMUX = PC4; // next time we will do ADC on PC4, to get the voltage
  24. } else if (adc_mode == MODE_MEASURE_VOLTAGE) {
  25. // the actual R13 and R14 resistors can be measured using a multimeter for better accuracy
  26. float R14 = 9.87, /*10k*/ R13 = 98.2; /*100K */
  27. voltage = analogVal / 1024.0 * (R13 + R14) / R14 * vref;
  28. // alternate mode
  29. adc_mode = MODE_MEASURE_CURRENT;
  30. ADMUX = PC5; // next time we will do ADC on PC5, to get the current
  31. }
  32.  
  33. // If free-running mode is enabled (ADFR), we don't need to restart conversion
  34. // But we can't use ADFR as we want to monitor two separate ADC channels alternatively
  35.  
  36. // start another ADC conversion
  37. ADCSRA = (1<<ADEN) | (1<<ADIE) | (1<<ADIF) /* | (1<<ADFR) */ | (1<<ADSC) |
  38. (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0); // set prescaler to 128 (16M/128=125kHz, above 200KHz 10bit results are not reliable)
  39.  
  40. // control operations, in order
  41. ...
  42. }
  43.  

Even if half of the ADC conversions are "wasted" on voltage measurements, we still get a very quick shortcircuit response:

And here are some nice oscillographs showing the quick recovery after a shortcircuit event. It takes less than 350ms to recover to the original voltage configured as shown in the second picture. The CH1 (Yellow) is connected to the output, and the CH2 (Cyan) is connected to the shunt. The first picture shows the quick voltage drop on output, and an increase on the shunt, as expected.
shortcircuit_response
Having the voltage and current precisely calculated, the next question is how to control the output? We use the duty cycle of the PWM generator for both!
If the digital bench power supply puts out more volts then selected, we decrease the duty cycle until we hit the target. Or the opposite case. For the current the same thing, again, thanks to Ohm's law applied on the load's resistance. But the order is important:
Right after the ADC measurements are done, and the voltage and current values saved in global volatile variables, first thing to check is the shortcircuit condition: a current considerably bigger than the limit. If we're in short circuit, the duty cycle is immediately reduced to compensate the risk factor. Then we get to check all the other cases. Why is this order important? If we were to adjust the voltage first, in case of shortcircuit we would see the voltage drop, and try to increase it back to where it was by pushing more power and incrementing the duty cycle, instead of prompt action of shutting the power down to save the power transistors from total destruction. The logic order makes a big difference!
The following code is done in the same ADC Interrupt call, due to it's maximum time-critical importance:

  1.  
  2. ...
  3. // control operations, in order
  4.  
  5. // 1. act on short circuit
  6. // decrease quickly on current spike
  7. if (current > targetCur + targetCur / 2) {
  8. pwm.setDuty(pwm.getDuty() / 2);
  9. alarm = true; // will sound alarm in main thread
  10. }
  11. // 2.act on over current
  12. // decrease slowly on higher current
  13. else if (current > targetCur) {
  14. pwm.setDuty(pwm.getDuty() - 1);
  15. }
  16. // 3.act on over temperature
  17. else if (tempval > TEMPERATURE_SHUTDOWN) {
  18. pwm.setDuty(pwm.getDuty() - 1);
  19. alarm = true;
  20. }
  21. // 4. act on over voltage
  22. // take care of voltage with lower priority
  23. else if (voltage > targetVol) pwm.setDuty(pwm.getDuty() - 1);
  24. // 5. act on under voltage
  25. else if (voltage < targetVol) pwm.setDuty(pwm.getDuty() + 1);
  26. }
  27.  

The temperature is also checked and we reduce the power output by decrementing the duty cycle, in case the supply is too hot, to prevent damage. The speaker becomes useful for adding an alarm sound. The alarm is triggered by setting a variable and played in the main program loop, to avoid unnecessary and non-critical delays in the interrupt code.
I've been using a transformer with a center tap and a relay commutes the input automatically: either the center tap if the configured voltage is set to less than 12V, otherwise the full secondary. The relay is controlled automatically based on the target voltage. If you're using a transformer that doesn't have a center tap, just connect the AC input to pin 1 and 2.
There is also a FAN, that goes on when the heatsink becomes warm, to help with heat dissipation. Use a 5V fan or two in parallel.

06. The user interface

The display is a 4x20 characters HD44780 LCD, but you can go with a 2x16 as well.
the_user_interface
The first line shows the voltage and maximum current configured. The second line shows the measurements: temperature, voltage and current. The third line shows the duty cycle in per-mille (‰) . The last line is free, and I've added my blog's address there.
The four buttons are used to set the voltage and current. Two for the voltage - up and down in 0.10Volt steps, and two for the current in 0.10Amps steps.
The self oscillating buzzer used as an alarm, is also part of the user interface, by providing audible notifications. This is useful when our workbench is crowded (happens so often!) and errors are likely.

07. Pictures and comments

Time to see the final results. Here's the first prototype PCB, done with toner transfer method:
digital_bench_power_supply_01 digital_bench_power_supply_02
Here are a few tests, with the output control, the shortcircuit response speed tests, and even a few negative temperature tests for the DS18B20 code where I've used ice and salt to go below zero:
digital_bench_power_supply_03 digital_bench_power_supply_04 digital_bench_power_supply_05
The multimeter was used in parallel, together with the oscilloscope, to confirm the output values as measured by the digital bench power supply:
digital_bench_power_supply_06 digital_bench_power_supply_07 digital_bench_power_supply_09
And last pics are reserved solely for the start of this article - the digital bench powersupply itself!
digital_bench_power_supply_08 digital_bench_power_supply_11 high_power_variable_power_supply_3
Yes, the last picture is the very old photo of my first power supply, constructed a few years ago. For any questions on this design, please use the comments section below or drop me a mail (my address is under the blog's About section).

08. Source code and PCB

This project is being released as open source, under GPL v2, hoping that it will be useful. Feel free to build your own variants, or even improve the design where possible. For any questions or feedback messages, use the comments form below.
circuit_diagram_pcb circuit_diagram_sch
This entire work has been released under GPL v2. It includes the circuit diagram as Eagle files, and the firmware source code. Revisions will be added from time to time, to fix bugs or bring enhancements. To review the GPL v2 license, click here.
Source code repos are available on Github or a first code release can be downloaded here.
BOM list: bom

09. Final notes

I just wanted to say thanks to all the great and knowledgable guys on elforum.ro who dedicated some of their precious time to contribute with suggestions to the development of this project.

Related Post