digitaleclab.com electronic load
Projects

DIY Programable DC Electronic Load

This is my project on making a programable DC electronic load which I can use to test power supplies, battery capacity, etc. Obviously there are many such instruments available in the market but those are pretty expensive for a hobbyist like me. So I decided to make my own DC electronic load which can fulfil my requirements with a good amount of precision.

Method 1: Using a power resistor

So the first idea that came up on my mind is to use a resistor which can dissipate a lot of power. Its quite simple to use. I just need to use Ohm’s law to calculate the power.

But there are many problems in using a resistor as a load. Some of these are:

  • To control the current at a given voltage we have to choose a specific value of resistor which means you should have a good collection of resistor values.
  • Current through the resistor changes linearly with change in voltage (from Ohm’s law), which means this cannot be used with a power source which changes voltage over time (e.g. battery).
  • There is no feedback control which can regulate the current and voltage.

So in order to counter these problems I have to look for another method.

Method 2: Using MOSFET as a load

This method is the most practical method of electronic load. Instead of a resistor we will use a MOSFET to dissipate the power. Now a MOSFET is a semiconductor switch which turns on when sufficient voltage is applied to the gate terminal. Take a look into the RDS value and VGS from the table.

If you observe the table carefully, you will notice that it has a minimum resistance (RDS) when 10V or more is applied to the gate. But the MOSFET starts conduction at the gate threshold voltage (2 – 4 V). Now take a look into the characteristics curve of the same MOSFET.

From the characteristics curve we can clearly observe that the MOSFET offers more resistance when we apply voltage between 4V to 10V. To use a MOSFET as load, we are going to use this property of MOSFET.

Now in order to apply a variable voltage at the gate of MOSFET we need to convert digital PWM signal to analog voltage. We can do this easily with an RC filter or LC filter. But that’s actually a very crude method and to my experiment it didn’t gave me a satisfactory result. If you want to have a look into my experiment on breadboard, you can definitely watch this video:

Now the other way to convert digital to analog is by using a dedicated DAC module. Here in this project I will be using the MCP4725 I2C module which has a 12-bit resolution.

Schematic of final design v1.0

digitaleclab.com schematic dc electronic load

Code

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <Adafruit_ADS1X15.h>
#include <Adafruit_MCP4725.h>
Adafruit_MCP4725 dac;
Adafruit_ADS1115 ads;
LiquidCrystal_I2C lcd(0x27, 16, 2);
#define clk 2
#define dt 3
#define sw 4
char screen = 0;
char arrowpos = 0;
float power = 0;
float current = 0;
float curcurrent = 0;
float curpower = 0;
float curvoltage = 0;
int counter = 0;
uint32_t dac_value;
volatile boolean currentmode = false;
volatile boolean powermode = false;
volatile boolean TurnDetected = false;
volatile boolean up = false;
volatile boolean button = false;

byte customChar1[8] = {
  0b10000,
  0b11000,
  0b11100,
  0b11110,
  0b11110,
  0b11100,
  0b11000,
  0b10000
};

byte customChar2[8] = {
  0b00100,
  0b01110,
  0b11111,
  0b00000,
  0b00000,
  0b11111,
  0b01110,
  0b00100,
};

ISR(PCINT2_vect) {
  if (digitalRead(sw) == LOW) {
    button = true;
  }
}

void isr0 ()  {
  TurnDetected = true;
  up = (digitalRead(clk) == digitalRead(dt));
}

void setup() {
  lcd.init();
  lcd.backlight();
  ads.begin();
  dac.begin(0x60);
  dac_value = 0;
  dac.setVoltage(dac_value, false);
  pinMode(sw, INPUT_PULLUP);
  pinMode(clk, INPUT);
  pinMode(dt, INPUT);
  PCICR |= 0b00000100;
  PCMSK2 |= 0b00010000;   // turn o PCINT20(D4)
  attachInterrupt(0, isr0, RISING);
  ICR1 = 2047;
  lcd.createChar(0, customChar1);
  lcd.createChar(1, customChar2);
  lcd.clear();
  lcd.print("digitaleclab.com");
  delay(3000);
  screen0();
  lcd.setCursor(0, 0);
  lcd.write((uint8_t)0);
}

void loop() {

  if (currentmode) {
    curcurrent = ads.readADC_Differential_0_1() * 0.1875 / 1000.00 / 0.0975;
    if (counter == 100) {
      lcd.setCursor(4, 1);
      lcd.print(curcurrent);
      lcd.print("A ");
      counter = 0;
    }
    if (curcurrent < current) {
      if (dac_value < 4095) {
        dac_value++;
        dac.setVoltage(dac_value, false);
      }
      else {
        dac.setVoltage(dac_value, false);
      }
    }
    else {
      if (dac_value > 0) {
        dac_value = dac_value - 1;
        dac.setVoltage(dac_value, false);
      }
      else {
        dac.setVoltage(dac_value, false);
      }
    }
    counter++;
    delayMicroseconds(100);
  }

  if (powermode) {
    curcurrent = ads.readADC_Differential_0_1() * 0.1875 / 1000.00 / 0.0975;
    curvoltage = ads.readADC_SingleEnded(2) * 0.1875 * 11.13 / 1000.00;
    curpower = curvoltage * curcurrent;
    //Serial.println(curpower);
    if (counter == 100) {
      lcd.setCursor(4, 1);
      lcd.print(curpower);
      lcd.print("W ");
      counter = 0;
    }
    if (curpower < power) {
      if (dac_value < 4095) {
        dac_value++;
        dac.setVoltage(dac_value, false);
      }
      else {
        dac.setVoltage(dac_value, false);
      }
    }
    else {
      if (dac_value > 0) {
        dac_value = dac_value - 1;
        dac.setVoltage(dac_value, false);
      }
      else {
        dac.setVoltage(dac_value, false);
      }
    }
    counter++;
    delayMicroseconds(100);
  }

  if (TurnDetected) {
    delay(200);
    switch (screen) {
      case 0:
        switch (arrowpos) {
          case 0:
            if (!up) {
              screen0();
              lcd.setCursor(0, 1);
              lcd.write((uint8_t)0);
              arrowpos = 1;
            }
            break;
          case 1:
            if (up) {
              screen0();
              lcd.setCursor(0, 0);
              lcd.write((uint8_t)0);
              arrowpos = 0;
            }
            break;
        }
        break;
      case 1:
        switch (arrowpos) {
          case 0:
            if (!up) {
              screen1();
              lcd.setCursor(0, 1);
              lcd.write((uint8_t)0);
              arrowpos = 1;
            }
            break;
          case 1:
            if (up) {
              screen1();
              lcd.setCursor(0, 0);
              lcd.write((uint8_t)0);
              arrowpos = 0;
            }
            else {
              screen1();
              lcd.setCursor(7, 1);
              lcd.write((uint8_t)0);
              arrowpos = 2;
            }
            break;
          case 2:
            if (up) {
              screen1();
              lcd.setCursor(0, 1);
              lcd.write((uint8_t)0);
              arrowpos = 1;
            }
            break;
        }
        break;
      case 2:
        if (up) {
          power = power + 0.1;
          lcd.setCursor(7, 0);
          lcd.print(power);
          lcd.print("W");
          lcd.write((uint8_t)1);
          lcd.print("  ");
        }
        else {
          power = power - 0.1;
          if (power < 0) {
            power = 0;
          }
          lcd.setCursor(7, 0);
          lcd.print(power);
          lcd.print("W");
          lcd.write((uint8_t)1);
          lcd.print("  ");
        }
        break;
      case 4:
        switch (arrowpos) {
          case 0:
            if (!up) {
              screen4();
              lcd.setCursor(0, 1);
              lcd.write((uint8_t)0);
              arrowpos = 1;
            }
            break;
          case 1:
            if (up) {
              screen4();
              lcd.setCursor(0, 0);
              lcd.write((uint8_t)0);
              arrowpos = 0;
            }
            else {
              screen4();
              lcd.setCursor(7, 1);
              lcd.write((uint8_t)0);
              arrowpos = 2;
            }
            break;
          case 2:
            if (up) {
              screen4();
              lcd.setCursor(0, 1);
              lcd.write((uint8_t)0);
              arrowpos = 1;
            }
            break;
        }
        break;
      case 5:
        if (up) {
          current = current + 0.1;
          lcd.setCursor(9, 0);
          lcd.print(current);
          lcd.print("A");
          lcd.write((uint8_t)1);
          lcd.print(" ");
        }
        else {
          current = current - 0.1;
          if (current < 0) {
            current = 0;
          }
          lcd.setCursor(9, 0);
          lcd.print(current);
          lcd.print("A");
          lcd.write((uint8_t)1);
          lcd.print(" ");
        }
        break;
    }
    TurnDetected = false;
  }

  if (button) {
    delay(200);
    switch (screen) {
      case 0:
        if (arrowpos == 0) {
          screen = 1;
          screen1();
          lcd.setCursor(0, 0);
          lcd.write((uint8_t)0);
        }
        else {
          screen = 4;
          screen4();
          lcd.setCursor(0, 0);
          lcd.write((uint8_t)0);
        }
        break;
      case 1:
        switch (arrowpos) {
          case 0:
            screen = 2;
            screen2();
            break;
          case 1:
            powermode = true;
            screen = 3;
            screen3();
            break;
          case 2:
            screen = 0;
            screen0();
            lcd.setCursor(0, 0);
            lcd.write((uint8_t)0);
            break;
        }
        break;
      case 2:
        screen = 1;
        screen1();
        lcd.setCursor(0, 0);
        lcd.write((uint8_t)0);
        break;
      case 3:
        powermode = false;
        dac.setVoltage(0, false);
        dac_value = 0;
        counter = 0;
        screen = 1;
        screen1();
        lcd.setCursor(0, 0);
        lcd.write((uint8_t)0);
        break;
      case 4:
        switch (arrowpos) {
          case 0:
            screen = 5;
            screen5();
            break;
          case 1:
            screen = 6;
            screen6();
            currentmode = true;
            counter = 0;
            break;
          case 2:
            screen = 0;
            screen0();
            lcd.setCursor(0, 0);
            lcd.write((uint8_t)0);
            break;
        }
        break;
      case 5:
        screen = 4;
        screen4();
        lcd.setCursor(0, 0);
        lcd.write((uint8_t)0);
        break;
      case 6:
        screen = 4;
        screen4();
        lcd.setCursor(0, 0);
        lcd.write((uint8_t)0);
        currentmode = false;
        dac.setVoltage(0, false);
        dac_value = 0;
        break;
    }
    arrowpos = 0;
    button = false;
  }
}

void screen0() {
  lcd.clear();
  lcd.setCursor(1, 0);
  lcd.print("Const. Power");
  lcd.setCursor(1, 1);
  lcd.print("Const. Current");
}

void screen1() {
  lcd.clear();
  lcd.setCursor(1, 0);
  lcd.print("Power:");
  lcd.print(power);
  lcd.print("W");
  lcd.setCursor(1, 1);
  lcd.print("Start");
  lcd.setCursor(8, 1);
  lcd.print("Back");
}

void screen2() {
  lcd.clear();
  lcd.setCursor(1, 0);
  lcd.print("Power:");
  lcd.print(power);
  lcd.print("W");
  lcd.write((uint8_t)1);
}

void screen3() {
  lcd.clear();
  lcd.print("Set:");
  lcd.print(power);
  lcd.print("W");
  lcd.setCursor(0, 1);
  lcd.print("Cur:");
  lcd.print(curpower);
  lcd.print("W");
  lcd.setCursor(11, 1);
  lcd.write((uint8_t)0);
  lcd.print("STOP");
}

void screen4() {
  lcd.clear();
  lcd.setCursor(1, 0);
  lcd.print("Current:");
  lcd.print(current);
  lcd.print("A");
  lcd.setCursor(1, 1);
  lcd.print("Start");
  lcd.setCursor(8, 1);
  lcd.print("Back");
}

void screen5() {
  lcd.clear();
  lcd.setCursor(1, 0);
  lcd.print("Current:");
  lcd.print(current);
  lcd.print("A");
  lcd.write((uint8_t)1);
}

void screen6() {
  lcd.clear();
  lcd.print("Set:");
  lcd.print(current);
  lcd.print("A");
  lcd.setCursor(0, 1);
  lcd.print("Cur:");
  lcd.print(curcurrent);
  lcd.print("A");
  lcd.setCursor(11, 1);
  lcd.write((uint8_t)0);
  lcd.print("STOP");
}

Final built part – 2

3 thoughts on “DIY Programable DC Electronic Load

  1. Dear Sir, very interesting project. I would like to build a similar project for a load 30V/25A. But I would replace the shunt resistor with a voltage devider to feed the ADS1115 with a max of 5V at a load of 30V. Also if you use a shunt resistor of 0,1 ohm it must be able to handle about 64 W. Maybe I will also use 2 MOSFETS instead of one to share the load, is this better, or can I use 1 MOSFET. Like to hear your feedback.

  2. You have a “Voltage” signal connected to ADS115 directly (AIN2) and it can be up to 30V, which is not permissible according to the Datasheet ADS1115 (6.144 MAX). I think here you need a voltage divider.

Leave a Reply

Your email address will not be published. Required fields are marked *