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