waverave_hackrf/
sweep.rs

1use std::sync::mpsc;
2
3use nusb::transfer::{ControlOut, ControlType, Recipient, RequestBuffer};
4
5use crate::{
6    Buffer, Error, HackRf,
7    consts::{ControlRequest, TransceiverMode},
8    error::StateChangeError,
9};
10
11/// Configuration settings for a receive sweep across multiple frequencies.
12pub struct SweepParams {
13    /// Sample rate to operate at.
14    pub sample_rate_hz: u32,
15    /// List of frequency pairs to sweep over, in MHz. There can be up to 10.
16    pub freq_mhz: Vec<(u16, u16)>,
17    /// Number of blocks to capture per tuning. Each block is 16384 bytes, or
18    /// 8192 samples.
19    pub blocks_per_tuning: u16,
20    /// Width of each tuning step, in Hz. `sample_rate` is a good value, in
21    /// general.
22    pub step_width_hz: u32,
23    /// Frequency offset added to tuned frequencies. `Sample_rate*3/8` is a good
24    /// value for Interleaved sweep mode.
25    pub offset_hz: u32,
26    /// Sweep mode.
27    pub mode: SweepMode,
28}
29
30impl SweepParams {
31    pub fn initialize_from_sample_rate(sample_rate_hz: u32) -> Self {
32        Self {
33            sample_rate_hz,
34            freq_mhz: Vec::new(),
35            blocks_per_tuning: 1,
36            step_width_hz: sample_rate_hz,
37            offset_hz: sample_rate_hz * 3 / 8,
38            mode: SweepMode::Interleaved,
39        }
40    }
41}
42
43#[derive(Clone, Copy, Debug, PartialEq, Eq)]
44pub enum SweepMode {
45    /// `step_width` is added to the current frequency at each step.
46    Linear,
47    /// Each step is divided into two interleaved sub-steps, allowing the host
48    /// to select the best portions of the FFT of each sub-step and discard the
49    /// rest.
50    Interleaved,
51}
52
53const SWEEP_BUF_SIZE: usize = 16384;
54const SWEEP_SAMPLE_SIZE: usize = (SWEEP_BUF_SIZE - 10) / 2;
55
56pub struct SweepBuf {
57    freq_hz: u64,
58    buf: Buffer,
59}
60
61impl SweepBuf {
62    pub fn freq_hz(&self) -> u64 {
63        self.freq_hz
64    }
65
66    pub fn samples(&self) -> &[num_complex::Complex<i8>] {
67        // We checked this range would be valid when we made SweepBuf.
68        unsafe { self.buf.samples().get_unchecked(5..) }
69    }
70
71    pub fn samples_mut(&mut self) -> &mut [num_complex::Complex<i8>] {
72        // We checked this range would be valid when we made SweepBuf.
73        unsafe { self.buf.samples_mut().get_unchecked_mut(5..) }
74    }
75
76    fn parse(buf: Buffer) -> Result<Self, Error> {
77        let bytes = buf.bytes();
78        if bytes.len() != SWEEP_BUF_SIZE {
79            return Err(Error::ReturnData);
80        }
81        // SAFETY: We literally just checked the buffer is 16384 bytes. The
82        // first 10 are there for sure.
83        let header: &[u8; 2] = unsafe { &*(bytes.as_ptr() as *const [u8; 2]) };
84        let freq: [u8; 8] = unsafe { *(bytes.as_ptr().add(2) as *const [u8; 8]) };
85        if header != &[0x7f, 0x7f] {
86            return Err(Error::ReturnData);
87        }
88        let freq_hz = u64::from_le_bytes(freq);
89        if !(100_000..=7_100_000_000).contains(&freq_hz) {
90            return Err(Error::ReturnData);
91        }
92        Ok(Self { freq_hz, buf })
93    }
94}
95
96/// A HackRF operating in sweep mode.
97pub struct Sweep {
98    rf: HackRf,
99    queue: nusb::transfer::Queue<nusb::transfer::RequestBuffer>,
100    buf_pool: mpsc::Receiver<Vec<u8>>,
101    buf_pool_send: mpsc::Sender<Vec<u8>>,
102}
103
104impl Sweep {
105    /// Start a new RX sweep using the provided parameters.
106    ///
107    /// The size of each internal USB block transfer can be set,
108    pub async fn new(rf: HackRf, params: &SweepParams) -> Result<Self, StateChangeError> {
109        if let Err(err) = rf.set_sample_rate(params.sample_rate_hz as f64).await {
110            return Err(StateChangeError { err, rf });
111        }
112
113        Self::new_with_custom_sample_rate(rf, params).await
114    }
115
116    /// Start a new RX sweep without configuring the sample rate or baseband filter.
117    pub async fn new_with_custom_sample_rate(
118        rf: HackRf,
119        params: &SweepParams,
120    ) -> Result<Self, StateChangeError> {
121        const MAX_SWEEP_RANGES: usize = 10;
122        const TUNING_BLOCK_BYTES: usize = 16384;
123        if params.freq_mhz.is_empty()
124            || params.freq_mhz.len() > MAX_SWEEP_RANGES
125            || params.blocks_per_tuning < 1
126            || params.step_width_hz < 1
127        {
128            return Err(StateChangeError {
129                rf,
130                err: Error::InvalidParameter("Invalid sweep parameters"),
131            });
132        }
133
134        // Build up the packed struct that we'll send to the HackRF
135        let mut data = Vec::with_capacity(params.freq_mhz.len() * 4 + 9);
136        data.extend_from_slice(&params.step_width_hz.to_le_bytes());
137        data.extend_from_slice(&params.offset_hz.to_be_bytes());
138        data.push(match params.mode {
139            SweepMode::Linear => 0,
140            SweepMode::Interleaved => 0,
141        });
142        for (lo, hi) in params.freq_mhz.iter().copied() {
143            data.extend_from_slice(&lo.to_le_bytes());
144            data.extend_from_slice(&hi.to_le_bytes());
145        }
146
147        let num_bytes = (params.blocks_per_tuning as u32) * (TUNING_BLOCK_BYTES as u32);
148
149        // Set up the HackRF
150        if let Err(e) = rf
151            .interface
152            .control_out(ControlOut {
153                control_type: ControlType::Vendor,
154                recipient: Recipient::Device,
155                request: ControlRequest::InitSweep as u8,
156                value: (num_bytes & 0xffff) as u16,
157                index: (num_bytes >> 16) as u16,
158                data: &data,
159            })
160            .await
161            .into_result()
162        {
163            return Err(StateChangeError { rf, err: e.into() });
164        }
165
166        // Round up to nearest 512-byte increment
167        if let Err(err) = rf.set_transceiver_mode(TransceiverMode::RxSweep).await {
168            return Err(StateChangeError { rf, err });
169        }
170        let queue = rf.interface.bulk_in_queue(0x81);
171        let (buf_pool_send, buf_pool) = mpsc::channel();
172        Ok(Self {
173            rf,
174            queue,
175            buf_pool,
176            buf_pool_send,
177        })
178    }
179
180    /// Queue up a sweep transfer.
181    ///
182    /// This will pull from a reusable buffer pool first, and allocate a new
183    /// buffer if none are available in the pool.
184    ///
185    /// The buffer pool will grow so long as completed buffers aren't dropped.
186    pub fn submit(&mut self) {
187        let req = if let Ok(buf) = self.buf_pool.try_recv() {
188            RequestBuffer::reuse(buf, SWEEP_BUF_SIZE)
189        } else {
190            RequestBuffer::new(SWEEP_BUF_SIZE)
191        };
192        self.queue.submit(req);
193    }
194
195    /// Retrieve the next chunk of receive data.
196    pub async fn next_complete(&mut self) -> Result<SweepBuf, Error> {
197        let buf = self.queue.next_complete().await.into_result()?;
198        let buf = Buffer::new(buf, self.buf_pool_send.clone());
199        SweepBuf::parse(buf)
200    }
201
202    /// Get the number of pending requests.
203    pub fn pending(&self) -> usize {
204        self.queue.pending()
205    }
206
207    /// Halt receiving and return to idle mode.
208    ///
209    /// This completes all outstanding transfers before halting. Transfer errors
210    /// are ignored.
211    pub async fn stop(mut self) -> Result<HackRf, Error> {
212        while self.pending() > 0 {
213            let _ = self.next_complete().await;
214        }
215        self.rf.set_transceiver_mode(TransceiverMode::Off).await?;
216        Ok(self.rf)
217    }
218}