use crate::core::scalar::ControlScalar;
#[derive(Debug, Clone, Copy)]
pub struct BuckBoostConverter<S: ControlScalar> {
pub l: S,
pub c: S,
pub r_load: S,
pub v_in: S,
i_l: S,
v_c: S,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum BuckBoostMode {
Buck,
Boost,
}
impl<S: ControlScalar> BuckBoostConverter<S> {
pub fn new(l: S, c: S, r_load: S, v_in: S) -> Self {
Self {
l,
c,
r_load,
v_in,
i_l: S::ZERO,
v_c: S::ZERO,
}
}
pub fn step_with_mode(&mut self, d: S, mode: BuckBoostMode, dt: S) {
let d = d.clamp_val(S::ZERO, S::ONE);
let (di_l, dv_c) = match mode {
BuckBoostMode::Buck => {
let di = (d * self.v_in - self.v_c) / self.l;
let dv = (self.i_l - self.v_c / self.r_load) / self.c;
(di, dv)
}
BuckBoostMode::Boost => {
let one_minus_d = S::ONE - d;
let di = (self.v_in - one_minus_d * self.v_c) / self.l;
let dv = (one_minus_d * self.i_l - self.v_c / self.r_load) / self.c;
(di, dv)
}
};
self.i_l += di_l * dt;
self.v_c += dv_c * dt;
if self.i_l < S::ZERO {
self.i_l = S::ZERO;
}
}
pub fn v_out(&self) -> S {
self.v_c
}
pub fn i_l(&self) -> S {
self.i_l
}
pub fn reset(&mut self) {
self.i_l = S::ZERO;
self.v_c = S::ZERO;
}
}
#[derive(Debug, Clone, Copy)]
pub struct BuckBoostController<S: ControlScalar> {
pub kp: S,
pub ki: S,
pub d_min: S,
pub d_max: S,
pub mode_threshold: S,
integral: S,
}
impl<S: ControlScalar> BuckBoostController<S> {
pub fn new(kp: S, ki: S, d_min: S, d_max: S) -> Self {
Self {
kp,
ki,
d_min,
d_max,
mode_threshold: S::from_f64(0.9),
integral: S::ZERO,
}
}
pub fn update(&mut self, v_ref: S, v_out: S, v_in: S, dt: S) -> (S, BuckBoostMode) {
let mode = if v_ref < v_in * self.mode_threshold {
BuckBoostMode::Buck
} else {
BuckBoostMode::Boost
};
let error = v_ref - v_out;
self.integral += error * dt;
let d = self.kp * error + self.ki * self.integral;
let d_clamped = d.clamp_val(self.d_min, self.d_max);
if d_clamped != d {
self.integral -= error * dt;
}
(d_clamped, mode)
}
pub fn reset(&mut self) {
self.integral = S::ZERO;
}
}
#[cfg(test)]
impl<S: ControlScalar> BuckBoostController<S> {
fn update_mode(&self, v_ref: S, v_in: S) -> BuckBoostMode {
if v_ref < v_in * self.mode_threshold {
BuckBoostMode::Buck
} else {
BuckBoostMode::Boost
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn buck_mode_steps_down() {
let mut conv = BuckBoostConverter::new(1e-3_f64, 100e-6, 10.0, 24.0);
for _ in 0..200_000 {
conv.step_with_mode(0.5, BuckBoostMode::Buck, 1e-6);
}
let v = conv.v_out();
assert!(v > 9.0 && v < 15.0, "Buck: v={:.2}V (expect ~12V)", v);
}
#[test]
fn boost_mode_steps_up() {
let mut conv = BuckBoostConverter::new(1e-3_f64, 100e-6, 20.0, 12.0);
for _ in 0..200_000 {
conv.step_with_mode(0.5, BuckBoostMode::Boost, 1e-6);
}
let v = conv.v_out();
assert!(v > 18.0 && v < 30.0, "Boost: v={:.2}V (expect ~24V)", v);
}
#[test]
fn mode_selector_chooses_correctly() {
let ctrl = BuckBoostController::new(0.01_f64, 5.0, 0.0, 0.95);
assert_eq!(ctrl.update_mode(12.0, 24.0), BuckBoostMode::Buck);
assert_eq!(ctrl.update_mode(24.0, 12.0), BuckBoostMode::Boost);
}
#[test]
fn closed_loop_buck_regulation() {
let mut conv = BuckBoostConverter::new(1e-3_f64, 100e-6, 20.0, 24.0);
let mut ctrl = BuckBoostController::new(0.1_f64, 100.0, 0.0, 1.0);
let v_ref = 12.0_f64;
for _ in 0..200_000 {
let (d, mode) = ctrl.update(v_ref, conv.v_out(), conv.v_in, 1e-5);
conv.step_with_mode(d, mode, 1e-5);
}
assert!(
(conv.v_out() - v_ref).abs() < 2.0,
"v_out={:.2}V, ref={:.2}V",
conv.v_out(),
v_ref
);
}
}