1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
//! Spectrometer.
//!
//! This module is used for the control of the spectrometer included in the Maia
//! SDR FPGA IP core.
use crate::fpga::{InterruptWaiter, IpCore};
use anyhow::Result;
use bytes::Bytes;
use maia_json::SpectrometerMode;
use std::sync::{Arc, Mutex};
use tokio::sync::broadcast;
// Used to obtain values in dB which are positive
const BASE_SCALE: f32 = 1e9;
/// Spectrometer.
///
/// This struct waits for interrupts from the spectrometer in the FPGA IP core,
/// reads the spectrum data, transforms it from `u64` to `f32` format, and sends
/// it (serialized into [`Bytes`]) into a [`tokio::sync::broadcast::Sender`].
#[derive(Debug)]
pub struct Spectrometer {
ip_core: Arc<Mutex<IpCore>>,
sender: broadcast::Sender<Bytes>,
interrupt: InterruptWaiter,
config: SpectrometerConfig,
}
/// Spectrometer configuration setter.
///
/// This struct gives shared access to getters and setters for the spectrometer
/// sample rate and mode. It is used to update the sample rate and mode from
/// other parts of the code.
#[derive(Debug, Clone)]
pub struct SpectrometerConfig(Arc<Mutex<Config>>);
#[derive(Debug, Clone)]
struct Config {
samp_rate: f32,
mode: SpectrometerMode,
}
impl Spectrometer {
/// Creates a new spectrometer struct.
///
/// The `interrupt` parameter should correspond to the [`InterruptWaiter`]
/// corresponding to the spectrometer. Each spectra received from the FPGA
/// is sent to the `sender`.
pub fn new(
ip_core: Arc<Mutex<IpCore>>,
interrupt: InterruptWaiter,
sender: broadcast::Sender<Bytes>,
) -> Spectrometer {
Spectrometer {
ip_core,
interrupt,
sender,
config: SpectrometerConfig::new(),
}
}
/// Returns a ['SpectrometerConfig`] object.
///
/// This returns a config setter object that can be used to update the
/// spectrometer sample rate and mode from other objects.
pub fn config(&self) -> SpectrometerConfig {
self.config.clone()
}
/// Runs the spectrometer.
///
/// This function only returns if there is an error. The function should be
/// run concurrently with the rest of the application for the spectrometer
/// to work.
#[tracing::instrument(name = "spectrometer", skip_all)]
pub async fn run(self) -> Result<()> {
loop {
self.interrupt.wait().await;
let (samp_rate, mode) = self.config.samp_rate_mode();
let mut ip_core = self.ip_core.lock().unwrap();
let num_integrations = ip_core.spectrometer_number_integrations() as f32;
let scale = match mode {
SpectrometerMode::Average => BASE_SCALE / (num_integrations * samp_rate),
SpectrometerMode::PeakDetect => BASE_SCALE / samp_rate,
};
tracing::trace!(
last_buffer = ip_core.spectrometer_last_buffer(),
samp_rate,
num_integrations,
scale
);
// TODO: potential optimization: do not hold the mutex locked while
// we iterate over the buffers.
for buffer in ip_core.get_spectrometer_buffers() {
if self.sender.receiver_count() > 0 {
// It is ok if send returns Err, because there might be
// no receiver handles in this moment.
let _ = self.sender.send(Self::buffer_u64_to_f32(buffer, scale));
}
}
}
}
fn buffer_u64_to_f32(buffer: &[u64], scale: f32) -> Bytes {
// TODO: optimize using Neon
buffer
.iter()
.flat_map(|&x| (x as f32 * scale).to_ne_bytes().into_iter())
.collect()
}
}
impl SpectrometerConfig {
fn new() -> SpectrometerConfig {
SpectrometerConfig(Arc::new(Mutex::new(Config {
samp_rate: 0.0,
mode: SpectrometerMode::Average,
})))
}
/// Returns the spectrometer sample rate.
///
/// The units are samples per second.
pub fn samp_rate(&self) -> f32 {
self.0.lock().unwrap().samp_rate
}
/// Returns the spectrometer mode.
pub fn mode(&self) -> SpectrometerMode {
self.0.lock().unwrap().mode
}
/// Returns the spectrometer sample rate and mode
pub fn samp_rate_mode(&self) -> (f32, SpectrometerMode) {
let conf = self.0.lock().unwrap();
(conf.samp_rate, conf.mode)
}
/// Sets the spectrometer sample rate.
///
/// Updates the spectrometer sample rate to the value give, in units of
/// samples per second.
pub fn set_samp_rate(&self, samp_rate: f32) {
self.0.lock().unwrap().samp_rate = samp_rate;
}
/// Sets the spectrometer mode.
pub fn set_mode(&self, mode: SpectrometerMode) {
self.0.lock().unwrap().mode = mode;
}
/// Sets the spectrometer sample rate and mode.
pub fn set_samp_rate_mode(&self, samp_rate: f32, mode: SpectrometerMode) {
let mut conf = self.0.lock().unwrap();
conf.samp_rate = samp_rate;
conf.mode = mode;
}
}