► Games

► Sound & Music

► Watches & Clocks


► Power Supplies

► Computers

► Graphics

► Thermometers

► Wearables

► Test Equipment

► Tutorials

► Libraries

► PCB-Based Projects

By processor

AVR ATtiny

► ATtiny10

► ATtiny2313

► ATtiny84

► ATtiny841

► ATtiny85

► ATtiny861

► ATtiny88

AVR ATmega

► ATmega328

► ATmega1284

AVR 0-series and 1-series

► ATmega4809

► ATtiny1604

► ATtiny1614

► ATtiny3216

► ATtiny3227

► ATtiny402

► ATtiny404

► ATtiny414

► ATtiny814

AVR DA/DB-series

► AVR128DA28

► AVR128DA32

► AVR128DA48

► AVR128DB28



► RP2040

► RA4M1

About me

  • About me
  • Twitter
  • Mastodon


RSS feed

Proto Power Supply

4th November 2017

This article describes a portable breadboard-friendly adjustable power supply, designed as an ideal supply for Arduino projects and other low-power circuits.

It is capable of providing from 0V to 5.5V at up to 0.5A, and is powered by two rechargable Li-ion cells. The output can be adjusted using a rotary encoder, and the voltage is displayed on a three-digit 7-segment display. The whole circuit is controlled by an ATmega328.

For example, here it's powering an ATtiny85V at its lowest specified voltage, about 1.8V, running the Blink program:


Proto Power Supply, fits in a standard breadboard and supplies 0 to 5.5V at up to 0.5A.


Existing breadboard power supplies usually offer 3.3V and 5V from a USB input, or an adjustable output from a fixed DC input. However, they all lack a few features that I wanted in a power supply:

  • It should be powered from rechargeable batteries, so I wasn't tied to a power socket.
  • It should be adjustable down to zero, so I could investigate the behaviour of a circuit with a failing battery, find the voltage at which an LED lights up, check brown-out detection, or build low-voltage circuits.
  • It should display the output voltage, so I don't have to tie up a multimeter checking the voltage.

As the supply is designed for testing AVR projects I didn't need more than 5.5V. This means that the supply could conveniently be powered by two 3.7V Li-ion batteries.

The power supply could have been based on a standard adjustable regulator, such as the LM317, but this would only have allowed the voltage to be adjusted down to about 1.25V. I therefore chose the LT3080 by Linear Technology [1], a very nice device which provides up to 1.1A all the way down to zero volts.

The circuit

Here's the full circuit of the Proto Power Supply:


Circuit of the Proto Power Supply, based on an ATmega328.

The LT3080 requires a minimum load for the regulation to work properly, specified on the datasheet as about 500µA. The simplest way to provide this is to connect a resistor across the output; I chose 220Ω, which still gives a small reading of 0.05V with no load, and draws an additional 25mA at full voltage. The ripple on the output is below about 2mV RMS.


In an earlier article Portable Lab Power Supply I based the project on the ATtiny861, which had just enough I/O lines. However, for this Proto Power Supply I decided to use the more popular ATmega328 because it's more widely available, often at a lower price than the ATtiny861, and although it has more pins, the SMD version actually has a smaller footprint than the SOIC ATtiny861.

In this application there's no need for accurate timing, so we can dispense with an external crystal, and use the ATmega328's internal clock.


The LT3080 is available in a variety of different packages. I chose the SOT-223 SMD package. The tab of the regulator is soldered to a large area of copper on the PCB, and I fitted a small heatsink to it with thermal adhesive tape for extra cooling [2]. I've tested the power supply giving a continuous 0.5A and it stays cool. In theory it should be capable of giving up to 1A, but at larger currents you would probably need a more substantial heatsink.

To generate the 5.5V to drive the digital circuitry and provide the reference for the ADC I used an LM317L adjustable regulator, in an SOT-89 package, which can be programmed with two resistors. The voltage is given by V = 1.25(1 + R2/R1); values of R1=200Ω and R2=680Ω give us exactly 5.5V as required.

Rotary encoder

For the rotary encoder I used Adafruit's rotary encoder, which gives 24 pulses per rotation [3]; for other options see Bounce-Free Rotary Encoder.


The circuit uses a low-cost 0.28" 3-digit 7-segment common anode LED display, type KYX2381BS, available on eBay [4]. You could also use a common-cathode display by changing the Display() routine.


The PCB is designed for an AA battery holder [5]. For the power input I chose two rechargable 3.7V 14250 batteries [6]; these are half the height of an AA battery, so two will fit in the battery holder, giving a total of 7.4V. Note that most 14250 rechargable batteries are not protected, so they shouldn't be allowed to discharge below 3V. Alternatively you could use non-rechargeable 14250 batteries (eg Saft), or a rechargeable NiCad PP3 battery. 


I designed the board in Eagle and sent it to Ragworm in the UK for fabrication [7]. The circuit uses a mixture of SMD and through-hole components, to achieve a compact layout while using readily available components:


The front and back of the completed Proto Power Supply circuit board.

None of the SMD components are particularly small so it should be possible to solder them by hand, using a fine-tipped soldering iron, but I used a Youyue 858D+ hot air gun set to 250°C. Fit the SMD components first, and then do the through-hole components.

The program

This section explains the various sections of the Proto Power Supply program.

Reading the rotary encoder

The rotary encoder is connected to two inputs, PC2 and PC3. They are configured with input pullups in setup(), and PC2 is configured with a pin-change interrupt:

  PORTC = 1<<PORTC2 | 1<<PORTC3;       // Set input pullups on A and B
  PCMSK1 = 1<<PCINT10;                 // Pin change interrupt on A (PC2/PCINT10)
  PCICR = 1<<PCIE1;                    // Enable interrupt
  PCIFR = 1<<PCIF1;                    // Clear interrupt flag

The pin-change interrupt service routine then reads the state of the two inputs, determines which direction the change in value should be, and calls ChangeValue(). Variables a and b hold the current states of the two inputs, the global variable a0 holds the previous state of a, and c0 holds the cleaned-up signal:

ISR (PCINT1_vect) {
  int a = PINC>>A & 1;
  int b = PINC>>B & 1;
  if (a != a0) {              // A changed
    a0 = a;
    if (b != c0) {
      c0 = b;
      ChangeValue(a == b);

This is based on my earlier article Bounce-Free Rotary Encoder.

Generating an analogue output

The power supply voltage is controlled by an analogue output, generated by a PWM signal on PD3. This is generated using Timer/Counter2, which is configured in setup() to generate phase-correct PWM:

  DDRD = 1<<DDD3;                      // Make PD3 (OC2B) an output for PWM
  TCCR2A = 2<<COM2B0 | 1<<WGM20;       // Normal output, Phase Correct PWM to OCR2A
  TCCR2B = 1<<WGM22 | 2<<CS20;         // Divide clock by 8
  OCR2A = Steps-1;                     // Divide range into 220 steps of 0.025V
  TIMSK2 = 1<<TOIE2;                   // Enable overflow interrupt

Rotating the rotary encoder calls ChangeValue() which increments or decrements the value of the global variable Count:

void ChangeValue (bool Up) {
  Count = max(min((Count + (Up ? 1 : -1)), Steps-1), 0);
  Overload = false;

This in turn calls SetVoltage() which changes the Timer/Counter2 compare match value OCR2B:

void SetVoltage (int Count) {
  Target = Count * Stepsize;
  OCR2B = Count;

Reading the output voltage

The regulated output voltage is read by the analogue input ADC0, on PC0. This is configured in setup() to use Vcc as the reference voltage:

  ADMUX = 1<<REFS0 | 0<<MUX0;          // Vcc as ref, ADC0 (PC0)
  ADCSRA = 1<<ADEN | 7<<ADPS0;         // Enable ADC, 62.5kHz clock

The analogue value is read by the routine ReadADC():

int ReadADC () {
  uint8_t low, high;
  ADCSRA = ADCSRA | 1<<ADSC;           // Start
  while (ADCSRA & 1<<ADSC);            // Wait while conversion in progress
  low = ADCL;
  high = ADCH;
  return high<<8 | low;

Displaying the voltage

Finally, the analogue voltage is displayed on the three-digit 7-segment display. For simplicity I used the same timer, Timer/Counter2, to multiplex the display. Every 220 counts the counter overflows, and this is used to call an interrupt service routine:

ISR (TIMER2_OVF_vect) {

This in turn calls DisplayNextDigit(). This puts the appropriate segment data onto PORTB for the next digit to be displayed, and then takes the digit common line high on PORTD. The number to be displayed on each display digit is specified by the array Buffer[]:

void DisplayNextDigit() {
  static int LastVoltage;
  DDRD = DDRD & ~(1<<Digits[digit]);         // Make previous digit bit an input
  digit = (digit+1) % (Ndigits+1);
  if (digit < Ndigits) {
    char segs = charArray[Buffer[digit]];
    if (digit==0) segs = segs | 0x80;        // Decimal point
    PORTB = ~segs;
    DDRB = segs;
    DDRD = DDRD | 1<<Digits[digit];          // Make digit bit an output
    PORTD = PORTD | 1<<Digits[digit];        // Take digit bit high
  } else {
    DDRB = 0;                                // All segments off
    int Voltage = (ReadADC() * (long)MaxVoltage)/1024;
    // Add hysteresis to stop display jumping
    if (abs(LastVoltage - Voltage) >= 10) {
      LastVoltage = Voltage;
      // Overload protection - if output is more than 0.5V below target
      if (Target - Voltage > 500) {
        Overload = true;
        Count = 0;
      if (Overload) Buffer[0] = Buffer[1] = Buffer[2] = Dash;
      else Display(Voltage/10);

Every fourth cycle this routine reads the ADC, and calls Display() to write the appropriate values to the display buffer, Buffer[]. To prevent the display flickering between two adjacent values it only updates the display if the voltage has changed by at least 10mV.

Overload trip

The LT3080 incorporates short-circuit protection, limiting the output current to about 1.4A, but this is still enough to damage an incorrectly-wired circuit. I therefore incorporated an overload trip, which is triggered if the target voltage, set by the rotary encoder, is more than 500mV higher than the measured output voltage. This is handled by a check in DisplayNextDigit(), which sets the Overload flag if an overload has occurred. The voltage is set to zero and the display shows dashes. To reset the overload trip rotate the encoder.

Push-button preset

The rotary encoder button presets the voltage to 3.3V. The button is wired to PD2 which is configured in setup() to generate an interrupt on INT0:

  PORTD = 1<<PORTD2;                   // Set input pullup
  EICRA = 2<<ISC00;                    // Interrupt on falling edge
  EIMSK = 1<<INT0;                     // Enable interrupt

The interrupt service routine then just simply sets Count to 3.3/0.025 or 132:

ISR (INT0_vect) {
  Count = 132;           // 3.3V

Compiling the program

You can program the ATmega328 using ISP (in-system programming) via the 6-pin ISP connector provided on the PCB. I used a USBASP programmer, available cheaply on eBay, which includes a ribbon cable that connects to the ISP connector on the Proto Power Supply board. Alternatively you could use a Sparkfun Tiny AVR Programmer, or an Arduino Uno as an ISP programmer.

The best Arduino core to use for programming a bare ATmega328P now is MCUdude's MiniCore. Choose the ATmega328 option under the MiniCore heading on the Board menu. Check that the subsequent options are set as follows (ignore any other options):

Clock: "Internal 8 MHz"
BOD: "BOD 4.3V"
Variant: "328P / 328PA"
Bootloader: "No Bootloader"

Select the USB port corresponding to the ISP programmer in the Port menu. Then choose Burn Bootloader to set the fuses appropriately, and select Upload to upload the program.

Here's the whole Proto Power Supply program: Proto Power Supply Program.

Alternatively, get it on GitHub here together with the latest Eagle files for the PCB: Proto Power Supply on GitHub.

Or order a board from OSH Park here: Proto Power Supply Board.


2nd April 2018: Changed the recommended fuse settings to B.O.D. Enabled (4.3V) so the circuit will switch off when the battery voltage drops.

26th July 2022: Have modified the program (and description) so that pressing the rotary encoder button presets the voltage to 3.3V, a frequently used voltage.

5th November 2023: Have updated the instructions for compiling and uploading to use MiniCore, which is now the recommended option.

  1. ^ LT3080 Datasheet from Linear Technology.
  2. ^ Heatsink SMD 63K/W 13 x 6.3 x 4.8mm on RS-Online.
  3. ^ Rotary Encoder + Extras on Adafruit.
  4. ^ 5Pcs Common Anode 12 Pin 3 Bit 7 Segment 0.28" Red LED Display on eBay.
  5. ^ Keystone 2460 Battery Holder AA x 1 on Farnell.
  6. ^ Kingwei 14250 3.7V 300mAh Li-ion Rechargeable Battery on Aliexpress.
  7. ^ Ragworm PCB prototyping service.

blog comments powered by Disqus