use crate::core::scalar::ControlScalar;
#[derive(Debug, Clone, Copy)]
pub struct HBridge<S: ControlScalar> {
pub dead_time: S,
pub dead_band: S,
command: S,
}
#[derive(Debug, Clone, Copy)]
pub struct HBridgeOutput<S: ControlScalar> {
pub ha: S,
pub la: S,
pub hb: S,
pub lb: S,
pub effective: S,
}
impl<S: ControlScalar> HBridge<S> {
pub fn new(dead_time: S, dead_band: S) -> Self {
Self {
dead_time,
dead_band,
command: S::ZERO,
}
}
pub fn compute(&mut self, command: S) -> HBridgeOutput<S> {
let cmd = command.clamp_val(-S::ONE, S::ONE);
let effective = if cmd.abs() < self.dead_band {
S::ZERO
} else {
cmd
};
self.command = effective;
let duty = effective.abs();
let duty_with_dt = (duty - self.dead_time).clamp_val(S::ZERO, S::ONE);
let complement = (S::ONE - duty - self.dead_time).clamp_val(S::ZERO, S::ONE);
if effective >= S::ZERO {
HBridgeOutput {
ha: duty_with_dt,
la: S::ZERO,
hb: S::ZERO,
lb: complement,
effective,
}
} else {
HBridgeOutput {
ha: S::ZERO,
la: complement,
hb: duty_with_dt,
lb: S::ZERO,
effective,
}
}
}
pub fn brake(&self) -> HBridgeOutput<S> {
HBridgeOutput {
ha: S::ZERO,
la: S::ONE,
hb: S::ZERO,
lb: S::ONE,
effective: S::ZERO,
}
}
pub fn coast(&self) -> HBridgeOutput<S> {
HBridgeOutput {
ha: S::ZERO,
la: S::ZERO,
hb: S::ZERO,
lb: S::ZERO,
effective: S::ZERO,
}
}
pub fn command(&self) -> S {
self.command
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn forward_command_activates_correct_switches() {
let mut hb = HBridge::new(0.02_f64, 0.02);
let out = hb.compute(0.5);
assert!(out.ha > 0.0, "HA should be active for +cmd");
assert_eq!(out.hb, 0.0, "HB should be off for +cmd");
assert_eq!(out.la, 0.0, "LA should be off for +cmd");
assert!(out.lb > 0.0, "LB should be active for +cmd");
}
#[test]
fn reverse_command_activates_correct_switches() {
let mut hb = HBridge::new(0.02_f64, 0.02);
let out = hb.compute(-0.5);
assert_eq!(out.ha, 0.0, "HA should be off for -cmd");
assert!(out.hb > 0.0, "HB should be active for -cmd");
assert!(out.la > 0.0, "LA should be active for -cmd");
assert_eq!(out.lb, 0.0, "LB should be off for -cmd");
}
#[test]
fn dead_band_clamps_small_commands() {
let mut hb = HBridge::new(0.0_f64, 0.05);
let out = hb.compute(0.03); assert_eq!(out.effective, 0.0, "Should be clamped by dead_band");
}
#[test]
fn duty_clamps_to_one() {
let mut hb = HBridge::new(0.02_f64, 0.02);
let out = hb.compute(1.0);
assert!(out.ha <= 1.0, "HA duty should not exceed 1");
assert!(out.ha >= 0.0, "HA duty should be non-negative");
}
#[test]
fn brake_mode_grounds_both_sides() {
let hb = HBridge::new(0.02_f64, 0.02);
let out = hb.brake();
assert_eq!(out.ha, 0.0);
assert_eq!(out.la, 1.0);
assert_eq!(out.hb, 0.0);
assert_eq!(out.lb, 1.0);
}
}