Transmit

Struct Transmit 

Source
pub struct Transmit { /* private fields */ }
Expand description

A HackRF operating in transmit mode.

To send data, first take a HackRF peripheral and call HackRf::start_tx, or use Transmit::new with it. Provide the maximum block transfer size, in samples, that will be used for the duration of the transmit.

Next, call get_buffer to allocate a buffer, and then fill that buffer with the next block of samples to transmit, up to the maximum block transfer size chosen. Set up around half a million samples worth of buffers before submitting any, ideally, or gaps in the transmit sequence may occur.

Next, start calling submit to submit the blocks. Continue the buffer filling and submitting process as long as needed, preferrably pausing with next_complete when there a more than half a million samples worth of buffers queued up. The number of queued buffers can be checked with pending. Once buffers are queued up, the process of completing, grabbing a buffer, and submitting it in a loop until finished.

When all buffers have been submitted, call flush to queue up a final set of zero-filled buffers to flush out any remaining data in the HackRF’s internal buffers. Then continue calling next_complete until pending returns 0. Finally, exit transmit mode with stop. Transmit mode can be exited without doing this sequence, but not all samples queued up will necessarily be sent otherwise.

Putting it all together, here’s an example program that transmits all the data in a file:

use anyhow::Result;
use tokio::io::AsyncReadExt;
use waverave_hackrf::Buffer;
#[tokio::main]
async fn main() -> Result<()> {
    let hackrf = waverave_hackrf::open_hackrf()?;

    // Configure: 20MHz sample rate, turn on RF amp, set TX IF gain to 16 dB,
    // and tune to 915 MHz.
    hackrf.set_sample_rate(20e6).await?;
    hackrf.set_txvga_gain(16).await?;
    hackrf.set_freq(915_000_000).await?;
    hackrf.set_amp_enable(true).await?;

    // Open up a file for buffered reading.
    let mut args = std::env::args();
    args.next();
    let file_name = args.next().unwrap_or_else(|| String::from("./tx.bin"));
    let mut file = tokio::fs::File::open(&file_name).await?;

    // Start transmitting, in bursts of 8192 samples
    let mut hackrf_tx = hackrf.start_tx(8192).await.map_err(|e| e.err)?;

    // Set up an asynchronous process that fills buffers and sends them on to
    // the transmitter.
    let (buf_send, mut buf_recv) = tokio::sync::mpsc::channel::<Buffer>(4);
    let (data_send, mut data_recv) = tokio::sync::mpsc::channel::<Buffer>(4);
    tokio::spawn(async move {
        loop {
            let Some(mut buf) = buf_recv.recv().await else {
                break;
            };
            buf.extend_zeros(buf.remaining_capacity());
            file.read_exact(buf.bytes_mut()).await?;
            let Ok(_) = data_send.send(buf).await else {
                break;
            };
        }
        Ok::<(), anyhow::Error>(())
    });

    // Start filling up queue
    let mut start = Vec::with_capacity(64);
    while start.len() < 64 {
        while buf_send.try_send(hackrf_tx.get_buffer()).is_ok() {}
        let Some(buf) = data_recv.recv().await else {
            break;
        };
        start.push(buf);
    }

    // Submit the whole starting queue in one go
    for buf in start {
        hackrf_tx.submit(buf);
    }

    // Continue filling the queue and submitting samples as often as possible
    loop {
        while buf_send.try_send(hackrf_tx.get_buffer()).is_ok() {}
        hackrf_tx.next_complete().await?;
        let Some(buf) = data_recv.recv().await else {
            break;
        };
        hackrf_tx.submit(buf);
    }

    // Flush the remainder
    hackrf_tx.flush();
    while hackrf_tx.pending() > 0 {
        hackrf_tx.next_complete().await?;
    }

    // Stop transmitting
    hackrf_tx.stop().await?;

    Ok(())
}

Implementations§

Source§

impl Transmit

Source

pub async fn new( rf: HackRf, max_transfer_size: usize, ) -> Result<Self, StateChangeError>

Switch a HackRF into transmit mode, with a set maximum number of samples per buffer block.

Buffers are reused across transmit operations, provided that the max_transfer_size is always the same.

Source

pub fn get_buffer(&self) -> Buffer

Get a buffer for holding transmit data.

All buffers have the same capacity, set when transmit is initialized. Actual transmitted data is rounded up to the nearest 256 samples, zero-filling as needed.

Source

pub fn max_transfer_size(&self) -> usize

The maximum number of samples that can be queued within a single buffer.

Source

pub fn submit(&mut self, tx: Buffer)

Queue up a transmit transfer.

This will pull from a reusable buffer pool first, and allocate a new buffer if none are available in the pool.

The buffer pool will grow so long as completed buffers aren’t dropped.

Source

pub fn flush(&mut self)

Flush whatever remaining samples are in the HackRF internal buffer.

This will generate additional pending operations.

Additional pending operations go up by 8192.div_ceil(max_transfer_size).

Source

pub async fn next_complete(&mut self) -> Result<(), Error>

Wait for a transmit operation to complete.

This future is cancel-safe, so feel free to use it alongside a timeout or a select!-type pattern.

Source

pub fn pending(&self) -> usize

Get the number of pending requests.

Source

pub async fn stop(self) -> Result<HackRf, StateChangeError>

Halt receiving and return to idle mode.

This attempts to cancel all transfers and then complete whatever is left. Transfer errors are ignored.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.