hackrfone/
lib.rs

1//! HackRF One API.
2//!
3//! To get started take a look at [`HackRfOne::new`].
4#![cfg_attr(docsrs, feature(doc_cfg))]
5#![warn(missing_docs)]
6
7pub use nusb;
8use std::io;
9use std::io::Read;
10
11use nusb::transfer::{Bulk, ControlIn, ControlOut, ControlType, Direction, In, Recipient};
12use nusb::{Interface, MaybeFuture, list_devices};
13use std::time::Duration;
14
15#[cfg(feature = "num-complex")]
16pub use num_complex;
17
18/// HackRF USB vendor ID.
19const HACKRF_USB_VID: u16 = 0x1D50;
20/// HackRF One USB product ID.
21const HACKRF_ONE_USB_PID: u16 = 0x6089;
22
23#[allow(dead_code)]
24#[repr(u8)]
25enum Request {
26    SetTransceiverMode = 1,
27    Max2837Write = 2,
28    Max2837Read = 3,
29    Si5351CWrite = 4,
30    Si5351CRead = 5,
31    SampleRateSet = 6,
32    BasebandFilterBandwidthSet = 7,
33    Rffc5071Write = 8,
34    Rffc5071Read = 9,
35    SpiflashErase = 10,
36    SpiflashWrite = 11,
37    SpiflashRead = 12,
38    BoardIdRead = 14,
39    VersionStringRead = 15,
40    SetFreq = 16,
41    AmpEnable = 17,
42    BoardPartidSerialnoRead = 18,
43    SetLnaGain = 19,
44    SetVgaGain = 20,
45    SetTxvgaGain = 21,
46    AntennaEnable = 23,
47    SetFreqExplicit = 24,
48    UsbWcidVendorReq = 25,
49    InitSweep = 26,
50    OperacakeGetBoards = 27,
51    OperacakeSetPorts = 28,
52    SetHwSyncMode = 29,
53    Reset = 30,
54    OperacakeSetRanges = 31,
55    ClkoutEnable = 32,
56    SpiflashStatus = 33,
57    SpiflashClearStatus = 34,
58    OperacakeGpioTest = 35,
59    CpldChecksum = 36,
60    UiEnable = 37,
61}
62
63impl From<Request> for u8 {
64    fn from(r: Request) -> Self {
65        r as u8
66    }
67}
68
69#[allow(dead_code)]
70#[repr(u8)]
71enum TransceiverMode {
72    Off = 0,
73    Receive = 1,
74    Transmit = 2,
75    Ss = 3,
76    CpldUpdate = 4,
77    RxSweep = 5,
78}
79
80impl From<TransceiverMode> for u8 {
81    fn from(tm: TransceiverMode) -> Self {
82        tm as u8
83    }
84}
85
86impl From<TransceiverMode> for u16 {
87    fn from(tm: TransceiverMode) -> Self {
88        tm as u16
89    }
90}
91
92/// HackRF One errors.
93#[derive(Debug)]
94pub enum Error {
95    /// USB error.
96    Usb(nusb::Error),
97    /// USB Connection Errors
98    UsbTransfer(nusb::transfer::TransferError),
99    /// IO Error
100    IO(io::Error),
101    /// Failed to transfer all bytes in a control transfer.
102    CtrlTransfer {
103        /// Control transfer direction.
104        dir: Direction,
105        /// Actual amount of bytes transferred.
106        actual: usize,
107        /// Excepted number of bytes transferred.
108        expected: usize,
109    },
110    /// An API call is not supported by your hardware.
111    ///
112    /// Try updating the firmware on your device.
113    Version {
114        /// Current device version.
115        device: Version,
116        /// Minimum version required.
117        min: Version,
118    },
119    /// A provided argument was out of range.
120    Argument,
121}
122
123impl From<nusb::Error> for Error {
124    fn from(e: nusb::Error) -> Self {
125        Error::Usb(e)
126    }
127}
128
129impl From<nusb::transfer::TransferError> for Error {
130    fn from(e: nusb::transfer::TransferError) -> Self {
131        Error::UsbTransfer(e)
132    }
133}
134
135impl From<io::Error> for Error {
136    fn from(e: io::Error) -> Self {
137        Error::IO(e)
138    }
139}
140
141impl std::fmt::Display for Error {
142    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143        write!(f, "{self:?}")
144    }
145}
146
147#[derive(Clone, Debug, PartialEq)]
148/// Version used to denote the parts of BCD
149pub struct Version {
150    /// Major version XX.0.0
151    pub major: u8,
152    /// Minor version 00.X.0
153    pub minor: u8,
154    /// Sub Minor version 00.0.X
155    pub sub_minor: u8,
156}
157
158impl Version {
159    fn from_bcd(raw: u16) -> Self {
160        // 0xJJMN JJ major, M minor, N sub-minor
161        // Binary Coded Decimal
162        let major0: u8 = ((raw & 0xF000) >> 12) as u8;
163        let major1: u8 = ((raw & 0x0F00) >> 8) as u8;
164
165        let minor: u8 = ((raw & 0x00F0) >> 4) as u8;
166
167        let sub_minor: u8 = (raw & 0x000F) as u8;
168
169        Self {
170            major: (major0 * 10) + major1,
171            minor,
172            sub_minor,
173        }
174    }
175}
176
177#[cfg(test)]
178mod version_bcd {
179    use super::Version;
180
181    #[test]
182    fn from_bcd() {
183        assert_eq!(
184            Version::from_bcd(0x1234),
185            Version {
186                major: 12,
187                minor: 3,
188                sub_minor: 4
189            }
190        );
191        assert_eq!(
192            Version::from_bcd(0x4321),
193            Version {
194                major: 43,
195                minor: 2,
196                sub_minor: 1
197            }
198        );
199        assert_eq!(
200            Version::from_bcd(0x0200),
201            Version {
202                major: 2,
203                minor: 0,
204                sub_minor: 0
205            }
206        );
207        assert_eq!(
208            Version::from_bcd(0x0110),
209            Version {
210                major: 1,
211                minor: 1,
212                sub_minor: 0
213            }
214        );
215    }
216}
217
218impl std::error::Error for Error {}
219
220/// Typestate for RX mode.
221#[derive(Debug)]
222pub struct RxMode;
223
224/// Typestate for an unknown mode.
225#[derive(Debug)]
226pub struct UnknownMode;
227
228/// HackRF One software defined radio.
229pub struct HackRfOne<MODE> {
230    dh: nusb::Device,
231    desc: nusb::descriptors::DeviceDescriptor,
232    interface: Interface,
233    #[allow(dead_code)]
234    mode: MODE,
235    timeout: Duration,
236}
237
238impl HackRfOne<UnknownMode> {
239    /// Open a new HackRF One.
240    ///
241    /// # Example
242    ///
243    /// ```no_run
244    /// use hackrfone::{HackRfOne, UnknownMode};
245    ///
246    /// let mut radio: HackRfOne<UnknownMode> = HackRfOne::new().unwrap();
247    /// ```
248    #[must_use]
249    pub fn new() -> Option<HackRfOne<UnknownMode>> {
250        let Ok(devices) = list_devices().wait() else {
251            return None;
252        };
253
254        for device in devices {
255            if device.vendor_id() == HACKRF_USB_VID && device.product_id() == HACKRF_ONE_USB_PID {
256                match device.open().wait() {
257                    Ok(handle) => {
258                        let Ok(interface) = handle.detach_and_claim_interface(0).wait() else {
259                            return None;
260                        };
261
262                        return Some(HackRfOne {
263                            desc: handle.device_descriptor(),
264                            dh: handle,
265                            interface,
266                            mode: UnknownMode,
267                            timeout: Duration::from_secs(1),
268                        });
269                    }
270                    Err(_) => continue,
271                }
272            }
273        }
274
275        None
276    }
277}
278
279impl<MODE> HackRfOne<MODE> {
280    fn read_control<const N: usize>(
281        &self,
282        request: Request,
283        value: u16,
284        index: u16,
285    ) -> Result<[u8; N], Error> {
286        let buf = self
287            .interface
288            .control_in(
289                ControlIn {
290                    control_type: ControlType::Vendor,
291                    recipient: Recipient::Device,
292                    request: request.into(),
293                    value,
294                    index,
295                    length: N as u16,
296                },
297                self.timeout,
298            )
299            .wait()?;
300
301        if N == buf.len() {
302            Ok(<[u8; N]>::try_from(buf).expect("This should never happen"))
303        } else {
304            Err(Error::CtrlTransfer {
305                dir: Direction::In,
306                actual: buf.len(),
307                expected: N,
308            })
309        }
310    }
311
312    fn write_control(
313        &mut self,
314        request: Request,
315        value: u16,
316        index: u16,
317        buf: &[u8],
318    ) -> Result<(), Error> {
319        self.interface
320            .control_out(
321                ControlOut {
322                    control_type: ControlType::Vendor,
323                    recipient: Recipient::Device,
324                    request: request.into(),
325                    value,
326                    index,
327                    data: buf,
328                },
329                self.timeout,
330            )
331            .wait()?;
332
333        Ok(())
334    }
335
336    fn check_api_version(&self, min: Version) -> Result<(), Error> {
337        fn version_to_u32(v: &Version) -> u32 {
338            (u32::from(v.major) << 16) | (u32::from(v.minor) << 8) | u32::from(v.sub_minor)
339        }
340
341        let v: Version = self.device_version();
342        let v_cmp: u32 = version_to_u32(&v);
343        let min_cmp: u32 = version_to_u32(&min);
344
345        if v_cmp >= min_cmp {
346            Ok(())
347        } else {
348            Err(Error::Version { device: v, min })
349        }
350    }
351
352    /// Get the device version from the USB descriptor.
353    ///
354    /// The HackRF C API calls the equivalent of this function
355    /// `hackrf_usb_api_version_read`.
356    ///
357    /// # Example
358    ///
359    /// ```no_run
360    /// use hackrfone::{HackRfOne, UnknownMode, Version};
361    ///
362    /// let mut radio: HackRfOne<UnknownMode> = HackRfOne::new().unwrap();
363    /// assert_eq!(
364    ///     radio.device_version(),
365    ///     Version {
366    ///         major: 1,
367    ///         minor: 0,
368    ///         sub_minor: 4
369    ///     }
370    /// );
371    /// ```
372    pub fn device_version(&self) -> Version {
373        Version::from_bcd(self.desc.device_version())
374    }
375
376    /// Set the timeout for USB transfers.
377    ///
378    /// # Example
379    ///
380    /// Set a 100ms timeout.
381    ///
382    /// ```no_run
383    /// use hackrfone::{HackRfOne, UnknownMode};
384    /// use std::time::Duration;
385    ///
386    /// let mut radio: HackRfOne<UnknownMode> = HackRfOne::new().unwrap();
387    /// radio.set_timeout(Duration::from_millis(100))
388    /// ```
389    pub fn set_timeout(&mut self, duration: Duration) {
390        self.timeout = duration;
391    }
392
393    /// Read the board ID.
394    ///
395    /// # Example
396    ///
397    /// ```no_run
398    /// use hackrfone::{HackRfOne, UnknownMode};
399    ///
400    /// let mut radio: HackRfOne<UnknownMode> = HackRfOne::new().unwrap();
401    /// assert_eq!(radio.board_id()?, 0x02);
402    /// # Ok::<(), hackrfone::Error>(())
403    /// ```
404    pub fn board_id(&self) -> Result<u8, Error> {
405        let data: [u8; 1] = self.read_control(Request::BoardIdRead, 0, 0)?;
406        Ok(data[0])
407    }
408
409    /// Read the firmware version.
410    ///
411    /// # Example
412    ///
413    /// ```no_run
414    /// use hackrfone::{HackRfOne, UnknownMode};
415    ///
416    /// let mut radio: HackRfOne<UnknownMode> = HackRfOne::new().unwrap();
417    /// assert_eq!(radio.version()?, "2021.03.1");
418    /// # Ok::<(), hackrfone::Error>(())
419    /// ```
420    pub fn version(&self) -> Result<String, Error> {
421        let buf = self
422            .interface
423            .control_in(
424                ControlIn {
425                    control_type: ControlType::Vendor,
426                    recipient: Recipient::Device,
427                    request: Request::VersionStringRead.into(),
428                    value: 0,
429                    index: 0,
430                    length: 16,
431                },
432                self.timeout,
433            )
434            .wait()?;
435        Ok(String::from_utf8_lossy(&buf).into())
436    }
437
438    /// Set the center frequency.
439    ///
440    /// # Example
441    ///
442    /// Set the frequency to 915MHz.
443    ///
444    /// ```no_run
445    /// use hackrfone::{HackRfOne, UnknownMode};
446    ///
447    /// let mut radio: HackRfOne<UnknownMode> = HackRfOne::new().unwrap();
448    /// radio.set_freq(915_000_000)?;
449    /// # Ok::<(), hackrfone::Error>(())
450    /// ```
451    pub fn set_freq(&mut self, hz: u64) -> Result<(), Error> {
452        let buf: [u8; 8] = freq_params(hz);
453        self.write_control(Request::SetFreq, 0, 0, &buf)
454    }
455
456    /// Enable the RX/TX RF amplifier.
457    ///
458    /// In GNU radio this is used as the RF gain, where a value of 0 dB is off,
459    /// and a value of 14 dB is on.
460    ///
461    /// # Example
462    ///
463    /// Disable the amplifier.
464    ///
465    /// ```no_run
466    /// use hackrfone::{HackRfOne, UnknownMode};
467    ///
468    /// let mut radio: HackRfOne<UnknownMode> = HackRfOne::new().unwrap();
469    /// radio.set_amp_enable(false)?;
470    /// # Ok::<(), hackrfone::Error>(())
471    /// ```
472    pub fn set_amp_enable(&mut self, en: bool) -> Result<(), Error> {
473        self.write_control(Request::AmpEnable, en.into(), 0, &[])
474    }
475
476    /// Set the baseband filter bandwidth.
477    ///
478    /// This is automatically set when the sample rate is changed with
479    /// [`set_sample_rate`].
480    ///
481    /// # Example
482    ///
483    /// Set the filter bandwidth to 70% of the sample rate.
484    ///
485    /// ```no_run
486    /// use hackrfone::{HackRfOne, UnknownMode};
487    ///
488    /// const SAMPLE_HZ: u32 = 20_000_000;
489    /// const SAMPLE_DIV: u32 = 2;
490    /// const FILTER_BW: u32 = (0.7 * (SAMPLE_HZ as f32) / (SAMPLE_DIV as f32)) as u32;
491    ///
492    /// let mut radio: HackRfOne<UnknownMode> = HackRfOne::new().unwrap();
493    /// radio.set_sample_rate(SAMPLE_HZ, SAMPLE_DIV)?;
494    /// radio.set_baseband_filter_bandwidth(FILTER_BW)?;
495    /// # Ok::<(), hackrfone::Error>(())
496    /// ```
497    ///
498    /// [`set_sample_rate`]: crate::HackRfOne::set_sample_rate
499    pub fn set_baseband_filter_bandwidth(&mut self, hz: u32) -> Result<(), Error> {
500        self.write_control(
501            Request::BasebandFilterBandwidthSet,
502            (hz & 0xFFFF) as u16,
503            (hz >> 16) as u16,
504            &[],
505        )
506    }
507
508    /// Set the sample rate.
509    ///
510    /// For anti-aliasing, the baseband filter bandwidth is automatically set to
511    /// the widest available setting that is no more than 75% of the sample rate.
512    /// This happens every time the sample rate is set.
513    /// If you want to override the baseband filter selection, you must do so
514    /// after setting the sample rate.
515    ///
516    /// Limits are 8MHz - 20MHz.
517    /// Preferred rates are 8, 10, 12.5, 16, 20MHz due to less jitter.
518    ///
519    /// # Example
520    ///
521    /// Set the sample rate to 10 MHz.
522    ///
523    /// ```no_run
524    /// use hackrfone::{HackRfOne, UnknownMode};
525    ///
526    /// let mut radio: HackRfOne<UnknownMode> = HackRfOne::new().unwrap();
527    /// radio.set_sample_rate(20_000_000, 2)?;
528    /// # Ok::<(), hackrfone::Error>(())
529    /// ```
530    pub fn set_sample_rate(&mut self, hz: u32, div: u32) -> Result<(), Error> {
531        let hz: u32 = hz.to_le();
532        let div: u32 = div.to_le();
533        let buf: [u8; 8] = [
534            (hz & 0xFF) as u8,
535            ((hz >> 8) & 0xFF) as u8,
536            ((hz >> 16) & 0xFF) as u8,
537            ((hz >> 24) & 0xFF) as u8,
538            (div & 0xFF) as u8,
539            ((div >> 8) & 0xFF) as u8,
540            ((div >> 16) & 0xFF) as u8,
541            ((div >> 24) & 0xFF) as u8,
542        ];
543        self.write_control(Request::SampleRateSet, 0, 0, &buf)?;
544        self.set_baseband_filter_bandwidth((0.75 * (hz as f32) / (div as f32)) as u32)
545    }
546
547    /// Set the LNA (low noise amplifier) gain.
548    ///
549    /// Range 0 to 40dB in 8dB steps.
550    ///
551    /// This is also known as the IF gain.
552    ///
553    /// # Example
554    ///
555    /// Set the LNA gain to 16 dB (generally a reasonable gain to start with).
556    ///
557    /// ```no_run
558    /// use hackrfone::{HackRfOne, UnknownMode};
559    ///
560    /// let mut radio: HackRfOne<UnknownMode> = HackRfOne::new().unwrap();
561    /// radio.set_lna_gain(16)?;
562    /// # Ok::<(), hackrfone::Error>(())
563    /// ```
564    pub fn set_lna_gain(&mut self, gain: u16) -> Result<(), Error> {
565        if gain > 40 {
566            Err(Error::Argument)
567        } else {
568            let buf: [u8; 1] = self.read_control(Request::SetLnaGain, 0, gain & !0x07)?;
569            if buf[0] == 0 {
570                Err(Error::Argument)
571            } else {
572                Ok(())
573            }
574        }
575    }
576
577    /// Set the VGA (variable gain amplifier) gain.
578    ///
579    /// Range 0 to 62dB in 2dB steps.
580    ///
581    /// This is also known as the baseband (BB) gain.
582    ///
583    /// # Example
584    ///
585    /// Set the VGA gain to 16 dB (generally a reasonable gain to start with).
586    ///
587    ///
588    /// ```no_run
589    /// use hackrfone::{HackRfOne, UnknownMode};
590    ///
591    /// let mut radio: HackRfOne<UnknownMode> = HackRfOne::new().unwrap();
592    /// radio.set_vga_gain(16)?;
593    /// # Ok::<(), hackrfone::Error>(())
594    /// ```
595    pub fn set_vga_gain(&mut self, gain: u16) -> Result<(), Error> {
596        if gain > 62 {
597            Err(Error::Argument)
598        } else {
599            let buf: [u8; 1] = self.read_control(Request::SetVgaGain, 0, gain & !0b1)?;
600            if buf[0] == 0 {
601                Err(Error::Argument)
602            } else {
603                Ok(())
604            }
605        }
606    }
607
608    /// Set the transmit VGA gain.
609    ///
610    /// Range 0 to 47dB in 1db steps.
611    pub fn set_txvga_gain(&mut self, gain: u16) -> Result<(), Error> {
612        if gain > 47 {
613            Err(Error::Argument)
614        } else {
615            let buf: [u8; 1] = self.read_control(Request::SetTxvgaGain, 0, gain)?;
616            if buf[0] == 0 {
617                Err(Error::Argument)
618            } else {
619                Ok(())
620            }
621        }
622    }
623
624    /// Antenna power port control.
625    ///
626    /// The source docs are a little lacking in terms of explanations here.
627    pub fn set_antenna_enable(&mut self, value: u8) -> Result<(), Error> {
628        self.write_control(Request::AntennaEnable, value.into(), 0, &[])
629    }
630
631    /// CLKOUT enable.
632    ///
633    /// The source docs are a little lacking in terms of explanations here.
634    pub fn set_clkout_enable(&mut self, en: bool) -> Result<(), Error> {
635        self.check_api_version(Version::from_bcd(0x0103))?;
636        self.write_control(Request::ClkoutEnable, en.into(), 0, &[])
637    }
638
639    /// Reset the HackRF radio.
640    ///
641    /// # Example
642    ///
643    /// ```no_run
644    /// use hackrfone::{HackRfOne, UnknownMode};
645    ///
646    /// let mut radio: HackRfOne<UnknownMode> = HackRfOne::new().unwrap();
647    /// let mut radio: HackRfOne<UnknownMode> = radio.reset()?;
648    /// # Ok::<(), hackrfone::Error>(())
649    /// ```
650    pub fn reset(mut self) -> Result<HackRfOne<UnknownMode>, Error> {
651        self.check_api_version(Version::from_bcd(0x0102))?;
652        self.write_control(Request::Reset, 0, 0, &[])?;
653        Ok(HackRfOne {
654            dh: self.dh,
655            desc: self.desc,
656            interface: self.interface,
657            mode: UnknownMode,
658            timeout: self.timeout,
659        })
660    }
661
662    fn set_transceiver_mode(&mut self, mode: TransceiverMode) -> Result<(), Error> {
663        self.write_control(Request::SetTransceiverMode, mode.into(), 0, &[])
664    }
665
666    /// Change the radio mode to RX.
667    ///
668    /// # Example
669    ///
670    /// ```no_run
671    /// use hackrfone::{HackRfOne, RxMode, UnknownMode};
672    ///
673    /// let mut radio: HackRfOne<UnknownMode> = HackRfOne::new().unwrap();
674    /// let mut radio: HackRfOne<RxMode> = radio.into_rx_mode()?;
675    /// # Ok::<(), hackrfone::Error>(())
676    /// ```
677    pub fn into_rx_mode(mut self) -> Result<HackRfOne<RxMode>, Error> {
678        self.set_transceiver_mode(TransceiverMode::Receive)?;
679        Ok(HackRfOne {
680            dh: self.dh,
681            desc: self.desc,
682            interface: self.interface,
683            mode: RxMode,
684            timeout: self.timeout,
685        })
686    }
687}
688
689impl HackRfOne<RxMode> {
690    /// Receive data from the radio.
691    ///
692    /// This uses a bulk transfer to get one MTU (maximum transmission unit)
693    /// of data in a single shot.  The data format is pairs of signed 8-bit IQ.
694    /// Use the [`iq_to_cplx_i8`] or [`iq_to_cplx_f32`] helpers to convert the
695    /// data to a more manageable format.
696    ///
697    /// Unlike `libhackrf` this does not spawn a sampling thread.
698    ///
699    /// # Example
700    ///
701    /// ```no_run
702    /// use hackrfone::{HackRfOne, RxMode, UnknownMode};
703    ///
704    /// let mut radio: HackRfOne<UnknownMode> = HackRfOne::new().unwrap();
705    /// let mut radio: HackRfOne<RxMode> = radio.into_rx_mode()?;
706    /// let data: Vec<u8> = radio.rx()?;
707    /// radio.stop_rx()?;
708    /// # Ok::<(), hackrfone::Error>(())
709    /// ```
710    ///
711    /// [`iq_to_cplx_i8`]: crate::iq_to_cplx_i8
712    /// [`iq_to_cplx_f32`]: crate::iq_to_cplx_f32
713    #[cfg_attr(not(feature = "num-complex"), allow(rustdoc::broken_intra_doc_links))]
714    pub fn rx(&mut self) -> Result<Vec<u8>, Error> {
715        const ENDPOINT: u8 = 0x81;
716        const MTU: usize = 128 * 1024;
717        let mut buf: Vec<u8> = vec![0; MTU];
718        let mut reader = self.interface.endpoint::<Bulk, In>(ENDPOINT)?.reader(MTU);
719        let n = reader.read(buf.as_mut_slice())?;
720        buf.truncate(n);
721        Ok(buf)
722    }
723
724    /// Stop receiving.
725    ///
726    /// # Example
727    ///
728    /// ```no_run
729    /// use hackrfone::{HackRfOne, RxMode, UnknownMode};
730    ///
731    /// let mut radio: HackRfOne<UnknownMode> = HackRfOne::new().unwrap();
732    /// let mut radio: HackRfOne<RxMode> = radio.into_rx_mode()?;
733    /// let data: Vec<u8> = radio.rx()?;
734    /// radio.stop_rx()?;
735    /// # Ok::<(), hackrfone::Error>(())
736    /// ```
737    pub fn stop_rx(mut self) -> Result<HackRfOne<UnknownMode>, Error> {
738        self.set_transceiver_mode(TransceiverMode::Off)?;
739        Ok(HackRfOne {
740            dh: self.dh,
741            desc: self.desc,
742            interface: self.interface,
743            mode: UnknownMode,
744            timeout: self.timeout,
745        })
746    }
747}
748
749// Helper for set_freq
750fn freq_params(hz: u64) -> [u8; 8] {
751    const MHZ: u64 = 1_000_000;
752
753    let l_freq_mhz: u32 = u32::try_from(hz / MHZ).unwrap_or(u32::MAX).to_le();
754    let l_freq_hz: u32 = u32::try_from(hz - u64::from(l_freq_mhz) * MHZ)
755        .unwrap_or(u32::MAX)
756        .to_le();
757
758    [
759        (l_freq_mhz & 0xFF) as u8,
760        ((l_freq_mhz >> 8) & 0xFF) as u8,
761        ((l_freq_mhz >> 16) & 0xFF) as u8,
762        ((l_freq_mhz >> 24) & 0xFF) as u8,
763        (l_freq_hz & 0xFF) as u8,
764        ((l_freq_hz >> 8) & 0xFF) as u8,
765        ((l_freq_hz >> 16) & 0xFF) as u8,
766        ((l_freq_hz >> 24) & 0xFF) as u8,
767    ]
768}
769
770#[cfg(test)]
771mod freq_params {
772    use super::freq_params;
773
774    #[test]
775    fn nominal() {
776        assert_eq!(freq_params(915_000_000), [0x93, 0x03, 0, 0, 0, 0, 0, 0]);
777        assert_eq!(freq_params(915_000_001), [0x93, 0x03, 0, 0, 1, 0, 0, 0]);
778        assert_eq!(
779            freq_params(123456789),
780            [0x7B, 0, 0, 0, 0x55, 0xF8, 0x06, 0x00]
781        );
782    }
783
784    #[test]
785    fn min() {
786        assert_eq!(freq_params(0), [0; 8]);
787    }
788
789    #[test]
790    fn max() {
791        assert_eq!(freq_params(u64::MAX), [0xFF; 8]);
792    }
793}
794
795/// Convert an IQ sample pair to a complex number.
796///
797/// # Example
798///
799/// Post-processing sample data.
800///
801/// ```no_run
802/// use hackrfone::{HackRfOne, RxMode, UnknownMode, iq_to_cplx_i8};
803///
804/// let mut radio: HackRfOne<UnknownMode> = HackRfOne::new().unwrap();
805/// let mut radio: HackRfOne<RxMode> = radio.into_rx_mode()?;
806/// let data: Vec<u8> = radio.rx()?;
807/// radio.stop_rx()?;
808///
809/// for iq in data.chunks_exact(2) {
810///     let cplx: num_complex::Complex<i8> = iq_to_cplx_i8(iq[0], iq[1]);
811///     // .. do whatever you want with cplx here
812/// }
813///
814/// # Ok::<(), hackrfone::Error>(())
815/// ```
816///
817/// Guide level explanation.
818///
819/// ```
820/// use hackrfone::iq_to_cplx_i8;
821/// use num_complex::Complex;
822///
823/// assert_eq!(iq_to_cplx_i8(255, 1), Complex::new(-1, 1));
824/// ```
825#[cfg(feature = "num-complex")]
826pub fn iq_to_cplx_i8(i: u8, q: u8) -> num_complex::Complex<i8> {
827    num_complex::Complex::new(i as i8, q as i8)
828}
829
830/// Convert an IQ sample pair to a floating point complex number.
831///
832/// Generally you will want to use [`iq_to_cplx_i8`] for storing or transfering
833/// data because the samples are 2-bytes in the native i8, vs 8-bytes in f32.
834///
835/// Floats are easier to work with for running samples through digital signal
836/// processing algorithms (e.g. discrete fourier transform) where the i8 can
837/// easily saturate.
838///
839/// # Example
840///
841/// Post-processing sample data.
842///
843/// ```no_run
844/// use hackrfone::{HackRfOne, RxMode, UnknownMode, iq_to_cplx_f32};
845///
846/// let mut radio: HackRfOne<UnknownMode> = HackRfOne::new().unwrap();
847/// let mut radio: HackRfOne<RxMode> = radio.into_rx_mode()?;
848/// let data: Vec<u8> = radio.rx()?;
849/// radio.stop_rx()?;
850///
851/// for iq in data.chunks_exact(2) {
852///     let cplx: num_complex::Complex<f32> = iq_to_cplx_f32(iq[0], iq[1]);
853///     // .. do whatever you want with cplx here
854/// }
855///
856/// # Ok::<(), hackrfone::Error>(())
857/// ```
858///
859/// Guide level explanation.
860///
861/// ```
862/// use hackrfone::iq_to_cplx_f32;
863/// use num_complex::Complex;
864///
865/// assert_eq!(iq_to_cplx_f32(255, 1), Complex::new(-1.0, 1.0));
866/// ```
867#[cfg(feature = "num-complex")]
868pub fn iq_to_cplx_f32(i: u8, q: u8) -> num_complex::Complex<f32> {
869    num_complex::Complex::new(i as i8 as f32, q as i8 as f32)
870}