1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
/*
Copyright (c) 2020 Todd Stellanova
LICENSE: BSD3 (see LICENSE file)
*/

#![no_std]

//!
//! This library provides decoding of PPM pulse edges into PPM frames.
//! It does not require a particular interrupt handling or input pin
//! measurement strategy.  All the user needs to provide is the
//! relative timing of pulses, and the library will extract PPM
//! frames from that.
//!
//! PPM channel values are encoded as the gap between multiple pulses.
//! Typically PPM pulses are high values and the gaps between them
//! are low values; however, some PPM receivers invert the signal,
//! where the PPM signal is pulled high and pulses are low values.
//! PPM frames consist of multiple channels encoded in this way,
//! with a longer duration gap between the last channel and the
//! first channel, after which comes the next frame.
//! This frame-separating gap is referred to as a frame sync or reset.
//! So a PPM frame with five channels might look like:
//!
//! ______|___|___|___|___|___|______
//! Where high values are the pulses and low values are the
//! gaps between pulses. The pulse duration itself is typically
//! tuned to be as short as possible and still reliably transmitted.
//!
//! The library provides defaults for common configuration values
//! such as:
//! - Minimum PPM channel value (the minimum gap between pulses)
//! - Maximum PPM channel value (the maximum gap between pulses)
//! - Minimum frame sync duration (the minimum time for a gap between
//! pulses to be considered a frame sync / reset)
//! - Minimum number of PPM channels to be considered a valid frame.
//!
//!

/// Base type for PPM timing
/// Your clock for measuring pulse edges will need at least microsecond resolution.
pub type Microseconds = u32;

/// PPM timing values
pub type PpmTime = Microseconds;

/// Default minimum channel value
pub const MIN_CHAN_VAL: PpmTime = 800;
/// Default maximum channel value
pub const MAX_CHAN_VAL: PpmTime = 2200;
/// Default midpoint channel value
pub const MID_CHAN_VAL: PpmTime = (MAX_CHAN_VAL + MIN_CHAN_VAL) / 2;

/// Default minimum gap between frames (no pulses / inactive/ sync)
pub const MIN_SYNC_WIDTH: PpmTime = 4000;

/// Default minimum number of channels per frame
pub const MIN_PPM_CHANNELS: u8 = 5;

/// Maximum PPM channels this library supports
pub const MAX_PPM_CHANNELS: usize = 20;

/// A single group of PPM channel values
#[derive(Copy, Clone, Debug)]
pub struct PpmFrame {
    /// Decoded PPM channel values
    pub chan_values: [PpmTime; MAX_PPM_CHANNELS],
    /// Number of channels decoded (≤ MAX_PPM_CHANNELS)
    pub chan_count: u8,
}

/// Configuration values for PpmParser
#[derive(Copy, Clone, Debug)]
pub struct ParserConfig {
    /// Configurable minimum channel value
    min_chan_value: PpmTime,

    /// Configurable maximum channel value
    max_chan_value: PpmTime,

    /// Configurable middle channel value
    mid_chan_value: PpmTime,

    /// Configurable start/reset signal width
    min_sync_width: PpmTime,

    /// Configurable minimum number of channels per valid frame
    min_channels: u8,

    /// The maximum timer value, after which the clock/timer wraps,
    /// eg 0xFFFF for a 16-bit timer, 0xFFFF_FFFF for a 32-bit timer
    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,
        }
    }
}

/// The main PPM decoder.
///
/// # Example:
/// ```
///     use ppm_decode::*;
///         let mut parser = PpmParser::new();
///         //arbitrary start time
///         let mut cur_time: PpmTime = 100;
///
///         //start with a trailing pulse from prior frame, to force resync
///         parser.handle_pulse_start(cur_time);
///         let frame = parser.next_frame();
///         assert!(frame.is_none(), "there should be no complete frame yet");
///
///         //this effectively starts a new frame:
///         cur_time += MIN_SYNC_WIDTH;
///         // send n+1 pulses where n is the channel counts
///         for _ in 0..MIN_PPM_CHANNELS + 1 {
///             parser.handle_pulse_start(cur_time);
///             let frame = parser.next_frame();
///             assert!(frame.is_none(), "frame should be incomplete");
///             // each pulse is separated by the same gap in this test,
///             // which means all channels have the same value in this frame
///             cur_time += MID_CHAN_VAL;
///         }
///
///         //send the next sync
///         cur_time += MIN_SYNC_WIDTH;
///         parser.handle_pulse_start(cur_time);
///         //should now have a complete frame available
///         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, MIN_PPM_CHANNELS, "wrong number of channels");
///             for i in 0..valid_chans as usize {
///                 let val = frame.chan_values[i];
///                 assert_eq!(val, MID_CHAN_VAL)
///             }
///         }
/// ```
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,
        }
    }

    /// Configure channel value range
    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
    }

    /// Configure duration of frame sync
    pub fn set_sync_width(&mut self, width: PpmTime) -> &mut Self {
        self.config.min_sync_width = width;
        self
    }

    /// Set the minimum number of channels in a valid frame
    pub fn set_minimum_channels(&mut self, channels: u8) -> &mut Self {
        self.config.min_channels = channels;
        self
    }

    /// Set the maximum timer value -- allows us to use timers with
    /// different resolution than the default 32 bits
    pub fn set_max_ppm_time(&mut self, value: PpmTime) -> &mut Self {
        self.config.max_ppm_time = value;
        self
    }

    /// Get the next available PPM frame, if any.
    /// This function may return `None` if a complete
    /// frame has not been received yet, or if no
    /// frame sync has been received.
    pub fn next_frame(&mut self) -> Option<PpmFrame> {
        self.parsed_frame.take()
    }

    /// Handle a pulse start.  This could be the time
    /// in microseconds of a pulse rising edge or falling edge
    /// (depending on the PPM input and your measurement strategy)
    /// -- it does not really matter as long as you measure the
    /// the pulses consistently.
    ///
    pub fn handle_pulse_start(&mut self, count: PpmTime) {
        //calculate pulse width using wrapping subtraction based on max_ppm_time
        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 => {
                // assume we've never received any pulses before:
                // detect a long sync/reset gap
                if width >= self.config.min_sync_width {
                    //received sync
                    self.reset_channel_counter();
                    self.state = ParserState::Synced;
                }
            }
            ParserState::Synced => {
                if width >= MIN_SYNC_WIDTH {
                    // Received sync -- check whether finished decoding a whole frame
                    // TODO add a feature to only allow slow drift of the channel count
                    if self.working_frame.chan_count >= self.config.min_channels
                    {
                        // We've received the configured minimum number of channels:
                        // frame is complete.
                        self.parsed_frame.replace(self.working_frame);
                    } else {
                        // We didn't receive the expected minimum number of channels.
                        self.parsed_frame = None;
                    }
                    self.reset_channel_counter();
                } else {
                    // Verify the pulse received is within limits, otherwise resync.
                    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;
                    //TODO verify we haven't received TOO MANY channels (<MAX_PPM_CHANNELS)
                    } else {
                        // bogus pulse -- resynchronize
                        self.reset_channel_counter();
                        self.state = ParserState::Scanning;
                    }
                }
            }
        }
    }

    /// We've either finished receiving all channels
    /// (and have received a sync/reset)
    /// or we received garbage and need to clear our buffers.
    fn reset_channel_counter(&mut self) {
        self.working_frame.chan_count = 0;
    }
}

pub struct PpmParser {
    /// Parser configuration
    config: ParserConfig,

    /// Current parsing state
    state: ParserState,

    /// the last time an (active) pulse started
    last_pulse_start: PpmTime,

    /// working memory for current frame capture
    working_frame: PpmFrame,

    /// frame ready for consumption
    parsed_frame: Option<PpmFrame>,
}

enum ParserState {
    /// we have not yet received a long reset/synchronization
    Scanning,
    /// we've received a sync and are trying to receive pulses
    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;
        //start with a garbage pulse from prior frame
        parser.handle_pulse_start(cur_time);
        let frame = parser.next_frame();
        assert!(frame.is_none(), "there should be no frame yet");

        //send a full frame
        //this effectively starts a new frame:
        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;
        }

        //send the next sync
        cur_time += TEST_RESYNC_WIDTH;
        parser.handle_pulse_start(cur_time);
        //should now have a complete frame available
        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);

        // for this test all the channel pulses are separated by the same gap (same channel value)
        const PULSE_GAP_TIME: PpmTime = MID_CHAN_VAL;

        // Send a pulse train that looks like this:
        // |______|___|___|___|______
        // where the third pulse arrives after PpmTime overflow
        // This calculated start time is for the first pulse:
        let mut cur_time: PpmTime =
            PpmTime::max_value() - PULSE_GAP_TIME - MIN_SYNC_WIDTH + 10;
        //start with a garbage pulse from prior frame
        parser.handle_pulse_start(cur_time);
        let frame = parser.next_frame();
        assert!(frame.is_none(), "there should be no complete frame yet");

        //this effectively starts a new frame:
        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");
            // this should overflow at the third pulse:
            cur_time = cur_time.wrapping_add(MID_CHAN_VAL);
        }

        //send the next sync
        cur_time += MIN_SYNC_WIDTH;
        parser.handle_pulse_start(cur_time);
        //should now have a complete frame available
        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)
            }
        }
    }
}