Issue with Peak Overshoot in DC-DC Buck-Boost Converter for EVs
I am currently working on a DC-DC Buck-Boost converter for electric vehicles (EVs). The controller I am using is a PIDA (Proportional-Integral-Derivative-Accelerated) controller, optimized with the Whale Optimization Algorithm (WOA). However, I am facing an issue with the peak overshoot in my system.
Regardless of the changes I make, I am unable to reduce the peak overshoot, which consistently reaches 20V, while my desired stable output is 12V. I have tried various configurations, including PI, PIDA, and PI with WOA, but the peak overshoot remains the same at 20V. The only difference between these configurations is the settling time; the overshoot is not affected. The simulation is being done in Proteus software.
Can anyone provide guidance on how to reduce the peak overshoot to achieve a more stable output?
import spidev
import time
import RPi.GPIO as GPIO
import random
PWM_PIN = 18
GPIO.setmode(GPIO.BCM)
GPIO.setup(PWM_PIN, GPIO.OUT)
FREQUENCY = 50000
pwm = GPIO.PWM(PWM_PIN, FREQUENCY)
pwm.start(23.8)
spi = spidev.SpiDev()
spi.open(0, 0)
CALIB_OFFSET = 0.0
Kp = 2.5
Ki = 1.0
Kd = 0.1
Ka = 0.05
iae_list = []
ise_list = []
itae_list = []
itse_list = []
min_error_function_list = []
duty_cycle_list = []
error_voltage_list = []
iteration_count = 0
min_error_value = float('inf')
final_duty_cycle = 30
SAMPLING_INTERVAL = 0.001
POPULATION_SIZE = 5
MAX_ITER = 500
BOUNDS = [(0, 5), (0, 2), (0, 0.1), (0, 0.01)]
class PIDA:
def __init__(self, Kp, Ki, Kd, Ka):
self.Kp = Kp
self.Ki = Ki
self.Kd = Kd
self.Ka = Ka
self.integral = 0
self.previous_error = 0
self.error_history = [0, 0]
self.previous_time = time.time()
def compute_control(self, error, dt):
P = self.Kp * error
self.integral += error * dt
self.integral = max(min(self.integral, 20), -20)
I = self.Ki * self.integral
derivative = (error - self.previous_error) / dt if dt > 0.0001 else 0
alpha_derivative = 0.1
derivative_filtered = alpha_derivative * derivative + (1 - alpha_derivative) * self.error_history[-1]
second_previous_error = self.error_history[-2] if len(self.error_history) > 1 else 0
acceleration = (error - 2 * self.previous_error + second_previous_error) / (dt ** 2) if dt > 0.0001 else 0
alpha_acceleration = 0.1
acceleration_filtered = alpha_acceleration * acceleration + (1 - alpha_acceleration) * self.error_history[-1]
self.error_history.append(error)
if len(self.error_history) > 2:
self.error_history.pop(0)
self.previous_error = error
self.previous_time = time.time()
return P + I + derivative_filtered + acceleration_filtered
class WOA:
def __init__(self, population_size, max_iter, bounds):
self.population_size = population_size
self.max_iter = max_iter
self.bounds = bounds
def optimize(self, evaluate_error):
population = [self._initialize_agent() for _ in range(self.population_size)]
best_agent = min(population, key=lambda agent: evaluate_error(*agent))
for iter in range(self.max_iter):
a = 2 - iter * (2 / self.max_iter)
for i, agent in enumerate(population):
r = random.random()
A = 2 * a * r - a
C = 2 * r
if r < 0.5:
if abs(A) < 1:
D = [abs(C * best_agent[j] - agent[j]) for j in range(4)]
new_agent = [best_agent[j] - A * D[j] for j in range(4)]
else:
random_agent = random.choice(population)
D = [abs(C * random_agent[j] - agent[j]) for j in range(4)]
new_agent = [random_agent[j] - A * D[j] for j in range(4)]
else:
D_prime = [abs(best_agent[j] - agent[j]) for j in range(4)]
l = random.uniform(-1, 1)
b = 1
new_agent = [D_prime[j] * (2.718 ** (b * l)) * (2 ** 3.14 * l) + best_agent[j] for j in range(4)]
new_agent = [max(self.bounds[j][0], min(self.bounds[j][1], new_agent[j])) for j in range(4)]
population[i] = new_agent
current_best = min(population, key=lambda agent: evaluate_error(*agent))
if evaluate_error(*current_best) < evaluate_error(*best_agent):
best_agent = current_best
print(f"Iteration {iter + 1}/{self.max_iter}, Best Fitness: {evaluate_error(*best_agent):.4f}")
return best_agent
def _initialize_agent(self):
return [random.uniform(bound[0], bound[1]) for bound in self.bounds]
def read_adc(channel):
try:
adc = spi.xfer2([1, (8 + channel) << 4, 0])
data = ((adc[1] & 3) << 8) + adc[2]
return data
except Exception as e:
print(f"Error reading ADC: {e}")
return 0
def convert_to_voltage(adc_value, vref=3.3, scale_factor=5.7):
return 12 - ((adc_value * vref * (3 / 2)) / 1023 * scale_factor)
def evaluate_error(Kp, Ki, Kd, Ka):
pida = PIDA(Kp, Ki, Kd, Ka)
start_time = time.time()
end_time = start_time + 5
error_sum = 0
while time.time() < end_time:
adc_value = read_adc(0)
error_voltage = convert_to_voltage(adc_value)
error = error_voltage
current_time = time.time()
dt = current_time - pida.previous_time
control = pidapute_control(error, dt)
control = max(0, min(100, control))
pwm.ChangeDutyCycle(control)
error_sum += abs(error)
time.sleep(SAMPLING_INTERVAL)
return error_sum
def main():
global final_duty_cycle
try:
print("Calibrating... (Place multimeter on output)")
pwm.ChangeDutyCycle(30)
time.sleep(1)
adc_value = read_adc(0)
actual_voltage = float(input("Enter multimeter reading (V): "))
CALIB_OFFSET = actual_voltage - convert_to_voltage(adc_value)
print(f"Calibration offset: {CALIB_OFFSET:.2f}V")
print("Optimizing PIDA parameters using WOA...")
woa = WOA(POPULATION_SIZE, MAX_ITER, BOUNDS)
optimal_params = woa.optimize(evaluate_error)
Kp, Ki, Kd, Ka = optimal_params
print(f"Optimized Parameters: Kp={Kp:.2f}, Ki={Ki:.2f}, Kd={Kd:.2f}, Ka={Ka:.2f}")
pida = PIDA(Kp, Ki, Kd, Ka)
while True:
adc_value = read_adc(0)
error_voltage = convert_to_voltage(adc_value)
error = error_voltage
current_time = time.time()
dt = current_time - pida.previous_time
control = pidapute_control(error, dt)
control = max(0, min(100, control))
pwm.ChangeDutyCycle(control)
print(f"Vout: {error_voltage:.2f}V | Duty: {control:.1f}% | Error: {error:.2f}V")
time.sleep(SAMPLING_INTERVAL)
except KeyboardInterrupt:
print("\nStopped by user.")
finally:
pwm.stop()
GPIO.cleanup()
spi.close()
print(f"Final Duty Cycle: {final_duty_cycle}")
if __name__ == "__main__":
main()