use embedded_hal::digital::InputPin;
#[cfg(not(feature = "std"))]
use heapless::Vec;
#[cfg(feature = "std")]
use std::vec::Vec;
#[cfg(not(feature = "std"))]
use crate::consts::ASK_MAX_BUF_LEN_USIZE;
use crate::consts::{ASK_MAX_PAYLOAD_LEN, ASK_START_SYMBOL};
use crate::encoding::decode_6b4b;
#[derive(Debug)]
pub struct SoftwarePLL {
bits: u16,
ramp: u16,
integrator: u8,
last_sample: bool,
pub bit_count: u8,
ramp_len: u16,
ramp_inc: u16,
ramp_retard: u16,
ramp_advance: u16,
inverted: bool,
pub active: bool,
pub buf_len: u8,
count: u8,
pub bad: u16,
pub full: bool,
#[cfg(not(feature = "std"))]
pub buf: Vec<u8, ASK_MAX_BUF_LEN_USIZE>,
#[cfg(feature = "std")]
pub buf: Vec<u8>,
}
impl SoftwarePLL {
pub fn new(ticks_per_bit: u8, inverted: bool) -> Self {
let ramp_len = (ticks_per_bit as u16) * 20;
let ramp_inc = ramp_len / ticks_per_bit as u16;
let ramp_adjust = 9;
let ramp_retard = ramp_inc - ramp_adjust;
let ramp_advance = ramp_inc + ramp_adjust;
Self {
ramp: 0,
integrator: 0,
last_sample: false,
bit_count: 0,
active: false,
bits: 0,
buf_len: 0,
count: 0,
full: false,
buf: Vec::new(),
ramp_len,
ramp_inc,
ramp_retard,
ramp_advance,
inverted,
bad: 0,
}
}
pub fn update<RX: InputPin>(&mut self, rx: &mut RX) {
let sample = if self.inverted {
!rx.is_high().unwrap_or(false)
} else {
rx.is_high().unwrap_or(false)
};
if sample {
self.integrator += 1;
}
if sample != self.last_sample {
self.ramp += if self.ramp < self.ramp_len / 2 {
self.ramp_retard
} else {
self.ramp_advance
};
self.last_sample = sample;
} else {
self.ramp += self.ramp_inc;
}
if self.ramp >= self.ramp_len {
self.bits >>= 1;
if self.integrator >= 5 {
self.bits |= 0x800;
}
self.ramp -= self.ramp_len;
self.integrator = 0;
if self.active {
self.bit_count += 1;
if self.bit_count >= 12 {
let this_byte =
decode_6b4b(&((self.bits & 0x3f) as u8), &((self.bits >> 6) as u8));
if self.buf_len == 0 {
self.count = this_byte;
if self.count < 7 || self.count > ASK_MAX_PAYLOAD_LEN {
self.active = false;
self.bad += 1;
return;
}
}
#[cfg(not(feature = "std"))]
let _ = self.buf.push(this_byte);
#[cfg(feature = "std")]
self.buf.push(this_byte);
self.buf_len += 1;
if self.buf_len >= self.count {
self.active = false;
self.full = true;
}
self.bit_count = 0;
}
} else if self.bits == ASK_START_SYMBOL {
self.active = true;
self.bit_count = 0;
self.buf_len = 0;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use embedded_hal_mock::eh1::digital::{
Mock as PinMock, State as PinState, Transaction as PinTransaction,
};
#[test]
fn test_pll_initialization_defaults() {
let pll = SoftwarePLL::new(8, false);
assert_eq!(pll.ramp, 0);
assert_eq!(pll.integrator, 0);
assert_eq!(pll.bit_count, 0);
assert_eq!(pll.active, false);
assert_eq!(pll.full, false);
assert_eq!(pll.buf_len, 0);
}
#[test]
fn test_pll_updates_on_tick_with_high_signal() {
let expectations = [PinTransaction::get(PinState::High)];
let mut rx = PinMock::new(&expectations);
let mut pll = SoftwarePLL::new(8, false);
pll.update(&mut rx);
assert!(pll.integrator > 0);
rx.done();
}
#[test]
fn test_pll_detects_start_symbol() {
let mut pll = SoftwarePLL::new(8, false);
pll.bits = ASK_START_SYMBOL << 1; pll.ramp = pll.ramp_len;
let expectations = [PinTransaction::get(PinState::High)];
let mut rx = PinMock::new(&expectations);
pll.update(&mut rx);
assert!(pll.active);
rx.done();
}
#[test]
fn test_pll_inverts_signal_when_flagged() {
let expectations = [PinTransaction::get(PinState::High)];
let mut rx = PinMock::new(&expectations);
let mut pll = SoftwarePLL::new(8, true);
pll.update(&mut rx);
assert_eq!(pll.integrator, 0);
rx.done();
}
}