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}