diff --git a/Cargo.lock b/Cargo.lock index 6e673181b..90ae99a1a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "ad9912" @@ -638,13 +638,14 @@ dependencies = [ [[package]] name = "idsp" -version = "0.15.1" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99b7ec7ce4a9fa0c4801301dc2dfc836bc98ea06e8e7f04372c86cfcd171a80e" +checksum = "ef18d83613898a86a398a5bb7fe15fd4d0b27c7e64fdad5ade81cbc6c19f6729" dependencies = [ "num-complex 0.4.6", "num-traits", "serde", + "thiserror", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index cd45bed94..e7c7116c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,7 +50,7 @@ rtic = { version = "2.1", features = ["thumbv7-backend"] } rtic-monotonics = { version = "2.0", features = ["cortex-m-systick"] } num_enum = { version = "0.7.3", default-features = false } paste = "1" -idsp = "0.15.1" +idsp = "0.16.0" ad9959 = { path = "ad9959", version = "0.3.0" } serial-settings = { version = "0.2", path = "serial-settings" } mcp230xx = "1.0" diff --git a/hitl/streaming.py b/hitl/streaming.py index 5431ea45e..b3d642ecd 100644 --- a/hitl/streaming.py +++ b/hitl/streaming.py @@ -61,7 +61,7 @@ async def _main(): try: logger.info("Testing stream reception") _transport, stream = await StabilizerStream.open( - args.ip, args.port, args.broker + args.port, args.ip, args.broker ) loss = await measure(stream, args.duration) if loss > args.max_loss: diff --git a/py/stabilizer/__init__.py b/py/stabilizer/__init__.py index dd86d1020..bfc9b8bc3 100644 --- a/py/stabilizer/__init__.py +++ b/py/stabilizer/__init__.py @@ -2,13 +2,13 @@ """Stabilizer data conversion and streaming utilities""" # Sample period in seconds, default 100 MHz timer clock and a reload value of 128 -SAMPLE_PERIOD = 10e-9*128 +SAMPLE_PERIOD = 10e-9 * 128 # The number of DAC LSB codes per volt on Stabilizer outputs. DAC_LSB_PER_VOLT = (1 << 16) / (4.096 * 5) # The number of volts per ADC LSB. -ADC_VOLTS_PER_LSB = (5.0 / 2.0 * 4.096) / (1 << 15) +ADC_VOLTS_PER_LSB = (5.0 / 2.0 * 4.096) / (1 << 15) # The number of volts per DAC LSB. DAC_VOLTS_PER_LSB = 1 / DAC_LSB_PER_VOLT @@ -17,6 +17,7 @@ # DAC. DAC_FULL_SCALE = float(0x7FFF / DAC_LSB_PER_VOLT) + def voltage_to_machine_units(voltage): """Convert a voltage to machine units.""" code = int(round(voltage * DAC_LSB_PER_VOLT)) diff --git a/py/stabilizer/plot_iir_frequency_response.py b/py/stabilizer/plot_iir_frequency_response.py index 6f2899c37..cc844c091 100644 --- a/py/stabilizer/plot_iir_frequency_response.py +++ b/py/stabilizer/plot_iir_frequency_response.py @@ -11,8 +11,7 @@ from stabilizer.iir_coefficients import get_filters # disable warnings about short variable names and similar code -#pylint: disable=invalid-name, duplicate-code, redefined-builtin - +# pylint: disable=invalid-name, duplicate-code, redefined-builtin def _main(): @@ -52,7 +51,7 @@ def _main(): if forward_gain == 0 and args.x_offset != 0: print("Filter has no DC gain but x_offset is non-zero") - f = np.logspace(-8.5, 0, 1024, endpoint=False)*(.5/args.sample_period) + f = np.logspace(-8.5, 0, 1024, endpoint=False) * (0.5 / args.sample_period) f, h = signal.freqz( coefficients[:3], np.r_[1, [-c for c in coefficients[3:]]], diff --git a/py/stabilizer/stream.py b/py/stabilizer/stream.py index 66d423729..7cdd9582c 100644 --- a/py/stabilizer/stream.py +++ b/py/stabilizer/stream.py @@ -1,4 +1,6 @@ #!/usr/bin/python3 +# pylint: disable=too-few-public-methods + """Stabilizer streaming receiver and parsers""" import argparse @@ -21,7 +23,7 @@ def wrap(wide): """Wrap to 32 bit integer""" - return wide & 0xffffffff + return wide & 0xFFFFFFFF def get_local_ip(remote): @@ -37,6 +39,7 @@ def get_local_ip(remote): class AdcDac: """Stabilizer default striming data format""" + format_id = 1 def __init__(self, header, body): @@ -69,15 +72,16 @@ def to_traces(self): """Convert the raw data to labelled Trace instances""" data = self.to_mu() return [ - Trace(data[0], scale=DAC_VOLTS_PER_LSB, label='ADC0'), - Trace(data[1], scale=DAC_VOLTS_PER_LSB, label='ADC1'), - Trace(data[2], scale=DAC_VOLTS_PER_LSB, label='DAC0'), - Trace(data[3], scale=DAC_VOLTS_PER_LSB, label='DAC1') + Trace(data[0], scale=DAC_VOLTS_PER_LSB, label="ADC0"), + Trace(data[1], scale=DAC_VOLTS_PER_LSB, label="ADC1"), + Trace(data[2], scale=DAC_VOLTS_PER_LSB, label="DAC0"), + Trace(data[3], scale=DAC_VOLTS_PER_LSB, label="DAC1"), ] -class StabilizerStream(asyncio.DatagramProtocol): - """Stabilizer streaming receiver protocol""" +class Frame: + """Stream frame constisting of a header and multiple data batches""" + # The magic header half-word at the start of each packet. magic = 0x057B header_fmt = struct.Struct("` - /// - /// * `` specifies which channel to configure. `` := [0, 1] - /// - /// # Value - /// Any of the variants of [Gain] enclosed in double quotes. afe: [Leaf; 2], /// Configure the IIR filter parameters. @@ -127,52 +119,27 @@ pub struct DualIir { /// See [iir::Biquad] iir_ch: [[Leaf>; IIR_CASCADE_LENGTH]; 2], - /// Specified true if DI1 should be used as a "hold" input. - /// - /// # Path - /// `allow_hold` - /// - /// # Value - /// "true" or "false" + /// Use DI0/1 to HOLD the biquad. allow_hold: Leaf, - /// Specified true if "hold" should be forced regardless of DI1 state and hold allowance. - /// - /// # Path - /// `force_hold` - /// - /// # Value - /// "true" or "false" + /// Force the biquad to HOLD. force_hold: Leaf, - /// Specifies the telemetry output period in seconds. - /// - /// # Path - /// `telemetry_period` - /// - /// # Value - /// Any non-zero value less than 65536. - telemetry_period: Leaf, + /// Telemetry output period in seconds. + telemetry_period: Leaf, - /// Specifies the target for data streaming. + /// Target IP and port for UDP streaming. /// - /// # Path - /// `stream` + /// Can be multicast. /// /// # Value /// See [StreamTarget#miniconf] stream: Leaf, - /// Specifies the config for signal generators to add on to DAC0/DAC1 outputs. - /// - /// # Path - /// `source/` - /// - /// * `` specifies which channel to configure. `` := [0, 1] - /// - /// # Value - /// See [signal_generator::BasicConfig#miniconf] - source: [signal_generator::BasicConfig; 2], + /// Signal generator configuration to add to the DAC0/DAC1 outputs + source: [signal_generator::Config; 2], + + trigger: Leaf, } impl Default for DualIir { @@ -180,6 +147,9 @@ impl Default for DualIir { let mut i = iir::Biquad::IDENTITY; i.set_min(-SCALE); i.set_max(SCALE); + let mut source = signal_generator::Config::default(); + source.period = SAMPLE_PERIOD; + source.scale = DacCode::FULL_SCALE; Self { // Analog frontend programmable gain amplifier gains (G1, G2, G5, G10) afe: Default::default(), @@ -195,9 +165,10 @@ impl Default for DualIir { // Force suppress filter output updates. force_hold: false.into(), // The default telemetry period in seconds. - telemetry_period: 10.into(), + telemetry_period: 10.0.into(), - source: Default::default(), + source: [source; 2], + trigger: false.into(), stream: Default::default(), } @@ -215,7 +186,7 @@ mod app { settings: Settings, active_settings: DualIir, telemetry: TelemetryBuffer, - source: [SignalGenerator; 2], + source: [Source; 2], } #[local] @@ -255,23 +226,16 @@ mod app { let generator = network.configure_streaming(StreamFormat::AdcDacData); + let source = + Source::try_from_config(&stabilizer.settings.dual_iir.source[0]) + .unwrap(); + let shared = Shared { usb: stabilizer.usb, network, active_settings: stabilizer.settings.dual_iir.clone(), telemetry: TelemetryBuffer::default(), - source: [ - SignalGenerator::new( - stabilizer.settings.dual_iir.source[0] - .try_into_config(SAMPLE_PERIOD, DacCode::FULL_SCALE) - .unwrap(), - ), - SignalGenerator::new( - stabilizer.settings.dual_iir.source[1] - .try_into_config(SAMPLE_PERIOD, DacCode::FULL_SCALE) - .unwrap(), - ), - ], + source: [source.clone(), source], settings: stabilizer.settings, }; @@ -385,7 +349,7 @@ mod app { // The truncation introduces 1/2 LSB distortion. let y: i16 = unsafe { y.to_int_unchecked() }; - let y = y.saturating_add(signal); + let y = y.saturating_add((signal >> 16) as _); // Convert to DAC code *di = DacCode::from(y).0; @@ -436,7 +400,7 @@ mod app { .lock(|net, settings| net.update(&mut settings.dual_iir)) { NetworkState::SettingsChanged => { - settings_update::spawn().unwrap() + settings_update::spawn().unwrap(); } NetworkState::Updated => {} NetworkState::NoChange => { @@ -458,20 +422,21 @@ mod app { c.local.afes.0.set_gain(*settings.dual_iir.afe[0]); c.local.afes.1.set_gain(*settings.dual_iir.afe[1]); - // Update the signal generators - for (i, &config) in settings.dual_iir.source.iter().enumerate() { - match config.try_into_config(SAMPLE_PERIOD, DacCode::FULL_SCALE) - { - Ok(config) => { - c.shared.source.lock(|generator| { - generator[i].update_waveform(config) - }); + if *settings.dual_iir.trigger { + settings.dual_iir.trigger = false.into(); + for (i, config) in settings.dual_iir.source.iter().enumerate() { + match Source::try_from_config(config) { + Ok(source) => { + c.shared.source.lock(|s| { + s[i] = source; + }); + } + Err(err) => log::error!( + "Failed to update source on channel {}: {:?}", + i, + err + ), } - Err(err) => log::error!( - "Failed to update signal generation on DAC{}: {:?}", - i, - err - ), } } @@ -504,7 +469,7 @@ mod app { )) }); - Systick::delay((telemetry_period as u32).secs()).await; + Systick::delay(((telemetry_period * 1000.0) as u32).millis()).await; } } diff --git a/src/bin/lockin.rs b/src/bin/lockin.rs index d8d193ddc..d1c9870ba 100644 --- a/src/bin/lockin.rs +++ b/src/bin/lockin.rs @@ -28,7 +28,7 @@ #![no_main] use core::{ - convert::TryFrom, + iter, mem::MaybeUninit, sync::atomic::{fence, Ordering}, }; @@ -49,7 +49,6 @@ use stabilizer::{ dac::{Dac0Output, Dac1Output, DacCode}, hal, input_stamper::InputStamper, - signal_generator, timers::SamplingTimer, DigitalInput0, DigitalInput1, SerialTerminal, SystemTimer, Systick, UsbDevice, AFE0, AFE1, @@ -265,7 +264,7 @@ mod app { dacs: (Dac0Output, Dac1Output), pll: RPLL, lockin: idsp::Lockin>>, - source: signal_generator::SignalGenerator, + source: idsp::AccuOsc>, generator: FrameGenerator, cpu_temp_sensor: stabilizer::hardware::cpu_temp_sensor::CpuTempSensor, } @@ -302,14 +301,8 @@ mod app { settings: stabilizer.settings, }; - let signal_config = signal_generator::Config { - // Same frequency as batch size. - phase_increment: [1 << (32 - BATCH_SIZE_LOG2); 2], - // 1V Amplitude - amplitude: DacCode::try_from(1.0).unwrap().into(), - signal: signal_generator::Signal::Cosine, - phase_offset: 0, - }; + let source = + idsp::AccuOsc::new(iter::repeat(1i64 << (64 - BATCH_SIZE_LOG2))); let mut local = Local { usb_terminal: stabilizer.usb_serial, @@ -322,7 +315,7 @@ mod app { pll: RPLL::new(SAMPLE_TICKS_LOG2 + BATCH_SIZE_LOG2), lockin: idsp::Lockin::default(), - source: signal_generator::SignalGenerator::new(signal_config), + source, generator, cpu_temp_sensor: stabilizer.temperature_sensor, @@ -443,7 +436,7 @@ mod app { Conf::InPhase => output.re >> 16, Conf::Quadrature => output.im >> 16, - Conf::Modulation => source.next().unwrap() as i32, + Conf::Modulation => source.next().unwrap().re, }; *sample = DacCode::from(value as i16).0; diff --git a/src/hardware/signal_generator.rs b/src/hardware/signal_generator.rs index 45b12a999..03a78f2bd 100644 --- a/src/hardware/signal_generator.rs +++ b/src/hardware/signal_generator.rs @@ -1,3 +1,6 @@ +use core::iter::Take; + +use idsp::{AccuOsc, Sweep}; use miniconf::{Leaf, Tree}; use rand_core::{RngCore, SeedableRng}; use rand_xorshift::XorShiftRng; @@ -10,202 +13,241 @@ pub enum Signal { Square, Triangle, WhiteNoise, + SweptSine, +} + +impl Signal { + #[inline] + fn map(&self, x: i32) -> i32 { + match self { + Self::Cosine => idsp::cossin(x).0, + Self::Square => { + if x.is_negative() { + -i32::MAX + } else { + i32::MAX + } + } + Self::Triangle => i32::MIN + (x.saturating_abs() << 1), + _ => unimplemented!(), + } + } } /// Basic configuration for a generated signal. -/// -/// # Miniconf -/// `{"signal": , "frequency", 1000.0, "symmetry": 0.5, "amplitude": 1.0}` -/// -/// Where `` may be any of [Signal] variants, `frequency` specifies the signal frequency -/// in Hertz, `symmetry` specifies the normalized signal symmetry which ranges from 0 - 1.0, and -/// `amplitude` specifies the signal amplitude in Volts. #[derive(Copy, Clone, Debug, Tree, Serialize, Deserialize)] -pub struct BasicConfig { +pub struct Config { /// The signal type that should be generated. See [Signal] variants. - pub signal: Leaf, + signal: Leaf, /// The frequency of the generated signal in Hertz. - pub frequency: Leaf, + frequency: Leaf, /// The normalized symmetry of the signal. At 0% symmetry, the duration of the first half oscillation is minimal. /// At 25% symmetry, the first half oscillation lasts for 25% of the signal period. For square wave output this /// symmetry is the duty cycle. - pub symmetry: Leaf, + symmetry: Leaf, - /// The amplitude of the output signal in volts. - pub amplitude: Leaf, + /// The amplitude of the output signal + amplitude: Leaf, - /// The phase of the output signal in turns. - pub phase: Leaf, + /// Output offset + offset: Leaf, + + /// The initial phase of the period output signal in turns + phase: Leaf, + + /// Number of half periods (periodic) or samples (sweep and noise), 0 for infinte + length: Leaf, + + /// Sweep: Number of cycles for the first octave + cycles: Leaf, + + /// Sweep: Sweep rate + rate: Leaf, + + /// Sample period + #[tree(skip)] + pub period: f32, + /// Output full scale + #[tree(skip)] + pub scale: f32, } -impl Default for BasicConfig { +impl Default for Config { fn default() -> Self { Self { - frequency: 1.0e3.into(), - symmetry: 0.5.into(), - signal: Signal::Cosine.into(), - amplitude: 0.0.into(), - phase: 0.0.into(), + frequency: Leaf(1.0e3), + symmetry: Leaf(0.5), + signal: Leaf(Signal::Cosine), + amplitude: Leaf(0.0), + phase: Leaf(0.0), + offset: Leaf(0.0), + cycles: Leaf(1), + rate: Leaf(0), + length: Leaf(0), + period: 1.0, + scale: 1.0, } } } +#[derive(Clone, Debug)] +pub struct AsymmetricAccu { + ftw: [i32; 2], + pow: i32, + accu: i32, + count: u32, +} + +impl Iterator for AsymmetricAccu { + type Item = i32; + fn next(&mut self) -> Option { + let sign = self.accu.is_negative(); + self.accu = self.accu.wrapping_add(self.ftw[sign as usize]); + self.count + .checked_sub(sign as u32 ^ self.accu.is_negative() as u32) + .map(|c| { + self.count = c; + self.accu.wrapping_add(self.pow) + }) + } +} + +#[derive(Clone, Debug)] +pub struct Scaler { + amp: i32, + offset: i32, +} + +impl Scaler { + fn map(&self, x: i32) -> i32 { + ((x as i64 * self.amp as i64) >> 31) as i32 + self.offset + } +} + /// Represents the errors that can occur when attempting to configure the signal generator. #[derive(Copy, Clone, Debug)] pub enum Error { /// The provided amplitude is out-of-range. - InvalidAmplitude, + Amplitude, /// The provided symmetry is out of range. - InvalidSymmetry, + Symmetry, /// The provided frequency is out of range. - InvalidFrequency, + Frequency, + /// Sweep would wrap/invalid + Wrap, +} + +#[derive(Clone, Debug)] +pub enum Source { + SweptSine { + sweep: Take>, + amp: Scaler, + }, + Periodic { + accu: AsymmetricAccu, + signal: Signal, + amp: Scaler, + }, + WhiteNoise { + rng: XorShiftRng, + count: u32, + amp: Scaler, + }, } -impl BasicConfig { - /// Convert configuration into signal generator values. - /// - /// # Args - /// * `sample_period` - The time in seconds between samples. - /// * `full_scale` - The full scale output voltage. - pub fn try_into_config( - self, - sample_period: f32, - full_scale: f32, - ) -> Result { - let symmetry_complement = 1.0 - *self.symmetry; - // Validate symmetry - if *self.symmetry < 0.0 || symmetry_complement < 0.0 { - return Err(Error::InvalidSymmetry); +impl Iterator for Source { + type Item = i32; + #[inline] + fn next(&mut self) -> Option { + let (s, a) = match self { + Self::SweptSine { sweep, amp } => (sweep.next().map(|c| c.im), amp), + Self::Periodic { accu, signal, amp } => { + (accu.next().map(|p| signal.map(p)), amp) + } + Self::WhiteNoise { rng, count, amp } => ( + count.checked_sub(1).map(|m| { + *count = m; + rng.next_u32() as i32 + }), + amp, + ), + }; + Some(a.map(s.unwrap_or_default())) + } +} + +impl Source { + /// Convert from SI config + pub fn try_from_config(value: &Config) -> Result { + if !(0.0..1.0).contains(&*value.symmetry) { + return Err(Error::Symmetry); } const NYQUIST: f32 = (1u32 << 31) as _; - let ftw = *self.frequency * sample_period * NYQUIST; - - // Validate base frequency tuning word to be below Nyquist. - if ftw < 0.0 || 2.0 * ftw > NYQUIST { - return Err(Error::InvalidFrequency); + let ftw0 = *value.frequency * value.period * NYQUIST; + if !(0.0..2.0 * NYQUIST).contains(&ftw0) { + return Err(Error::Frequency); } - // Calculate the frequency tuning words. // Clip both frequency tuning words to within Nyquist before rounding. - let phase_increment = [ - if *self.symmetry * NYQUIST > ftw { - ftw / *self.symmetry + let ftw = [ + if *value.symmetry * NYQUIST > ftw0 { + ftw0 / *value.symmetry } else { NYQUIST } as i32, - if symmetry_complement * NYQUIST > ftw { - ftw / symmetry_complement + if (1.0 - *value.symmetry) * NYQUIST > ftw0 { + ftw0 / (1.0 - *value.symmetry) } else { NYQUIST } as i32, ]; - let amplitude = *self.amplitude * (i16::MIN as f32 / -full_scale); - if !(i16::MIN as f32..=i16::MAX as f32).contains(&litude) { - return Err(Error::InvalidAmplitude); - } - - let phase = *self.phase * (1u64 << 32) as f32; - - Ok(Config { - amplitude: amplitude as i16, - signal: *self.signal, - phase_increment, - phase_offset: phase as i32, - }) - } -} - -#[derive(Copy, Clone, Debug)] -pub struct Config { - /// The type of signal being generated - pub signal: Signal, - - /// The full-scale output code of the signal - pub amplitude: i16, - - /// The frequency tuning word of the signal. Phase is incremented by this amount - pub phase_increment: [i32; 2], - - /// The phase offset - pub phase_offset: i32, -} - -impl Default for Config { - fn default() -> Self { - Self { - signal: Signal::Cosine, - amplitude: 0, - phase_increment: [0, 0], - phase_offset: 0, + let offset = *value.offset / value.scale; + let amplitude = *value.amplitude / value.scale; + fn abs(x: f32) -> f32 { + if x.is_sign_negative() { + -x + } else { + x + } } - } -} - -#[derive(Debug)] -pub struct SignalGenerator { - phase_accumulator: i32, - config: Config, - rng: XorShiftRng, -} - -impl SignalGenerator { - /// Construct a new signal generator with some specific config. - /// - /// # Args - /// * `config` - The config to use for generating signals. - /// - /// # Returns - /// The generator - pub fn new(config: Config) -> Self { - Self { - config, - phase_accumulator: 0, - rng: XorShiftRng::from_seed([0; 16]), // zeros will initialize with XorShiftRng internal seed + if abs(offset) + abs(amplitude) >= 1.0 { + return Err(Error::Amplitude); } - } - - /// Update waveform generation settings. - pub fn update_waveform(&mut self, new_config: Config) { - self.config = new_config; - } - - /// Clear the phase accumulator. - pub fn clear_phase_accumulator(&mut self) { - self.phase_accumulator = 0; - } -} + let amp = Scaler { + amp: (amplitude * NYQUIST) as _, + offset: (offset * NYQUIST) as _, + }; -impl core::iter::Iterator for SignalGenerator { - type Item = i16; - - /// Get the next value in the generator sequence. - fn next(&mut self) -> Option { - let phase = self - .phase_accumulator - .wrapping_add(self.config.phase_offset); - let sign = phase.is_negative(); - self.phase_accumulator = self - .phase_accumulator - .wrapping_add(self.config.phase_increment[sign as usize]); - - let scale = match self.config.signal { - Signal::Cosine => idsp::cossin(phase).0 >> 16, - Signal::Square => { - if sign { - i16::MIN as i32 - } else { - -(i16::MIN as i32) + Ok(match *value.signal { + signal @ (Signal::Cosine | Signal::Square | Signal::Triangle) => { + Self::Periodic { + accu: AsymmetricAccu { + ftw, + pow: (*value.phase * NYQUIST) as i32, + accu: 0, + count: *value.length, + }, + signal, + amp, } } - Signal::Triangle => i16::MIN as i32 + (phase >> 15).abs(), - Signal::WhiteNoise => self.rng.next_u32() as i32 >> 16, - }; - - // Calculate the final output result as an i16. - Some(((self.config.amplitude as i32 * scale) >> 15) as _) + Signal::SweptSine => Self::SweptSine { + sweep: AccuOsc::new(Sweep::new( + *value.rate, + ((*value.rate * *value.cycles) as i64) << 32, + )) + .take(*value.length as _), + amp, + }, + Signal::WhiteNoise => Self::WhiteNoise { + rng: XorShiftRng::from_seed(Default::default()), + count: *value.length, + amp, + }, + }) } } diff --git a/src/net/data_stream.rs b/src/net/data_stream.rs index 714ec57fb..fdbc28e4c 100644 --- a/src/net/data_stream.rs +++ b/src/net/data_stream.rs @@ -52,8 +52,8 @@ const FRAME_COUNT: usize = 4; // The size of each frame in bytes. // Ensure the resulting ethernet frame is within the MTU: -// 1500 MTU - 40 IP6 header - 8 UDP header -const FRAME_SIZE: usize = 1500 - 40 - 8; +// 1500 MTU - 40 IP6 header - 8 UDP header - 32 VPN - 20 IP4 +const FRAME_SIZE: usize = 1500 - 40 - 8 - 32 - 20; // The size of the frame queue must be at least as large as the number of frame buffers. Every // allocated frame buffer should fit in the queue. @@ -103,12 +103,6 @@ impl core::str::FromStr for StreamTarget { } } -impl core::fmt::Display for StreamTarget { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - self.0.fmt(f) - } -} - /// Specifies the format of streamed data #[repr(u8)] #[derive(Debug, Copy, Clone, PartialEq, Eq, IntoPrimitive)] @@ -260,31 +254,31 @@ impl FrameGenerator { let sequence_number = self.sequence_number; self.sequence_number = self.sequence_number.wrapping_add(1); - if self.current_frame.is_none() { - if let Ok(buffer) = - FRAME_POOL.alloc([MaybeUninit::uninit(); FRAME_SIZE]) - { - self.current_frame.replace(StreamFrame::new( - buffer, - self.format, - sequence_number, - )); - } else { - return; + let current_frame = match self.current_frame.as_mut() { + None => { + if let Ok(buffer) = + FRAME_POOL.alloc([MaybeUninit::uninit(); FRAME_SIZE]) + { + self.current_frame.insert(StreamFrame::new( + buffer, + self.format, + sequence_number, + )) + } else { + return; + } } - } - - // Note(unwrap): We ensure the frame is present above. - let current_frame = self.current_frame.as_mut().unwrap(); + Some(frame) => frame, + }; let len = current_frame.add_batch(func); if current_frame.is_full(len) { // Note(unwrap): The queue is designed to be at least as large as the frame buffer // count, so this enqueue should always succeed. - self.queue - .enqueue(self.current_frame.take().unwrap()) - .unwrap(); + if let Some(frame) = self.current_frame.take() { + self.queue.enqueue(frame).unwrap(); + } } } }