use crate::core::scalar::ControlScalar;
pub type HallState = u8;
pub fn hall_to_sector(hall: HallState) -> Option<u8> {
match hall & 0x07 {
5 => Some(0),
1 => Some(1),
3 => Some(2),
2 => Some(3),
6 => Some(4),
4 => Some(5),
_ => None, }
}
pub fn sector_to_angle<S: ControlScalar>(sector: u8) -> S {
let deg_per_sector = S::from_f64(60.0_f64.to_radians());
S::from_f64(sector as f64) * deg_per_sector + S::from_f64(30.0_f64.to_radians())
}
#[derive(Debug, Clone, Copy)]
pub struct HallSensor<S: ControlScalar> {
pub hall: HallState,
pub sector: u8,
theta: S,
omega: S,
sector_time: S,
alpha: S,
pub pole_pairs: u8,
direction: i8,
prev_sector: u8,
}
impl<S: ControlScalar> HallSensor<S> {
pub fn new(pole_pairs: u8, alpha: S) -> Self {
Self {
hall: 5,
sector: 0,
theta: S::ZERO,
omega: S::ZERO,
sector_time: S::ZERO,
alpha,
pole_pairs,
direction: 1,
prev_sector: 0,
}
}
pub fn update(&mut self, hall: HallState, dt: S) {
self.sector_time += dt;
let new_sector = match hall_to_sector(hall) {
Some(s) => s,
None => return, };
if new_sector != self.sector {
if self.sector_time > S::ZERO {
let pi_over_3 = S::PI / S::from_f64(3.0);
let omega_raw = pi_over_3 / self.sector_time;
let expected_fwd = (self.sector + 1) % 6;
self.direction = if new_sector == expected_fwd { 1 } else { -1 };
let omega_signed = if self.direction > 0 {
omega_raw
} else {
-omega_raw
};
self.omega += self.alpha * (omega_signed - self.omega);
}
self.prev_sector = self.sector;
self.sector = new_sector;
self.sector_time = S::ZERO;
}
self.hall = hall;
let sector_start = S::from_f64(self.sector as f64) * (S::PI / S::from_f64(3.0));
let interp = if self.omega.abs() > S::ZERO && self.sector_time < S::from_f64(0.1) {
(self.omega.abs() * self.sector_time).clamp_val(S::ZERO, S::PI / S::from_f64(3.0))
} else {
S::PI / S::from_f64(6.0) };
self.theta = sector_start + interp;
}
pub fn theta_e(&self) -> S {
self.theta
}
pub fn omega_e(&self) -> S {
self.omega
}
pub fn omega_mech(&self) -> S {
if self.pole_pairs > 0 {
self.omega / S::from_f64(self.pole_pairs as f64)
} else {
self.omega
}
}
pub fn direction(&self) -> i8 {
self.direction
}
pub fn is_fault(&self) -> bool {
let h = self.hall & 0x07;
h == 0 || h == 7
}
pub fn reset(&mut self) {
self.hall = 5;
self.sector = 0;
self.theta = S::ZERO;
self.omega = S::ZERO;
self.sector_time = S::ZERO;
self.direction = 1;
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::f64::consts::PI;
#[test]
fn decode_all_valid_states() {
let valid = [(5, 0), (1, 1), (3, 2), (2, 3), (6, 4), (4, 5)];
for (hall, expected) in &valid {
assert_eq!(hall_to_sector(*hall), Some(*expected), "hall={}", hall);
}
}
#[test]
fn invalid_states_return_none() {
assert_eq!(hall_to_sector(0), None);
assert_eq!(hall_to_sector(7), None);
}
#[test]
fn sector_angles_spaced_60_degrees() {
for i in 0u8..5 {
let a0 = sector_to_angle::<f64>(i);
let a1 = sector_to_angle::<f64>(i + 1);
let diff = (a1 - a0).abs();
assert!((diff - PI / 3.0).abs() < 1e-10, "diff={:.4}", diff);
}
}
#[test]
fn speed_estimation_from_transitions() {
let mut hall = HallSensor::new(1_u8, 1.0_f64); let sequence = [5u8, 1, 3, 2, 6, 4, 5];
let dt = 0.001_f64;
for &h in &sequence {
hall.update(h, dt);
}
assert!(
hall.omega_e().abs() > 500.0,
"omega={:.1} rad/s",
hall.omega_e()
);
}
#[test]
fn direction_detection() {
let mut hall = HallSensor::new(1_u8, 1.0_f64);
let fwd = [5u8, 1, 3, 2, 6, 4];
for &h in &fwd {
hall.update(h, 0.001);
}
assert_eq!(hall.direction(), 1, "Forward sequence should give +1");
}
#[test]
fn fault_detection() {
let hall = HallSensor::new(1_u8, 1.0_f64);
assert!(!hall.is_fault());
}
}