I am trying to create a microcontroller (Arduino) based DC to DC buck converter with CC and CV features. For now, I am using a 20 V 5 A input source. Output voltage (target voltage) is settable by the potentiometer. Output current is fixed at 2.5 A in the Arduino code for the time being (but it will also be made adjustable in future). The ultimate goal to make a multipurpose battery charger with profiles for various battery chemistries and configurations, and with various safety features. But for now I am trying to get the buck part to work properly. I am using a 12 V 21 W bike lamp as test load. The inductor (L1) used in the circuit is salvaged from a desktop PC SMPS. I don’t know its value and I don’t have the equipment to measure it. It is working but I am facing the following two difficulties that I am unable to solve:
- If I connect capacitor C1 for smoothing the pulsing DC output, the capacitor as well as the MOSFETs get extremely hot within 30 seconds, so much overheated that if I let it run for some more time they will get fried. I tried various capacitor values from 100 uF to 4700 uF but the result is the same. Without the capacitor, it is able to drive the load without the MOSFETs overheating. They just get warm but I can touch them comfortably. It is pertinent to mention that I am not using any heat sinks for the MOSFETs as I am drawing less than 2.5 A total current. So why is the capacitor causing overheating of itself and MOSFETs and what is the solution?
- The Arduino is currently set to produce a PWM frequency of around 7.8 kHz. I tried various other frequencies like 10 kHz, 15 kHz, 31 kHz etc. but the MOSFETs overheat (even without the filter capacitor) at duty cycles < 60% (approx). However at 7.8 kHz or lower frequencies, the MOSFETs stay safe to touch across all duty cycle ranges.
I don’t have an oscilloscope so I can’t provide useful waveform data, but I am using the Raspberry Pi Pico based Scoppy.
NOTES: I am using a 1:10 resistor divider network (1 and 10 kΩ) for Scoppy probe and the readings are not compensated. So I have included the actual measured voltages in all the screen shots. Also, the PWM frequency and duty cycles are incorrect when the output filter capacitor is connected, so I have included the actual frequency and duty cycles in the respective screen shots.
Following screenshots are taken from the Scoppy app:
Arduino Code:
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <TimerOne.h>
LiquidCrystal_I2C lcd(0x27, 16, 2);
const int PWM_TIME_PERIOD_MICRO_SECONDS = 128; // Frequency = 1/time in seconds Hz
const int CURRENT_SENSOR_SENSITIVITY = 100;
const int CURRENT_SENSOR_OFFSET_VOLTAGE = 2500;
const int VOLTAGE_SENSOR = A0;
const int CURRENT_SENSOR = A1;
const int VOLTAGE_CONTROLLER_POT = A2;
const int PWM_PIN = 9;
const int MAX_SETTABLE_VOLTAGE = 30;
const double TARGET_VOLTAGE_HARDCODED = 16.4;
const double PRECISION = 0.005; // + or - 0.5% error
const int VOLTAGE_SAMPLES = 1000;
const int BULK_DECREMENT_VALUE = 20;
const int BULK_INCREMENT_VALUE = 15;
const int BULK_INCREMENT_VOLTAGE_DEVIATION_PERCENT_THRESHOLD = 20; // Bulk increment when current voltage deviates from target voltage by 20% or more
const int BULK_DECREMENT_VOLTAGE_DEVIATION_PERCENT_THRESHOLD = 13; // Bulk decrement when current voltage deviates from target voltage by 13% or more
const int BULK_INCREMENT_CURRENT_DEVIATION_PERCENT_THRESHOLD = 20; // Bulk increment when current voltage deviates from target voltage by 20% or more
const int BULK_DECREMENT_CURRENT_DEVIATION_PERCENT_THRESHOLD = 13;
const bool VOLTAGE_SETTABLE_BY_KNOB = true;
const int MAX_DUTY_CYCLE_VALUE = 1023;
const double CURRENT_TOLERANCE_PERCENT = 5.0;
double TARGET_CURRENT = 2.5; // 2.5 Amps
double TARGET_VOLTAGE;
int pwmDutyCycle = 0;
float outputVoltage;
float outputCurrent;
double acceptableLow;
double acceptableHigh;
// TODO:
// Ideally input voltage should be read from a sensor.
// So i need to implement that
int inputVoltage = 20;
void setup() {
Serial.begin(115200);
lcd.init();
lcd.backlight();
pinMode(PWM_PIN,OUTPUT);
pinMode(VOLTAGE_SENSOR,INPUT);
pinMode(CURRENT_SENSOR,INPUT);
TARGET_VOLTAGE = TARGET_VOLTAGE_HARDCODED;
Timer1.initialize(PWM_TIME_PERIOD_MICRO_SECONDS);
Timer1.pwm(PWM_PIN, pwmDutyCycle);
}
void loop() {
if(VOLTAGE_SETTABLE_BY_KNOB == true) {
TARGET_VOLTAGE = readVoltageControlKnob();
}
outputVoltage = readOutputVoltage();
outputCurrent = readOutputCurrent();
printOutputVoltageAndCurrent(outputVoltage, outputCurrent);
adjustDutyCycle(outputVoltage, outputCurrent);
}
double readOutputVoltage(){
double rawReading;
double sumOfReadings = 0;
double average;
double outputVoltageDetected = 0;
int counter = 0;
while(counter < VOLTAGE_SAMPLES) {
rawReading = analogRead(VOLTAGE_SENSOR);
sumOfReadings += rawReading;
counter++;
}
average = sumOfReadings / VOLTAGE_SAMPLES;
outputVoltageDetected = ((average / 1024.0 ) * 25);
return outputVoltageDetected;
}
double readOutputCurrent() {
int counter = 0;
double rawReading = 0;
double sumOfReadings = 0;
double average = 0;
double detectedAnalogVoltage = 0;
double outputCurrentDetected = 0;
while(counter < VOLTAGE_SAMPLES) {
rawReading = analogRead(CURRENT_SENSOR);
sumOfReadings += rawReading;
counter++;
}
average = sumOfReadings / VOLTAGE_SAMPLES;
detectedAnalogVoltage = ((average / 1024.0 ) * 5000);
outputCurrentDetected = ((detectedAnalogVoltage - CURRENT_SENSOR_OFFSET_VOLTAGE) / CURRENT_SENSOR_SENSITIVITY);
return outputCurrentDetected;
}
double readVoltageControlKnob(){
double rawReading;
double sumOfReadings = 0;
double average;
double targetSetByKnob = 0;
int counter = 0;
while(counter < VOLTAGE_SAMPLES) {
rawReading = analogRead(VOLTAGE_CONTROLLER_POT);
sumOfReadings += rawReading;
counter++;
}
average = sumOfReadings / VOLTAGE_SAMPLES;
targetSetByKnob = ((average / 1024.0 ) * MAX_SETTABLE_VOLTAGE);
return targetSetByKnob;
}
void adjustDutyCycle(double volts, double amps) {
calculateAcceptableVoltageLimits();
if(volts < acceptableLow && amps < tolerableCurrentLowerLimit()) {
while(volts < acceptableLow && amps < tolerableCurrentLowerLimit()) {
if(needsBulkIncrement(volts, amps)) {
bulkIncrementDutyCycle();
} else {
incrementDutyCycle();
}
volts = readOutputVoltage();
amps = readOutputCurrent();
printOutputVoltageAndCurrent(volts, amps);
adjustTargetVoltageAccordingToKnob();
Serial.println(volts);
}
} if(volts > acceptableHigh || amps > tolerableCurrentUpperLimit()) {
while(volts > acceptableHigh || amps > tolerableCurrentUpperLimit()) {
if(needsBulkDecrement(volts, amps)) {
bulkDecrementDutyCycle();
} else {
decrementDutyCycle();
}
volts = readOutputVoltage();
amps = readOutputCurrent();
printOutputVoltageAndCurrent(volts, amps);
adjustTargetVoltageAccordingToKnob();
Serial.println(volts);
}
}
}
void calculateAcceptableVoltageLimits() {
acceptableLow = TARGET_VOLTAGE - (TARGET_VOLTAGE * PRECISION);
acceptableHigh = TARGET_VOLTAGE + (TARGET_VOLTAGE * PRECISION);
}
void adjustTargetVoltageAccordingToKnob(){
if(VOLTAGE_SETTABLE_BY_KNOB == true) {
TARGET_VOLTAGE = readVoltageControlKnob();
}
calculateAcceptableVoltageLimits();
}
void incrementDutyCycle() {
if(pwmDutyCycle < MAX_DUTY_CYCLE_VALUE) {
pwmDutyCycle++;
Timer1.pwm(PWM_PIN, pwmDutyCycle);
}
}
void bulkIncrementDutyCycle() {
if(pwmDutyCycle < (MAX_DUTY_CYCLE_VALUE - (3 * BULK_INCREMENT_VALUE))) {
pwmDutyCycle = pwmDutyCycle + BULK_INCREMENT_VALUE;
Timer1.pwm(PWM_PIN, pwmDutyCycle);
} else if(pwmDutyCycle < MAX_DUTY_CYCLE_VALUE) {
pwmDutyCycle++;
Timer1.pwm(PWM_PIN, pwmDutyCycle);
}
}
void decrementDutyCycle() {
if(pwmDutyCycle > 0) {
pwmDutyCycle--;
Timer1.pwm(PWM_PIN, pwmDutyCycle);
}
}
void bulkDecrementDutyCycle() {
if(pwmDutyCycle > BULK_DECREMENT_VALUE) {
pwmDutyCycle = pwmDutyCycle - BULK_DECREMENT_VALUE;
Timer1.pwm(PWM_PIN, pwmDutyCycle);
} else if(pwmDutyCycle > 0) {
pwmDutyCycle--;
Timer1.pwm(PWM_PIN, pwmDutyCycle);
}
}
bool needsBulkIncrement(double volts, double amps) {
double voltageDeviation = (float)((TARGET_VOLTAGE * 100)/volts) - 100.00;
double currentDeviation = (float)((TARGET_CURRENT * 100)/amps) - 100;
if(voltageDeviation > BULK_INCREMENT_VOLTAGE_DEVIATION_PERCENT_THRESHOLD && currentDeviation > BULK_INCREMENT_CURRENT_DEVIATION_PERCENT_THRESHOLD) {
return true;
}
return false;
}
bool needsBulkDecrement(double volts,double amps) {
double voltageDeviation = ((volts * 100)/TARGET_VOLTAGE) - 100.00;
double currentDeviation = ((amps * 100)/TARGET_CURRENT) - 100.00;
if(voltageDeviation > BULK_DECREMENT_VOLTAGE_DEVIATION_PERCENT_THRESHOLD || currentDeviation > BULK_DECREMENT_CURRENT_DEVIATION_PERCENT_THRESHOLD) {
return true;
}
return false;
}
void printOutputVoltageAndCurrent(double currentVoltage, double amps) {
String bufr;
// Clear LCD Screen
lcd.setCursor(0,0);
lcd.print(" ");
lcd.setCursor(0,1);
lcd.print(" ");
bufr = String("TV:");
bufr.concat(TARGET_VOLTAGE);
bufr.concat(" ");
bufr.concat("PWM:");
bufr.concat(pwmDutyCycle);
lcd.setCursor(0,0);
lcd.print(bufr);
bufr = String("V:");
bufr.concat(currentVoltage);
bufr.concat(" ");
bufr.concat("A:");
bufr.concat(amps);
lcd.setCursor(0,1);
lcd.print(bufr);
}
double tolerableCurrentUpperLimit() {
double tCurrent = (float) TARGET_CURRENT + ((TARGET_CURRENT * CURRENT_TOLERANCE_PERCENT)/100);
return tCurrent;
}
double tolerableCurrentLowerLimit() {
double tCurrent = (float) TARGET_CURRENT - ((TARGET_CURRENT * CURRENT_TOLERANCE_PERCENT)/100);
return tCurrent;
}
````