dcc_rs/
lib.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4#![doc = include_str!("../README.md")]
5#![cfg_attr(not(test), no_std)]
6#![deny(missing_docs)]
7
8pub use bitvec;
9use bitvec::prelude::*;
10use embedded_hal::digital::v2::OutputPin;
11
12pub mod packets;
13
14const BUFFER_SIZE: usize = 24 * 8;
15type BufferType = BitArr!(for 24*8, in u8, Msb0);
16const ZERO_MICROS: u32 = 100;
17const ONE_MICROS: u32 = 58;
18
19/// Error types returned by this crate
20#[derive(Copy, Clone, Debug, Eq, PartialEq)]
21pub enum Error {
22    /// Data packet was too long for the internal buffer
23    TooLong,
24    /// Not a valid short-mode DCC address (must be in range 1-126)
25    InvalidAddress,
26    /// Not a valid short-mode DCC speed (must be in range 0-16)
27    InvalidSpeed,
28    /// Invalid bit offset
29    InvalidOffset,
30    /// A required data field is missing
31    MissingField,
32}
33
34#[derive(Debug)]
35enum TxState {
36    Idle {
37        second_half_of_bit: bool,
38    },
39    Transmitting {
40        offset: usize,
41        second_half_of_bit: bool,
42    },
43}
44
45/// The main interrupt handler. Calling the `tick` method advances the
46/// internal state and toggles the provided output pin to control the
47/// track polarity
48pub struct DccInterruptHandler<P: OutputPin> {
49    write_buffer: BufferType,
50    write_buffer_len: usize,
51    buffer: BufferType,
52    buffer_num_bits: usize,
53    state: TxState,
54    output_pin: P,
55}
56
57impl<P: OutputPin> DccInterruptHandler<P> {
58    /// Initialise the interrupt handler. `output_pin` is the GPIO pin
59    /// connected to e.g. a motor shield's `direction` pin to control the
60    /// track polarity.
61    pub fn new(output_pin: P) -> Self {
62        Self {
63            write_buffer: BitArray::default(),
64            write_buffer_len: 0,
65            buffer: BitArray::default(),
66            buffer_num_bits: 0,
67            state: TxState::Idle {
68                second_half_of_bit: false,
69            },
70            output_pin,
71        }
72    }
73
74    /// Run on interrupt; returns the new clock count to set the interrupt to
75    #[inline(always)]
76    pub fn tick(&mut self) -> Result<u32, P::Error> {
77        #[cfg(test)]
78        {
79            eprintln!("[tick] DCC state:");
80            eprintln!(
81                "  write_buffer: (len {}) {:?}",
82                self.write_buffer_len,
83                &self.write_buffer[..self.write_buffer_len]
84            );
85            eprintln!("  state {:?}", self.state,);
86        }
87
88        let new_clock;
89        self.state = match self.state {
90            TxState::Idle { second_half_of_bit } => {
91                // transmit a zero
92                if second_half_of_bit {
93                    self.output_pin.set_high()?;
94                } else {
95                    self.output_pin.set_low()?;
96                }
97                new_clock = ZERO_MICROS;
98
99                if second_half_of_bit && self.write_buffer_len != 0 {
100                    // copy write buffer into internal buffer
101                    self.buffer.copy_from_bitslice(&self.write_buffer);
102                    self.buffer_num_bits = self.write_buffer_len;
103                    self.write_buffer_len = 0;
104                    #[cfg(test)]
105                    eprintln!("Loaded new data into tx buffer");
106
107                    TxState::Transmitting {
108                        offset: 0,
109                        second_half_of_bit: false,
110                    }
111                } else {
112                    TxState::Idle {
113                        second_half_of_bit: !second_half_of_bit,
114                    }
115                }
116            }
117            TxState::Transmitting {
118                mut offset,
119                second_half_of_bit,
120            } => {
121                // transmit the next bit-half in the sequence
122                let current_bit = *self.buffer.get(offset).unwrap();
123
124                new_clock = if current_bit { ONE_MICROS } else { ZERO_MICROS };
125
126                if second_half_of_bit {
127                    self.output_pin.set_high()?;
128                    // increment offset
129                    offset += 1;
130                } else {
131                    self.output_pin.set_low()?;
132                }
133
134                // if there is remaining data then continue transmitting,
135                // otherwise go back to Idle mode
136                if offset < self.buffer_num_bits {
137                    TxState::Transmitting {
138                        offset,
139                        second_half_of_bit: !second_half_of_bit,
140                    }
141                } else {
142                    TxState::Idle {
143                        second_half_of_bit: false,
144                    }
145                }
146            }
147        };
148
149        Ok(new_clock)
150    }
151
152    /// Stage a packet for transmission
153    pub fn write(&mut self, buf: &BitSlice<u8, Msb0>) -> Result<(), Error> {
154        if buf.len() > BUFFER_SIZE {
155            Err(Error::TooLong)
156        } else {
157            self.write_buffer[0..buf.len()].copy_from_bitslice(buf);
158            self.write_buffer_len = buf.len();
159            #[cfg(test)]
160            eprintln!("Written {} bits to write buffer", buf.len());
161            Ok(())
162        }
163    }
164}
165
166#[cfg(test)]
167mod test {
168    use super::*;
169    use embedded_hal::digital::v2::*;
170    use std::convert::Infallible;
171
172    #[derive(Default)]
173    struct MockPin {
174        state: bool,
175    }
176
177    impl OutputPin for MockPin {
178        type Error = Infallible;
179
180        #[inline(always)]
181        fn set_high(&mut self) -> Result<(), Self::Error> {
182            self.state = true;
183            Ok(())
184        }
185
186        #[inline(always)]
187        fn set_low(&mut self) -> Result<(), Self::Error> {
188            self.state = false;
189            Ok(())
190        }
191    }
192
193    impl StatefulOutputPin for MockPin {
194        #[inline(always)]
195        fn is_set_high(&self) -> Result<bool, Self::Error> {
196            Ok(self.state)
197        }
198
199        #[inline(always)]
200        fn is_set_low(&self) -> Result<bool, Self::Error> {
201            Ok(!self.state)
202        }
203    }
204
205    #[test]
206    fn mock_pin_works() {
207        let mut pin = MockPin::default();
208        assert!(pin.is_set_low().unwrap());
209        pin.set_high().unwrap();
210        assert!(pin.is_set_high().unwrap());
211        pin.set_low().unwrap();
212        assert!(pin.is_set_low().unwrap());
213    }
214
215    #[test]
216    fn send_a_packet() {
217        const ZERO: u32 = 100;
218        const ONE: u32 = 58;
219        let pin = MockPin::default();
220        let mut dcc = DccInterruptHandler::new(pin);
221        let buffer = [0x00, 0xff].view_bits();
222        dcc.write(buffer).unwrap();
223
224        // first two ticks are idle
225        for _ in 0..2 {
226            let new_delay = dcc.tick().unwrap();
227            eprintln!("new delay: {new_delay}");
228            assert_eq!(new_delay, ZERO);
229        }
230
231        // run 32 ticks to make sure that the clock settings are correct
232        // (2 ticks per bit)
233        // 16 ticks are one
234        for _ in 0..16 {
235            let new_delay = dcc.tick().unwrap();
236            eprintln!("new delay: {new_delay}");
237            assert_eq!(new_delay, ZERO);
238        }
239
240        // 16 ticks are zero
241        for _ in 0..16 {
242            let new_delay = dcc.tick().unwrap();
243            eprintln!("new delay: {new_delay}");
244            assert_eq!(new_delay, ONE);
245        }
246
247        // after packet is finished we just have idle zeroes
248        for _ in 0..8 {
249            let new_delay = dcc.tick().unwrap();
250            eprintln!("new delay: {new_delay}");
251            assert_eq!(new_delay, ZERO);
252        }
253    }
254}