Op-Amp Based Adjustable Load
Table of Contents
1. Introduction
Hi! This will be my project for the Op-Amp-a-Palooza. For this Project14 I decided to make a piece of test equipment, an adjustable load. It was supposed to have an adjustable current and power mode, but I only had time to implement the constant current mode. This device is useful when testing out things ranging from batteries, buck/boost converters, power supplies, current sensors (what I plan on using at one point), and so on. What the device actually tries to do is to adjust its internal load so that the current flowing through that load is constant. There are several ways of achieving this, I decided to go with the analog route using an OpAmp and a MOSFET but used an Arduino to leave space for new possible functionality down the line.
2. How it works
Before I went out and made the final version, I first conducted some tests as a proof of concept. For the proof of concept test only a few components are needed, a power resistor, a MOSFET, and an Op-Amp. For the Op-Amp, I decided to go with the OP07CP precision Op-Amp. The circuit for testing out this concept is rather simple, here is the schematic.
To simplify the explanation of how this circuit works, the Op-Amp wants to keep the + and - inputs at the same potential, on the + input we have connected a potentiometer which will be the user input, while on the - input we have essentially the voltage across the power resistor (in my case a 0.33Ohm resistor). The Op-Amp will adjust the voltage on the gate of the MOSFET and use the MOSFET as a variable resistor so that we get the same voltage on the power resistor and on the potentiometer. Of course, the voltage on the power resistor is directly dependent on the current through it, and in that way, we adjust the current that we want our adjustable current load to pull from the source. Here is a short video showing how this works.
As you can see in the video, as I adjust the small trim potentiometer I adjust the current that I'm drawing from my bench power supply. One thing you can see from the video is that the power resistor is mounted to a big heatsink while the MOSFET isn't, this was just for testing purposes, and I didn't try going over 200mA in this setup, though, even then the MOSFET got really hot. This test is the backbone of this project and how it will work, but I decided to make it a bit more complicated and leave room for future improvements.
3. Plan
While I love the simplicity of this analog circuit and how it works, I wanted to add a bit to the project. My plan was to keep the basics as they are with the Op-Amp, MOSFET, power resistor setup, but instead of using a potentiometer as the input, I decided to go with an Arduino that has a built-in DAC. All of the Arduino from the MKR family and the new Nano family have a DAC that can be used on the A0 pin. One thing I want to point out is that I don't need this to be a precise piece of equipment and that I can get away with it being rather crude. For a finer adjustment, I could have bought a higher bit count DAC module to add to the Arduino but I decided to go with this simpler route. With the 10-bit resolution and the 0.33Ohm resistor theoretically, I could go up to 10A with the step of 10mA, where both of these are more than I need. My plan is to use this to test out the current sensor as well as power supplies for the Raspberry Pi since it can draw a lot of current, but nowhere near close to 10A. Here is the final schematic I came up with.
Here are some notable things about the circuit:
- Instead of using a potentiometer, I decided to go with an encoder with a button
- I used 2 MOSFETS in parallel so that they heat up less
- There is an outside switch for turning on the main relay for the load, I didn't want it to be dependent on the program
- That relay also has an added 10A glass fuse
- I added another relay for turning the fan on through the code once the current goes above a certain limit
- The limit I initially set and left was 100mA which is too low considering how big the heatsink is
- I supplied the Op-Amp with +12V and -12V by using a boost converter to step up 12V to 24V and then use the original 12V as the ground for the circuit
- I also added a small current and voltmeter
- There are 2 TM1637 displays for showing the set current/power
4. Build
The build for this project consists of 2 parts, the first is the front user interface and the other one is the electronics inside. I'll start with the more simple one, which is the front interface for the adjustable load.
User interface
The user interface consists of all of the things that the user has access to on the outside of the box. Here is the list of things that are included:
- Set current display
- Set power display
- mW LED & W LED
- Since the set power display has only 4 digits, we can go up to 9999mW and when we go over that, the LED switches to W LED and the writing on the TM1637 display is adjusted
- Power On switch & LED
- Fan On LED
- Main relay switch & LED
- Encoder
- VA meter
- Screw terminals for the power resistor
- I left 2 contacts on the outside that are connected to the power resistor so that we can attach an oscilloscope probe to look at the current if we wish
- Banana plugs as the input for the device
I first wanted to test out the TM1637 displays and the DAC on the Arduino, so I wired it all up on a breadboard and tried to see if the voltage changed as I turned the encoder.
All of the things worked great except the VA meter. I tried following several tutorials that I've found online for the model I have and when everything is connected as it should be, it would just short circuit the banana plug inputs. So with a bit of trial and error, I managed to get the current meter part of it working but not the voltage measurement. Here is how the finished setup looks and works.
The encoder for now only adjusts the set current, while the set power is just multiplied by a set voltage. During testing, I had 2 more LED-s to show which mode was chosen but didn't have enough time to implement that as well. The encoder changes the set current in 10mA steps, but if we click it, we can go to 100mA and if we click it again it goes to 1000mA. Once we click it again, it goes back to 10mA. I've done this so you could adjust the set current faster, rather than turning it around for a while. This is another reason why I wanted to use an encoder instead of a 10-turn potentiometer on the Arduino.
As I've said above, the fan turns on once the set current goes over 100mA. Looking at it now, that current is too low to justify the fan considering the size of the heatsink, but it doesn't hurt either.
Electronics
This is the part of the build that took the most time. Besides the Op-Amp, the system is based around the power resistor and MOSFET-s meaning that I had to make sure that they had adequate cooling. I found a heatsink from the CPU cooler of my first computer (a Pentium4) and first cleaned up the old thermal paste of it.
After cleaning it up nicely, I first measured the holes for the power resistor which I had to mount in the middle due to its size of it. To mount it, I drilled and tapped 2 M3 holes for mounting.
After taking the resistor off, I drilled and tapped 2 more holes on the sides for the 2 MOSFET-s I planned on using in the build. Another thing I did also was to get some thermal paste to make sure that the heat transferred as efficiently as possible to the heatsink from the components. Here is what the finished heatsink looks like.
The other part of the electronics is the carrier board for the Arduino with the Op-Amp and all of the other components. I made the board so it fits a standard MKR layout board on the top, but I managed to fry the MKR WiFi I planned on using by accident, so I just wired it directly to an Arduino Nano 33 IoT, but the code, and everything else, stays the same.
The thing I suspect killed the Arduino was the 3.3V regulator where I shorted it by accident. For it to not happen again, I later added a small standalone linear 3.3V regulator and used that.
Final setup test
To make sure that everything was soldered on properly and working before I started packing everything inside the box, I wired it all up with wires going everywhere to make sure that it is actually working.
The video you see above is essentially the final setup for this, but just not packed inside the enclosure. As you can see from the video, everything worked as expected, the only thing not working is the VA meter, but at least it shows the current is fairly accurate and matched the multimeter at some higher currents.
5. Code
There isn't much to say about the code, for the most part, it looks for an encoder input and takes of the displays and LED-s. The main function part that it does is converting the set current to a value that we use with the analogWrite function on the A0 pin.
// Libraries #include <Arduino.h> #include <TM1637Display.h> // Pins #define ENC1 2 #define ENC2 3 #define ENC_SW 4 #define DISP_CLK 5 #define DISP1_DIO 6 #define DISP2_DIO 7 #define LED_MW_PIN 8 #define LED_W_PIN 9 #define FAN_RELAY_PIN 10 // Variables float measured_voltage = 30.00; int set_current = 0; int set_power = 0; int currentStateENC1; int lastStateENC1; String currentDir =""; unsigned long lastButtonPress = 0; int btnState = 0; int current_mode = 1; int fan_current_threshold = 100; int multiplier = 1; float power_resistor = 0.33; float voltage_ref = 0.00; int dac_input = 0; // Displays TM1637Display current_display(DISP_CLK, DISP1_DIO); TM1637Display power_display(DISP_CLK, DISP2_DIO); void setup() { // Setting the analog write resolution analogWriteResolution(10); // Pins pinMode(ENC1,INPUT_PULLUP); pinMode(ENC2,INPUT_PULLUP); pinMode(ENC_SW, INPUT_PULLUP); pinMode(LED_MW_PIN, OUTPUT); pinMode(LED_W_PIN, OUTPUT); pinMode(FAN_RELAY_PIN, OUTPUT); // Starting the Serial Serial.begin(9600); // Setting the display brightness current_display.setBrightness(0x0f); power_display.setBrightness(0x0f); digitalWrite(FAN_RELAY_PIN, HIGH); } void loop() { // Read the current state of ENC1 currentStateENC1 = digitalRead(ENC1); // If last and current state of ENC1 are different, then pulse occurred // React to only 1 state change to avoid double count if (currentStateENC1 != lastStateENC1 && currentStateENC1 == 1){ // If the DT state is different than the CLK state then // the encoder is rotating CCW so decrement if (digitalRead(ENC2) != currentStateENC1) { set_current -= 10 * multiplier; if(set_current <= 0) { set_current = 0; } currentDir = "CCW"; } else { // Encoder is rotating CW so increment set_current += 10 * multiplier; if(set_current > 9990) { set_current = 9990; } currentDir = "CW"; } Serial.print("Direction: "); Serial.print(currentDir); Serial.print(" | Counter: "); Serial.println(set_current); current_display.showNumberDec(set_current, false); set_power = set_current * measured_voltage; voltage_ref = set_current * power_resistor / 1000.00; dac_input = 1024 * voltage_ref / 3.3; analogWrite(A0, dac_input); // We can show up to 9999 mW before we have to switch to showing W if(set_power <= 9999) { power_display.showNumberDecEx(set_power, 0b00000000, false, 4, 0); digitalWrite(LED_MW_PIN, HIGH); digitalWrite(LED_W_PIN, LOW); } else if (set_power <= 99999) { //power_display.showNumberDec(set_power / 1000, false); power_display.showNumberDecEx(set_power / 10.00, 0b11100000, false, 4, 0); digitalWrite(LED_MW_PIN, LOW); digitalWrite(LED_W_PIN, HIGH); } else { power_display.showNumberDecEx(set_power / 1000, 0b00000000, false, 4, 0); digitalWrite(LED_MW_PIN, LOW); digitalWrite(LED_W_PIN, HIGH); } } // Remember last ENC1 state lastStateENC1 = currentStateENC1; // Read the button state btnState = digitalRead(ENC_SW); //If we detect LOW signal, button is pressed if (btnState == LOW) { //if 50ms have passed since last LOW pulse, it means that the //button has been pressed, released and pressed again if (millis() - lastButtonPress > 50) { Serial.println("Button pressed!"); current_mode = current_mode * (-1); if(multiplier == 1) { multiplier = 10; } else if(multiplier == 10) { multiplier = 100; } else { multiplier = 1; } } // Remember last button press event lastButtonPress = millis(); } // Setting the fan pin LED if(set_current >= fan_current_threshold) { digitalWrite(FAN_RELAY_PIN, LOW); } else { digitalWrite(FAN_RELAY_PIN, HIGH); } // Put in a slight delay to help debounce the reading delay(1); }
6. Finished look
After the test you've seen above where I've made that everything worked as expected, it was time to disconnect all of the wires and try to pack it into the box as neatly as possible (so it could actually fit the whole Arduino off the board). After half an hour of threading the wires and slowly closing the lid making sure not to disconnect something, here is what I ended up with.
I am extremely happy with how it turned out all packaged inside the box (okay, mostly packaged inside the box if we don't count the giant heatsink and fan sticking out in the back). On the right side you can see the banana plugs that are used for connecting the source that we plan on testing.
On the left side, you can see the 3D printed locking barrel jack connector that I designed last year since I decided on using the 12V power supply I had laying around from back then. This also means I can leave it on the table without having to worry about knocking the power connector out by accident since, in my experience, the barrel jack connectors don't hold on too well on their own. On the top you can see the 2 screws poking out, those are the screws for connecting the oscilloscope if you wish to do that.
I didn't have enough time to design and 3D print a special bracket to mount the fan to the heatsink, so I just drilled a few holes and attached the fan using a few zip-ties, which proved to be a surprisingly steady connecting to the heatsink. I'll also design and print out some legs for this so I can keep the fan and heatsink off the ground, though, I love how it works as a great little stand in the back with a great viewing angle how it's tilted!
7. Testing
The last thing left to do is to do a few tests to make sure everything works as expected and that nothing got loose during the final assembly of this thing.
Bench PSU test
In this first test as the source, I used a bench power supply. This insured that I can have a current limit there so I don't destroy anything on the adjustable load while testing. Here is the video of that test:
As you can see from the video, the test went great, it works! There is a noticeable difference between the set and actual current (10-50mA), but I haven't been able to experiment with it too much to see what exactly is going on. I'm setting the voltage by just calculating using the set current and a value of 0.33Ohms which probably isn't too accurate when you take into consideration the whole setup. I'll fine tweak it at some point but still does a great job for what I need it.
LiPo cell test
I also wanted to test out the higher currents on this thing, so I had to use something a bit stronger than the bench PSU that can only go up to 3.1A. I decided to use my drone battery which can go much higher than the 3.1A
Buck converter test
As the final test, I tried out a cheap buck converter I had laying around in my parts. I wanted to see how it reacted to higher currents and look at how fast it got hot at those currents.
I'm truly amazed by this little thing, that I could actually survive 2.5A, but as you could have seen by the temperatures I measured, it wouldn't have really survived for too long.
8. Summary
This was a fun little project using Op-Amps. While there is a constant drift and the steps are at 10mA, it will still be really useful for some of the things I plan on testing with it. I'll go back at some point to try and fine-tune the values in the code, though a current sensor attached to the Arduino and use that as feedback would be great. Either way, I'm thrilled about how it turned out and that I managed to finish it so fast! Thanks for reading the blog, hope you like it!
Milos