#![no_std]
pub type Microseconds = u32;
pub type PpmTime = Microseconds;
pub const MIN_CHAN_VAL: PpmTime = 800;
pub const MAX_CHAN_VAL: PpmTime = 2200;
pub const MID_CHAN_VAL: PpmTime = (MAX_CHAN_VAL + MIN_CHAN_VAL) / 2;
pub const MIN_SYNC_WIDTH: PpmTime = 4000;
pub const MIN_PPM_CHANNELS: u8 = 5;
pub const MAX_PPM_CHANNELS: usize = 20;
#[derive(Copy, Clone, Debug)]
pub struct PpmFrame {
pub chan_values: [PpmTime; MAX_PPM_CHANNELS],
pub chan_count: u8,
}
#[derive(Copy, Clone, Debug)]
pub struct ParserConfig {
min_chan_value: PpmTime,
max_chan_value: PpmTime,
mid_chan_value: PpmTime,
min_sync_width: PpmTime,
min_channels: u8,
max_ppm_time: u32,
}
impl Default for ParserConfig {
fn default() -> Self {
Self {
min_chan_value: MIN_CHAN_VAL,
max_chan_value: MAX_CHAN_VAL,
mid_chan_value: MID_CHAN_VAL,
min_sync_width: MIN_SYNC_WIDTH,
min_channels: MIN_PPM_CHANNELS,
max_ppm_time: 0xFFFF_FFFF,
}
}
}
impl PpmParser {
pub fn new() -> Self {
Self {
config: Default::default(),
working_frame: PpmFrame {
chan_values: [0; MAX_PPM_CHANNELS],
chan_count: 0,
},
parsed_frame: None,
state: ParserState::Scanning,
last_pulse_start: 0,
}
}
pub fn set_channel_limits(
&mut self,
min: PpmTime,
max: PpmTime,
) -> &mut Self {
self.config.min_chan_value = min;
self.config.max_chan_value = max;
self.config.mid_chan_value = (max + min) / 2;
self
}
pub fn set_sync_width(&mut self, width: PpmTime) -> &mut Self {
self.config.min_sync_width = width;
self
}
pub fn set_minimum_channels(&mut self, channels: u8) -> &mut Self {
self.config.min_channels = channels;
self
}
pub fn set_max_ppm_time(&mut self, value: PpmTime) -> &mut Self {
self.config.max_ppm_time = value;
self
}
pub fn next_frame(&mut self) -> Option<PpmFrame> {
self.parsed_frame.take()
}
pub fn handle_pulse_start(&mut self, count: PpmTime) {
let width = if count > self.last_pulse_start {
count - self.last_pulse_start
} else {
(self.config.max_ppm_time - self.last_pulse_start) + count
};
self.last_pulse_start = count;
match self.state {
ParserState::Scanning => {
if width >= self.config.min_sync_width {
self.reset_channel_counter();
self.state = ParserState::Synced;
}
}
ParserState::Synced => {
if width >= MIN_SYNC_WIDTH {
if self.working_frame.chan_count >= self.config.min_channels
{
self.parsed_frame.replace(self.working_frame);
} else {
self.parsed_frame = None;
}
self.reset_channel_counter();
} else {
if width >= self.config.min_chan_value
&& width <= self.config.max_chan_value
{
self.working_frame.chan_values
[self.working_frame.chan_count as usize] = width;
self.working_frame.chan_count += 1;
} else {
self.reset_channel_counter();
self.state = ParserState::Scanning;
}
}
}
}
}
fn reset_channel_counter(&mut self) {
self.working_frame.chan_count = 0;
}
}
pub struct PpmParser {
config: ParserConfig,
state: ParserState,
last_pulse_start: PpmTime,
working_frame: PpmFrame,
parsed_frame: Option<PpmFrame>,
}
enum ParserState {
Scanning,
Synced,
}
#[cfg(test)]
mod tests {
use crate::*;
#[test]
fn process_pulses() {
const TEST_CHAN_COUNT: u8 = 16;
const TEST_RESYNC_WIDTH: PpmTime = 2500;
let mut parser = PpmParser::new();
parser
.set_channel_limits(800, 2200)
.set_sync_width(TEST_RESYNC_WIDTH - 10);
let mut cur_time: PpmTime = 100;
parser.handle_pulse_start(cur_time);
let frame = parser.next_frame();
assert!(frame.is_none(), "there should be no frame yet");
cur_time += TEST_RESYNC_WIDTH;
for _ in 0..TEST_CHAN_COUNT + 1 {
parser.handle_pulse_start(cur_time);
let frame = parser.next_frame();
assert!(frame.is_none(), "frame should be incomplete");
cur_time += MID_CHAN_VAL;
}
cur_time += TEST_RESYNC_WIDTH;
parser.handle_pulse_start(cur_time);
let frame_opt = parser.next_frame();
assert!(frame_opt.is_some(), "frame should be complete");
if let Some(frame) = frame_opt {
let valid_chans = frame.chan_count;
assert_eq!(
valid_chans, TEST_CHAN_COUNT,
"wrong number of channels"
);
for i in 0..valid_chans as usize {
let val = frame.chan_values[i];
assert_eq!(val, MID_CHAN_VAL)
}
}
}
#[test]
fn overflow_timer() {
const TEST_CHAN_COUNT: u8 = 3;
let mut parser = PpmParser::new();
parser.set_minimum_channels(TEST_CHAN_COUNT);
const PULSE_GAP_TIME: PpmTime = MID_CHAN_VAL;
let mut cur_time: PpmTime =
PpmTime::max_value() - PULSE_GAP_TIME - MIN_SYNC_WIDTH + 10;
parser.handle_pulse_start(cur_time);
let frame = parser.next_frame();
assert!(frame.is_none(), "there should be no complete frame yet");
cur_time += MIN_SYNC_WIDTH;
for _ in 0..TEST_CHAN_COUNT + 1 {
parser.handle_pulse_start(cur_time);
let frame = parser.next_frame();
assert!(frame.is_none(), "frame should be incomplete");
cur_time = cur_time.wrapping_add(MID_CHAN_VAL);
}
cur_time += MIN_SYNC_WIDTH;
parser.handle_pulse_start(cur_time);
let frame_opt = parser.next_frame();
assert!(frame_opt.is_some(), "frame should be complete");
if let Some(frame) = frame_opt {
let valid_chans = frame.chan_count;
assert_eq!(
valid_chans, TEST_CHAN_COUNT,
"wrong number of channels"
);
for i in 0..valid_chans as usize {
let val = frame.chan_values[i];
assert_eq!(val, MID_CHAN_VAL)
}
}
}
}