waverave_hackrf/lib.rs
1/*!
2
3This is a complete, strongly-asynchronous host crate for the [HackRF][hackrf],
4made using the pure-rust [`nusb`] crate for USB interfacing. It reproduces *all*
5the functionality of the original [`libhackrf`][libhackrf] library.
6
7[hackrf]: https://greatscottgadgets.com/hackrf/one/
8[libhackrf]: https://github.com/greatscottgadgets/hackrf/tree/master/host
9
10The standard entry point for this library is [`open_hackrf()`], which will open
11the first available HackRF device.
12
13Getting started is easy: open up a HackRF peripheral, configure it as needed,
14and enter into transmit, receive, or RX sweep mode. Changing the operating mode
15also changes the struct used, i.e. it uses the typestate pattern. The different
16states and their corresponding structs are:
17
18- [`HackRf`] - The default, off, state.
19- [`Receive`] - Receiving RF signals.
20- [`Transmit`] - Transmitting RF signals.
21- [`Sweep`] - Running a receive sweep through multiple tuning frequencies.
22
23If a mode change error occurs, the [`HackRf`] struct is returned alongside the
24error, and it can potentially be reset back to the off state by running
25[`HackRf::turn_off`].
26
27As for what using this library looks like in practice, here's an example program
28that configures the system, enters receive mode, and processes samples to
29estimate the average received power relative to full scale:
30
31```no_run
32use anyhow::Result;
33#[tokio::main]
34async fn main() -> Result<()> {
35 let hackrf = waverave_hackrf::open_hackrf()?;
36
37 // Configure: 20MHz sample rate, turn on the RF amp, set IF & BB gains to 16 dB,
38 // and tune to 915 MHz.
39 hackrf.set_sample_rate(20e6).await?;
40 hackrf.set_amp_enable(true).await?;
41 hackrf.set_lna_gain(16).await?;
42 hackrf.set_vga_gain(16).await?;
43 hackrf.set_freq(915_000_000).await?;
44
45 // Start receiving, in bursts of 16384 samples
46 let mut hackrf_rx = hackrf.start_rx(16384).await.map_err(|e| e.err)?;
47
48 // Queue up 64 transfers, retrieve them, and measure average power.
49 for _ in 0..64 {
50 hackrf_rx.submit();
51 }
52 let mut count = 0;
53 let mut pow_sum = 0.0;
54 while hackrf_rx.pending() > 0 {
55 let buf = hackrf_rx.next_complete().await?;
56 for x in buf.samples() {
57 let re = x.re as f64;
58 let im = x.im as f64;
59 pow_sum += re * re + im * im;
60 }
61 count += buf.len();
62 }
63
64 // Stop receiving
65 hackrf_rx.stop().await?;
66
67 // Print out our measurement
68 let average_power = (pow_sum / (count as f64 * 127.0 * 127.0)).log10() * 10.;
69 println!("Average Power = {average_power} dbFS");
70 Ok(())
71}
72
73```
74
75*/
76
77#![warn(missing_docs)]
78
79mod consts;
80pub mod debug;
81mod error;
82pub mod info;
83mod rx;
84mod sweep;
85mod tx;
86use std::ops::Range;
87
88use bytemuck::Pod;
89use core::mem::size_of;
90use nusb::transfer::{ControlIn, ControlOut, ControlType, Recipient};
91
92use crate::consts::*;
93use crate::debug::Debug;
94use crate::info::Info;
95
96pub use crate::error::{Error, StateChangeError};
97pub use crate::rx::Receive;
98pub use crate::sweep::{Sweep, SweepBuf, SweepMode, SweepParams};
99pub use crate::tx::Transmit;
100
101/// Complex 8-bit signed data, as used by the HackRF.
102pub type ComplexI8 = num_complex::Complex<i8>;
103
104/// Operacake port A1
105pub const PORT_A1: u8 = 0;
106/// Operacake port A2
107pub const PORT_A2: u8 = 1;
108/// Operacake port A3
109pub const PORT_A3: u8 = 2;
110/// Operacake port A4
111pub const PORT_A4: u8 = 3;
112/// Operacake port B1
113pub const PORT_B1: u8 = 4;
114/// Operacake port B2
115pub const PORT_B2: u8 = 5;
116/// Operacake port B3
117pub const PORT_B3: u8 = 6;
118/// Operacake port B4
119pub const PORT_B4: u8 = 7;
120
121/// A Buffer holding HackRF transfer data.
122///
123/// Samples can be directly accessed as slices, and can be extended up to the
124/// length of the fixed-size underlying buffer.
125///
126/// When dropped, this buffer returns to the internal buffer pool it came from.
127/// It can either be backed by an allocation from the system allocator, or by
128/// some platform-specific way of allocating memory for zero-copy USB transfers.
129pub struct Buffer {
130 buf: Vec<u8>,
131 pool: crossbeam_channel::Sender<Vec<u8>>,
132}
133
134impl Buffer {
135 pub(crate) fn new(buf: Vec<u8>, pool: crossbeam_channel::Sender<Vec<u8>>) -> Self {
136 assert!(buf.len() & 0x1FF == 0);
137 Self { buf, pool }
138 }
139
140 pub(crate) fn into_vec(mut self) -> Vec<u8> {
141 core::mem::take(&mut self.buf)
142 }
143
144 /// Get how many samples this buffer can hold.
145 pub fn capacity(&self) -> usize {
146 // Force down to the nearest 512-byte boundary, which is the transfer
147 // size the HackRF requires.
148 (self.buf.capacity() & !0x1FF) / size_of::<ComplexI8>()
149 }
150
151 /// Clear out the buffer's samples.
152 pub fn clear(&mut self) {
153 self.buf.clear();
154 }
155
156 /// Size of the buffer, in samples.
157 pub fn len(&self) -> usize {
158 self.buf.len() / size_of::<ComplexI8>()
159 }
160
161 /// Returns true if there are no samples in the buffer.
162 pub fn is_empty(&self) -> bool {
163 self.buf.is_empty()
164 }
165
166 /// Remaining capacity in the buffer, in samples.
167 pub fn remaining_capacity(&self) -> usize {
168 self.capacity() - self.len()
169 }
170
171 /// Extend the buffer with some number of samples set to 0, and get a
172 /// mutable slice to the newly initialized samples.
173 ///
174 /// # Panics
175 /// - If there is not enough space left for the added samples.
176 pub fn extend_zeros(&mut self, len: usize) -> &mut [ComplexI8] {
177 assert!(self.remaining_capacity() >= len);
178 let old_len = self.buf.len();
179 let new_len = old_len + len * size_of::<ComplexI8>();
180 self.buf.resize(new_len, 0);
181 let buf: &mut [u8] = &mut self.buf;
182 // SAFETY: We only ever resize according to the size of a ComplexI8,
183 // the buffer always holds ComplexI8 internally, and ComplexI8 has an
184 // alignment of 1.
185 unsafe {
186 core::slice::from_raw_parts_mut(
187 buf.as_mut_ptr().add(old_len) as *mut ComplexI8,
188 len / size_of::<ComplexI8>(),
189 )
190 }
191 }
192
193 /// Extend the buffer with a slice of samples.
194 ///
195 /// # Panics
196 /// - If there is no space left in the buffer for the slice.
197 pub fn extend_from_slice(&mut self, slice: &[ComplexI8]) {
198 assert!(self.remaining_capacity() >= slice.len());
199 // SAFETY: We can always cast a ComplexI8 to bytes, as it meets all the
200 // "plain old data" requirements.
201 let slice = unsafe {
202 core::slice::from_raw_parts(slice.as_ptr() as *const u8, core::mem::size_of_val(slice))
203 };
204 self.buf.extend_from_slice(slice);
205 }
206
207 /// Push a value onto the buffer.
208 ///
209 /// # Panics
210 /// - If there is no space left in the buffer.
211 pub fn push(&mut self, val: ComplexI8) {
212 assert!(self.remaining_capacity() > 0);
213 let slice: &[u8; 2] = unsafe { &*((&val) as *const ComplexI8 as *const [u8; 2]) };
214 self.buf.extend_from_slice(slice);
215 }
216
217 /// Get the sample sequence as a slice of bytes instead of complex values.
218 pub fn bytes(&self) -> &[u8] {
219 &self.buf
220 }
221
222 /// Get the sample sequence as a mutable slice of bytes instead of complex values.
223 pub fn bytes_mut(&mut self) -> &mut [u8] {
224 &mut self.buf
225 }
226
227 /// Get the samples in the buffer.
228 pub fn samples(&self) -> &[ComplexI8] {
229 let buf: &[u8] = &self.buf;
230 // SAFETY: the buffer is aligned because `ComplexI8` has an alignment of
231 // 1, same as a byte buffer, the data is valid, and we truncate to only
232 // valid populated pairs. Also we shouldn't ever have a byte buffer that
233 // isn't an even number of bytes anyway...
234 unsafe {
235 core::slice::from_raw_parts(
236 buf.as_ptr() as *const ComplexI8,
237 self.buf.len() / size_of::<ComplexI8>(),
238 )
239 }
240 }
241
242 /// Mutably get the samples in the buffer.
243 pub fn samples_mut(&mut self) -> &mut [ComplexI8] {
244 let buf: &mut [u8] = &mut self.buf;
245 // SAFETY: the buffer is aligned because `ComplexI8` has an alignment of
246 // 1, same as a byte buffer, the data is valid, and we truncate to only
247 // valid populated pairs. Also we shouldn't ever have a byte buffer that
248 // isn't an even number of bytes anyway...
249 unsafe {
250 core::slice::from_raw_parts_mut(
251 buf.as_mut_ptr() as *mut ComplexI8,
252 self.buf.len() / size_of::<ComplexI8>(),
253 )
254 }
255 }
256}
257
258impl Drop for Buffer {
259 fn drop(&mut self) {
260 let inner = core::mem::take(&mut self.buf);
261 if inner.capacity() > 0 {
262 let _ = self.pool.send(inner);
263 }
264 }
265}
266
267/// Configuration settings for the Bias-T switch.
268///
269/// Used when calling [`HackRf::set_user_bias_t_opts`].
270#[derive(Clone, Debug)]
271pub struct BiasTSetting {
272 /// What mode change to apply when switching to transmit.
273 pub tx: BiasTMode,
274 /// What mode change to apply when switching to receive.
275 pub rx: BiasTMode,
276 /// What mode change to apply when switching off.
277 pub off: BiasTMode,
278}
279
280/// A Bias-T setting change to apply on a mode change.
281///
282/// See [`BiasTSetting`] for where to use this.
283#[allow(missing_docs)]
284#[derive(Clone, Copy, Debug, PartialEq, Eq)]
285pub enum BiasTMode {
286 NoChange,
287 Enable,
288 Disable,
289}
290
291impl BiasTMode {
292 fn as_u16(self) -> u16 {
293 match self {
294 Self::NoChange => 0x0,
295 Self::Disable => 0x2,
296 Self::Enable => 0x3,
297 }
298 }
299}
300
301/// RF Filter Setting Option.
302///
303/// Use when calling [`HackRf::set_freq_explicit`].
304#[repr(u8)]
305#[derive(Clone, Copy, Debug, PartialEq, Eq)]
306pub enum RfPathFilter {
307 /// No filter selected - mixer bypassed.
308 Bypass = 0,
309 /// Low pass filter, `f_c = f_IF - f_LO`
310 LowPass = 1,
311 /// High pass filter, `f_c = f_IF + f_LO`
312 HighPass = 2,
313}
314
315impl std::fmt::Display for RfPathFilter {
316 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
317 match self {
318 Self::Bypass => f.write_str("mixer bypass"),
319 Self::LowPass => f.write_str("low pass filter"),
320 Self::HighPass => f.write_str("high pass filter"),
321 }
322 }
323}
324
325/// Configuration for an Operacake board.
326///
327/// An Operacake board has three different operating modes:
328///
329/// - Manual: the switches are manually set and don't change until the next
330/// configuration operation.
331/// - Frequency: the switches change depending on the center frequency the board
332/// is tuned to.
333/// - Time: the switches change after some number of samples have been
334/// sent/received.
335///
336/// Use when calling [`HackRf::operacake_set_mode`].
337#[derive(Clone, Copy, Debug, PartialEq, Eq)]
338#[repr(u16)]
339#[allow(missing_docs)]
340pub enum OperacakeMode {
341 Manual = 0,
342 Frequency = 1,
343 Time = 2,
344}
345
346/// A Frequency band allocated to a specific port for all Operacakes operating
347/// in frequency mode.
348///
349/// This is used in [`HackRf::operacake_config_freq`].
350///
351/// Ports are zero-indexed, but can also be referred to with the top-level
352/// constants:
353/// - PORT_A1 = 0
354/// - PORT_A2 = 1
355/// - PORT_A3 = 2
356/// - PORT_A4 = 3
357/// - PORT_B1 = 4
358/// - PORT_B2 = 5
359/// - PORT_B3 = 6
360/// - PORT_B4 = 7
361#[derive(Clone, Copy, Debug)]
362pub struct OperacakeFreq {
363 /// Start frequency, in MHz.
364 pub min: u16,
365 /// Stop frequency, in MHz.
366 pub max: u16,
367 /// Port for A0 to use for the range. B0 will use the mirror image.
368 pub port: u8,
369}
370
371/// A dwell time allocated to a specific port for all Operacakes operating in
372/// dwell time mode.
373///
374/// This is used in [`HackRf::operacake_config_time`].
375///
376/// Ports are zero-indexed, but can also be referred to with the top-level
377/// constants:
378/// - PORT_A1 = 0
379/// - PORT_A2 = 1
380/// - PORT_A3 = 2
381/// - PORT_A4 = 3
382/// - PORT_B1 = 4
383/// - PORT_B2 = 5
384/// - PORT_B3 = 6
385/// - PORT_B4 = 7
386#[derive(Clone, Copy, Debug)]
387pub struct OperacakeDwell {
388 /// Dwell time, in number of samples
389 pub dwell: u32,
390 /// Port for A0 to use for the range. B0 will use the mirror image.
391 pub port: u8,
392}
393
394/// A HackRF device descriptor, which can be opened.
395///
396/// These are mostly returned from calling [`list_hackrf_devices`], but can also
397/// be formed by trying to convert a [`nusb::DeviceInfo`] into one.
398pub struct HackRfDescriptor {
399 info: nusb::DeviceInfo,
400}
401
402/// The type of HackRF device that was detected.
403#[allow(missing_docs)]
404#[derive(Clone, Copy, Debug, PartialEq, Eq)]
405pub enum HackRfType {
406 Jawbreaker,
407 One,
408 Rad1o,
409}
410
411impl std::fmt::Display for HackRfType {
412 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
413 match self {
414 Self::Jawbreaker => f.write_str("Jawbreaker"),
415 Self::One => f.write_str("HackRF One"),
416 Self::Rad1o => f.write_str("rad1o"),
417 }
418 }
419}
420
421impl HackRfDescriptor {
422 /// Get the serial number of this HackRF, as a string.
423 pub fn serial(&self) -> Option<&str> {
424 self.info.serial_number()
425 }
426
427 /// Get the [type][HackRfType] of HackRF radio this is.
428 pub fn radio_type(&self) -> HackRfType {
429 match self.info.product_id() {
430 HACKRF_JAWBREAKER_USB_PID => HackRfType::Jawbreaker,
431 HACKRF_ONE_USB_PID => HackRfType::One,
432 RAD1O_USB_PID => HackRfType::Rad1o,
433 _ => panic!("Created a HackRfDescriptor without using a known product ID"),
434 }
435 }
436
437 /// Try and open this HackRf device descriptor.
438 pub fn open(self) -> Result<HackRf, std::io::Error> {
439 let version = self.info.device_version();
440 let ty = self.radio_type();
441 let device = self.info.open()?;
442 #[cfg(not(target_os = "windows"))]
443 {
444 if device.active_configuration()?.configuration_value() != 1 {
445 device.detach_kernel_driver(0)?;
446 device.set_configuration(1)?;
447 }
448 }
449 let interface = device.detach_and_claim_interface(0)?;
450
451 let (buf_pool_send, buf_pool) = crossbeam_channel::unbounded();
452 let tx = TxEndpoint {
453 queue: interface.bulk_out_queue(TX_ENDPOINT_ADDRESS),
454 buf_pool,
455 buf_pool_send,
456 };
457 let (buf_pool_send, buf_pool) = crossbeam_channel::unbounded();
458 let rx = RxEndpoint {
459 queue: interface.bulk_in_queue(RX_ENDPOINT_ADDRESS),
460 buf_pool,
461 buf_pool_send,
462 };
463
464 Ok(HackRf {
465 interface,
466 version,
467 ty,
468 rx,
469 tx,
470 })
471 }
472}
473
474/// Try and turn any [`nusb::DeviceInfo`] descriptor into a HackRF, failing if
475/// the VID and PID don't match any known devices.
476impl TryFrom<nusb::DeviceInfo> for HackRfDescriptor {
477 type Error = &'static str;
478 fn try_from(value: nusb::DeviceInfo) -> Result<Self, Self::Error> {
479 if value.vendor_id() == HACKRF_USB_VID {
480 if matches!(
481 value.product_id(),
482 HACKRF_JAWBREAKER_USB_PID | HACKRF_ONE_USB_PID | RAD1O_USB_PID
483 ) {
484 Ok(HackRfDescriptor { info: value })
485 } else {
486 Err("VID recognized, PID not recognized")
487 }
488 } else {
489 Err("VID doesn't match for HackRF")
490 }
491 }
492}
493
494/// List all available HackRF devices.
495pub fn list_hackrf_devices() -> Result<Vec<HackRfDescriptor>, std::io::Error> {
496 Ok(nusb::list_devices()?
497 .filter(|d| {
498 d.vendor_id() == HACKRF_USB_VID
499 && matches!(
500 d.product_id(),
501 HACKRF_JAWBREAKER_USB_PID | HACKRF_ONE_USB_PID | RAD1O_USB_PID
502 )
503 })
504 .map(|d| HackRfDescriptor { info: d })
505 .collect::<Vec<HackRfDescriptor>>())
506}
507
508/// Open the first detected HackRF device in the system.
509///
510/// This is a shortcut for calling [`list_hackrf_devices`] and opening the first one.
511pub fn open_hackrf() -> Result<HackRf, std::io::Error> {
512 list_hackrf_devices()?
513 .into_iter()
514 .next()
515 .ok_or_else(|| std::io::Error::other("No HackRF devices"))?
516 .open()
517}
518
519/// A HackRF device. This is the main struct for talking to the HackRF.
520///
521/// This provides all the settings to actively configure the HackRF while it is
522/// off, as well as the ability to use debug or info fetching operations with
523/// the [`HackRf::info`] and [`HackRf::debug`] functions. Some of these
524/// operations are also exposed while receiving & transmitting, if it makes
525/// sense to do so.
526pub struct HackRf {
527 pub(crate) interface: nusb::Interface,
528 pub(crate) version: u16,
529 pub(crate) ty: HackRfType,
530 pub(crate) rx: RxEndpoint,
531 pub(crate) tx: TxEndpoint,
532}
533
534struct RxEndpoint {
535 queue: nusb::transfer::Queue<nusb::transfer::RequestBuffer>,
536 buf_pool: crossbeam_channel::Receiver<Vec<u8>>,
537 buf_pool_send: crossbeam_channel::Sender<Vec<u8>>,
538}
539
540struct TxEndpoint {
541 queue: nusb::transfer::Queue<Vec<u8>>,
542 buf_pool: crossbeam_channel::Receiver<Vec<u8>>,
543 buf_pool_send: crossbeam_channel::Sender<Vec<u8>>,
544}
545
546impl HackRf {
547 fn api_check(&self, needed: u16) -> Result<(), Error> {
548 if self.version < needed {
549 Err(Error::ApiVersion {
550 needed,
551 actual: self.version,
552 })
553 } else {
554 Ok(())
555 }
556 }
557
558 async fn write_u32(&self, req: ControlRequest, val: u32) -> Result<(), Error> {
559 Ok(self
560 .interface
561 .control_out(ControlOut {
562 control_type: ControlType::Vendor,
563 recipient: Recipient::Device,
564 request: req as u8,
565 value: (val & 0xffff) as u16,
566 index: (val >> 16) as u16,
567 data: &[],
568 })
569 .await
570 .status?)
571 }
572
573 async fn write_u16(&self, req: ControlRequest, idx: u16, val: u16) -> Result<(), Error> {
574 Ok(self
575 .interface
576 .control_out(ControlOut {
577 control_type: ControlType::Vendor,
578 recipient: Recipient::Device,
579 request: req as u8,
580 value: val,
581 index: idx,
582 data: &[],
583 })
584 .await
585 .status?)
586 }
587
588 async fn read_u16(&self, req: ControlRequest, idx: u16) -> Result<u16, Error> {
589 let ret = self
590 .interface
591 .control_in(ControlIn {
592 control_type: ControlType::Vendor,
593 recipient: Recipient::Device,
594 request: req as u8,
595 value: 0,
596 index: idx,
597 length: 2,
598 })
599 .await
600 .into_result()?;
601 let ret: [u8; 2] = ret.as_slice().try_into().map_err(|_| Error::ReturnData)?;
602 Ok(u16::from_le_bytes(ret))
603 }
604
605 async fn write_u8(&self, req: ControlRequest, idx: u16, val: u8) -> Result<(), Error> {
606 self.write_u16(req, idx, val as u16).await?;
607 Ok(())
608 }
609
610 async fn read_u8(&self, req: ControlRequest, idx: u16) -> Result<u8, Error> {
611 let ret = self
612 .interface
613 .control_in(ControlIn {
614 control_type: ControlType::Vendor,
615 recipient: Recipient::Device,
616 request: req as u8,
617 value: 0,
618 index: idx,
619 length: 1,
620 })
621 .await
622 .into_result()?;
623 ret.first().copied().ok_or(Error::ReturnData)
624 }
625
626 async fn write_bytes(&self, req: ControlRequest, data: &[u8]) -> Result<(), Error> {
627 self.interface
628 .control_out(ControlOut {
629 control_type: ControlType::Vendor,
630 recipient: Recipient::Device,
631 request: req as u8,
632 value: 0,
633 index: 0,
634 data,
635 })
636 .await
637 .into_result()?;
638 Ok(())
639 }
640
641 async fn read_bytes(&self, req: ControlRequest, len: usize) -> Result<Vec<u8>, Error> {
642 assert!(len < u16::MAX as usize);
643 Ok(self
644 .interface
645 .control_in(ControlIn {
646 control_type: ControlType::Vendor,
647 recipient: Recipient::Device,
648 request: req as u8,
649 value: 0,
650 index: 0,
651 length: len as u16,
652 })
653 .await
654 .into_result()?)
655 }
656
657 async fn read_struct<T>(&self, req: ControlRequest) -> Result<T, Error>
658 where
659 T: Pod,
660 {
661 let size = size_of::<T>();
662 let mut resp = self.read_bytes(req, size).await?;
663 if resp.len() < size {
664 return Err(Error::ReturnData);
665 }
666 resp.truncate(size);
667 Ok(bytemuck::pod_read_unaligned(&resp))
668 }
669
670 async fn set_transceiver_mode(&self, mode: TransceiverMode) -> Result<(), Error> {
671 self.write_u16(ControlRequest::SetTransceiverMode, 0, mode as u16)
672 .await
673 }
674
675 /// Set the baseband filter bandwidth.
676 ///
677 /// The possible settings are: 1.75, 2.5, 3.5, 5, 5.5, 6, 7, 8, 9, 10, 12,
678 /// 14, 15, 20, 24, and 28 MHz. This function will choose the nearest,
679 /// rounded down.
680 ///
681 /// The default is to set this to 3/4 of the sample rate, rounded down to
682 /// the nearest setting.
683 ///
684 /// Setting the sample rate with [`set_sample_rate`][Self::set_sample_rate]
685 /// will modify this setting.
686 pub async fn set_baseband_filter_bandwidth(&self, bandwidth_hz: u32) -> Result<(), Error> {
687 let bandwidth_hz = baseband_filter_bw(bandwidth_hz);
688 self.write_u32(ControlRequest::BasebandFilterBandwidthSet, bandwidth_hz)
689 .await
690 }
691
692 /// Set the transmit underrun limit. This will cause the HackRF to stop
693 /// operation if transmit runs out of samples to send. Set to 0 to disable.
694 ///
695 /// This will also cause all outstanding transmits to stall forever, so some
696 /// timeout will need to be added to the transmit completion futures.
697 pub async fn set_tx_underrun_limit(&self, val: u32) -> Result<(), Error> {
698 self.api_check(0x0106)?;
699 self.write_u32(ControlRequest::SetTxUnderrunLimit, val)
700 .await
701 }
702
703 /// Set the receive overrun limit. This will cause the HackRF to stop
704 /// operation if more than the specified amount of samples get lost. Set to
705 /// 0 to disable.
706 ///
707 /// This will also cause all outstanding receives to stall forever, so some
708 /// timeout will need to be added to the receive completion futures.
709 pub async fn set_rx_overrun_limit(&self, val: u32) -> Result<(), Error> {
710 self.api_check(0x0106)?;
711 self.write_u32(ControlRequest::SetRxOverrunLimit, val).await
712 }
713
714 /// Access the debug/programming commands for the HackRF.
715 pub fn debug(&mut self) -> Debug<'_> {
716 Debug::new(self)
717 }
718
719 /// Access the info commands for the HackRF.
720 pub fn info(&self) -> Info<'_> {
721 Info::new(self)
722 }
723
724 /// Set the operating frequency (recommended method).
725 ///
726 /// This uses the internal frequency tuning code onboard the HackRF, which
727 /// can differ between boards. It automatically sets the LO and IF
728 /// frequencies, as well as the RF path filter.
729 pub async fn set_freq(&self, freq_hz: u64) -> Result<(), Error> {
730 const ONE_MHZ: u64 = 1_000_000;
731 #[repr(C)]
732 #[derive(Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)]
733 struct FreqParams {
734 mhz: u32,
735 hz: u32,
736 }
737 let mhz = freq_hz / ONE_MHZ;
738 let hz = freq_hz % ONE_MHZ;
739 let params = FreqParams {
740 mhz: (mhz as u32).to_le(),
741 hz: (hz as u32).to_le(),
742 };
743
744 self.write_bytes(ControlRequest::SetFreq, bytemuck::bytes_of(¶ms))
745 .await
746 }
747
748 /// Set the IF & LO tuning frequencies, and the RF path filter.
749 ///
750 /// You may be looking for [`set_freq`][HackRf::set_freq] instead.
751 ///
752 /// This sets the center frequency to `f_c = f_IF + k * f_LO`, where k is
753 /// -1, 0, or 1 depending on the filter selected.
754 ///
755 /// IF frequency *must* be between 2-3 GHz, and it's strongly recommended to
756 /// be between 2170-2740 MHz.
757 ///
758 /// LO frequency must be between 84.375-5400 MHz. No effect if the filter is
759 /// set to bypass mode.
760 pub async fn set_freq_explicit(
761 &self,
762 if_freq_hz: u64,
763 lo_freq_hz: u64,
764 path: RfPathFilter,
765 ) -> Result<(), Error> {
766 #[repr(C)]
767 #[derive(Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)]
768 struct FreqParams {
769 if_freq_hz: u64,
770 lo_freq_hz: u64,
771 path: u8,
772 reserved: [u8; 7],
773 }
774
775 const IF_RANGE: Range<u64> = Range {
776 start: 2_000_000_000,
777 end: 3_000_000_001,
778 };
779 const LO_RANGE: Range<u64> = Range {
780 start: 84_375_000,
781 end: 5_400_000_001,
782 };
783
784 if !IF_RANGE.contains(&if_freq_hz) {
785 return Err(Error::TuningRange {
786 range: IF_RANGE,
787 val: if_freq_hz,
788 });
789 }
790 if path != RfPathFilter::Bypass && !LO_RANGE.contains(&lo_freq_hz) {
791 return Err(Error::TuningRange {
792 range: LO_RANGE,
793 val: lo_freq_hz,
794 });
795 }
796
797 let params = FreqParams {
798 if_freq_hz: if_freq_hz.to_le(),
799 lo_freq_hz: lo_freq_hz.to_le(),
800 path: path as u8,
801 reserved: [0u8; 7],
802 };
803
804 self.write_bytes(ControlRequest::SetFreqExplicit, bytemuck::bytes_of(¶ms))
805 .await
806 }
807
808 /// Set the sample rate using a clock frequency in Hz and a divider value.
809 ///
810 /// The resulting sample rate is `freq_hz/divider`. Divider value can be
811 /// 1-31, and the rate range should be 2-20MHz. Lower & higher values are
812 /// technically possible, but not recommended.
813 ///
814 /// This function will always call
815 /// [`set_baseband_filter_bandwidth`][Self::set_baseband_filter_bandwidth],
816 /// so any changes to the filter should be done *after* this function.
817 ///
818 /// You may want to just use [`set_sample_rate`][Self::set_sample_rate]
819 /// instead.
820 ///
821 pub async fn set_sample_rate_manual(&self, freq_hz: u32, divider: u32) -> Result<(), Error> {
822 #[repr(C)]
823 #[derive(Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)]
824 struct FracRateParams {
825 freq_hz: u32,
826 divider: u32,
827 }
828
829 const DIV_RANGE: Range<u32> = Range { start: 1, end: 32 };
830 if !DIV_RANGE.contains(÷r) {
831 return Err(Error::ValueRange {
832 range: DIV_RANGE,
833 val: divider,
834 });
835 }
836
837 let params = FracRateParams {
838 freq_hz: freq_hz.to_le(),
839 divider: divider.to_le(),
840 };
841
842 self.write_bytes(ControlRequest::SampleRateSet, bytemuck::bytes_of(¶ms))
843 .await?;
844
845 let filter_bw = baseband_filter_bw(freq_hz * 3 / (divider * 4));
846 self.set_baseband_filter_bandwidth(filter_bw).await?;
847 Ok(())
848 }
849
850 /// Set the sample rate, which should be between 2-20 MHz.
851 ///
852 /// Lower & higher rates are possible, but not recommended.
853 ///
854 /// This function will always call
855 /// [`set_baseband_filter_bandwidth`][Self::set_baseband_filter_bandwidth],
856 /// so any changes to the filter should be done *after* this function.
857 ///
858 /// This function is a convenience wrapper around
859 /// [`set_sample_rate_manual`][Self::set_sample_rate_manual].
860 ///
861 pub async fn set_sample_rate(&self, freq: f64) -> Result<(), Error> {
862 let freq = freq.clamp(2e6, 20e6);
863
864 let mut freq_hz = 0;
865 let mut divider = 1;
866 let mut diff = f64::MAX;
867
868 // Just blindly check the closest of all possible divider values,
869 // preferring the smaller divider value on ties
870 for i in 1u32..32 {
871 let new_freq_hz = (freq * (i as f64)).round() as u32;
872 let new_diff = ((freq_hz as f64) / (i as f64) - freq).abs();
873 if new_diff < diff {
874 freq_hz = new_freq_hz;
875 divider = i;
876 diff = new_diff;
877 }
878 }
879
880 self.set_sample_rate_manual(freq_hz, divider).await
881 }
882
883 /// Enable/disable the 14dB RF amplifiers.
884 ///
885 /// Enable/disable the RX/TX amplifiers U13/U25 via the controlling switches
886 /// U9 and U14.
887 pub async fn set_amp_enable(&self, enable: bool) -> Result<(), Error> {
888 self.write_u16(ControlRequest::AmpEnable, 0, enable as u16)
889 .await
890 }
891
892 /// Set the LNA gain.
893 ///
894 /// Sets the RF RX gain of the MAX2837 transceiver IC. Must be in the range
895 /// of 0-40 dB, and is forced to 8 dB steps. Intermediate values are rounded
896 /// down.
897 pub async fn set_lna_gain(&self, value: u16) -> Result<(), Error> {
898 if value > 40 {
899 return Err(Error::ValueRange {
900 range: Range { start: 0, end: 41 },
901 val: value as u32,
902 });
903 }
904
905 let ret = self
906 .read_u8(ControlRequest::SetLnaGain, value & (!0x07))
907 .await?;
908 if ret == 0 {
909 return Err(Error::ReturnData);
910 }
911 Ok(())
912 }
913
914 /// Set the VGA gain.
915 ///
916 /// Sets the baseband RX gain of the MAX2837 transceiver IC. Must be in the range
917 /// of 0-62 dB, and is forced to 2 dB steps. Intermediate values are rounded
918 /// down.
919 pub async fn set_vga_gain(&self, value: u16) -> Result<(), Error> {
920 if value > 62 {
921 return Err(Error::ValueRange {
922 range: Range { start: 0, end: 63 },
923 val: value as u32,
924 });
925 }
926
927 let ret = self
928 .read_u8(ControlRequest::SetVgaGain, value & (!0x01))
929 .await?;
930 if ret == 0 {
931 return Err(Error::ReturnData);
932 }
933 Ok(())
934 }
935
936 /// Set the RF TX gain.
937 ///
938 /// Sets the RF TX gain of the MAX2837 transceiver IC. Must be in the range
939 /// of 0-47 dB.
940 pub async fn set_txvga_gain(&self, value: u16) -> Result<(), Error> {
941 if value > 47 {
942 return Err(Error::ValueRange {
943 range: Range { start: 0, end: 48 },
944 val: value as u32,
945 });
946 }
947
948 let ret = self.read_u8(ControlRequest::SetTxvgaGain, value).await?;
949 if ret == 0 {
950 return Err(Error::ReturnData);
951 }
952 Ok(())
953 }
954
955 /// Temporarily enable/disable the bias-tee (antenna port power).
956 ///
957 /// Enable or disable the **3.3v (max 50 mA)** bias-tee. Defaults to
958 /// disabled on power-up.
959 ///
960 /// The firmware auto-disables this after returning to IDLE mode. Consider
961 /// using [`set_user_bias_t_opts`][Self::set_user_bias_t_opts] instead to
962 /// configure the bias to work exactly the way you want it to.
963 pub async fn set_antenna_enable(&self, enable: bool) -> Result<(), Error> {
964 self.write_u16(ControlRequest::AntennaEnable, 0, enable as u16)
965 .await
966 }
967
968 /// Set hardware sync mode (hardware triggering).
969 ///
970 /// See the documentation
971 /// [here](https://hackrf.readthedocs.io/en/latest/hardware_triggering.html).
972 ///
973 /// When enabled, the next operating mode (RX, TX, or Sweep) will not start
974 /// until the input hardware trigger occurs.
975 ///
976 /// Requires API version 0x0102 or higher.
977 pub async fn set_hw_sync_mode(&self, enable: bool) -> Result<(), Error> {
978 self.api_check(0x0102)?;
979 self.write_u16(ControlRequest::SetHwSyncMode, 0, enable as u16)
980 .await
981 }
982
983 /// Get a list of what operacake boards are attached (up to 8).
984 ///
985 /// Requires API version 0x0105 or higher.
986 pub async fn operacake_boards(&self) -> Result<Vec<u8>, Error> {
987 self.api_check(0x0105)?;
988 let mut resp = self
989 .read_bytes(ControlRequest::OperacakeGetBoards, 8)
990 .await?;
991 resp.retain(|&x| x != 0xFF);
992 Ok(resp)
993 }
994
995 /// Set an Operacake board to a specific operating mode.
996 ///
997 /// When set to frequency or dwell time mode, the settings are shared
998 /// between all operacakes in that operating mode.
999 ///
1000 /// Requires API version 0x0105 or higher.
1001 pub async fn operacake_set_mode(
1002 &self,
1003 address: u8,
1004 setting: OperacakeMode,
1005 ) -> Result<(), Error> {
1006 self.api_check(0x0105)?;
1007 if address > 7 {
1008 return Err(Error::InvalidParameter("Operacake address is out of range"));
1009 }
1010 self.write_u8(ControlRequest::OperacakeSetMode, setting as u16, address)
1011 .await
1012 }
1013
1014 /// Get the operating mode of an operacake board.
1015 ///
1016 /// Requires API version 0x0105 or higher.
1017 pub async fn operacake_get_mode(&self, address: u8) -> Result<OperacakeMode, Error> {
1018 self.api_check(0x0105)?;
1019 if address > 7 {
1020 return Err(Error::InvalidParameter("Operacake address is out of range"));
1021 }
1022 let ret = self
1023 .interface
1024 .control_in(ControlIn {
1025 control_type: ControlType::Vendor,
1026 recipient: Recipient::Device,
1027 request: ControlRequest::OperacakeGetMode as u8,
1028 value: address as u16,
1029 index: 0,
1030 length: 1,
1031 })
1032 .await
1033 .into_result()?;
1034 let ret = ret.first().ok_or(Error::ReturnData)?;
1035 match ret {
1036 0 => Ok(OperacakeMode::Manual),
1037 1 => Ok(OperacakeMode::Frequency),
1038 2 => Ok(OperacakeMode::Time),
1039 _ => Err(Error::ReturnData),
1040 }
1041 }
1042
1043 /// Set an operacake's switches manually.
1044 ///
1045 /// Should be called after setting manual mode with
1046 /// [`operacake_set_mode`][Self::operacake_set_mode].
1047 ///
1048 /// Requires API version 0x0102 or higher.
1049 pub async fn operacake_config_manual(&self, address: u8, a: u8, b: u8) -> Result<(), Error> {
1050 self.api_check(0x0102)?;
1051 if address > 7 {
1052 return Err(Error::InvalidParameter("Operacake address is out of range"));
1053 }
1054
1055 if a > 7 || b > 7 {
1056 return Err(Error::InvalidParameter(
1057 "One or more port numbers is out of range (0-7)",
1058 ));
1059 }
1060 if (a < 4 && b < 4) || (a >= 4 && b >= 4) {
1061 return Err(Error::InvalidParameter(
1062 "A0 & B0 ports are using same quad of multiplexed ports",
1063 ));
1064 }
1065
1066 let a = a as u16;
1067 let b = b as u16;
1068 self.write_u8(ControlRequest::OperacakeSetPorts, a | (b << 8), address)
1069 .await
1070 }
1071
1072 /// Match frequency bands to operacake ports.
1073 ///
1074 /// These frequency settings are used by any operacake operating in
1075 /// frequency mode.
1076 ///
1077 /// Requires API version 0x0103 or higher.
1078 pub async fn operacake_config_freq(&self, freqs: &[OperacakeFreq]) -> Result<(), Error> {
1079 self.api_check(0x0103)?;
1080 if freqs.len() > 8 {
1081 return Err(Error::InvalidParameter(
1082 "Operacake can only support 8 frequency bands max",
1083 ));
1084 }
1085 let mut data = Vec::with_capacity(5 * freqs.len());
1086 for f in freqs {
1087 if f.port > 7 {
1088 return Err(Error::InvalidParameter(
1089 "Operacake frequency band port selection is out of range",
1090 ));
1091 }
1092 data.push((f.min >> 8) as u8);
1093 data.push((f.min & 0xFF) as u8);
1094 data.push((f.max >> 8) as u8);
1095 data.push((f.max & 0xFF) as u8);
1096 data.push(f.port);
1097 }
1098
1099 self.write_bytes(ControlRequest::OperacakeSetRanges, &data)
1100 .await
1101 }
1102
1103 /// Match dwell times to operacake ports.
1104 ///
1105 /// These dwell time settings are used by any operacake operating in
1106 /// time mode.
1107 ///
1108 /// Requires API version 0x0105 or higher.
1109 pub async fn operacake_config_time(&self, times: &[OperacakeDwell]) -> Result<(), Error> {
1110 self.api_check(0x0105)?;
1111 if times.len() > 16 {
1112 return Err(Error::InvalidParameter(
1113 "Operacake can only support 16 time slices max",
1114 ));
1115 }
1116 let mut data = Vec::with_capacity(5 * times.len());
1117 for t in times {
1118 if t.port > 7 {
1119 return Err(Error::InvalidParameter(
1120 "Operacake time slice port selection is out of range",
1121 ));
1122 }
1123 data.extend_from_slice(&t.dwell.to_le_bytes());
1124 data.push(t.port);
1125 }
1126 self.write_bytes(ControlRequest::OperacakeSetDwellTimes, &data)
1127 .await
1128 }
1129
1130 /// Reset the HackRF.
1131 ///
1132 /// Requires API version 0x0102 or higher.
1133 pub async fn reset(&self) -> Result<(), Error> {
1134 self.api_check(0x0102)?;
1135 self.write_u16(ControlRequest::Reset, 0, 0).await
1136 }
1137
1138 /// Turn on the CLKOUT port.
1139 ///
1140 /// Requires API version 0x0103 or higher.
1141 pub async fn clkout_enable(&self, enable: bool) -> Result<(), Error> {
1142 self.api_check(0x0103)?;
1143 self.write_u16(ControlRequest::ClkoutEnable, 0, enable as u16)
1144 .await
1145 }
1146
1147 /// Check the CLKIN port status.
1148 ///
1149 /// Set to true if the CLKIN port is used as the reference clock.
1150 ///
1151 /// Requires API version 0x0106 or higher.
1152 pub async fn clkin_status(&self) -> Result<bool, Error> {
1153 self.api_check(0x0106)?;
1154 Ok(self.read_u8(ControlRequest::GetClkinStatus, 0).await? != 0)
1155 }
1156
1157 /// Perform a GPIO test of an Operacake board.
1158 ///
1159 /// Value 0xFFFF means "GPIO mode disabled" - remove additional add-on
1160 /// boards and retry.
1161 ///
1162 /// Value 0 means all tests passed.
1163 ///
1164 /// In any other values, a 1 bit signals an error. Bits are grouped in
1165 /// groups of 3. Encoding:
1166 ///
1167 /// ```text
1168 /// 0 - u1ctrl - u3ctrl0 - u3ctrl1 - u2ctrl0 - u2ctrl1
1169 /// ```
1170 ///
1171 /// Requires API version 0x0103 or higher.
1172 pub async fn operacake_gpio_test(&self, address: u8) -> Result<u16, Error> {
1173 self.api_check(0x0103)?;
1174 if address > 7 {
1175 return Err(Error::InvalidParameter("Operacake address is out of range"));
1176 }
1177 let ret = self
1178 .interface
1179 .control_in(ControlIn {
1180 control_type: ControlType::Vendor,
1181 recipient: Recipient::Device,
1182 request: ControlRequest::OperacakeGpioTest as u8,
1183 value: address as u16,
1184 index: 0,
1185 length: 2,
1186 })
1187 .await
1188 .into_result()?;
1189 let ret: [u8; 2] = ret.as_slice().try_into().map_err(|_| Error::ReturnData)?;
1190 Ok(u16::from_le_bytes(ret))
1191 }
1192
1193 /// Enable/disable the UI display on devices with one (Rad1o, PortaPack).
1194 ///
1195 /// Requires API version 0x0104 or higher.
1196 pub async fn set_ui_enable(&self, val: u8) -> Result<(), Error> {
1197 self.api_check(0x0104)?;
1198 self.write_u8(ControlRequest::UiEnable, 0, val).await
1199 }
1200
1201 /// Turn the LEDs on or off, overriding the default.
1202 ///
1203 /// There are normally 3 controllable LEDs: USB, RX, and TX. The Rad1o board
1204 /// has 4. After setting them individually, they may get overridden later
1205 /// by other functions.
1206 ///
1207 /// | Bit | LED |
1208 /// | -- | -- |
1209 /// | 0 | USB |
1210 /// | 1 | RX |
1211 /// | 2 | TX |
1212 /// | 3 | User |
1213 ///
1214 /// Requires API version 0x0107 or higher.
1215 pub async fn set_leds(&self, state: u8) -> Result<(), Error> {
1216 self.api_check(0x0107)?;
1217 self.write_u8(ControlRequest::SetLeds, 0, state).await
1218 }
1219
1220 /// Set the Bias-Tee behavior.
1221 ///
1222 /// This function will configure what change, if any, to apply to the
1223 /// bias-tee circuit on a mode change. The default is for it to always be
1224 /// off, but with a custom config, it can turn on when switching to RX, TX,
1225 /// or even to always be on. The settings in `opts` are always applied when
1226 /// first changing to that mode, with [`BiasTMode::NoChange`] not changing
1227 /// from whatever it is set to before the transition.
1228 ///
1229 /// Requires API version 0x0108 or higher.
1230 pub async fn set_user_bias_t_opts(&self, opts: BiasTSetting) -> Result<(), Error> {
1231 self.api_check(0x0108)?;
1232 let state: u16 =
1233 0x124 | opts.off.as_u16() | (opts.rx.as_u16() << 3) | (opts.tx.as_u16() << 6);
1234 self.write_u16(ControlRequest::SetUserBiasTOpts, 0, state)
1235 .await
1236 }
1237
1238 /// Switch a HackRF into receive mode, getting `transfer_size` samples at a
1239 /// time. The transfer size is always rounded up to the nearest 256-sample
1240 /// block increment; it's recommended to be 8192 samples but can be smaller
1241 /// or larger as needed. If the same size is used repeatedly with
1242 /// `start_rx`, buffers won't need to be reallocated.
1243 pub async fn start_rx(self, transfer_size: usize) -> Result<Receive, StateChangeError> {
1244 Receive::new(self, transfer_size).await
1245 }
1246
1247 /// Start a RX sweep, which will also set the sample rate and baseband filter.
1248 ///
1249 /// Buffers are reused across sweep operations, provided that
1250 /// [`HackRf::start_rx`] isn't used, or is used with a 8192 sample buffer
1251 /// size.
1252 ///
1253 /// See [`Sweep`] for more details on the RX sweep mode.
1254 pub async fn start_rx_sweep(self, params: &SweepParams) -> Result<Sweep, StateChangeError> {
1255 Sweep::new(self, params).await
1256 }
1257
1258 /// Start a RX sweep, but don't set up the sample rate and baseband filter before starting.
1259 ///
1260 /// Buffers are reused across sweep operations, provided that
1261 /// [`HackRf::start_rx`] isn't used, or is used with a 8192 sample buffer
1262 /// size.
1263 ///
1264 /// See [`Sweep`] for more details on the RX sweep mode.
1265 pub async fn start_rx_sweep_custom_sample_rate(
1266 self,
1267 params: &SweepParams,
1268 ) -> Result<Sweep, StateChangeError> {
1269 Sweep::new_with_custom_sample_rate(self, params).await
1270 }
1271
1272 /// Switch a HackRF into transmit mode, with a set maximum number of samples
1273 /// per buffer block.
1274 ///
1275 /// Buffers are reused across transmit operations, provided that the
1276 /// `max_transfer_size` is always the same.
1277 pub async fn start_tx(self, max_transfer_size: usize) -> Result<Transmit, StateChangeError> {
1278 Transmit::new(self, max_transfer_size).await
1279 }
1280
1281 /// Try and turn the HackRF to the off state, regardless of what mode it is currently in.
1282 pub async fn turn_off(&self) -> Result<(), Error> {
1283 self.set_transceiver_mode(TransceiverMode::Off).await
1284 }
1285}
1286
1287fn baseband_filter_bw(freq: u32) -> u32 {
1288 const MAX2837_FT: &[u32] = &[
1289 1750000, 2500000, 3500000, 5000000, 5500000, 6000000, 7000000, 8000000, 9000000, 10000000,
1290 12000000, 14000000, 15000000, 20000000, 24000000, 28000000,
1291 ];
1292
1293 MAX2837_FT
1294 .iter()
1295 .rev()
1296 .find(|f| freq >= **f)
1297 .copied()
1298 .unwrap_or(MAX2837_FT[0])
1299}
1300
1301#[cfg(test)]
1302mod tests {
1303 use crate::baseband_filter_bw;
1304
1305 #[test]
1306 fn baseband_filter() {
1307 assert_eq!(baseband_filter_bw(1000), 1750000);
1308 assert_eq!(baseband_filter_bw(30_000_000), 28_000_000);
1309 assert_eq!(baseband_filter_bw(3_000_000), 2_500_000);
1310 }
1311}