► 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

About me

  • About me
  • Twitter
  • Mastodon


RSS feed

Adjustable Load

24th March 2023

This is an adjustable load that provides a constant-current load for testing power supplies and batteries. It allows you to set the load current to up to 1.05A, using a potentiometer, and displays the current on a three-digit LED display controlled by an ATtiny84:


The Adjustable Load, based on an LT3080 regulator and an ATtiny84A processor,
drawing 1A from a power supply being tested.

As well as testing power supplies, the adjustable load can be used for testing rechargeable NiMH and Li-ion batteries under different load conditions. It measures the current over time, and displays the battery's capacity in mAh or Ah. This is useful for comparing different makes of battery, or for deciding whether a battery has reached the end of its life.


To test a power supply you typically want to run it into a load, to see how it handles at the limit of its current handling capability, check its heat dissipation, and measure the noise and ripple on the output. One way is to use a high wattage resistor, but this only lets you check one fixed load. The solution is an adjustable load; a circuit that simulates a load and lets you adjust the current through the load over a wide range.

I recently needed an adjustable load for two different projects I was working on. I wanted to check my Power Deliverer with Current Monitor at different voltages and currents, and I also wanted to check the capacity of several lithium rechargeable batteries to determine whether they actually matched their published mAh rating.

I had already built an adjustable load in an earlier project, Adjustable Load, but unfortunately I had dismantled the prototype, so I decided to design a PCB for a new circuit. Initially I thought of updating the design to use an OLED display, rather than the original LED display, and a new 0-series ATtiny processor rather than the ATtiny84 used in the original, but I decided there were advantages in keeping to the original circuit. It's a proven circuit, so I could go straight to designing a PCB, and all the components are through-hole, so you don't need SMD skills to build it. I've made a few improvements to the circuit and program, but it's essentially the same as the original.

The circuit

Here's the full circuit of the adjustable load:


Circuit of the adjustable load, based on an LT3080 regulator and ATtiny84 processor.

The constant-current circuit is based on an LT3080 regulator [1]. This uses a current reference of 10µA, rather than a voltage reference like regulators such as the LM317, and will operate down to 1.5V and a few mA.

The current is sensed by a 1Ω resistor, and this is fed to the sense input of the regulator, via a 100kΩ potentiometer and 5.1kΩ resistor. Since the current through the potentiometer and resistor is 10µA, at the potentiometer's maximum setting the voltage across them will be 1.05V, so the output of the regulator will be maintained at 1.05V across the 1Ω resistor, producing a constant current of 1.05A.

The LT3080 is available in a few different packages. I chose the TO220-5 package which provides a separate VCONTROL pin, and is convenient to mount on a heatsink.

The current is displayed on a three-digit 7-segment display. I used a 0.28" three-digit common anode seven-segment LED display.

You could power the ATtiny84 and display from the power supply you are testing, via a 3.3V regulator. However, I decided to use a separate 3.7V Lipo battery because this gives two advantages: First, it allows you to test inputs down to as low as 1V. Secondly, it ensures that the microcontroller and display remain powered even when the test supply drops to zero, which is essential for battery testing.

The potentiometer is a 100kΩ linear type. I used an Alps Alpine RK09K1130C94 [2]. Another suitable one is the Bourns PTV09A-4020U-B104 [3]. I added the 5.1kΩ series resistor because I wanted to be able to adjust the current slightly above 1A.

For the LT3080 to regulate, the VCONTROL voltage must be more than 1.2V to 1.35V greater than the output voltage. In some applications this pin can simply be connected to VIN, but here this would limit the minimum voltage that the adjustable load can control to about 2V. The solution is to connect it to the 3.7V supply used to power the ATtiny84 and display.

► Parts list


I designed a PCB in Eagle and ordered boards from PCBWay. I give links at the end of the article so you can order your own boards. I designed the PCB to fit the heatsink I used. This has a thermal rating of 6.8°C/W [4]. With a current of 1A at 5V the voltage across the LT3080 will be 4V, so the power will be 4W; the heatsink will therefore be 27.2°C above the ambient temperature, or typically 47.2°C. This is safely below the LT3080's recommended maximum operating temperature of 90°C.

I've provided a 2.35" x 1.25" area of copper on the PCB which you could use as a heatsink. There's a rule of thumb that says that "one Watt of power dissipated in one square inch of two-ounce copper generates a 100°C temperature rise" [5] so this will only provide a thermal rating of about 34°C/W and won't be sufficient if you want to run the Adjustable Load at its full current.

The TO-220 LT3080 is designed for vertical mounting so you need to gently bend the leads with a pair of flat-nose pliers to fit the PCB. Attach the regulator to the heat sink and board with an M3 screw and nut.

All the components are through-hole, so it should be easy to solder by hand. Mount the 1Ω 2W resistor slightly above the board to improve airflow around it.

The PCB includes space for a 2x3 pin ICSP connector that you can use to program the ATtiny84A. For details see Compiling the program below.

The total current consumption of the controller is about 10mA; I power it from a small 3.7V Lipo cell connected to the two terminals and attached to the bottom of the board with a double-sided foam sticky pad. You could alternatively use a pair of 1.5V AAA batteries in a battery box.

The program

Displaying the current

The voltage across the 1Ω resistor is read by an analogue input on an ATtiny84, and this then displays it on a three-digit seven-segment display. The analogue input, ADC7 on PA7, is configured by SetupADC() to use the 1.1V reference voltage:

void SetupADC () {
  // Set up ADC on ADC7 (PA7)
  ADMUX = 2<<REFS0 | 7<<MUX0;                // 1.1V ref, ADC7 (PA7)
  ADCSRA = 1<<ADEN | 4<<ADPS0;               // Enable ADC, 62.5kHz clock

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

int ReadADC () {
  unsigned char low, high;
  ADCSRA = ADCSRA | 1<<ADSC;                 // Start
  while (ADCSRA & 1<<ADSC);                  // Wait while conversion in progress
  return ADC;

I used a common-anode seven-segment LED display, but you could use a common cathode display by changing a few lines in the DisplayNextDigit() routine.

Timer/Counter0 multiplexes the display, and this is configured by SetupTimer0() to generate an interrupt at 312.5Hz, which is fast enough to avoid flicker:

void SetupTimer0 () {
  TCCR0A = 2<<WGM00;                         // CTC mode
  TCCR0B = 0<<WGM02 | 3<<CS00;               // Divide by 64
  OCR0A = 49;                                // Divide by 50 for 312.5Hz interrupt
  TIMSK0 = 1<<OCIE0A;                        // Enable compare match interrupt

The interrupt service routine simply calls DisplayNextDigit():

ISR(TIM0_COMPA_vect) {

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

void DisplayNextDigit() {
  static int LastReading;
  DDRB = 0;                                  // Make all digits inputs 
  DDRA = 0;                                  // Make all segments inputs 
  digit = (digit+1) % (Ndigits+1);
  if (digit < Ndigits) {
    char segs = charArray[Buffer[digit]];
    if (digit == DP) {
      DDRB = DDRB | 0x04;                    // Decimal point output
      PORTB = PORTB & 0xFB;                  // Decimal point low
    DDRA = segs & 0x7F;                      // Segments outputs
    PORTA = ~segs & 0x7F;                    // Segments low
    DDRB = DDRB | 1<<Digits[digit];          // Make digit an output
    PORTB = PORTB | 1<<Digits[digit];        // Take digit bit high
  } else {
    // Read analogue input
    Current = (ReadADC()*29)/27;
    // If current near zero, display charge
    if (Current < 100) {                     // < 0.1A
      int mAh = Charge/3600;
      if (mAh < 1000) Display(mAh, -1); else Display(mAh/10, 0);
      LastReading = 0;
    } else {
      // Add hysteresis to stop display jumping
      if (abs(LastReading - Current) >= 10) {
        Display((Current + 5)/10, 0);
        LastReading = Current;

Every fourth cycle this routine blanks the display, to minimise interference with the analogue input, and then calls ReadADC() to read the voltage across the 1Ω resistor. Since the ADC is using a 1.1V reference, a reading of 1024 corresponds to 1.1V, so the current in mA is 1100/1024 times the reading. 29/27 is a good approximation to this, and avoids long arithmetic. See Calibration below for details of how to fine-tune this.

The routine then calls Display() to display the current in amps or, if the current is zero, the charge in Ah or mAh. The routine Display() simply writes the three digits of the value into the array Buffer[].

Calculating battery capacity

The battery capacity feature uses Timer/Counter1 to generate a 1Hz interrupt; this is configured by SetupTimer1():

void SetupTimer1 () {
  TCCR1A = 0<<WGM10;
  TCCR1B = 1<<WGM12 | 3<<CS10;               // Divide by 64
  OCR1A = 15624;                             // Divide by 15625 for 1Hz interrupt
  TIMSK1 = 1<<OCIE1A;                        // Enable compare match interrupt

The interrupt service routine sums the current every second to calculate the total charge. It also flashes the decimal point to show that the circuit is operating:

ISR(TIM1_COMPA_vect) {
  Charge = Charge + Current;
  if (Current != 0) DP = DP ^ 0xFF;          // Flash decimal point


Testing a power supply

The adjustable load allows you to test a power supply you have built, or a commercial power supply, to see how it handles different loads, check its heat dissipation, and measure the noise and ripple on its output.

Connect the power supply to the adjustable load, observing polarity, and adjust the potentiometer to the load you want to simulate.

Checking rechargeable battery capacity

To test the capacity of a rechargeable battery, charge it fully, and then connect the battery to the adjustable load, observing polarity. Adjust the current to the maximum value that the battery can handle.

Unless otherwise specified, most Li-ion batteries have a maximum discharge rate of 2C, which means that the maximum current is twice their rating in mAh; for example, a 350mAh battery has a maximum safe discharge rate of 700mA. This has the interesting consequence that at their maximum discharge rate every battery should last half an hour.

When the current drops to zero, either because the battery is exhausted, or because you have disconnected the battery, the meter automatically switches to displaying capacity rather than current. For capacities of up to 999mAh the display shows the number of mAh; otherwise it shows the number of Ah, up to 9.99Ah. To reset the capacity measure switch the power off and on again.

I used the Adjustable Load to compare two 3.7V lithium 10440 batteries. These are convenient cells to use for projects because they fit in a standard AAA battery holder. One was a PKCELL battery [6] rated at 350mAh, and the other was a TrustFire battery rated at 600mAh. It seemed a bit implausible that the second manufacturer managed to squeeze nearly twice the capacity from the same size of cell.

For each cell, after fully recharging it, I connected it to the Adjustable Load with the current set to 0.70A. After about half an hour each cell had stopped producing a current through the load, and the displays switched to showing the capacity. 


Using the adjustable load to measure the capacity of a rechargeable battery.

The PKCELL showed a capacity of 334mAh, which is close to the rated capacity, but the TrustFire battery showed a capacity of 342mAh, which was what you might expect from its size, but well below its advertised capacity of 600mAh.


There are a couple of calibrations you can make to improve the accuracy of the adjustable load.


The LT3080 datasheet gives the accuracy of the 10µA reference current as ±1%, but the main source of inaccuracy is probably the 1Ω load resistor. Fortunately it's easy to correct for this by adjusting the ratio 29/27 in the line of the program:

Current = (ReadADC()*29)/27;

Connect a power supply to the Adjustable Load through a digital multimeter capable of measuring 1A, and set the Adjustable Load current to read 1.00A. Then, scale the factor 29/27 by d/1000, where d is the digital multimeter reading in mA.

Battery capacity

The battery capacity calculation relies on the internal oscillator for timing, which is accurate to about 10%. If you want greater accuracy you can calibrate it using the OSCCAL register to within about 1%. Measure the frequency on PB1 (the left-hand lead of R2, the 220Ω resistor nearest the front of the board) with the frequency range of a multimeter, and recompile the program with different values of OSCCAL until the frequency is as close as possible to 78.125Hz.

Compiling the program

Compile the program using Spence Konde's ATTiny Core on GitHub. Choose the ATtiny24/44/84(a) (No bootloader) option under the ATTinyCore heading on the Board menu. Check that the subsequent options are set as follows:

Chip: "ATtiny84(a)"
Clock: "1 MHz internal"

This is the default fuse setting on new ATtiny84s; otherwise choose Burn Bootloader to set the fuses appropriately. Then upload the program to the ATtiny84 using a suitable ISP programmer such as the Tiny AVR Programmer Board; see ATtiny-Based Beginner's Kit.

Note that while the ISP programmer is connected to the circuit the MOSI connection may cause the A segments of the displays to light up, so to avoid misleading displays when testing the board disconnect the programmer.


Here's the program for the Adjustable Load: Adjustable Load Program.

Get the Eagle files for the PCB here:

Or order boards from OSH Park here: Adjustable Load.

Or order boards from PCBWay here: Adjustable Load.

  1. ^ LT3080 Datasheet from Linear Technology.
  2. ^ Alps Alpine RK09K1130C94 on Farnell.
  3. ^ Bourns PTV09A-4020U-B104 on Digi-Key.
  4. ^ Aavid Thermalloy 1.25GY50 Heatsink on Farnell.
  5. ^ Accurate Thermal Calculations on the Back of a Napkin on
  6. ^ AAA 3.7V 350mAh Li-ion batteries on Amazon.

blog comments powered by Disqus