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