use crate::diode::V_T;
#[derive(Debug, Clone)]
pub struct ZenerParams {
pub vz: f64,
pub rz: f64,
pub is: f64,
}
impl ZenerParams {
pub fn new(vz: f64) -> Self {
Self {
vz,
rz: 1.0,
is: 1e-14,
}
}
}
impl Default for ZenerParams {
fn default() -> Self {
Self::new(5.1)
}
}
pub fn zener_companion(v_d: f64, params: &ZenerParams) -> (f64, f64) {
const CLAMP: f64 = 40.0;
if v_d > -params.vz {
let nv_t = V_T; let v_clamped = v_d.min(CLAMP * nv_t);
let exp_v = (v_clamped / nv_t).exp();
let i_d = params.is * (exp_v - 1.0);
let g_d = (params.is / nv_t) * exp_v;
let i_eq = i_d - g_d * v_clamped;
(g_d, i_eq)
} else {
let g_z = 1.0 / params.rz;
let i_eq = params.vz / params.rz;
(g_z, i_eq)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn forward_bias_conducts() {
let p = ZenerParams::new(5.1);
let (g, _) = zener_companion(0.65, &p);
assert!(g > 1e-3);
}
#[test]
fn reverse_before_breakdown_low_conductance() {
let p = ZenerParams::new(5.1);
let (g, _) = zener_companion(-1.0, &p);
assert!(g < 1e-3); }
#[test]
fn breakdown_clamps_at_vz() {
let p = ZenerParams::new(5.1);
let (g, i_eq) = zener_companion(-6.0, &p);
let i_at_vz = g * (-p.vz) + i_eq;
assert!(
i_at_vz.abs() < 1e-10,
"Current at -Vz should be zero, got {i_at_vz}"
);
}
}