Active Dummy Load

Wasting power never felt so good

After I built the power supply testing jig, I thought the dummy load side of things could have been better. Before I talk about the changes I wanted to make, let’s first look at the original dummy load.

dummy load(1)

The way this circuit works, is I had an op-amp setup as a comparator, which controls the gate of an N-channel MOSFET. The voltage drop across my five 1Ω resistors, gives me an equivalent resistance 0f 0.2Ω, which gives me a transfer function, where CURRENT = 5*I (Volts). If that voltage drop exceeds the voltage of the wiper of the trimmer pot, current is stopped by the MOSFET, but now that the current is too low, the current is allowed to flow again, which then repeats. The capacitors help to average out the switching and read an analog voltage.

The main goal I wanted in my new design was an easy way to display the current, voltage, and power being dissipated through the dummy load. I could have gotten a cheap panel meter for the voltage and current, but that wouldn’t have helped with the power draw, so I decided to use a 16×2 character LCD driven by an ATmega328. In the original circuit, the current was set using a 10k trim pot on the non-inverting pin of my comparator, but now that I had a microcontroller, I decided to remove it and control it digitally using a rotary encoder. I connected a PWM output to the comparator, using a two-stage low pass filter to convert the duty cycle to an analog voltage. The new circuit is below.

dummy load

I also increased the size of the heat sink on the MOSFET and added a fan to allow me to connect larger loads without getting the FET too hot. The added benefit of adjusting the current digitally, was that I could now through software configure the dummy load to be not just constant current, but constant power too, by adjusting the current proportionally to any changes in the measured supply voltage at V+.  Here’s a picture of the finished dummy load, with the source code below.

Note on the source code: I made some changes where it was painfully obvious how new to programming I was when I made this. So while it still isn’t an example of my coding today, it should work for those interested in building their own.


#include <LiquidCrystal.h>
#include "TimerOne.h"

#define ENCODER_SELECT 4
#define ENCODER_A 2
#define ENCODER_B 3

#define SUPPLY_VOLTAGE A2
#define SUPPLY_CURRENT A3
#define PWM_CONTROL 9

LiquidCrystal lcd(5,6,7,8,10,A0);

byte ohm[8]= {B00000,B01110,B10001,B10001,B10001,B01010,B11011,B00000};
byte down[8]={B00000,B00000,B00000,B00000,B11111,B01110,B00100,B00000};

byte mode=0, screen_mode, powerOld;
boolean rotating=false;
int lastReportedPos[] = {1,1,1}, encoderPos[] = {0,0,0,0}, pwmValue;
float old_time1, I_set, Iactual, voltage;
byte counter;

// interrupt service routine vars
boolean A_set = false; 
boolean B_set = false;

void setup(){
  lcd.begin(16,2);
  lcd.createChar(1,ohm);
  lcd.createChar(2,down);

  pinMode(ENCODER_A, INPUT); 
  pinMode(ENCODER_B, INPUT);
  pinMode(ENCODER_SELECT, INPUT);
  pinMode(PWM_CONTROL,OUTPUT);
  digitalWrite(ENCODER_A, HIGH);
  digitalWrite(ENCODER_B, HIGH);
  digitalWrite(ENCODER_SELECT, HIGH);

  attachInterrupt(0, doEncoderA, CHANGE);
  attachInterrupt(1, doEncoderB, CHANGE);

  //Start 10bit PWM 
  Timer1.initialize(100);
  Timer1.pwm(9, 0);
}

void loop(){
  Encoder();
  if(mode==0) Menu();
  else if(mode==1) Current(); 
  else if(mode==2) Power();
}


//Rotary Encoder/////////////////////////////////////////////////////////////////////////////////////////////////////////
void Encoder(){
  rotating = true; // reset the debouncer
 
  if (lastReportedPos[mode] != encoderPos[mode]) {
    if(encoderPos[mode]<0)encoderPos[mode]=0; if(mode==0)counter=(encoderPos[0]); lastReportedPos[mode] = encoderPos[mode]; } } //Menu//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void Menu(){ screen_mode=0; if(!digitalRead(ENCODER_SELECT)) { while(!digitalRead(ENCODER_SELECT)) delay(10); mode=counter; } lcd.setCursor(0,0); lcd.print(" Menu "); if(counter==1) //Current Mode { lcd.setCursor(0,1); lcd.print(" Current >");
  }
  else if(counter==2) //Power Mode
  {
    lcd.setCursor(0,1);
    lcd.print("< Power "); } if(counter>2)counter=2;
  else if(counter<1)counter=1;
}

//Current////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Current(){
  I_set = encoderPos[mode]/20.00; //Set current adjustment resolution to 50mA

  //Read current
  unsigned int sample=0;
  for(byte i=0;i<200;i++) { sample += analogRead(SUPPLY_CURRENT); } Iactual=0.0072*(sample/200.0)+0.0158; if(Iactual>I_set && abs(Iactual-I_set)>0.005){
    pwmValue--;
    if(pwmValue<0)pwmValue=0;
    Timer1.pwm(9,pwmValue);
  } 
  else if(Iactual<I_set && abs(Iactual-I_set)>0.005){
    pwmValue++;
    if(pwmValue>1023)pwmValue=1023;
    Timer1.pwm(9,pwmValue);
  }
 
  //Update Display
  lcd.setCursor(0,0);
  lcd.print(" ");
  lcd.setCursor(0,1);
  lcd.print("I = ");
  if((millis()-old_time1)<=250){ //update display every 250ms
    if(I_set < 1){
      I_set *= 1000;
      lcd.setCursor(4,1);
      lcd.print(I_set,0);
      lcd.print(" mA ");
    }
    else{
      lcd.setCursor(3,1);
      lcd.print(I_set,3);
      lcd.print(" A ");
    }
  }
  else{
    if(Iactual < 1){
      Iactual *= 1000;
      lcd.setCursor(4,1);
      lcd.print(Iactual,0);
      lcd.print(" mA ");
    }
    else{
      lcd.setCursor(3,1);
      lcd.print(Iactual,3);
      lcd.setCursor(8,1);
      lcd.print(" A ");
    }
 }
 
  if(!digitalRead(ENCODER_SELECT)){ //Go back to menu
    while(!digitalRead(ENCODER_SELECT)) delay(10);
    encoderPos[mode]=0;
    mode=0;
    Timer1.pwm(9,0);
  }
}

//Power//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Power(){
  lcd.setCursor(13,1);
  ////////////Read Voltage//////////////////// 
  unsigned int sample=0;
  for(byte i=0;i<200;i++)
  {
    sample += analogRead(SUPPLY_VOLTAGE);
  } 
  voltage=0.1107*(sample/200.0)+0.249;
  ////////////Read Current//////////////////// 
  sample = 0;
  for(int g=0;g<200;g++) { sample += analogRead(SUPPLY_CURRENT); } Iactual=0.0072*(sample/200.0)+0.0158; float power = Iactual*voltage; //////////////////////////////////////////// if(encoderPos[mode]==0){ pwmValue=0; Timer1.pwm(9,pwmValue); } if(encoderPos[mode] != powerOld){ pwmValue=0; powerOld=encoderPos[mode]; } else if(power>encoderPos[mode] && abs(power-encoderPos[mode])>0.1){
    pwmValue-=1;
    if(pwmValue<0)pwmValue=0;
    Timer1.pwm(9,pwmValue); 
  }
  else if(power<encoderPos[mode] && abs(power-encoderPos[mode])>0.1){
    pwmValue+=1;
    if(pwmValue>1023)pwmValue=1023;
    if(abs(power-encoderPos[mode])>0.2)Timer1.pwm(9,pwmValue);
  }
 
  //Update Display
  lcd.setCursor(0,0);
  lcd.print(" Power ");
  lcd.setCursor(0,1);
  if(screen_mode==0)
  {
    lcd.setCursor(0,1);
    lcd.print(" ");
    screen_mode=1;
  }
  lcd.print("P = ");
  lcd.setCursor(4,1);
  if(power<10.0 || encoderPos[mode] < 10.0)lcd.print(" ");
  if((millis()-old_time1)<=250){
    lcd.print(encoderPos[mode]);
    lcd.print(" ");
  }
  else{
    lcd.print(power);
    lcd.print(" "); 
  }
  lcd.setCursor(10,1);
  lcd.print("W ");
 
  if(!digitalRead(ENCODER_SELECT)){ //Go back to menu
    while(!digitalRead(ENCODER_SELECT)) delay(10);
    encoderPos[mode]=0;
    mode=0;
    Timer1.pwm(9,0);
  }
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void doEncoderA(){
  if( digitalRead(ENCODER_A) != A_set ){
    A_set = !A_set;
    // adjust counter + if A leads B
    if ( A_set && !B_set ){
      encoderPos[mode]++;
      old_time1=millis();
    }
    rotating = false;
  }
}

void doEncoderB(){
  if( digitalRead(ENCODER_B) != B_set ){
    B_set = !B_set;
    // adjust counter - 1 if B leads A
    if( B_set && !A_set ){ 
      encoderPos[mode]--;
      old_time1=millis();
    }
    rotating = false;
  }
}

Leave a comment