flight_computer/
ublox_device.rs

1//! Control the GNSS receiver
2use bbqueue::{Consumer, GrantR};
3use embedded_sdmmc::Timestamp;
4use fugit::{ExtU64, MillisDuration};
5use ublox::{
6    AlignmentToReferenceTime, CfgMsgSinglePortBuilder, CfgNav5DynModel, CfgNav5FixMode,
7    CfgNav5Params, CfgNav5UtcStandard, FixedLinearBuffer, NavPosVelTime, ParserError,
8};
9
10/// ublox parser static buffer length
11pub const PARSER_BUFFER_LEN: usize = 1024;
12
13/// Log messages at 17.86 Hz in release mode
14#[cfg(not(debug_assertions))]
15pub const ACTIVE_MESSAGE_RATE: u16 = 56;
16
17/// Log messages at 10 Hz in debug mode
18#[cfg(debug_assertions)]
19pub const ACTIVE_MESSAGE_RATE: u16 = 100;
20
21/// On startup, receive messages at 1 Hz
22pub const STARTUP_MESSAGE_RATE: u16 = 1000;
23
24#[derive(Debug)]
25pub enum Error {
26    InvalidFix,
27    ParserError(ParserError),
28}
29
30impl From<ParserError> for Error {
31    fn from(item: ParserError) -> Error {
32        Error::ParserError(item)
33    }
34}
35
36#[derive(Clone)]
37pub struct HeaderData {
38    /// The UTC timestamp
39    pub utc: embedded_sdmmc::Timestamp,
40    /// The millisecond timestamp since reset
41    pub timestamp: MillisDuration<u64>,
42}
43
44impl core::default::Default for HeaderData {
45    fn default() -> Self {
46        HeaderData {
47            utc: Timestamp {
48                year_since_1970: 0,
49                zero_indexed_month: 0,
50                zero_indexed_day: 0,
51                hours: 0,
52                minutes: 0,
53                seconds: 0,
54            },
55            timestamp: 0.millis(),
56        }
57    }
58}
59
60pub trait UbloxDriver {
61    /// Enable GNSS circuitry
62    fn enable_gnss(&mut self);
63
64    /// Disable GNSS circuitry
65    fn disable_gnss(&mut self);
66
67    /// Send a byte array to the device
68    fn send_blocking(&mut self, packet: &[u8]);
69}
70
71/// Driver for the u-blox SAM-M8Q module
72pub struct Device<D: UbloxDriver, const LEN: usize> {
73    /// Device driver
74    driver: D,
75    /// UBX message parser
76    parser: ublox::Parser<FixedLinearBuffer<'static>>,
77    /// Incoming RX messages consumer
78    rx_cons: Consumer<'static, LEN>,
79    /// Has the signal been acquired since the device is on?
80    signal_acquired: bool,
81    /// The latest file header data received
82    header_data: Option<HeaderData>,
83}
84
85impl<D: UbloxDriver, const LEN: usize> Device<D, LEN> {
86    /// Create new device
87    #[inline]
88    pub fn new(
89        driver: D,
90        rx_cons: Consumer<'static, LEN>,
91        buffer: &'static mut [u8; PARSER_BUFFER_LEN],
92    ) -> Self {
93        let buffer = FixedLinearBuffer::new(buffer);
94
95        let mut device = Device {
96            driver,
97            rx_cons,
98            parser: ublox::Parser::new(buffer),
99            signal_acquired: false,
100            header_data: None,
101        };
102
103        device.gnss_power(true);
104
105        // Configure dynamic model (UBX-CFG-NAV5)
106        device.driver.send_blocking(
107            &ublox::CfgNav5Builder {
108                mask: CfgNav5Params::DYN | CfgNav5Params::POS_FIX_MODE,
109                dyn_model: CfgNav5DynModel::AirborneWith4gAcceleration,
110                fix_mode: CfgNav5FixMode::Auto2D3D,
111                fixed_alt: 0.0,
112                fixed_alt_var: 1.0,
113                min_elev_degrees: 5,
114                dr_limit: 0,
115                pdop: 25.0,
116                tdop: 25.0,
117                pacc: 100,
118                tacc: 100,
119                static_hold_thresh: 0.0,
120                dgps_time_out: 60,
121                cno_thresh_num_svs: 0,
122                cno_thresh: 0,
123                reserved1: [0; 2],
124                static_hold_max_dist: 0,
125                utc_standard: CfgNav5UtcStandard::Automatic,
126                reserved2: [0; 5],
127            }
128            .into_packet_bytes(),
129        );
130
131        // Configure Position/Velocity/Time message output (UBX-CFG-MSG)
132        // Send a UBX-NAV-PVT message every solution
133        device.driver.send_blocking(
134            &CfgMsgSinglePortBuilder::set_rate_for::<NavPosVelTime>(1).into_packet_bytes(),
135        );
136
137        /*
138        // Configure navigation status message output (UBX-CFG-MSG)
139        self.driver.send_blocking(
140            &CfgMsgSinglePortBuilder::set_rate_for::<NavStatus>(1).into_packet_bytes(),
141        );
142        */
143
144        device
145    }
146
147    /// Set the navigation message rate in full power mode
148    #[inline]
149    pub fn full_power(&mut self) {
150        let power_setup = cfg_pms::PowerSetupValue::FullPower;
151        self.driver.send_blocking(
152            &cfg_pms::CfgPms::new()
153                .with_power_setup_value(power_setup)
154                .into_bytes(),
155        );
156
157        // Configure message rate (UBX-CFG-RATE)
158        // 56 ms (17.85 Hz) is the fastest we seem to be able to get out of the SAM-M8Q
159        // receiver.
160        self.driver.send_blocking(
161            &ublox::CfgRateBuilder {
162                measure_rate_ms: ACTIVE_MESSAGE_RATE,
163                nav_rate: 1,
164                time_ref: AlignmentToReferenceTime::Utc,
165            }
166            .into_packet_bytes(),
167        );
168    }
169
170    /// Enter low-power mode. The message rate is set to 1 Hz in this mode.
171    #[inline]
172    pub fn low_power_1hz(&mut self) {
173        let power_setup = cfg_pms::PowerSetupValue::Aggressive1Hz;
174        self.driver.send_blocking(
175            &cfg_pms::CfgPms::new()
176                .with_power_setup_value(power_setup)
177                .into_bytes(),
178        );
179    }
180
181    /// Enable or disable the receiver
182    #[inline]
183    pub fn gnss_power(&mut self, enabled: bool) {
184        self.signal_acquired = false;
185        if enabled {
186            self.driver.enable_gnss();
187        } else {
188            self.driver.disable_gnss();
189        }
190    }
191
192    /// Parse received data. This is an unfortunate workaround because of how
193    /// lifetimes work. How to use this function:
194    ///
195    /// ```
196    /// use flight_computer::ublox_device::{UbloxDriver, Device};
197    ///
198    /// fn do_parse<D: UbloxDriver, const LEN: usize>(gps: &mut Device<D, LEN>) -> Result<(), bbqueue::Error>{
199    ///     let (parser, grant) = gps.parse()?;
200    ///     let mut iter = parser.consume(&grant);
201    ///
202    ///     while let Some(packet) = iter.next() {
203    ///         // Do something with the packet here
204    ///     }
205    ///
206    ///     Ok(())
207    /// }
208    /// ```
209    ///
210    /// # Errors
211    ///
212    /// May return `Err` if there is a problem while trying to acquire a
213    /// `bbqueue` grant.
214    #[inline]
215    pub fn parse(
216        &mut self,
217    ) -> Result<
218        (
219            &mut ublox::Parser<FixedLinearBuffer<'static>>,
220            GrantR<'static, LEN>,
221        ),
222        bbqueue::Error,
223    > {
224        let mut grant = self.rx_cons.read()?;
225        grant.to_release(usize::MAX);
226        Ok((&mut self.parser, grant))
227    }
228
229    /// Has the signal been acquired since the device is on?
230    #[inline]
231    pub fn signal_acquired(&self) -> bool {
232        self.signal_acquired
233    }
234
235    /// Notify that a valid signal has been acquired
236    #[inline]
237    pub fn acquire_signal(&mut self) {
238        self.signal_acquired = true;
239    }
240
241    /// Set header data
242    #[inline]
243    pub fn set_header_data(&mut self, data: &HeaderData) {
244        self.header_data.replace(data.clone());
245    }
246
247    /// Return header data if it is valid.
248    #[inline]
249    pub fn header_data(&self) -> Option<HeaderData> {
250        self.header_data.clone()
251    }
252
253    /// Invalidate header data
254    #[inline]
255    pub fn invalidate_header_data(&mut self) {
256        self.header_data = None;
257    }
258}
259
260#[derive(Debug)]
261pub struct GnssData {
262    pub lat: i32,
263    pub lon: i32,
264    pub ground_speed: u32,
265    pub vel_down: i32,
266    pub heading: Option<i32>,
267}
268
269pub mod cfg_pms {
270    use modular_bitfield::prelude::*;
271
272    #[bitfield]
273    pub struct CfgPms {
274        #[skip(setters)]
275        pub version: u8,
276        pub power_setup_value: PowerSetupValue,
277        pub period: u16,
278        pub on_time: u16,
279        #[skip]
280        _reserved: B16,
281    }
282
283    #[derive(BitfieldSpecifier)]
284    #[bits = 8]
285    pub enum PowerSetupValue {
286        FullPower = 0x00,
287        Balanced = 0x01,
288        Interval = 0x02,
289        Aggressive1Hz = 0x03,
290        Aggressive2Hz = 0x04,
291        Aggressive4Hz = 0x05,
292        Invalid = 0xFF,
293    }
294}
295
296pub mod cfg_prt_uart {
297    #![allow(clippy::identity_op)]
298    #![allow(unused_braces)]
299
300    use modular_bitfield::prelude::*;
301    #[bitfield]
302    #[repr(u16)]
303    pub struct TxReady {
304        pub en: bool,
305        pub pol: Polarity,
306        pub pin: B5,
307        pub thres: B9,
308    }
309
310    #[bitfield]
311    #[repr(u32)]
312    pub struct Mode {
313        #[skip]
314        _reserved: B6,
315        pub char_len: CharLen,
316        #[skip]
317        _reserved: B1,
318        pub parity: Parity,
319        pub n_stop_bits: NumStopBits,
320        #[skip]
321        _reserved: B18,
322    }
323
324    #[bitfield]
325    #[repr(u16)]
326    pub struct ProtoMask {
327        pub ubx: bool,
328        pub nmea: bool,
329        pub rtcm: bool,
330        #[skip]
331        _reserved: B2,
332        pub rtcm3: bool,
333        #[skip]
334        _reserved: B10,
335    }
336
337    #[bitfield]
338    #[repr(u16)]
339    pub struct Flags {
340        #[skip]
341        _reserved: B1,
342        pub extended_tx_timeout: bool,
343        #[skip]
344        _reserved: B14,
345    }
346
347    #[derive(BitfieldSpecifier)]
348    #[bits = 2]
349    pub enum NumStopBits {
350        Bits1 = 0b00,
351        Bits1p5 = 0b01,
352        Bits2 = 0b10,
353        Bits0p5 = 0b11,
354    }
355
356    #[derive(BitfieldSpecifier)]
357    #[bits = 2]
358    pub enum CharLen {
359        Bits7 = 0b10,
360        Bits8 = 0b11,
361    }
362
363    #[derive(BitfieldSpecifier)]
364    #[bits = 3]
365    pub enum Parity {
366        Even = 0b000,
367        Odd = 0b001,
368        None = 0b100,
369    }
370
371    #[derive(BitfieldSpecifier)]
372    #[bits = 1]
373    pub enum Polarity {
374        HighActive = 0,
375        LowActive = 1,
376    }
377}
378
379#[cfg(test)]
380mod tests {
381    use super::*;
382    use cfg_prt_uart::*;
383
384    use ublox::{
385        AlignmentToReferenceTime, CfgNav5DynModel, CfgNav5FixMode, CfgNav5Params,
386        CfgNav5UtcStandard,
387    };
388
389    #[test]
390    fn build_packet_prt() {
391        const BUS_SPEED: u32 = 9600_u32;
392
393        let packet = ublox::CfgPrtUartBuilder {
394            portid: ublox::UartPortId::Uart1,
395            reserved0: 0,
396            // TODO Configuration of the TX Ready
397            tx_ready: TxReady::new().with_en(false).into(),
398            mode: Mode::new()
399                .with_char_len(CharLen::Bits8)
400                .with_parity(Parity::None)
401                .with_n_stop_bits(NumStopBits::Bits1)
402                .into(),
403            baud_rate: BUS_SPEED,
404            in_proto_mask: ProtoMask::new().with_ubx(true).into(),
405            out_proto_mask: ProtoMask::new().with_ubx(true).with_nmea(true).into(),
406            flags: Flags::new().into(),
407            reserved5: 0,
408        }
409        .into_packet_bytes();
410
411        let frame = [
412            0xb5, 0x62, 0x6, 0x0, 0x14, 0x0, 0x1, 0x0, 0x0, 0x0, 0xc0, 0x8, 0x0, 0x0, 0x80, 0x25,
413            0x0, 0x0, 0x1, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8c, 0x85,
414        ];
415
416        assert_eq!(packet, frame);
417    }
418
419    #[test]
420    fn build_packet_nav5() {
421        let packet = ublox::CfgNav5Builder {
422            mask: CfgNav5Params::DYN | CfgNav5Params::POS_FIX_MODE,
423            dyn_model: CfgNav5DynModel::AirborneWith4gAcceleration,
424            fix_mode: CfgNav5FixMode::Auto2D3D,
425            fixed_alt: 0.0,
426            fixed_alt_var: 0.0,
427            min_elev_degrees: 0,
428            dr_limit: 0,
429            pdop: 0.0,
430            tdop: 0.0,
431            pacc: 0,
432            tacc: 0,
433            static_hold_thresh: 0.0,
434            dgps_time_out: 0,
435            cno_thresh_num_svs: 0,
436            cno_thresh: 0,
437            reserved1: [0; 2],
438            static_hold_max_dist: 0,
439            utc_standard: CfgNav5UtcStandard::Automatic,
440            reserved2: [0; 5],
441        }
442        .into_packet_bytes();
443
444        let frame = [
445            0xb5, 0x62, 0x6, 0x24, 0x24, 0x0, 0x5, 0x0, 0x8, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
446            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
447            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5e, 0xeb,
448        ];
449
450        assert_eq!(packet, frame);
451    }
452
453    #[test]
454    fn build_packet_rate() {
455        let packet = ublox::CfgRateBuilder {
456            measure_rate_ms: 50,
457            nav_rate: 1,
458            time_ref: AlignmentToReferenceTime::Utc,
459        }
460        .into_packet_bytes();
461
462        let frame = [
463            0xb5, 0x62, 0x6, 0x8, 0x6, 0x0, 0x32, 0x0, 0x1, 0x0, 0x0, 0x0, 0x47, 0xe4,
464        ];
465
466        assert_eq!(packet, frame);
467    }
468
469    /// Test that `ublox::Parser` can receive partially formed packets and
470    /// release them when the rest is sent
471    #[test]
472    fn send_partial_packets() {
473        let mut buffer = [0u8; 512];
474        let buffer = ublox::FixedLinearBuffer::new(&mut buffer);
475        let mut parser = ublox::Parser::new(buffer);
476
477        {
478            // Partial AckAck packet
479            let packet_1 = [0xb5, 0x62, 0x05, 0x01, 0x02];
480            let mut iter = parser.consume(&packet_1);
481            println!("Print packet 1 (partial AckAck): ");
482
483            if iter.next().is_some() {
484                panic!("Returned some packet when only a partial packet was supplied");
485            }
486        }
487
488        {
489            // Finish sending AckAck packet, send partial AckNak packet
490            let packet_2 = [0x00, 0x06, 0x00, 0x0e, 0x37, 0xb5, 0x62, 0x05, 0x00, 0x02];
491            // Finish AckAck here --------------------^    ^
492            // Start new packet here ----------------------|
493            let mut iter = parser.consume(&packet_2);
494            println!("Print packet 2 (finish AckAck, partial AckNak): ");
495            match iter.next() {
496                Some(Ok(ublox::PacketRef::AckAck(_))) => (),
497                _ => panic!("Did not return expected packet"),
498            }
499        }
500
501        {
502            // Finish sending AckNak packet
503            let packet_3 = [0x00, 0x06, 0x00, 0x0d, 0x32];
504            let mut iter = parser.consume(&packet_3);
505            println!("Print packet 3 (finish AckNak): ");
506            match iter.next() {
507                Some(Ok(ublox::PacketRef::AckNak(_))) => (),
508                _ => panic!("Did not return expected packet"),
509            }
510        }
511    }
512}