Document not found (404)
+This URL is invalid, sorry. Please use the navigation bar or search to continue.
+ +diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000000..f17311098f --- /dev/null +++ b/.nojekyll @@ -0,0 +1 @@ +This file makes sure that Github Pages doesn't process mdBook's output. diff --git a/404.html b/404.html new file mode 100644 index 0000000000..36f9920830 --- /dev/null +++ b/404.html @@ -0,0 +1,189 @@ + + +
+ + +This URL is invalid, sorry. Please use the navigation bar or search to continue.
+ +pub enum Error {
+ Interface,
+ Check,
+ Bounds,
+ Pin,
+ Frequency,
+}
Possible errors generated by the AD9959 driver.
+#[repr(u8)]pub enum Mode {
+ SingleBitTwoWire,
+ SingleBitThreeWire,
+ TwoBitSerial,
+ FourBitSerial,
+}
Indicates various communication modes of the DDS. The value of this enumeration is equivalent to +the configuration bits of the DDS CSR register.
+#[repr(u8)]pub enum Register {
+Show 25 variants
CSR,
+ FR1,
+ FR2,
+ CFR,
+ CFTW0,
+ CPOW0,
+ ACR,
+ LSRR,
+ RDW,
+ FDW,
+ CW1,
+ CW2,
+ CW3,
+ CW4,
+ CW5,
+ CW6,
+ CW7,
+ CW8,
+ CW9,
+ CW10,
+ CW11,
+ CW12,
+ CW13,
+ CW14,
+ CW15,
+}
The configuration registers within the AD9959 DDS device. The values of each register are +equivalent to the address.
+pub struct Ad9959<INTERFACE> { /* private fields */ }
A device driver for the AD9959 direct digital synthesis (DDS) chip.
+This chip provides four independently controllable digital-to-analog output sinusoids with +configurable phase, amplitude, and frequency. All channels are inherently synchronized as they +are derived off a common system clock.
+The chip contains a configurable PLL and supports system clock frequencies up to 500 MHz.
+The chip supports a number of serial interfaces to improve data throughput, including normal, +dual, and quad SPI configurations.
+Construct and initialize the DDS.
+Args:
+interface
- An interface to the DDS.reset_pin
- A pin connected to the DDS reset input.io_update
- A pin connected to the DDS io_update input.delay
- A delay implementation for blocking operation for specific amounts of time.desired_mode
- The desired communication mode of the interface to the DDS.clock_frequency
- The clock frequency of the reference clock input.multiplier
- The desired clock multiplier for the system clock. This multiplies
+clock_frequency
to generate the system clock.Get the current reference clock frequency in Hz.
+Get the current reference clock multiplier.
+Perform a self-test of the communication interface.
+Note: +This modifies the existing channel enables. They are restored upon exit.
+Returns: +True if the self test succeeded. False otherwise.
+Configure the phase of a specified channel.
+Arguments:
+channel
- The channel to configure the frequency of.phase_turns
- The desired phase offset in turns.Returns: +The actual programmed phase offset of the channel in turns.
+Get the current phase of a specified channel.
+Args:
+channel
- The channel to get the phase of.Returns: +The phase of the channel in turns.
+Configure the amplitude of a specified channel.
+Arguments:
+channel
- The channel to configure the frequency of.amplitude
- A normalized amplitude setting [0, 1].Returns: +The actual normalized amplitude of the channel relative to full-scale range.
+Get the configured amplitude of a channel.
+Args:
+channel
- The channel to get the amplitude of.Returns: +The normalized amplitude of the channel.
+Configure the frequency of a specified channel.
+Arguments:
+channel
- The channel to configure the frequency of.frequency
- The desired output frequency in Hz.Returns: +The actual programmed frequency of the channel.
+pub struct Channel(_);
Specifies an output channel of the AD9959 DDS chip.
+Get the underlying bits value.
+The returned value is exactly the bits set in this flags value.
+Convert from a bits value.
+This method will return None
if any unknown bits are set.
Convert from a bits value, unsetting any unknown bits.
+Convert from a bits value exactly.
+Get a flags value with the bits of a flag with the given name set.
+This method will return None
if name
is empty or doesn’t
+correspond to any named flag.
Whether any set bits in a source flags value are also set in a target flags value.
+Whether all set bits in a source flags value are also set in a target flags value.
+The intersection of a source flags value with the complement of a target flags value (&!
).
This method is not equivalent to self & !other
when other
has unknown bits set.
+remove
won’t truncate other
, but the !
operator will.
The bitwise exclusive-or (^
) of the bits in two flags values.
Call insert
when value
is true
or remove
when value
is false
.
The bitwise and (&
) of the bits in two flags values.
The bitwise or (|
) of the bits in two flags values.
The intersection of a source flags value with the complement of a target flags value (&!
).
This method is not equivalent to self & !other
when other
has unknown bits set.
+difference
won’t truncate other
, but the !
operator will.
The bitwise exclusive-or (^
) of the bits in two flags values.
The bitwise negation (!
) of the bits in a flags value, truncating the result.
Yield a set of contained flags values.
+Each yielded flags value will correspond to a defined named flag. Any unknown bits +will be yielded together as a final flags value.
+Yield a set of contained named flags values.
+This method is like iter
, except only yields bits in contained named flags.
+Any unknown bits, or bits not corresponding to a contained flag will not be yielded.
The bitwise and (&
) of the bits in two flags values.
The bitwise or (|
) of the bits in two flags values.
The bitwise exclusive-or (^
) of the bits in two flags values.
The bitwise or (|
) of the bits in each flags value.
extend_one
)extend_one
)|
) of the bits in two flags values.&!
). Read more^
) of the bits in two flags values.Flags::insert
] when value
is true
or [Flags::remove
] when value
is false
.&
) of the bits in two flags values.&!
). Read more^
) of the bits in two flags values.!
) of the bits in a flags value, truncating the result.The bitwise or (|
) of the bits in each flags value.
The intersection of a source flags value with the complement of a target flags value (&!
).
This method is not equivalent to self & !other
when other
has unknown bits set.
+difference
won’t truncate other
, but the !
operator will.
pub struct ProfileSerializer { /* private fields */ }
Represents a means of serializing a DDS profile for writing to a stream.
+Update a number of channels with the requested profile.
+channels
- A set of channels to apply the configuration to.ftw
- If provided, indicates a frequency tuning word for the channels.pow
- If provided, indicates a phase offset word for the channels.acr
- If provided, indicates the amplitude control register for the channels. The ACR
+should be stored in the 3 LSB of the word. Note that if amplitude scaling is to be used,
+the “Amplitude multiplier enable” bit must be set.pub trait Interface {
+ type Error;
+
+ // Required methods
+ fn configure_mode(&mut self, mode: Mode) -> Result<(), Self::Error>;
+ fn write(&mut self, addr: u8, data: &[u8]) -> Result<(), Self::Error>;
+ fn read(&mut self, addr: u8, dest: &mut [u8]) -> Result<(), Self::Error>;
+}
A trait that allows a HAL to provide a means of communicating with the AD9959.
+pub struct Context {}
Execution context
+Software task
+pub use Monotonic::spawn_after;
pub use Monotonic::spawn_at;
pub use Monotonic::SpawnHandle;
ethernet_link
has access topub struct Context<'a> {
+ pub shared: SharedResources<'a>,
+}
Execution context
+Shared Resources this task has access to
+pub struct SharedResources<'a> {
+ pub network: network_that_needs_to_be_locked<'a>,
+}
Shared resources ethernet_link
has access to
network: network_that_needs_to_be_locked<'a>
Resource proxy resource network
. Use method .lock()
to gain access
pub fn __rtic_internal_ethernet_link_Monotonic_spawn_after(
+ duration: <Systick as Monotonic>::Duration
+) -> Result<SpawnHandle, ()>
Spawns the task after a set duration relative to the current time
+This will use the time Instant::new(0)
as baseline if called in #[init]
,
+so if you use a non-resetable timer use spawn_at
when in #[init]
pub fn __rtic_internal_settings_update_Monotonic_spawn_after(
+ duration: <Systick as Monotonic>::Duration
+) -> Result<SpawnHandle, ()>
Spawns the task after a set duration relative to the current time
+This will use the time Instant::new(0)
as baseline if called in #[init]
,
+so if you use a non-resetable timer use spawn_at
when in #[init]
pub fn __rtic_internal_start_Monotonic_spawn_after(
+ duration: <Systick as Monotonic>::Duration
+) -> Result<SpawnHandle, ()>
Spawns the task after a set duration relative to the current time
+This will use the time Instant::new(0)
as baseline if called in #[init]
,
+so if you use a non-resetable timer use spawn_at
when in #[init]
pub fn __rtic_internal_telemetry_Monotonic_spawn_after(
+ duration: <Systick as Monotonic>::Duration
+) -> Result<SpawnHandle, ()>
Spawns the task after a set duration relative to the current time
+This will use the time Instant::new(0)
as baseline if called in #[init]
,
+so if you use a non-resetable timer use spawn_at
when in #[init]
pub fn __rtic_internal_usb_Monotonic_spawn_after(
+ duration: <Systick as Monotonic>::Duration
+) -> Result<SpawnHandle, ()>
Spawns the task after a set duration relative to the current time
+This will use the time Instant::new(0)
as baseline if called in #[init]
,
+so if you use a non-resetable timer use spawn_at
when in #[init]
fn ethernet_link(c: Context<'_>)
User SW task ethernet_link
+fn settings_update(c: Context<'_>)
User SW task settings_update
+pub struct Context<'a> {
+ pub shared: SharedResources<'a>,
+}
Execution context
+Shared Resources this task has access to
+pub struct SharedResources<'a> {
+ pub network: network_that_needs_to_be_locked<'a>,
+ pub usb_terminal: usb_terminal_that_needs_to_be_locked<'a>,
+}
Shared resources idle
has access to
network: network_that_needs_to_be_locked<'a>
Resource proxy resource network
. Use method .lock()
to gain access
usb_terminal: usb_terminal_that_needs_to_be_locked<'a>
Resource proxy resource usb_terminal
. Use method .lock()
to gain access
App module
+The RTIC application module
+pub use rtic::Monotonic as _;
ethernet_link
has access toidle
has access toprocess
has access toprocess
has access tosettings_update
has access tosettings_update
has access tostart
has access totelemetry
has access totelemetry
has access tousb
has access topub struct Context<'a> {
+ pub core: Peripherals,
+ pub device: Peripherals,
+ pub cs: CriticalSection<'a>,
+}
Execution context
+core: Peripherals
Core (Cortex-M) peripherals
+device: Peripherals
Device peripherals
+cs: CriticalSection<'a>
Critical section token for init
+pub struct Monotonics(pub Systick);
Monotonics used by the system
+0: Systick
Holds static methods for each monotonic.
+pub use Monotonic::now;
Monotonic::now()
Hardware task
+process
has access toprocess
has access topub struct Context<'a> {
+ pub local: LocalResources<'a>,
+ pub shared: SharedResources<'a>,
+}
Execution context
+local: LocalResources<'a>
Local Resources this task has access to
+Shared Resources this task has access to
+pub struct LocalResources<'a> {
+ pub digital_inputs: &'a mut (DigitalInput0, DigitalInput1),
+ pub adcs: &'a mut (Adc0Input, Adc1Input),
+ pub dacs: &'a mut (Dac0Output, Dac1Output),
+ pub iir_state: &'a mut [[Vec5<f32>; 1]; 2],
+ pub generator: &'a mut FrameGenerator,
+}
Local resources process
has access to
digital_inputs: &'a mut (DigitalInput0, DigitalInput1)
Local resource digital_inputs
adcs: &'a mut (Adc0Input, Adc1Input)
Local resource adcs
dacs: &'a mut (Dac0Output, Dac1Output)
Local resource dacs
iir_state: &'a mut [[Vec5<f32>; 1]; 2]
Local resource iir_state
generator: &'a mut FrameGenerator
Local resource generator
pub struct SharedResources<'a> {
+ pub settings: settings_that_needs_to_be_locked<'a>,
+ pub signal_generator: signal_generator_that_needs_to_be_locked<'a>,
+ pub telemetry: telemetry_that_needs_to_be_locked<'a>,
+}
Shared resources process
has access to
settings: settings_that_needs_to_be_locked<'a>
Resource proxy resource settings
. Use method .lock()
to gain access
signal_generator: signal_generator_that_needs_to_be_locked<'a>
Resource proxy resource signal_generator
. Use method .lock()
to gain access
telemetry: telemetry_that_needs_to_be_locked<'a>
Resource proxy resource telemetry
. Use method .lock()
to gain access
Software task
+pub use Monotonic::spawn_after;
pub use Monotonic::spawn_at;
pub use Monotonic::SpawnHandle;
settings_update
has access tosettings_update
has access topub struct Context<'a> {
+ pub local: LocalResources<'a>,
+ pub shared: SharedResources<'a>,
+}
Execution context
+local: LocalResources<'a>
Local Resources this task has access to
+Shared Resources this task has access to
+pub struct LocalResources<'a> {
+ pub afes: &'a mut (AFE0, AFE1),
+}
Local resources settings_update
has access to
afes: &'a mut (AFE0, AFE1)
Local resource afes
pub struct SharedResources<'a> {
+ pub network: network_that_needs_to_be_locked<'a>,
+ pub settings: settings_that_needs_to_be_locked<'a>,
+ pub signal_generator: signal_generator_that_needs_to_be_locked<'a>,
+}
Shared resources settings_update
has access to
network: network_that_needs_to_be_locked<'a>
Resource proxy resource network
. Use method .lock()
to gain access
settings: settings_that_needs_to_be_locked<'a>
Resource proxy resource settings
. Use method .lock()
to gain access
signal_generator: signal_generator_that_needs_to_be_locked<'a>
Resource proxy resource signal_generator
. Use method .lock()
to gain access
pub struct Context {}
Execution context
+pub struct Context {}
Execution context
+pub struct Context {}
Execution context
+pub struct Context {}
Execution context
+Software task
+pub use Monotonic::spawn_after;
pub use Monotonic::spawn_at;
pub use Monotonic::SpawnHandle;
start
has access topub struct Context<'a> {
+ pub local: LocalResources<'a>,
+}
Execution context
+local: LocalResources<'a>
Local Resources this task has access to
+pub struct LocalResources<'a> {
+ pub sampling_timer: &'a mut SamplingTimer,
+}
Local resources start
has access to
sampling_timer: &'a mut SamplingTimer
Local resource sampling_timer
struct Local {
+ sampling_timer: SamplingTimer,
+ digital_inputs: (DigitalInput0, DigitalInput1),
+ afes: (AFE0, AFE1),
+ adcs: (Adc0Input, Adc1Input),
+ dacs: (Dac0Output, Dac1Output),
+ iir_state: [[Vec5<f32>; 1]; 2],
+ generator: FrameGenerator,
+ cpu_temp_sensor: CpuTempSensor,
+}
RTIC local resource struct
+sampling_timer: SamplingTimer
§digital_inputs: (DigitalInput0, DigitalInput1)
§afes: (AFE0, AFE1)
§adcs: (Adc0Input, Adc1Input)
§dacs: (Dac0Output, Dac1Output)
§iir_state: [[Vec5<f32>; 1]; 2]
§generator: FrameGenerator
§cpu_temp_sensor: CpuTempSensor
struct Shared {
+ usb_terminal: SerialTerminal,
+ network: NetworkUsers<Settings, Telemetry, 3>,
+ settings: Settings,
+ telemetry: TelemetryBuffer,
+ signal_generator: [SignalGenerator; 2],
+}
RTIC shared resource struct
+usb_terminal: SerialTerminal
§network: NetworkUsers<Settings, Telemetry, 3>
§settings: Settings
§telemetry: TelemetryBuffer
§signal_generator: [SignalGenerator; 2]
pub struct __rtic_internal_Monotonics(pub Systick);
Monotonics used by the system
+0: Systick
pub struct __rtic_internal_eth_Context {}
Execution context
+pub struct __rtic_internal_ethernet_linkSharedResources<'a> {
+ pub network: network_that_needs_to_be_locked<'a>,
+}
Shared resources ethernet_link
has access to
network: network_that_needs_to_be_locked<'a>
Resource proxy resource network
. Use method .lock()
to gain access
pub struct __rtic_internal_ethernet_link_Context<'a> {
+ pub shared: SharedResources<'a>,
+}
Execution context
+Shared Resources this task has access to
+pub struct __rtic_internal_idleSharedResources<'a> {
+ pub network: network_that_needs_to_be_locked<'a>,
+ pub usb_terminal: usb_terminal_that_needs_to_be_locked<'a>,
+}
Shared resources idle
has access to
network: network_that_needs_to_be_locked<'a>
Resource proxy resource network
. Use method .lock()
to gain access
usb_terminal: usb_terminal_that_needs_to_be_locked<'a>
Resource proxy resource usb_terminal
. Use method .lock()
to gain access
App module
+pub struct __rtic_internal_idle_Context<'a> {
+ pub shared: SharedResources<'a>,
+}
Execution context
+Shared Resources this task has access to
+pub struct __rtic_internal_init_Context<'a> {
+ pub core: Peripherals,
+ pub device: Peripherals,
+ pub cs: CriticalSection<'a>,
+}
Execution context
+core: Peripherals
Core (Cortex-M) peripherals
+device: Peripherals
Device peripherals
+cs: CriticalSection<'a>
Critical section token for init
+pub struct __rtic_internal_processLocalResources<'a> {
+ pub digital_inputs: &'a mut (DigitalInput0, DigitalInput1),
+ pub adcs: &'a mut (Adc0Input, Adc1Input),
+ pub dacs: &'a mut (Dac0Output, Dac1Output),
+ pub iir_state: &'a mut [[Vec5<f32>; 1]; 2],
+ pub generator: &'a mut FrameGenerator,
+}
Local resources process
has access to
digital_inputs: &'a mut (DigitalInput0, DigitalInput1)
Local resource digital_inputs
adcs: &'a mut (Adc0Input, Adc1Input)
Local resource adcs
dacs: &'a mut (Dac0Output, Dac1Output)
Local resource dacs
iir_state: &'a mut [[Vec5<f32>; 1]; 2]
Local resource iir_state
generator: &'a mut FrameGenerator
Local resource generator
pub struct __rtic_internal_processSharedResources<'a> {
+ pub settings: settings_that_needs_to_be_locked<'a>,
+ pub signal_generator: signal_generator_that_needs_to_be_locked<'a>,
+ pub telemetry: telemetry_that_needs_to_be_locked<'a>,
+}
Shared resources process
has access to
settings: settings_that_needs_to_be_locked<'a>
Resource proxy resource settings
. Use method .lock()
to gain access
signal_generator: signal_generator_that_needs_to_be_locked<'a>
Resource proxy resource signal_generator
. Use method .lock()
to gain access
telemetry: telemetry_that_needs_to_be_locked<'a>
Resource proxy resource telemetry
. Use method .lock()
to gain access
pub struct __rtic_internal_process_Context<'a> {
+ pub local: LocalResources<'a>,
+ pub shared: SharedResources<'a>,
+}
Execution context
+local: LocalResources<'a>
Local Resources this task has access to
+Shared Resources this task has access to
+pub struct __rtic_internal_settings_updateLocalResources<'a> {
+ pub afes: &'a mut (AFE0, AFE1),
+}
Local resources settings_update
has access to
afes: &'a mut (AFE0, AFE1)
Local resource afes
pub struct __rtic_internal_settings_updateSharedResources<'a> {
+ pub network: network_that_needs_to_be_locked<'a>,
+ pub settings: settings_that_needs_to_be_locked<'a>,
+ pub signal_generator: signal_generator_that_needs_to_be_locked<'a>,
+}
Shared resources settings_update
has access to
network: network_that_needs_to_be_locked<'a>
Resource proxy resource network
. Use method .lock()
to gain access
settings: settings_that_needs_to_be_locked<'a>
Resource proxy resource settings
. Use method .lock()
to gain access
signal_generator: signal_generator_that_needs_to_be_locked<'a>
Resource proxy resource signal_generator
. Use method .lock()
to gain access
pub struct __rtic_internal_settings_update_Context<'a> {
+ pub local: LocalResources<'a>,
+ pub shared: SharedResources<'a>,
+}
Execution context
+local: LocalResources<'a>
Local Resources this task has access to
+Shared Resources this task has access to
+pub struct __rtic_internal_spi2_Context {}
Execution context
+pub struct __rtic_internal_spi3_Context {}
Execution context
+pub struct __rtic_internal_spi4_Context {}
Execution context
+pub struct __rtic_internal_spi5_Context {}
Execution context
+pub struct __rtic_internal_startLocalResources<'a> {
+ pub sampling_timer: &'a mut SamplingTimer,
+}
Local resources start
has access to
sampling_timer: &'a mut SamplingTimer
Local resource sampling_timer
pub struct __rtic_internal_start_Context<'a> {
+ pub local: LocalResources<'a>,
+}
Execution context
+local: LocalResources<'a>
Local Resources this task has access to
+pub struct __rtic_internal_telemetryLocalResources<'a> {
+ pub cpu_temp_sensor: &'a mut CpuTempSensor,
+}
Local resources telemetry
has access to
cpu_temp_sensor: &'a mut CpuTempSensor
Local resource cpu_temp_sensor
pub struct __rtic_internal_telemetrySharedResources<'a> {
+ pub network: network_that_needs_to_be_locked<'a>,
+ pub settings: settings_that_needs_to_be_locked<'a>,
+ pub telemetry: telemetry_that_needs_to_be_locked<'a>,
+}
Shared resources telemetry
has access to
network: network_that_needs_to_be_locked<'a>
Resource proxy resource network
. Use method .lock()
to gain access
settings: settings_that_needs_to_be_locked<'a>
Resource proxy resource settings
. Use method .lock()
to gain access
telemetry: telemetry_that_needs_to_be_locked<'a>
Resource proxy resource telemetry
. Use method .lock()
to gain access
pub struct __rtic_internal_telemetry_Context<'a> {
+ pub local: LocalResources<'a>,
+ pub shared: SharedResources<'a>,
+}
Execution context
+local: LocalResources<'a>
Local Resources this task has access to
+Shared Resources this task has access to
+pub struct __rtic_internal_usbSharedResources<'a> {
+ pub usb_terminal: usb_terminal_that_needs_to_be_locked<'a>,
+}
Shared resources usb
has access to
usb_terminal: usb_terminal_that_needs_to_be_locked<'a>
Resource proxy resource usb_terminal
. Use method .lock()
to gain access
pub struct __rtic_internal_usb_Context<'a> {
+ pub shared: SharedResources<'a>,
+}
Execution context
+Shared Resources this task has access to
+Software task
+pub use Monotonic::spawn_after;
pub use Monotonic::spawn_at;
pub use Monotonic::SpawnHandle;
telemetry
has access totelemetry
has access topub struct Context<'a> {
+ pub local: LocalResources<'a>,
+ pub shared: SharedResources<'a>,
+}
Execution context
+local: LocalResources<'a>
Local Resources this task has access to
+Shared Resources this task has access to
+pub struct LocalResources<'a> {
+ pub cpu_temp_sensor: &'a mut CpuTempSensor,
+}
Local resources telemetry
has access to
cpu_temp_sensor: &'a mut CpuTempSensor
Local resource cpu_temp_sensor
pub struct SharedResources<'a> {
+ pub network: network_that_needs_to_be_locked<'a>,
+ pub settings: settings_that_needs_to_be_locked<'a>,
+ pub telemetry: telemetry_that_needs_to_be_locked<'a>,
+}
Shared resources telemetry
has access to
network: network_that_needs_to_be_locked<'a>
Resource proxy resource network
. Use method .lock()
to gain access
settings: settings_that_needs_to_be_locked<'a>
Resource proxy resource settings
. Use method .lock()
to gain access
telemetry: telemetry_that_needs_to_be_locked<'a>
Resource proxy resource telemetry
. Use method .lock()
to gain access
Software task
+pub use Monotonic::spawn_after;
pub use Monotonic::spawn_at;
pub use Monotonic::SpawnHandle;
usb
has access topub struct Context<'a> {
+ pub shared: SharedResources<'a>,
+}
Execution context
+Shared Resources this task has access to
+pub struct SharedResources<'a> {
+ pub usb_terminal: usb_terminal_that_needs_to_be_locked<'a>,
+}
Shared resources usb
has access to
usb_terminal: usb_terminal_that_needs_to_be_locked<'a>
Resource proxy resource usb_terminal
. Use method .lock()
to gain access
pub(crate) const BATCH_SIZE: usize = 8;
pub(crate) const IIR_CASCADE_LENGTH: usize = 1;
pub(crate) const SAMPLE_PERIOD: f32 = _; // 1.27999999E-6f32
pub(crate) const SAMPLE_TICKS: u32 = _; // 128u32
pub(crate) const SAMPLE_TICKS_LOG2: u8 = 7;
The Dual IIR application exposes two configurable channels. Stabilizer samples input at a fixed +rate, digitally filters the data, and then generates filtered output signals on the respective +channel outputs.
+Refer to the Settings structure for documentation of run-time configurable settings for this +application.
+Refer to Telemetry for information about telemetry reported by this application.
+This application streams raw ADC and DAC data over UDP. Refer to +stabilizer::net::data_stream for more information.
+pub struct Settings {
+ pub(crate) afe: [Gain; 2],
+ pub(crate) iir_ch: [[IIR<f32>; 1]; 2],
+ pub(crate) allow_hold: bool,
+ pub(crate) force_hold: bool,
+ pub(crate) telemetry_period: u16,
+ pub(crate) stream_target: StreamTarget,
+ pub(crate) signal_generator: [BasicConfig; 2],
+}
afe: [Gain; 2]
Configure the Analog Front End (AFE) gain.
+afe/<n>
<n>
specifies which channel to configure. <n>
:= [0, 1]Any of the variants of Gain enclosed in double quotes.
+iir_ch: [[IIR<f32>; 1]; 2]
Configure the IIR filter parameters.
+iir_ch/<n>/<m>
<n>
specifies which channel to configure. <n>
:= [0, 1]<m>
specifies which cascade to configure. <m>
:= [0, 1], depending on IIR_CASCADE_LENGTHSee iir::IIR
+allow_hold: bool
Specified true if DI1 should be used as a “hold” input.
+allow_hold
“true” or “false”
+force_hold: bool
Specified true if “hold” should be forced regardless of DI1 state and hold allowance.
+force_hold
“true” or “false”
+telemetry_period: u16
Specifies the telemetry output period in seconds.
+telemetry_period
Any non-zero value less than 65536.
+stream_target: StreamTarget
§signal_generator: [BasicConfig; 2]
Specifies the config for signal generators to add on to DAC0/DAC1 outputs.
+signal_generator/<n>
<n>
specifies which channel to configure. <n>
:= [0, 1]indices
. Read moreRedirecting to ../../idsp/struct.Accu.html...
+ + + \ No newline at end of file diff --git a/firmware/idsp/all.html b/firmware/idsp/all.html new file mode 100644 index 0000000000..fab5581b53 --- /dev/null +++ b/firmware/idsp/all.html @@ -0,0 +1 @@ +Redirecting to ../../idsp/fn.atan2.html...
+ + + \ No newline at end of file diff --git a/firmware/idsp/complex/struct.Complex.html b/firmware/idsp/complex/struct.Complex.html new file mode 100644 index 0000000000..f35ad587c7 --- /dev/null +++ b/firmware/idsp/complex/struct.Complex.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../idsp/struct.Complex.html...
+ + + \ No newline at end of file diff --git a/firmware/idsp/complex/trait.ComplexExt.html b/firmware/idsp/complex/trait.ComplexExt.html new file mode 100644 index 0000000000..67c86964a4 --- /dev/null +++ b/firmware/idsp/complex/trait.ComplexExt.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../idsp/trait.ComplexExt.html...
+ + + \ No newline at end of file diff --git a/firmware/idsp/complex/trait.MulScaled.html b/firmware/idsp/complex/trait.MulScaled.html new file mode 100644 index 0000000000..60a9d55ba3 --- /dev/null +++ b/firmware/idsp/complex/trait.MulScaled.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../idsp/trait.MulScaled.html...
+ + + \ No newline at end of file diff --git a/firmware/idsp/cossin/fn.cossin.html b/firmware/idsp/cossin/fn.cossin.html new file mode 100644 index 0000000000..1b3c54abd4 --- /dev/null +++ b/firmware/idsp/cossin/fn.cossin.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../idsp/fn.cossin.html...
+ + + \ No newline at end of file diff --git a/firmware/idsp/filter/struct.Cascade.html b/firmware/idsp/filter/struct.Cascade.html new file mode 100644 index 0000000000..1ef560069a --- /dev/null +++ b/firmware/idsp/filter/struct.Cascade.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../idsp/struct.Cascade.html...
+ + + \ No newline at end of file diff --git a/firmware/idsp/filter/struct.Chain.html b/firmware/idsp/filter/struct.Chain.html new file mode 100644 index 0000000000..0339d65fe4 --- /dev/null +++ b/firmware/idsp/filter/struct.Chain.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../idsp/struct.Chain.html...
+ + + \ No newline at end of file diff --git a/firmware/idsp/filter/struct.Nyquist.html b/firmware/idsp/filter/struct.Nyquist.html new file mode 100644 index 0000000000..2dd1cfc2cd --- /dev/null +++ b/firmware/idsp/filter/struct.Nyquist.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../idsp/struct.Nyquist.html...
+ + + \ No newline at end of file diff --git a/firmware/idsp/filter/trait.Filter.html b/firmware/idsp/filter/trait.Filter.html new file mode 100644 index 0000000000..f40a9dd97f --- /dev/null +++ b/firmware/idsp/filter/trait.Filter.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../idsp/trait.Filter.html...
+ + + \ No newline at end of file diff --git a/firmware/idsp/fn.abs.html b/firmware/idsp/fn.abs.html new file mode 100644 index 0000000000..880863a66b --- /dev/null +++ b/firmware/idsp/fn.abs.html @@ -0,0 +1,2 @@ +pub fn atan2(y: i32, x: i32) -> i32
2-argument arctangent function.
+This implementation uses all integer arithmetic for fast +computation.
+y
- Y-axis component.x
- X-axis component.The angle between the x-axis and the ray to the point (x,y). The +result range is from i32::MIN to i32::MAX, where i32::MIN +represents -pi and, equivalently, +pi. i32::MAX represents one +count less than +pi.
+pub fn cossin(phase: i32) -> (i32, i32)
Compute the cosine and sine of an angle. +This is ported from the MiSoC cossin core. +https://github.com/m-labs/misoc/blob/master/misoc/cores/cossin.py
+phase
- 32-bit phase.The cos and sin values of the provided phase as a (i32, i32)
+tuple. With a 7-bit deep LUT there is 9e-6 max and 4e-6 RMS error
+in each quadrature over 20 bit phase.
pub fn overflowing_sub<T>(y: T, x: T) -> (T, i32)where
+ T: WrappingSub + Zero + PartialOrd,
Subtract y - x
with signed overflow.
This is very similar to i32::overflowing_sub(y, x)
except that the
+overflow indicator is not a boolean but the signum of the overflow.
+Additionally it’s typically faster.
Returns:
+A tuple containg the (wrapped) difference y - x
and the signum of the
+overflow.
pub fn saturating_scale(lo: i32, hi: i32, shift: u32) -> i32
Combine high and low i32 into a single downscaled i32, saturating monotonically.
+Args:
+lo
: LSB i32 to scale down by shift
and range-extend with hi
+hi
: MSB i32 to scale up and extend lo
with. Output will be clipped if
+hi
exceeds the output i32 range.
+shift
: Downscale lo
by that many bits. Values from 1 to 32 inclusive
+are valid.
pub const HBF_CASCADE_BLOCK: usize = _; // 64usize
Max low-rate block size (HbfIntCascade input, HbfDecCascade output)
+pub const HBF_PASSBAND: f32 = 0.4;
Passband width in units of lowest sample rate
+pub const HBF_TAPS_98: ([f32; 15], [f32; 6], [f32; 3], [f32; 3], [f32; 2]);
Standard/optimal half-band filter cascade taps
+2*signal.remez(4*n - 1, bands=(0, .5-df/2, .5+df/2, 1), desired=(1, 0), fs=2, grid_density=512)[:2*n:2]
HBF_TAPS_98
.pub struct HbfDec<'a, const M: usize, const N: usize> { /* private fields */ }
Half band decimator (decimate by two)
+The effective number of DSP taps is 4*M - 1.
+M: number of taps +N: state size: N = 2*M - 1 + output.len()
+pub struct HbfDecCascade { /* private fields */ }
Half-band decimation filter cascade with optimal taps
+See HBF_TAPS. +Only in-place processing is implemented. +Supports rate changes of 1, 2, 4, 8, and 16.
+source
. Read morepub struct HbfInt<'a, const M: usize, const N: usize> { /* private fields */ }
Half band interpolator (interpolation rate 2)
+The effective number of DSP taps is 4*M - 1.
+M: number of taps +N: state size: N = 2*M - 1 + input.len()
+pub struct HbfIntCascade {
+ pub stages: (HbfInt<'static, { _ }, { _ }>, HbfInt<'static, { _ }, { _ }>, HbfInt<'static, { _ }, { _ }>, HbfInt<'static, { _ }, { _ }>),
+ /* private fields */
+}
Half-band interpolation filter cascade with optimal taps.
+This is a no_alloc version without trait objects. +The price to pay is fixed and maximal memory usage independent +of block size and cascade length.
+See HBF_TAPS. +Only in-place processing is implemented. +Supports rate changes of 1, 2, 4, 8, and 16.
+stages: (HbfInt<'static, { _ }, { _ }>, HbfInt<'static, { _ }, { _ }>, HbfInt<'static, { _ }, { _ }>, HbfInt<'static, { _ }, { _ }>)
source
. Read morepub struct SymFir<'a, const M: usize, const N: usize> { /* private fields */ }
Symmetric FIR filter prototype.
+M
: number of taps, one-sided. The filter has effectively 2*M DSP tapsN
: state size: N = 2*M - 1 + {input/output}.len()Half-band filters (rate change of 2) and cascades of HBFs are implemented in
+HbfDec
and HbfInt
etc.
+The half-band filter has unique properties that make it preferrable in many cases:
The implementations here are all no_std
and no-alloc
.
+They support (but don’t require) in-place filtering to reduce memory usage.
+They unroll and optimmize extremely well targetting current architectures,
+e.g. requiring less than 4 instructions per input item for the full HbfDecCascade
on Skylake.
+The filters are optimized for decent block sizes and perform best (i.e. with negligible
+overhead) for blocks of 32 high-rate items or more, depending very much on architecture.
Create a new SymFir
.
taps
: one-sided FIR coefficients, expluding center tap, oldest to one-before-centerObtain a mutable reference to the input items buffer space.
+Perform the FIR convolution and yield results iteratively.
+Move items as new filter state.
+offset
: Keep the 2*M-1
items at offset
as the new filter state.pub trait Filter {
+ type Item;
+
+ // Required methods
+ fn process_block<'a>(
+ &mut self,
+ x: Option<&[Self::Item]>,
+ y: &'a mut [Self::Item]
+ ) -> &'a mut [Self::Item];
+ fn block_size(&self) -> (usize, usize);
+ fn response_length(&self) -> usize;
+}
Filter input items into output items.
+Process a block of items.
+Input items can be either in x
or in y
.
+In the latter case the filtering operation is done in-place.
+Output is always written into y
.
+The slice of items written into y
is returned.
+Input and output size relations must match the filter requirements
+(decimation/interpolation and maximum block size).
+When using in-place operation, y
needs to contain the input items
+(fewer than y.len()
in the case of interpolation) and must be able to
+contain the output items.
Return the block size granularity and the maximum block size.
+For in-place processing, this refers to constraints on y
.
+Otherwise this refers to the larger of x
and y
(x
for decimation and y
for interpolation).
+The granularity is also the rate change in the case of interpolation/decimation filters.
Finite impulse response length in numer of output items minus one +Get this many to drain all previous memory
+pub struct IIR<T> {
+ pub ba: Vec5<T>,
+ pub y_offset: T,
+ pub y_min: T,
+ pub y_max: T,
+}
IIR configuration.
+Contains the coeeficients ba
, the output offset y_offset
, and the
+output limits y_min
and y_max
. Data is represented in variable precision
+floating-point. The dataformat is the same for all internal signals, input
+and output.
This implementation achieves several important properties:
+{"y_offset": y_offset, "y_min": y_min, "y_max": y_max, "ba": [b0, b1, b2, a1, a2]}
y0
is the output offset codeym
is the lower saturation limityM
is the upper saturation limitIIR filter tap gains (ba
) are an array [b0, b1, b2, a1, a2]
such that the
+new output is computed as y0 = a1*y1 + a2*y2 + b0*x0 + b1*x1 + b2*x2
.
+The IIR coefficients can be mapped to other transfer function
+representations, for example as described in https://arxiv.org/abs/1508.06319
ba: Vec5<T>
§y_offset: T
§y_min: T
§y_max: T
Configures IIR filter coefficients for proportional-integral behavior +with gain limit.
+kp
- Proportional gain. Also defines gain sign.ki
- Integral gain at Nyquist. Sign taken from kp
.g
- Gain limit.Convert input (x
) offset to equivalent output (y
) offset and apply.
xo
: Input (x
) offset.pub type Vec5<T> = [T; 5];
IIR state and coefficients type.
+To represent the IIR state (input and output memory) during the filter update +this contains the three inputs (x0, x1, x2) and the two outputs (y1, y2) +concatenated. Lower indices correspond to more recent samples. +To represent the IIR coefficients, this contains the feed-forward +coefficients (b0, b1, b2) followd by the negated feed-back coefficients +(-a1, -a2), all five normalized such that a0 = 1.
+pub struct IIR {
+ pub ba: Vec5,
+ pub y_offset: i32,
+ pub y_min: i32,
+ pub y_max: i32,
+}
Integer biquad IIR
+See dsp::iir::IIR
for general implementation details.
+Offset and limiting disabled to suit lowpass applications.
+Coefficient scaling fixed and optimized.
ba: Vec5
§y_offset: i32
§y_min: i32
§y_max: i32
y - x
with signed overflow.Redirecting to ../../idsp/struct.Lockin.html...
+ + + \ No newline at end of file diff --git a/firmware/idsp/lowpass/struct.Lowpass.html b/firmware/idsp/lowpass/struct.Lowpass.html new file mode 100644 index 0000000000..6032756d16 --- /dev/null +++ b/firmware/idsp/lowpass/struct.Lowpass.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../idsp/struct.Lowpass.html...
+ + + \ No newline at end of file diff --git a/firmware/idsp/lowpass/type.Lowpass1.html b/firmware/idsp/lowpass/type.Lowpass1.html new file mode 100644 index 0000000000..df807601a8 --- /dev/null +++ b/firmware/idsp/lowpass/type.Lowpass1.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../idsp/type.Lowpass1.html...
+ + + \ No newline at end of file diff --git a/firmware/idsp/lowpass/type.Lowpass2.html b/firmware/idsp/lowpass/type.Lowpass2.html new file mode 100644 index 0000000000..586164772e --- /dev/null +++ b/firmware/idsp/lowpass/type.Lowpass2.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../idsp/type.Lowpass2.html...
+ + + \ No newline at end of file diff --git a/firmware/idsp/pll/struct.PLL.html b/firmware/idsp/pll/struct.PLL.html new file mode 100644 index 0000000000..08d8ffe9e3 --- /dev/null +++ b/firmware/idsp/pll/struct.PLL.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../idsp/struct.PLL.html...
+ + + \ No newline at end of file diff --git a/firmware/idsp/rpll/struct.RPLL.html b/firmware/idsp/rpll/struct.RPLL.html new file mode 100644 index 0000000000..a4532964e4 --- /dev/null +++ b/firmware/idsp/rpll/struct.RPLL.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../idsp/struct.RPLL.html...
+ + + \ No newline at end of file diff --git a/firmware/idsp/sidebar-items.js b/firmware/idsp/sidebar-items.js new file mode 100644 index 0000000000..0118472262 --- /dev/null +++ b/firmware/idsp/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["abs","atan2","copysign","cossin","macc","macc_i32","overflowing_sub","saturating_scale"],"mod":["hbf","iir","iir_int"],"struct":["Accu","Cascade","Chain","Complex","Lockin","Lowpass","Nyquist","PLL","RPLL","Unwrapper"],"trait":["ComplexExt","Filter","MulScaled"],"type":["Lowpass1","Lowpass2"]}; \ No newline at end of file diff --git a/firmware/idsp/struct.Accu.html b/firmware/idsp/struct.Accu.html new file mode 100644 index 0000000000..af39c3132a --- /dev/null +++ b/firmware/idsp/struct.Accu.html @@ -0,0 +1,198 @@ +pub struct Accu<T> { /* private fields */ }
iter_next_chunk
)N
values. Read moreiter_advance_by
)n
elements. Read moren
th element of the iterator. Read moreiter_intersperse
)separator
+between adjacent items of the original iterator. Read moren
elements. Read moren
elements, or fewer
+if the underlying iterator ends sooner. Read moreiter_map_windows
)f
for each contiguous window of size N
over
+self
and returns an iterator over the outputs of f
. Like slice::windows()
,
+the windows during mapping overlap as well. Read moreiter_collect_into
)iter_is_partitioned
)true
precede all those that return false
. Read moreiterator_try_reduce
)try_find
)iter_array_chunks
)N
elements of the iterator at a time. Read moreiter_order_by
)Iterator
with those
+of another with respect to the specified comparison function. Read morePartialOrd
elements of
+this Iterator
with those of another. The comparison works like short-circuit
+evaluation, returning a result without comparing the remaining elements.
+As soon as an order can be determined, the evaluation stops and a result is returned. Read moreiter_order_by
)Iterator
with those
+of another with respect to the specified comparison function. Read moreiter_order_by
)Iterator
are lexicographically
+less than those of another. Read moreIterator
are lexicographically
+less or equal to those of another. Read moreIterator
are lexicographically
+greater than those of another. Read moreIterator
are lexicographically
+greater than or equal to those of another. Read moreis_sorted
)is_sorted
)pub struct Cascade<T, U>(_, _);
pub struct Chain<const N: usize, T>(_);
#[repr(C)]pub struct Complex<T> {
+ pub re: T,
+ pub im: T,
+}
A complex number in Cartesian form.
+Complex<T>
is memory layout compatible with an array [T; 2]
.
Note that Complex<F>
where F is a floating point type is only memory
+layout compatible with C’s complex types, not necessarily calling
+convention compatible. This means that for FFI you can only pass
+Complex<F>
behind a pointer, not as a value.
Example of extern function declaration.
+ +use num_complex::Complex;
+use std::os::raw::c_int;
+
+extern "C" {
+ fn zaxpy_(n: *const c_int, alpha: *const Complex<f64>,
+ x: *const Complex<f64>, incx: *const c_int,
+ y: *mut Complex<f64>, incy: *const c_int);
+}
re: T
Real portion of the complex number
+im: T
Imaginary portion of the complex number
+Returns the L1 norm |re| + |im|
– the Manhattan distance from the origin.
+=
operation. Read more+=
operation. Read more+=
operation. Read more+=
operation. Read moreReturn a Complex on the unit circle given an angle.
+Example:
+ +use idsp::{Complex, ComplexExt};
+Complex::<i32>::from_angle(0);
+Complex::<i32>::from_angle(1 << 30); // pi/2
+Complex::<i32>::from_angle(-1 << 30); // -pi/2
Return the absolute square (the squared magnitude).
+Note: Normalization is 1 << 32
, i.e. U0.32.
Note(panic): This will panic for Complex(i32::MIN, i32::MIN)
Example:
+ +use idsp::{Complex, ComplexExt};
+assert_eq!(Complex::new(i32::MIN, 0).abs_sqr(), 1 << 31);
+assert_eq!(Complex::new(i32::MAX, i32::MAX).abs_sqr(), u32::MAX - 3);
log2(power) re full scale approximation
+TODO: scale up, interpolate
+Panic:
+This will panic for Complex(i32::MIN, i32::MIN)
Example:
+ +use idsp::{Complex, ComplexExt};
+assert_eq!(Complex::new(i32::MAX, i32::MAX).log2(), -1);
+assert_eq!(Complex::new(i32::MAX, 0).log2(), -2);
+assert_eq!(Complex::new(1, 0).log2(), -63);
+assert_eq!(Complex::new(0, 0).log2(), -64);
Return the angle.
+Note: Normalization is 1 << 31 == pi
.
Example:
+ +use idsp::{Complex, ComplexExt};
+assert_eq!(Complex::new(0, 0).arg(), 0);
/=
operation. Read more/=
operation. Read more/=
operation. Read more/=
operation. Read moreusize
to return an optional value of this type. If the
+value cannot be represented by this type, then None
is returned.isize
to return an optional value of this type. If the
+value cannot be represented by this type, then None
is returned.u8
to return an optional value of this type. If the
+value cannot be represented by this type, then None
is returned.u16
to return an optional value of this type. If the
+value cannot be represented by this type, then None
is returned.u32
to return an optional value of this type. If the
+value cannot be represented by this type, then None
is returned.u64
to return an optional value of this type. If the
+value cannot be represented by this type, then None
is returned.i8
to return an optional value of this type. If the
+value cannot be represented by this type, then None
is returned.i16
to return an optional value of this type. If the
+value cannot be represented by this type, then None
is returned.i32
to return an optional value of this type. If the
+value cannot be represented by this type, then None
is returned.i64
to return an optional value of this type. If the
+value cannot be represented by this type, then None
is returned.u128
to return an optional value of this type. If the
+value cannot be represented by this type, then None
is returned. Read morei128
to return an optional value of this type. If the
+value cannot be represented by this type, then None
is returned. Read more*self = (*self * a) + b
*self = (*self * a) + b
*=
operation. Read more*=
operation. Read more*=
operation. Read more*=
operation. Read moreParses a +/- bi
; ai +/- b
; a
; or bi
where a
and b
are of type T
radix
must be <= 18; larger radix would include i and j as digits,
+which cannot be supported.
The conversion returns an error if 18 <= radix <= 36; it panics if radix > 36.
+The elements of T
are parsed using Num::from_str_radix
too, and errors
+(or panics) from that are reflected here as well.
%=
operation. Read more%=
operation. Read more%=
operation. Read more%=
operation. Read more-=
operation. Read more-=
operation. Read more-=
operation. Read more-=
operation. Read moreself
to a usize
. If the value cannot be
+represented by a usize
, then None
is returned.self
to an isize
. If the value cannot be
+represented by an isize
, then None
is returned.self
to a u8
. If the value cannot be
+represented by a u8
, then None
is returned.self
to a u16
. If the value cannot be
+represented by a u16
, then None
is returned.self
to a u32
. If the value cannot be
+represented by a u32
, then None
is returned.self
to a u64
. If the value cannot be
+represented by a u64
, then None
is returned.self
to an i8
. If the value cannot be
+represented by an i8
, then None
is returned.self
to an i16
. If the value cannot be
+represented by an i16
, then None
is returned.self
to an i32
. If the value cannot be
+represented by an i32
, then None
is returned.self
to an i64
. If the value cannot be
+represented by an i64
, then None
is returned.self
to a u128
. If the value cannot be
+represented by a u128
(u64
under the default implementation), then
+None
is returned. Read moreself
to an i128
. If the value cannot be
+represented by an i128
(i64
under the default implementation), then
+None
is returned. Read morepub struct Lockin<T> { /* private fields */ }
pub struct Lowpass<const N: usize>(_);
Arbitrary order, high dynamic range, wide coefficient range, +lowpass filter implementation. DC gain is 1.
+Type argument N is the filter order. N must be 1
or 2
.
The filter will cleanly saturate towards the i32
range.
Both filters have been optimized for accuracy, dynamic range, and +speed on Cortex-M7.
+The filter configuration Config
contains the filter gains.
For the first-order lowpass this is a single element array [k]
with
+the corner frequency in scaled Q31:
+k = pi*(1 << 31)*f0/fn
where
+f0
is the 3dB corner frequency and
+fn
is the Nyquist frequency.
+The corner frequency is warped in the usual way.
For the second-order lowpass this is [k**2/(1 << 32), -k/q]
with q = 1/sqrt(2)
+for a Butterworth response.
In addition to the poles at the corner frequency the filters have zeros at Nyquist.
+The first-order lowpass works fine and accurate for any positive gain
+1 <= k <= (1 << 31) - 1
.
+The second-order lowpass works and is accurate for
+1 << 16 <= k <= q*(1 << 31)
.
pub struct Nyquist(_);
pub struct PLL { /* private fields */ }
Type-II, sampled phase, discrete time PLL
+This PLL tracks the frequency and phase of an input signal with respect to the sampling clock. +The open loop transfer function is I^2,I from input phase to output phase and P,I from input +phase to output frequency.
+The transfer functions (for phase and frequency) contain an additional zero at Nyquist.
+The PLL locks to any frequency (i.e. it locks to the alias in the first Nyquist zone) and is +stable for any gain (1 <= shift <= 30). It has a single parameter that determines the loop +bandwidth in octave steps. The gain can be changed freely between updates.
+The frequency and phase settling time constants for a frequency/phase jump are 1 << shift
+update cycles. The loop bandwidth is 1/(2*pi*(1 << shift))
in units of the sample rate.
+While the phase is being settled after settling the frequency, there is a typically very
+small frequency overshoot.
All math is naturally wrapping 32 bit integer. Phase and frequency are understood modulo that +overflow in the first Nyquist zone. Expressing the IIR equations in other ways (e.g. single +(T)-DF-{I,II} biquad/IIR) would break on overflow (i.e. every cycle).
+There are no floating point rounding errors here. But there is integer quantization/truncation
+error of the shift
lowest bits leading to a phase offset for very low gains. Truncation
+bias is applied. Rounding is “half up”. The phase truncation error can be removed very
+efficiently by dithering.
This PLL does not unwrap phase slips accumulated during (frequency) lock acquisition. +This can and should be implemented elsewhere by unwrapping and scaling the input phase +and un-scaling and wrapping output phase and frequency. This then affects dynamic range, +gain, and noise accordingly.
+The extension to I^3,I^2,I behavior to track chirps phase-accurately or to i64 data to +increase resolution for extremely narrowband applications is obvious.
+Update the PLL with a new phase sample. This needs to be called (sampled) periodically. +The signal’s phase/frequency is reconstructed relative to the sampling period.
+Args:
+x
: New input phase sample or None if a sample has been missed.k
: Feedback gain.Returns: +A tuple of instantaneous phase and frequency estimates.
+pub struct RPLL { /* private fields */ }
Reciprocal PLL.
+Consumes noisy, quantized timestamps of a reference signal and reconstructs
+the phase and frequency of the update() invocations with respect to (and in units of
+1 << 32 of) that reference.
+In other words, update()
rate ralative to reference frequency,
+u32::MAX
corresponding to both being equal.
Create a new RPLL instance.
+Args:
+Returns: +Initialized RPLL instance.
+Advance the RPLL and optionally supply a new timestamp.
+Args:
+update()
cycle (1 << dt2 counter cycles).shift_frequency
(see there).Returns: +A tuple containing the current phase (wrapping at the i32 boundary, pi) and +frequency.
+pub struct Unwrapper<T> { /* private fields */ }
Overflow unwrapper.
+This is unwrapping as in the phase and overflow unwrapping context, not
+unwrapping as in the Result
/Option
context.
Redirecting to ../../idsp/fn.abs.html...
+ + + \ No newline at end of file diff --git a/firmware/idsp/tools/fn.copysign.html b/firmware/idsp/tools/fn.copysign.html new file mode 100644 index 0000000000..9b62625a60 --- /dev/null +++ b/firmware/idsp/tools/fn.copysign.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../idsp/fn.copysign.html...
+ + + \ No newline at end of file diff --git a/firmware/idsp/tools/fn.macc.html b/firmware/idsp/tools/fn.macc.html new file mode 100644 index 0000000000..03715c19e2 --- /dev/null +++ b/firmware/idsp/tools/fn.macc.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../idsp/fn.macc.html...
+ + + \ No newline at end of file diff --git a/firmware/idsp/tools/fn.macc_i32.html b/firmware/idsp/tools/fn.macc_i32.html new file mode 100644 index 0000000000..59fbdc3a89 --- /dev/null +++ b/firmware/idsp/tools/fn.macc_i32.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../idsp/fn.macc_i32.html...
+ + + \ No newline at end of file diff --git a/firmware/idsp/trait.ComplexExt.html b/firmware/idsp/trait.ComplexExt.html new file mode 100644 index 0000000000..b79ae43141 --- /dev/null +++ b/firmware/idsp/trait.ComplexExt.html @@ -0,0 +1,10 @@ +pub trait ComplexExt<T, U> {
+ // Required methods
+ fn from_angle(angle: T) -> Self;
+ fn abs_sqr(&self) -> U;
+ fn log2(&self) -> T;
+ fn arg(&self) -> T;
+ fn saturating_add(&self, other: Self) -> Self;
+ fn saturating_sub(&self, other: Self) -> Self;
+}
Complex extension trait offering DSP (fast, good accuracy) functionality.
+pub trait Filter {
+ type Config;
+
+ // Required methods
+ fn update(&mut self, x: i32, k: &Self::Config) -> i32;
+ fn get(&self) -> i32;
+ fn set(&mut self, x: i32);
+}
pub trait MulScaled<T> {
+ // Required method
+ fn mul_scaled(self, other: T) -> Self;
+}
Full scale fixed point multiplication.
+Redirecting to ../../idsp/fn.overflowing_sub.html...
+ + + \ No newline at end of file diff --git a/firmware/idsp/unwrap/fn.saturating_scale.html b/firmware/idsp/unwrap/fn.saturating_scale.html new file mode 100644 index 0000000000..a31e78d7ce --- /dev/null +++ b/firmware/idsp/unwrap/fn.saturating_scale.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../idsp/fn.saturating_scale.html...
+ + + \ No newline at end of file diff --git a/firmware/idsp/unwrap/struct.Unwrapper.html b/firmware/idsp/unwrap/struct.Unwrapper.html new file mode 100644 index 0000000000..27f2996bba --- /dev/null +++ b/firmware/idsp/unwrap/struct.Unwrapper.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../idsp/struct.Unwrapper.html...
+ + + \ No newline at end of file diff --git a/firmware/implementors/ad9959/trait.Interface.js b/firmware/implementors/ad9959/trait.Interface.js new file mode 100644 index 0000000000..311de467aa --- /dev/null +++ b/firmware/implementors/ad9959/trait.Interface.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"stabilizer":[["impl Interface for QspiInterface"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/bitflags/traits/trait.Flags.js b/firmware/implementors/bitflags/traits/trait.Flags.js new file mode 100644 index 0000000000..01b62e4a5a --- /dev/null +++ b/firmware/implementors/bitflags/traits/trait.Flags.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"ad9959":[["impl Flags for Channel"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/bitflags/traits/trait.PublicFlags.js b/firmware/implementors/bitflags/traits/trait.PublicFlags.js new file mode 100644 index 0000000000..00f3a1c1cd --- /dev/null +++ b/firmware/implementors/bitflags/traits/trait.PublicFlags.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"ad9959":[["impl PublicFlags for Channel"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/core/clone/trait.Clone.js b/firmware/implementors/core/clone/trait.Clone.js new file mode 100644 index 0000000000..3aa4afd8b8 --- /dev/null +++ b/firmware/implementors/core/clone/trait.Clone.js @@ -0,0 +1,8 @@ +(function() {var implementors = { +"ad9959":[["impl Clone for Mode"]], +"dual_iir":[["impl Clone for Settings"]], +"idsp":[["impl Clone for HbfDecCascade"],["impl Clone for Nyquist"],["impl<'a, const M: usize, const N: usize> Clone for SymFir<'a, M, N>"],["impl Clone for PLL"],["impl<T: Clone> Clone for Accu<T>"],["impl Clone for HbfIntCascade"],["impl<'a, const M: usize, const N: usize> Clone for HbfDec<'a, M, N>"],["impl Clone for RPLL"],["impl<T: Clone, U: Clone> Clone for Cascade<T, U>"],["impl<T: Clone> Clone for IIR<T>"],["impl<T: Clone> Clone for Unwrapper<T>"],["impl<'a, const M: usize, const N: usize> Clone for HbfInt<'a, M, N>"],["impl<const N: usize, T: Clone> Clone for Chain<N, T>"],["impl<const N: usize> Clone for Lowpass<N>"],["impl Clone for IIR"],["impl<T: Clone> Clone for Lockin<T>"]], +"lockin":[["impl Clone for Settings"],["impl Clone for Conf"],["impl Clone for LockinMode"]], +"miniconf":[["impl<'a, M: Clone + ?Sized, const Y: usize, P: Clone> Clone for PathIter<'a, M, Y, P>"],["impl Clone for Metadata"],["impl<E: Clone> Clone for Error<E>"],["impl Clone for SliceShort"]], +"stabilizer":[["impl Clone for ChannelState"],["impl Clone for BasicConfig"],["impl Clone for Error"],["impl Clone for DacCode"],["impl Clone for StreamFormat"],["impl Clone for TelemetryBuffer"],["impl Clone for TcpSocketStorage"],["impl Clone for UdpSocketStorage"],["impl Clone for DdsClockConfig"],["impl Clone for InputChannelState"],["impl Clone for StreamTarget"],["impl Clone for AdcError"],["impl Clone for DdsChannelState"],["impl Clone for Channel"],["impl Clone for GpioPin"],["impl Clone for AdcCode"],["impl Clone for Config"],["impl Clone for OutputChannelState"],["impl Clone for Error"],["impl Clone for Gain"],["impl Clone for Signal"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/core/cmp/trait.Eq.js b/firmware/implementors/core/cmp/trait.Eq.js new file mode 100644 index 0000000000..6740245edc --- /dev/null +++ b/firmware/implementors/core/cmp/trait.Eq.js @@ -0,0 +1,6 @@ +(function() {var implementors = { +"ad9959":[["impl Eq for Mode"]], +"idsp":[["impl<T: Eq> Eq for Accu<T>"]], +"miniconf":[["impl<'a, M: Eq + ?Sized, const Y: usize, P: Eq> Eq for PathIter<'a, M, Y, P>"],["impl Eq for Metadata"],["impl<E: Eq> Eq for Error<E>"],["impl Eq for SliceShort"]], +"stabilizer":[["impl Eq for StreamFormat"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/core/cmp/trait.PartialEq.js b/firmware/implementors/core/cmp/trait.PartialEq.js new file mode 100644 index 0000000000..8020d5adcd --- /dev/null +++ b/firmware/implementors/core/cmp/trait.PartialEq.js @@ -0,0 +1,7 @@ +(function() {var implementors = { +"ad9959":[["impl PartialEq<Mode> for Mode"]], +"idsp":[["impl<T: PartialEq> PartialEq<Accu<T>> for Accu<T>"]], +"lockin":[["impl PartialEq<LockinMode> for LockinMode"]], +"miniconf":[["impl PartialEq<Metadata> for Metadata"],["impl<E: PartialEq> PartialEq<Error<E>> for Error<E>"],["impl<'a, M: PartialEq + ?Sized, const Y: usize, P: PartialEq> PartialEq<PathIter<'a, M, Y, P>> for PathIter<'a, M, Y, P>"],["impl PartialEq<SliceShort> for SliceShort"]], +"stabilizer":[["impl PartialEq<StreamFormat> for StreamFormat"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/core/convert/trait.From.js b/firmware/implementors/core/convert/trait.From.js new file mode 100644 index 0000000000..383ebdaa08 --- /dev/null +++ b/firmware/implementors/core/convert/trait.From.js @@ -0,0 +1,4 @@ +(function() {var implementors = { +"miniconf":[["impl<T> From<T> for Error<T>"]], +"stabilizer":[["impl From<i16> for AdcCode"],["impl From<XspiError> for Error"],["impl From<AdcCode> for u16"],["impl From<AdcCode> for f32"],["impl From<Channel> for Channel"],["impl From<DacCode> for i16"],["impl From<StreamTarget> for SocketAddr"],["impl From<u16> for AdcCode"],["impl From<i16> for DacCode"],["impl From<u16> for DacCode"],["impl From<Channel> for GpioPin"],["impl From<StreamFormat> for u8"],["impl From<AdcCode> for i16"],["impl From<DacCode> for f32"],["impl From<GpioPin> for Mcp23017"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/core/convert/trait.TryFrom.js b/firmware/implementors/core/convert/trait.TryFrom.js new file mode 100644 index 0000000000..c0b6153bd8 --- /dev/null +++ b/firmware/implementors/core/convert/trait.TryFrom.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"stabilizer":[["impl TryFrom<f32> for DacCode"],["impl TryFrom<u8> for Prescaler"],["impl TryFrom<f32> for AdcCode"],["impl TryFrom<u8> for Gain"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/core/default/trait.Default.js b/firmware/implementors/core/default/trait.Default.js new file mode 100644 index 0000000000..2e35320078 --- /dev/null +++ b/firmware/implementors/core/default/trait.Default.js @@ -0,0 +1,7 @@ +(function() {var implementors = { +"dual_iir":[["impl Default for Settings"]], +"idsp":[["impl Default for PLL"],["impl Default for IIR"],["impl Default for HbfDecCascade"],["impl Default for HbfIntCascade"],["impl<T: Default, U: Default> Default for Cascade<T, U>"],["impl<const N: usize, T: Default + Copy> Default for Chain<N, T>"],["impl<T: Default> Default for Accu<T>"],["impl<T: Default> Default for Unwrapper<T>"],["impl Default for Nyquist"],["impl<const N: usize> Default for Lowpass<N>"],["impl<T: Default> Default for Lockin<T>"],["impl<T: Default> Default for IIR<T>"],["impl Default for RPLL"]], +"lockin":[["impl Default for Settings"]], +"miniconf":[["impl Default for Metadata"]], +"stabilizer":[["impl Default for StreamTarget"],["impl Default for MqttStorage"],["impl Default for BasicConfig"],["impl Default for TelemetryBuffer"],["impl Default for Config"],["impl Default for NetStorage"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/core/fmt/trait.Binary.js b/firmware/implementors/core/fmt/trait.Binary.js new file mode 100644 index 0000000000..ea72d7cebb --- /dev/null +++ b/firmware/implementors/core/fmt/trait.Binary.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"ad9959":[["impl Binary for Channel"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/core/fmt/trait.Debug.js b/firmware/implementors/core/fmt/trait.Debug.js new file mode 100644 index 0000000000..414a5b1419 --- /dev/null +++ b/firmware/implementors/core/fmt/trait.Debug.js @@ -0,0 +1,8 @@ +(function() {var implementors = { +"ad9959":[["impl Debug for Error"]], +"dual_iir":[["impl Debug for Settings"]], +"idsp":[["impl<T: Debug> Debug for Accu<T>"],["impl<T: Debug> Debug for IIR<T>"],["impl<'a, const M: usize, const N: usize> Debug for SymFir<'a, M, N>"],["impl<'a, const M: usize, const N: usize> Debug for HbfInt<'a, M, N>"],["impl Debug for HbfIntCascade"],["impl Debug for IIR"],["impl Debug for HbfDecCascade"],["impl<'a, const M: usize, const N: usize> Debug for HbfDec<'a, M, N>"]], +"lockin":[["impl Debug for LockinMode"],["impl Debug for Settings"],["impl Debug for Conf"]], +"miniconf":[["impl<'a, M: Debug + ?Sized, const Y: usize, P: Debug> Debug for PathIter<'a, M, Y, P>"],["impl Debug for SliceShort"],["impl<E: Debug> Debug for Error<E>"],["impl Debug for Metadata"]], +"stabilizer":[["impl Debug for DdsClockConfig"],["impl Debug for GpioPin"],["impl Debug for OutputChannelState"],["impl Debug for StreamTarget"],["impl Debug for BasicConfig"],["impl Debug for Error"],["impl Debug for DdsChannelState"],["impl Debug for Gain"],["impl Debug for Error"],["impl Debug for ChannelState"],["impl Debug for SignalGenerator"],["impl Debug for Signal"],["impl Debug for StreamFormat"],["impl Debug for Channel"],["impl Debug for InputChannelState"],["impl Debug for Config"],["impl Debug for AdcError"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/core/fmt/trait.Display.js b/firmware/implementors/core/fmt/trait.Display.js new file mode 100644 index 0000000000..a6b8791822 --- /dev/null +++ b/firmware/implementors/core/fmt/trait.Display.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"miniconf":[["impl<E: Display> Display for Error<E>"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/core/fmt/trait.LowerHex.js b/firmware/implementors/core/fmt/trait.LowerHex.js new file mode 100644 index 0000000000..17570bd5d6 --- /dev/null +++ b/firmware/implementors/core/fmt/trait.LowerHex.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"ad9959":[["impl LowerHex for Channel"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/core/fmt/trait.Octal.js b/firmware/implementors/core/fmt/trait.Octal.js new file mode 100644 index 0000000000..c17b0bbcfb --- /dev/null +++ b/firmware/implementors/core/fmt/trait.Octal.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"ad9959":[["impl Octal for Channel"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/core/fmt/trait.UpperHex.js b/firmware/implementors/core/fmt/trait.UpperHex.js new file mode 100644 index 0000000000..ba7a517314 --- /dev/null +++ b/firmware/implementors/core/fmt/trait.UpperHex.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"ad9959":[["impl UpperHex for Channel"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/core/fmt/trait.Write.js b/firmware/implementors/core/fmt/trait.Write.js new file mode 100644 index 0000000000..600593df6b --- /dev/null +++ b/firmware/implementors/core/fmt/trait.Write.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"stabilizer":[["impl Write for OutputBuffer"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/core/iter/traits/collect/trait.Extend.js b/firmware/implementors/core/iter/traits/collect/trait.Extend.js new file mode 100644 index 0000000000..e9aaa3377c --- /dev/null +++ b/firmware/implementors/core/iter/traits/collect/trait.Extend.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"ad9959":[["impl Extend<Channel> for Channel"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/core/iter/traits/collect/trait.FromIterator.js b/firmware/implementors/core/iter/traits/collect/trait.FromIterator.js new file mode 100644 index 0000000000..3795482627 --- /dev/null +++ b/firmware/implementors/core/iter/traits/collect/trait.FromIterator.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"ad9959":[["impl FromIterator<Channel> for Channel"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/core/iter/traits/collect/trait.IntoIterator.js b/firmware/implementors/core/iter/traits/collect/trait.IntoIterator.js new file mode 100644 index 0000000000..996f90d549 --- /dev/null +++ b/firmware/implementors/core/iter/traits/collect/trait.IntoIterator.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"ad9959":[["impl IntoIterator for Channel"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/core/iter/traits/iterator/trait.Iterator.js b/firmware/implementors/core/iter/traits/iterator/trait.Iterator.js new file mode 100644 index 0000000000..e827c4e607 --- /dev/null +++ b/firmware/implementors/core/iter/traits/iterator/trait.Iterator.js @@ -0,0 +1,5 @@ +(function() {var implementors = { +"idsp":[["impl<T> Iterator for Accu<T>where\n T: WrappingAdd + Copy,"]], +"miniconf":[["impl<'a, M, const Y: usize, P> Iterator for PathIter<'a, M, Y, P>where\n M: TreeKey<Y> + ?Sized,\n P: Write + Default,"]], +"stabilizer":[["impl Iterator for SignalGenerator"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/core/iter/traits/marker/trait.FusedIterator.js b/firmware/implementors/core/iter/traits/marker/trait.FusedIterator.js new file mode 100644 index 0000000000..f103c2e28e --- /dev/null +++ b/firmware/implementors/core/iter/traits/marker/trait.FusedIterator.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"miniconf":[["impl<'a, M, const Y: usize, P> FusedIterator for PathIter<'a, M, Y, P>where\n M: TreeKey<Y>,\n P: Write + Default,"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/core/marker/trait.Copy.js b/firmware/implementors/core/marker/trait.Copy.js new file mode 100644 index 0000000000..0cdc62834f --- /dev/null +++ b/firmware/implementors/core/marker/trait.Copy.js @@ -0,0 +1,8 @@ +(function() {var implementors = { +"ad9959":[["impl Copy for Mode"]], +"dual_iir":[["impl Copy for Settings"]], +"idsp":[["impl<T: Copy> Copy for Accu<T>"],["impl<T: Copy> Copy for IIR<T>"],["impl Copy for Nyquist"],["impl Copy for HbfDecCascade"],["impl Copy for HbfIntCascade"],["impl Copy for IIR"],["impl Copy for RPLL"],["impl<const N: usize> Copy for Lowpass<N>"],["impl<const N: usize, T: Copy> Copy for Chain<N, T>"],["impl<'a, const M: usize, const N: usize> Copy for SymFir<'a, M, N>"],["impl<'a, const M: usize, const N: usize> Copy for HbfDec<'a, M, N>"],["impl<T: Copy, U: Copy> Copy for Cascade<T, U>"],["impl<T: Copy> Copy for Lockin<T>"],["impl Copy for PLL"],["impl<T: Copy> Copy for Unwrapper<T>"],["impl<'a, const M: usize, const N: usize> Copy for HbfInt<'a, M, N>"]], +"lockin":[["impl Copy for Conf"],["impl Copy for Settings"],["impl Copy for LockinMode"]], +"miniconf":[["impl Copy for SliceShort"],["impl<'a, M: Copy + ?Sized, const Y: usize, P: Copy> Copy for PathIter<'a, M, Y, P>"],["impl<E: Copy> Copy for Error<E>"],["impl Copy for Metadata"]], +"stabilizer":[["impl Copy for Error"],["impl Copy for OutputChannelState"],["impl Copy for Channel"],["impl Copy for DacCode"],["impl Copy for AdcError"],["impl Copy for Config"],["impl Copy for StreamFormat"],["impl Copy for TcpSocketStorage"],["impl Copy for DdsClockConfig"],["impl Copy for StreamTarget"],["impl Copy for Error"],["impl Copy for Gain"],["impl Copy for BasicConfig"],["impl Copy for ChannelState"],["impl Copy for TelemetryBuffer"],["impl Copy for UdpSocketStorage"],["impl Copy for DdsChannelState"],["impl Copy for InputChannelState"],["impl Copy for GpioPin"],["impl Copy for Signal"],["impl Copy for AdcCode"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/core/marker/trait.Freeze.js b/firmware/implementors/core/marker/trait.Freeze.js new file mode 100644 index 0000000000..63e72b6d29 --- /dev/null +++ b/firmware/implementors/core/marker/trait.Freeze.js @@ -0,0 +1,8 @@ +(function() {var implementors = { +"ad9959":[["impl<INTERFACE> Freeze for Ad9959<INTERFACE>where\n INTERFACE: Freeze,",1,["ad9959::Ad9959"]],["impl Freeze for Mode",1,["ad9959::Mode"]],["impl Freeze for Channel",1,["ad9959::Channel"]],["impl Freeze for Register",1,["ad9959::Register"]],["impl Freeze for Error",1,["ad9959::Error"]],["impl Freeze for ProfileSerializer",1,["ad9959::ProfileSerializer"]]], +"dual_iir":[["impl Freeze for __rtic_internal_Monotonics",1,["dual_iir::app::__rtic_internal_Monotonics"]],["impl<'a> Freeze for __rtic_internal_init_Context<'a>",1,["dual_iir::app::__rtic_internal_init_Context"]],["impl<'a> Freeze for __rtic_internal_idleSharedResources<'a>",1,["dual_iir::app::__rtic_internal_idleSharedResources"]],["impl<'a> Freeze for __rtic_internal_idle_Context<'a>",1,["dual_iir::app::__rtic_internal_idle_Context"]],["impl<'a> Freeze for __rtic_internal_processLocalResources<'a>",1,["dual_iir::app::__rtic_internal_processLocalResources"]],["impl<'a> Freeze for __rtic_internal_processSharedResources<'a>",1,["dual_iir::app::__rtic_internal_processSharedResources"]],["impl<'a> Freeze for __rtic_internal_process_Context<'a>",1,["dual_iir::app::__rtic_internal_process_Context"]],["impl Freeze for __rtic_internal_eth_Context",1,["dual_iir::app::__rtic_internal_eth_Context"]],["impl Freeze for __rtic_internal_spi2_Context",1,["dual_iir::app::__rtic_internal_spi2_Context"]],["impl Freeze for __rtic_internal_spi3_Context",1,["dual_iir::app::__rtic_internal_spi3_Context"]],["impl Freeze for __rtic_internal_spi4_Context",1,["dual_iir::app::__rtic_internal_spi4_Context"]],["impl Freeze for __rtic_internal_spi5_Context",1,["dual_iir::app::__rtic_internal_spi5_Context"]],["impl<'a> Freeze for __rtic_internal_startLocalResources<'a>",1,["dual_iir::app::__rtic_internal_startLocalResources"]],["impl<'a> Freeze for __rtic_internal_start_Context<'a>",1,["dual_iir::app::__rtic_internal_start_Context"]],["impl<'a> Freeze for __rtic_internal_settings_updateLocalResources<'a>",1,["dual_iir::app::__rtic_internal_settings_updateLocalResources"]],["impl<'a> Freeze for __rtic_internal_settings_updateSharedResources<'a>",1,["dual_iir::app::__rtic_internal_settings_updateSharedResources"]],["impl<'a> Freeze for __rtic_internal_settings_update_Context<'a>",1,["dual_iir::app::__rtic_internal_settings_update_Context"]],["impl<'a> Freeze for __rtic_internal_telemetryLocalResources<'a>",1,["dual_iir::app::__rtic_internal_telemetryLocalResources"]],["impl<'a> Freeze for __rtic_internal_telemetrySharedResources<'a>",1,["dual_iir::app::__rtic_internal_telemetrySharedResources"]],["impl<'a> Freeze for __rtic_internal_telemetry_Context<'a>",1,["dual_iir::app::__rtic_internal_telemetry_Context"]],["impl<'a> Freeze for __rtic_internal_usbSharedResources<'a>",1,["dual_iir::app::__rtic_internal_usbSharedResources"]],["impl<'a> Freeze for __rtic_internal_usb_Context<'a>",1,["dual_iir::app::__rtic_internal_usb_Context"]],["impl<'a> Freeze for __rtic_internal_ethernet_linkSharedResources<'a>",1,["dual_iir::app::__rtic_internal_ethernet_linkSharedResources"]],["impl<'a> Freeze for __rtic_internal_ethernet_link_Context<'a>",1,["dual_iir::app::__rtic_internal_ethernet_link_Context"]],["impl Freeze for Shared",1,["dual_iir::app::Shared"]],["impl Freeze for Local",1,["dual_iir::app::Local"]],["impl Freeze for Settings",1,["dual_iir::Settings"]]], +"idsp":[["impl<T> Freeze for Accu<T>where\n T: Freeze,",1,["idsp::accu::Accu"]],["impl Freeze for Nyquist",1,["idsp::filter::Nyquist"]],["impl<const N: usize, T> Freeze for Chain<N, T>where\n T: Freeze,",1,["idsp::filter::Chain"]],["impl<T, U> Freeze for Cascade<T, U>where\n T: Freeze,\n U: Freeze,",1,["idsp::filter::Cascade"]],["impl<T> Freeze for IIR<T>where\n T: Freeze,",1,["idsp::iir::IIR"]],["impl Freeze for IIR",1,["idsp::iir_int::IIR"]],["impl<T> Freeze for Lockin<T>where\n T: Freeze,",1,["idsp::lockin::Lockin"]],["impl<const N: usize> Freeze for Lowpass<N>",1,["idsp::lowpass::Lowpass"]],["impl Freeze for PLL",1,["idsp::pll::PLL"]],["impl Freeze for RPLL",1,["idsp::rpll::RPLL"]],["impl<T> Freeze for Unwrapper<T>where\n T: Freeze,",1,["idsp::unwrap::Unwrapper"]],["impl<'a, const M: usize, const N: usize> Freeze for SymFir<'a, M, N>",1,["idsp::hbf::SymFir"]],["impl<'a, const M: usize, const N: usize> Freeze for HbfDec<'a, M, N>",1,["idsp::hbf::HbfDec"]],["impl<'a, const M: usize, const N: usize> Freeze for HbfInt<'a, M, N>",1,["idsp::hbf::HbfInt"]],["impl Freeze for HbfDecCascade",1,["idsp::hbf::HbfDecCascade"]],["impl Freeze for HbfIntCascade",1,["idsp::hbf::HbfIntCascade"]]], +"lockin":[["impl Freeze for __rtic_internal_Monotonics",1,["lockin::app::__rtic_internal_Monotonics"]],["impl<'a> Freeze for __rtic_internal_init_Context<'a>",1,["lockin::app::__rtic_internal_init_Context"]],["impl<'a> Freeze for __rtic_internal_idleSharedResources<'a>",1,["lockin::app::__rtic_internal_idleSharedResources"]],["impl<'a> Freeze for __rtic_internal_idle_Context<'a>",1,["lockin::app::__rtic_internal_idle_Context"]],["impl<'a> Freeze for __rtic_internal_processLocalResources<'a>",1,["lockin::app::__rtic_internal_processLocalResources"]],["impl<'a> Freeze for __rtic_internal_processSharedResources<'a>",1,["lockin::app::__rtic_internal_processSharedResources"]],["impl<'a> Freeze for __rtic_internal_process_Context<'a>",1,["lockin::app::__rtic_internal_process_Context"]],["impl Freeze for __rtic_internal_eth_Context",1,["lockin::app::__rtic_internal_eth_Context"]],["impl<'a> Freeze for __rtic_internal_startLocalResources<'a>",1,["lockin::app::__rtic_internal_startLocalResources"]],["impl<'a> Freeze for __rtic_internal_start_Context<'a>",1,["lockin::app::__rtic_internal_start_Context"]],["impl<'a> Freeze for __rtic_internal_settings_updateLocalResources<'a>",1,["lockin::app::__rtic_internal_settings_updateLocalResources"]],["impl<'a> Freeze for __rtic_internal_settings_updateSharedResources<'a>",1,["lockin::app::__rtic_internal_settings_updateSharedResources"]],["impl<'a> Freeze for __rtic_internal_settings_update_Context<'a>",1,["lockin::app::__rtic_internal_settings_update_Context"]],["impl<'a> Freeze for __rtic_internal_telemetryLocalResources<'a>",1,["lockin::app::__rtic_internal_telemetryLocalResources"]],["impl<'a> Freeze for __rtic_internal_telemetrySharedResources<'a>",1,["lockin::app::__rtic_internal_telemetrySharedResources"]],["impl<'a> Freeze for __rtic_internal_telemetry_Context<'a>",1,["lockin::app::__rtic_internal_telemetry_Context"]],["impl<'a> Freeze for __rtic_internal_usbSharedResources<'a>",1,["lockin::app::__rtic_internal_usbSharedResources"]],["impl<'a> Freeze for __rtic_internal_usb_Context<'a>",1,["lockin::app::__rtic_internal_usb_Context"]],["impl<'a> Freeze for __rtic_internal_ethernet_linkSharedResources<'a>",1,["lockin::app::__rtic_internal_ethernet_linkSharedResources"]],["impl<'a> Freeze for __rtic_internal_ethernet_link_Context<'a>",1,["lockin::app::__rtic_internal_ethernet_link_Context"]],["impl Freeze for Shared",1,["lockin::app::Shared"]],["impl Freeze for Local",1,["lockin::app::Local"]],["impl Freeze for Conf",1,["lockin::Conf"]],["impl Freeze for LockinMode",1,["lockin::LockinMode"]],["impl Freeze for Settings",1,["lockin::Settings"]]], +"miniconf":[["impl<E> Freeze for Error<E>where\n E: Freeze,",1,["miniconf::tree::Error"]],["impl Freeze for SliceShort",1,["miniconf::tree::SliceShort"]],["impl Freeze for Metadata",1,["miniconf::tree::Metadata"]],["impl<'a, M: ?Sized, const Y: usize, P> Freeze for PathIter<'a, M, Y, P>",1,["miniconf::iter::PathIter"]],["impl<'buf, Settings, Stack, Clock, Broker, const Y: usize> Freeze for MqttClient<'buf, Settings, Stack, Clock, Broker, Y>where\n Broker: Freeze,\n Clock: Freeze,\n Settings: Freeze,\n Stack: Freeze,\n <Clock as Clock>::T: Freeze,\n <Stack as TcpClientStack>::TcpSocket: Freeze,",1,["miniconf::mqtt_client::MqttClient"]]], +"stabilizer":[["impl Freeze for AdcCode",1,["stabilizer::hardware::adc::AdcCode"]],["impl Freeze for Adc0Input",1,["stabilizer::hardware::adc::Adc0Input"]],["impl Freeze for Adc1Input",1,["stabilizer::hardware::adc::Adc1Input"]],["impl Freeze for Gain",1,["stabilizer::hardware::afe::Gain"]],["impl<A0, A1> Freeze for ProgrammableGainAmplifier<A0, A1>where\n A0: Freeze,\n A1: Freeze,",1,["stabilizer::hardware::afe::ProgrammableGainAmplifier"]],["impl Freeze for CpuTempSensor",1,["stabilizer::hardware::cpu_temp_sensor::CpuTempSensor"]],["impl Freeze for DacCode",1,["stabilizer::hardware::dac::DacCode"]],["impl Freeze for Dac0Output",1,["stabilizer::hardware::dac::Dac0Output"]],["impl Freeze for Dac1Output",1,["stabilizer::hardware::dac::Dac1Output"]],["impl Freeze for AsmDelay",1,["stabilizer::hardware::delay::AsmDelay"]],["impl Freeze for InputStamper",1,["stabilizer::hardware::input_stamper::InputStamper"]],["impl Freeze for DdsOutput",1,["stabilizer::hardware::pounder::dds_output::DdsOutput"]],["impl<'a> Freeze for ProfileBuilder<'a>",1,["stabilizer::hardware::pounder::dds_output::ProfileBuilder"]],["impl Freeze for Channel",1,["stabilizer::hardware::pounder::hrtimer::Channel"]],["impl Freeze for HighResTimerE",1,["stabilizer::hardware::pounder::hrtimer::HighResTimerE"]],["impl Freeze for Timestamper",1,["stabilizer::hardware::pounder::timestamp::Timestamper"]],["impl Freeze for GpioPin",1,["stabilizer::hardware::pounder::GpioPin"]],["impl Freeze for Error",1,["stabilizer::hardware::pounder::Error"]],["impl Freeze for Channel",1,["stabilizer::hardware::pounder::Channel"]],["impl Freeze for DdsChannelState",1,["stabilizer::hardware::pounder::DdsChannelState"]],["impl Freeze for ChannelState",1,["stabilizer::hardware::pounder::ChannelState"]],["impl Freeze for InputChannelState",1,["stabilizer::hardware::pounder::InputChannelState"]],["impl Freeze for OutputChannelState",1,["stabilizer::hardware::pounder::OutputChannelState"]],["impl Freeze for DdsClockConfig",1,["stabilizer::hardware::pounder::DdsClockConfig"]],["impl Freeze for QspiInterface",1,["stabilizer::hardware::pounder::QspiInterface"]],["impl Freeze for PounderDevices",1,["stabilizer::hardware::pounder::PounderDevices"]],["impl Freeze for OutputBuffer",1,["stabilizer::hardware::serial_terminal::OutputBuffer"]],["impl Freeze for SerialTerminal",1,["stabilizer::hardware::serial_terminal::SerialTerminal"]],["impl Freeze for NetStorage",1,["stabilizer::hardware::setup::NetStorage"]],["impl Freeze for UdpSocketStorage",1,["stabilizer::hardware::setup::UdpSocketStorage"]],["impl Freeze for TcpSocketStorage",1,["stabilizer::hardware::setup::TcpSocketStorage"]],["impl Freeze for NetworkDevices",1,["stabilizer::hardware::setup::NetworkDevices"]],["impl Freeze for EemGpioDevices",1,["stabilizer::hardware::setup::EemGpioDevices"]],["impl Freeze for StabilizerDevices",1,["stabilizer::hardware::setup::StabilizerDevices"]],["impl Freeze for PounderDevices",1,["stabilizer::hardware::setup::PounderDevices"]],["impl Freeze for AdcError",1,["stabilizer::hardware::shared_adc::AdcError"]],["impl<'a, Adc, PIN> Freeze for AdcChannel<'a, Adc, PIN>where\n PIN: Freeze,",1,["stabilizer::hardware::shared_adc::AdcChannel"]],["impl<Adc> !Freeze for SharedAdc<Adc>",1,["stabilizer::hardware::shared_adc::SharedAdc"]],["impl Freeze for Signal",1,["stabilizer::hardware::signal_generator::Signal"]],["impl Freeze for BasicConfig",1,["stabilizer::hardware::signal_generator::BasicConfig"]],["impl Freeze for Error",1,["stabilizer::hardware::signal_generator::Error"]],["impl Freeze for Config",1,["stabilizer::hardware::signal_generator::Config"]],["impl Freeze for SignalGenerator",1,["stabilizer::hardware::signal_generator::SignalGenerator"]],["impl Freeze for UpdateEvent",1,["stabilizer::hardware::timers::tim2::UpdateEvent"]],["impl Freeze for Channels",1,["stabilizer::hardware::timers::tim2::Channels"]],["impl Freeze for Channel1",1,["stabilizer::hardware::timers::tim2::Channel1"]],["impl Freeze for Channel1InputCapture",1,["stabilizer::hardware::timers::tim2::Channel1InputCapture"]],["impl Freeze for Channel2",1,["stabilizer::hardware::timers::tim2::Channel2"]],["impl Freeze for Channel2InputCapture",1,["stabilizer::hardware::timers::tim2::Channel2InputCapture"]],["impl Freeze for Channel3",1,["stabilizer::hardware::timers::tim2::Channel3"]],["impl Freeze for Channel3InputCapture",1,["stabilizer::hardware::timers::tim2::Channel3InputCapture"]],["impl Freeze for Channel4",1,["stabilizer::hardware::timers::tim2::Channel4"]],["impl Freeze for Channel4InputCapture",1,["stabilizer::hardware::timers::tim2::Channel4InputCapture"]],["impl Freeze for UpdateEvent",1,["stabilizer::hardware::timers::tim3::UpdateEvent"]],["impl Freeze for Channels",1,["stabilizer::hardware::timers::tim3::Channels"]],["impl Freeze for Channel1",1,["stabilizer::hardware::timers::tim3::Channel1"]],["impl Freeze for Channel1InputCapture",1,["stabilizer::hardware::timers::tim3::Channel1InputCapture"]],["impl Freeze for Channel2",1,["stabilizer::hardware::timers::tim3::Channel2"]],["impl Freeze for Channel2InputCapture",1,["stabilizer::hardware::timers::tim3::Channel2InputCapture"]],["impl Freeze for Channel3",1,["stabilizer::hardware::timers::tim3::Channel3"]],["impl Freeze for Channel3InputCapture",1,["stabilizer::hardware::timers::tim3::Channel3InputCapture"]],["impl Freeze for Channel4",1,["stabilizer::hardware::timers::tim3::Channel4"]],["impl Freeze for Channel4InputCapture",1,["stabilizer::hardware::timers::tim3::Channel4InputCapture"]],["impl Freeze for UpdateEvent",1,["stabilizer::hardware::timers::tim5::UpdateEvent"]],["impl Freeze for Channels",1,["stabilizer::hardware::timers::tim5::Channels"]],["impl Freeze for Channel1",1,["stabilizer::hardware::timers::tim5::Channel1"]],["impl Freeze for Channel1InputCapture",1,["stabilizer::hardware::timers::tim5::Channel1InputCapture"]],["impl Freeze for Channel2",1,["stabilizer::hardware::timers::tim5::Channel2"]],["impl Freeze for Channel2InputCapture",1,["stabilizer::hardware::timers::tim5::Channel2InputCapture"]],["impl Freeze for Channel3",1,["stabilizer::hardware::timers::tim5::Channel3"]],["impl Freeze for Channel3InputCapture",1,["stabilizer::hardware::timers::tim5::Channel3InputCapture"]],["impl Freeze for Channel4",1,["stabilizer::hardware::timers::tim5::Channel4"]],["impl Freeze for Channel4InputCapture",1,["stabilizer::hardware::timers::tim5::Channel4InputCapture"]],["impl Freeze for UpdateEvent",1,["stabilizer::hardware::timers::tim8::UpdateEvent"]],["impl Freeze for Channels",1,["stabilizer::hardware::timers::tim8::Channels"]],["impl Freeze for Channel1",1,["stabilizer::hardware::timers::tim8::Channel1"]],["impl Freeze for Channel1InputCapture",1,["stabilizer::hardware::timers::tim8::Channel1InputCapture"]],["impl Freeze for Channel2",1,["stabilizer::hardware::timers::tim8::Channel2"]],["impl Freeze for Channel2InputCapture",1,["stabilizer::hardware::timers::tim8::Channel2InputCapture"]],["impl Freeze for Channel3",1,["stabilizer::hardware::timers::tim8::Channel3"]],["impl Freeze for Channel3InputCapture",1,["stabilizer::hardware::timers::tim8::Channel3InputCapture"]],["impl Freeze for Channel4",1,["stabilizer::hardware::timers::tim8::Channel4"]],["impl Freeze for Channel4InputCapture",1,["stabilizer::hardware::timers::tim8::Channel4InputCapture"]],["impl Freeze for TriggerGenerator",1,["stabilizer::hardware::timers::TriggerGenerator"]],["impl Freeze for TriggerSource",1,["stabilizer::hardware::timers::TriggerSource"]],["impl Freeze for Prescaler",1,["stabilizer::hardware::timers::Prescaler"]],["impl Freeze for SlaveMode",1,["stabilizer::hardware::timers::SlaveMode"]],["impl Freeze for InputFilter",1,["stabilizer::hardware::timers::InputFilter"]],["impl Freeze for SamplingTimer",1,["stabilizer::hardware::timers::SamplingTimer"]],["impl Freeze for ShadowSamplingTimer",1,["stabilizer::hardware::timers::ShadowSamplingTimer"]],["impl Freeze for TimestampTimer",1,["stabilizer::hardware::timers::TimestampTimer"]],["impl Freeze for PounderTimestampTimer",1,["stabilizer::hardware::timers::PounderTimestampTimer"]],["impl Freeze for StreamTarget",1,["stabilizer::net::data_stream::StreamTarget"]],["impl Freeze for StreamFormat",1,["stabilizer::net::data_stream::StreamFormat"]],["impl Freeze for FrameGenerator",1,["stabilizer::net::data_stream::FrameGenerator"]],["impl Freeze for DataStream",1,["stabilizer::net::data_stream::DataStream"]],["impl Freeze for NetworkProcessor",1,["stabilizer::net::network_processor::NetworkProcessor"]],["impl<T> Freeze for TelemetryClient<T>",1,["stabilizer::net::telemetry::TelemetryClient"]],["impl Freeze for TelemetryBuffer",1,["stabilizer::net::telemetry::TelemetryBuffer"]],["impl Freeze for Telemetry",1,["stabilizer::net::telemetry::Telemetry"]],["impl Freeze for MqttStorage",1,["stabilizer::net::MqttStorage"]],["impl Freeze for UpdateState",1,["stabilizer::net::UpdateState"]],["impl Freeze for NetworkState",1,["stabilizer::net::NetworkState"]],["impl<S, T, const Y: usize> Freeze for NetworkUsers<S, T, Y>where\n S: Freeze,",1,["stabilizer::net::NetworkUsers"]]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/core/marker/trait.Send.js b/firmware/implementors/core/marker/trait.Send.js new file mode 100644 index 0000000000..d4657c35ed --- /dev/null +++ b/firmware/implementors/core/marker/trait.Send.js @@ -0,0 +1,8 @@ +(function() {var implementors = { +"ad9959":[["impl<INTERFACE> Send for Ad9959<INTERFACE>where\n INTERFACE: Send,",1,["ad9959::Ad9959"]],["impl Send for Mode",1,["ad9959::Mode"]],["impl Send for Channel",1,["ad9959::Channel"]],["impl Send for Register",1,["ad9959::Register"]],["impl Send for Error",1,["ad9959::Error"]],["impl Send for ProfileSerializer",1,["ad9959::ProfileSerializer"]]], +"dual_iir":[["impl Send for __rtic_internal_Monotonics",1,["dual_iir::app::__rtic_internal_Monotonics"]],["impl<'a> Send for __rtic_internal_init_Context<'a>",1,["dual_iir::app::__rtic_internal_init_Context"]],["impl<'a> !Send for __rtic_internal_idleSharedResources<'a>",1,["dual_iir::app::__rtic_internal_idleSharedResources"]],["impl<'a> !Send for __rtic_internal_idle_Context<'a>",1,["dual_iir::app::__rtic_internal_idle_Context"]],["impl<'a> Send for __rtic_internal_processLocalResources<'a>",1,["dual_iir::app::__rtic_internal_processLocalResources"]],["impl<'a> !Send for __rtic_internal_processSharedResources<'a>",1,["dual_iir::app::__rtic_internal_processSharedResources"]],["impl<'a> !Send for __rtic_internal_process_Context<'a>",1,["dual_iir::app::__rtic_internal_process_Context"]],["impl Send for __rtic_internal_eth_Context",1,["dual_iir::app::__rtic_internal_eth_Context"]],["impl Send for __rtic_internal_spi2_Context",1,["dual_iir::app::__rtic_internal_spi2_Context"]],["impl Send for __rtic_internal_spi3_Context",1,["dual_iir::app::__rtic_internal_spi3_Context"]],["impl Send for __rtic_internal_spi4_Context",1,["dual_iir::app::__rtic_internal_spi4_Context"]],["impl Send for __rtic_internal_spi5_Context",1,["dual_iir::app::__rtic_internal_spi5_Context"]],["impl<'a> Send for __rtic_internal_startLocalResources<'a>",1,["dual_iir::app::__rtic_internal_startLocalResources"]],["impl<'a> Send for __rtic_internal_start_Context<'a>",1,["dual_iir::app::__rtic_internal_start_Context"]],["impl<'a> Send for __rtic_internal_settings_updateLocalResources<'a>",1,["dual_iir::app::__rtic_internal_settings_updateLocalResources"]],["impl<'a> !Send for __rtic_internal_settings_updateSharedResources<'a>",1,["dual_iir::app::__rtic_internal_settings_updateSharedResources"]],["impl<'a> !Send for __rtic_internal_settings_update_Context<'a>",1,["dual_iir::app::__rtic_internal_settings_update_Context"]],["impl<'a> Send for __rtic_internal_telemetryLocalResources<'a>",1,["dual_iir::app::__rtic_internal_telemetryLocalResources"]],["impl<'a> !Send for __rtic_internal_telemetrySharedResources<'a>",1,["dual_iir::app::__rtic_internal_telemetrySharedResources"]],["impl<'a> !Send for __rtic_internal_telemetry_Context<'a>",1,["dual_iir::app::__rtic_internal_telemetry_Context"]],["impl<'a> !Send for __rtic_internal_usbSharedResources<'a>",1,["dual_iir::app::__rtic_internal_usbSharedResources"]],["impl<'a> !Send for __rtic_internal_usb_Context<'a>",1,["dual_iir::app::__rtic_internal_usb_Context"]],["impl<'a> !Send for __rtic_internal_ethernet_linkSharedResources<'a>",1,["dual_iir::app::__rtic_internal_ethernet_linkSharedResources"]],["impl<'a> !Send for __rtic_internal_ethernet_link_Context<'a>",1,["dual_iir::app::__rtic_internal_ethernet_link_Context"]],["impl Send for Shared",1,["dual_iir::app::Shared"]],["impl Send for Local",1,["dual_iir::app::Local"]],["impl Send for Settings",1,["dual_iir::Settings"]]], +"idsp":[["impl<T> Send for Accu<T>where\n T: Send,",1,["idsp::accu::Accu"]],["impl Send for Nyquist",1,["idsp::filter::Nyquist"]],["impl<const N: usize, T> Send for Chain<N, T>where\n T: Send,",1,["idsp::filter::Chain"]],["impl<T, U> Send for Cascade<T, U>where\n T: Send,\n U: Send,",1,["idsp::filter::Cascade"]],["impl<T> Send for IIR<T>where\n T: Send,",1,["idsp::iir::IIR"]],["impl Send for IIR",1,["idsp::iir_int::IIR"]],["impl<T> Send for Lockin<T>where\n T: Send,",1,["idsp::lockin::Lockin"]],["impl<const N: usize> Send for Lowpass<N>",1,["idsp::lowpass::Lowpass"]],["impl Send for PLL",1,["idsp::pll::PLL"]],["impl Send for RPLL",1,["idsp::rpll::RPLL"]],["impl<T> Send for Unwrapper<T>where\n T: Send,",1,["idsp::unwrap::Unwrapper"]],["impl<'a, const M: usize, const N: usize> Send for SymFir<'a, M, N>",1,["idsp::hbf::SymFir"]],["impl<'a, const M: usize, const N: usize> Send for HbfDec<'a, M, N>",1,["idsp::hbf::HbfDec"]],["impl<'a, const M: usize, const N: usize> Send for HbfInt<'a, M, N>",1,["idsp::hbf::HbfInt"]],["impl Send for HbfDecCascade",1,["idsp::hbf::HbfDecCascade"]],["impl Send for HbfIntCascade",1,["idsp::hbf::HbfIntCascade"]]], +"lockin":[["impl Send for __rtic_internal_Monotonics",1,["lockin::app::__rtic_internal_Monotonics"]],["impl<'a> Send for __rtic_internal_init_Context<'a>",1,["lockin::app::__rtic_internal_init_Context"]],["impl<'a> !Send for __rtic_internal_idleSharedResources<'a>",1,["lockin::app::__rtic_internal_idleSharedResources"]],["impl<'a> !Send for __rtic_internal_idle_Context<'a>",1,["lockin::app::__rtic_internal_idle_Context"]],["impl<'a> Send for __rtic_internal_processLocalResources<'a>",1,["lockin::app::__rtic_internal_processLocalResources"]],["impl<'a> !Send for __rtic_internal_processSharedResources<'a>",1,["lockin::app::__rtic_internal_processSharedResources"]],["impl<'a> !Send for __rtic_internal_process_Context<'a>",1,["lockin::app::__rtic_internal_process_Context"]],["impl Send for __rtic_internal_eth_Context",1,["lockin::app::__rtic_internal_eth_Context"]],["impl<'a> Send for __rtic_internal_startLocalResources<'a>",1,["lockin::app::__rtic_internal_startLocalResources"]],["impl<'a> Send for __rtic_internal_start_Context<'a>",1,["lockin::app::__rtic_internal_start_Context"]],["impl<'a> Send for __rtic_internal_settings_updateLocalResources<'a>",1,["lockin::app::__rtic_internal_settings_updateLocalResources"]],["impl<'a> !Send for __rtic_internal_settings_updateSharedResources<'a>",1,["lockin::app::__rtic_internal_settings_updateSharedResources"]],["impl<'a> !Send for __rtic_internal_settings_update_Context<'a>",1,["lockin::app::__rtic_internal_settings_update_Context"]],["impl<'a> Send for __rtic_internal_telemetryLocalResources<'a>",1,["lockin::app::__rtic_internal_telemetryLocalResources"]],["impl<'a> !Send for __rtic_internal_telemetrySharedResources<'a>",1,["lockin::app::__rtic_internal_telemetrySharedResources"]],["impl<'a> !Send for __rtic_internal_telemetry_Context<'a>",1,["lockin::app::__rtic_internal_telemetry_Context"]],["impl<'a> !Send for __rtic_internal_usbSharedResources<'a>",1,["lockin::app::__rtic_internal_usbSharedResources"]],["impl<'a> !Send for __rtic_internal_usb_Context<'a>",1,["lockin::app::__rtic_internal_usb_Context"]],["impl<'a> !Send for __rtic_internal_ethernet_linkSharedResources<'a>",1,["lockin::app::__rtic_internal_ethernet_linkSharedResources"]],["impl<'a> !Send for __rtic_internal_ethernet_link_Context<'a>",1,["lockin::app::__rtic_internal_ethernet_link_Context"]],["impl Send for Shared",1,["lockin::app::Shared"]],["impl Send for Local",1,["lockin::app::Local"]],["impl Send for Conf",1,["lockin::Conf"]],["impl Send for LockinMode",1,["lockin::LockinMode"]],["impl Send for Settings",1,["lockin::Settings"]]], +"miniconf":[["impl<E> Send for Error<E>where\n E: Send,",1,["miniconf::tree::Error"]],["impl Send for SliceShort",1,["miniconf::tree::SliceShort"]],["impl Send for Metadata",1,["miniconf::tree::Metadata"]],["impl<'a, M: ?Sized, const Y: usize, P> Send for PathIter<'a, M, Y, P>where\n M: Send,\n P: Send,",1,["miniconf::iter::PathIter"]],["impl<'buf, Settings, Stack, Clock, Broker, const Y: usize> Send for MqttClient<'buf, Settings, Stack, Clock, Broker, Y>where\n Broker: Send,\n Clock: Send,\n Settings: Send,\n Stack: Send,\n <Clock as Clock>::T: Send,\n <Stack as TcpClientStack>::TcpSocket: Send,",1,["miniconf::mqtt_client::MqttClient"]]], +"stabilizer":[["impl Send for AdcCode",1,["stabilizer::hardware::adc::AdcCode"]],["impl Send for Adc0Input",1,["stabilizer::hardware::adc::Adc0Input"]],["impl Send for Adc1Input",1,["stabilizer::hardware::adc::Adc1Input"]],["impl Send for Gain",1,["stabilizer::hardware::afe::Gain"]],["impl<A0, A1> Send for ProgrammableGainAmplifier<A0, A1>where\n A0: Send,\n A1: Send,",1,["stabilizer::hardware::afe::ProgrammableGainAmplifier"]],["impl Send for CpuTempSensor",1,["stabilizer::hardware::cpu_temp_sensor::CpuTempSensor"]],["impl Send for DacCode",1,["stabilizer::hardware::dac::DacCode"]],["impl Send for Dac0Output",1,["stabilizer::hardware::dac::Dac0Output"]],["impl Send for Dac1Output",1,["stabilizer::hardware::dac::Dac1Output"]],["impl Send for AsmDelay",1,["stabilizer::hardware::delay::AsmDelay"]],["impl Send for InputStamper",1,["stabilizer::hardware::input_stamper::InputStamper"]],["impl Send for DdsOutput",1,["stabilizer::hardware::pounder::dds_output::DdsOutput"]],["impl<'a> Send for ProfileBuilder<'a>",1,["stabilizer::hardware::pounder::dds_output::ProfileBuilder"]],["impl Send for Channel",1,["stabilizer::hardware::pounder::hrtimer::Channel"]],["impl Send for HighResTimerE",1,["stabilizer::hardware::pounder::hrtimer::HighResTimerE"]],["impl Send for Timestamper",1,["stabilizer::hardware::pounder::timestamp::Timestamper"]],["impl Send for GpioPin",1,["stabilizer::hardware::pounder::GpioPin"]],["impl Send for Error",1,["stabilizer::hardware::pounder::Error"]],["impl Send for Channel",1,["stabilizer::hardware::pounder::Channel"]],["impl Send for DdsChannelState",1,["stabilizer::hardware::pounder::DdsChannelState"]],["impl Send for ChannelState",1,["stabilizer::hardware::pounder::ChannelState"]],["impl Send for InputChannelState",1,["stabilizer::hardware::pounder::InputChannelState"]],["impl Send for OutputChannelState",1,["stabilizer::hardware::pounder::OutputChannelState"]],["impl Send for DdsClockConfig",1,["stabilizer::hardware::pounder::DdsClockConfig"]],["impl Send for QspiInterface",1,["stabilizer::hardware::pounder::QspiInterface"]],["impl Send for PounderDevices",1,["stabilizer::hardware::pounder::PounderDevices"]],["impl Send for OutputBuffer",1,["stabilizer::hardware::serial_terminal::OutputBuffer"]],["impl Send for SerialTerminal",1,["stabilizer::hardware::serial_terminal::SerialTerminal"]],["impl Send for NetStorage",1,["stabilizer::hardware::setup::NetStorage"]],["impl Send for UdpSocketStorage",1,["stabilizer::hardware::setup::UdpSocketStorage"]],["impl Send for TcpSocketStorage",1,["stabilizer::hardware::setup::TcpSocketStorage"]],["impl Send for NetworkDevices",1,["stabilizer::hardware::setup::NetworkDevices"]],["impl Send for EemGpioDevices",1,["stabilizer::hardware::setup::EemGpioDevices"]],["impl Send for StabilizerDevices",1,["stabilizer::hardware::setup::StabilizerDevices"]],["impl Send for PounderDevices",1,["stabilizer::hardware::setup::PounderDevices"]],["impl Send for AdcError",1,["stabilizer::hardware::shared_adc::AdcError"]],["impl<'a, Adc, PIN> Send for AdcChannel<'a, Adc, PIN>where\n Adc: Send,\n PIN: Send,",1,["stabilizer::hardware::shared_adc::AdcChannel"]],["impl<Adc> Send for SharedAdc<Adc>where\n Adc: Send,",1,["stabilizer::hardware::shared_adc::SharedAdc"]],["impl Send for Signal",1,["stabilizer::hardware::signal_generator::Signal"]],["impl Send for BasicConfig",1,["stabilizer::hardware::signal_generator::BasicConfig"]],["impl Send for Error",1,["stabilizer::hardware::signal_generator::Error"]],["impl Send for Config",1,["stabilizer::hardware::signal_generator::Config"]],["impl Send for SignalGenerator",1,["stabilizer::hardware::signal_generator::SignalGenerator"]],["impl Send for UpdateEvent",1,["stabilizer::hardware::timers::tim2::UpdateEvent"]],["impl Send for Channels",1,["stabilizer::hardware::timers::tim2::Channels"]],["impl Send for Channel1",1,["stabilizer::hardware::timers::tim2::Channel1"]],["impl Send for Channel1InputCapture",1,["stabilizer::hardware::timers::tim2::Channel1InputCapture"]],["impl Send for Channel2",1,["stabilizer::hardware::timers::tim2::Channel2"]],["impl Send for Channel2InputCapture",1,["stabilizer::hardware::timers::tim2::Channel2InputCapture"]],["impl Send for Channel3",1,["stabilizer::hardware::timers::tim2::Channel3"]],["impl Send for Channel3InputCapture",1,["stabilizer::hardware::timers::tim2::Channel3InputCapture"]],["impl Send for Channel4",1,["stabilizer::hardware::timers::tim2::Channel4"]],["impl Send for Channel4InputCapture",1,["stabilizer::hardware::timers::tim2::Channel4InputCapture"]],["impl Send for UpdateEvent",1,["stabilizer::hardware::timers::tim3::UpdateEvent"]],["impl Send for Channels",1,["stabilizer::hardware::timers::tim3::Channels"]],["impl Send for Channel1",1,["stabilizer::hardware::timers::tim3::Channel1"]],["impl Send for Channel1InputCapture",1,["stabilizer::hardware::timers::tim3::Channel1InputCapture"]],["impl Send for Channel2",1,["stabilizer::hardware::timers::tim3::Channel2"]],["impl Send for Channel2InputCapture",1,["stabilizer::hardware::timers::tim3::Channel2InputCapture"]],["impl Send for Channel3",1,["stabilizer::hardware::timers::tim3::Channel3"]],["impl Send for Channel3InputCapture",1,["stabilizer::hardware::timers::tim3::Channel3InputCapture"]],["impl Send for Channel4",1,["stabilizer::hardware::timers::tim3::Channel4"]],["impl Send for Channel4InputCapture",1,["stabilizer::hardware::timers::tim3::Channel4InputCapture"]],["impl Send for UpdateEvent",1,["stabilizer::hardware::timers::tim5::UpdateEvent"]],["impl Send for Channels",1,["stabilizer::hardware::timers::tim5::Channels"]],["impl Send for Channel1",1,["stabilizer::hardware::timers::tim5::Channel1"]],["impl Send for Channel1InputCapture",1,["stabilizer::hardware::timers::tim5::Channel1InputCapture"]],["impl Send for Channel2",1,["stabilizer::hardware::timers::tim5::Channel2"]],["impl Send for Channel2InputCapture",1,["stabilizer::hardware::timers::tim5::Channel2InputCapture"]],["impl Send for Channel3",1,["stabilizer::hardware::timers::tim5::Channel3"]],["impl Send for Channel3InputCapture",1,["stabilizer::hardware::timers::tim5::Channel3InputCapture"]],["impl Send for Channel4",1,["stabilizer::hardware::timers::tim5::Channel4"]],["impl Send for Channel4InputCapture",1,["stabilizer::hardware::timers::tim5::Channel4InputCapture"]],["impl Send for UpdateEvent",1,["stabilizer::hardware::timers::tim8::UpdateEvent"]],["impl Send for Channels",1,["stabilizer::hardware::timers::tim8::Channels"]],["impl Send for Channel1",1,["stabilizer::hardware::timers::tim8::Channel1"]],["impl Send for Channel1InputCapture",1,["stabilizer::hardware::timers::tim8::Channel1InputCapture"]],["impl Send for Channel2",1,["stabilizer::hardware::timers::tim8::Channel2"]],["impl Send for Channel2InputCapture",1,["stabilizer::hardware::timers::tim8::Channel2InputCapture"]],["impl Send for Channel3",1,["stabilizer::hardware::timers::tim8::Channel3"]],["impl Send for Channel3InputCapture",1,["stabilizer::hardware::timers::tim8::Channel3InputCapture"]],["impl Send for Channel4",1,["stabilizer::hardware::timers::tim8::Channel4"]],["impl Send for Channel4InputCapture",1,["stabilizer::hardware::timers::tim8::Channel4InputCapture"]],["impl Send for TriggerGenerator",1,["stabilizer::hardware::timers::TriggerGenerator"]],["impl Send for TriggerSource",1,["stabilizer::hardware::timers::TriggerSource"]],["impl Send for Prescaler",1,["stabilizer::hardware::timers::Prescaler"]],["impl Send for SlaveMode",1,["stabilizer::hardware::timers::SlaveMode"]],["impl Send for InputFilter",1,["stabilizer::hardware::timers::InputFilter"]],["impl Send for SamplingTimer",1,["stabilizer::hardware::timers::SamplingTimer"]],["impl Send for ShadowSamplingTimer",1,["stabilizer::hardware::timers::ShadowSamplingTimer"]],["impl Send for TimestampTimer",1,["stabilizer::hardware::timers::TimestampTimer"]],["impl Send for PounderTimestampTimer",1,["stabilizer::hardware::timers::PounderTimestampTimer"]],["impl Send for StreamTarget",1,["stabilizer::net::data_stream::StreamTarget"]],["impl Send for StreamFormat",1,["stabilizer::net::data_stream::StreamFormat"]],["impl Send for FrameGenerator",1,["stabilizer::net::data_stream::FrameGenerator"]],["impl Send for DataStream",1,["stabilizer::net::data_stream::DataStream"]],["impl Send for NetworkProcessor",1,["stabilizer::net::network_processor::NetworkProcessor"]],["impl<T> Send for TelemetryClient<T>where\n T: Send,",1,["stabilizer::net::telemetry::TelemetryClient"]],["impl Send for TelemetryBuffer",1,["stabilizer::net::telemetry::TelemetryBuffer"]],["impl Send for Telemetry",1,["stabilizer::net::telemetry::Telemetry"]],["impl Send for MqttStorage",1,["stabilizer::net::MqttStorage"]],["impl Send for UpdateState",1,["stabilizer::net::UpdateState"]],["impl Send for NetworkState",1,["stabilizer::net::NetworkState"]],["impl<S, T, const Y: usize> Send for NetworkUsers<S, T, Y>where\n S: Send,\n T: Send,",1,["stabilizer::net::NetworkUsers"]]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/core/marker/trait.StructuralEq.js b/firmware/implementors/core/marker/trait.StructuralEq.js new file mode 100644 index 0000000000..916b54b615 --- /dev/null +++ b/firmware/implementors/core/marker/trait.StructuralEq.js @@ -0,0 +1,6 @@ +(function() {var implementors = { +"ad9959":[["impl StructuralEq for Mode"]], +"idsp":[["impl<T> StructuralEq for Accu<T>"]], +"miniconf":[["impl StructuralEq for SliceShort"],["impl<E> StructuralEq for Error<E>"],["impl<'a, M: ?Sized, const Y: usize, P> StructuralEq for PathIter<'a, M, Y, P>"],["impl StructuralEq for Metadata"]], +"stabilizer":[["impl StructuralEq for StreamFormat"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/core/marker/trait.StructuralPartialEq.js b/firmware/implementors/core/marker/trait.StructuralPartialEq.js new file mode 100644 index 0000000000..dec0c55eaf --- /dev/null +++ b/firmware/implementors/core/marker/trait.StructuralPartialEq.js @@ -0,0 +1,7 @@ +(function() {var implementors = { +"ad9959":[["impl StructuralPartialEq for Mode"]], +"idsp":[["impl<T> StructuralPartialEq for Accu<T>"]], +"lockin":[["impl StructuralPartialEq for LockinMode"]], +"miniconf":[["impl<E> StructuralPartialEq for Error<E>"],["impl<'a, M: ?Sized, const Y: usize, P> StructuralPartialEq for PathIter<'a, M, Y, P>"],["impl StructuralPartialEq for Metadata"],["impl StructuralPartialEq for SliceShort"]], +"stabilizer":[["impl StructuralPartialEq for StreamFormat"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/core/marker/trait.Sync.js b/firmware/implementors/core/marker/trait.Sync.js new file mode 100644 index 0000000000..eb21523791 --- /dev/null +++ b/firmware/implementors/core/marker/trait.Sync.js @@ -0,0 +1,8 @@ +(function() {var implementors = { +"ad9959":[["impl<INTERFACE> Sync for Ad9959<INTERFACE>where\n INTERFACE: Sync,",1,["ad9959::Ad9959"]],["impl Sync for Mode",1,["ad9959::Mode"]],["impl Sync for Channel",1,["ad9959::Channel"]],["impl Sync for Register",1,["ad9959::Register"]],["impl Sync for Error",1,["ad9959::Error"]],["impl Sync for ProfileSerializer",1,["ad9959::ProfileSerializer"]]], +"dual_iir":[["impl !Sync for __rtic_internal_Monotonics",1,["dual_iir::app::__rtic_internal_Monotonics"]],["impl<'a> !Sync for __rtic_internal_init_Context<'a>",1,["dual_iir::app::__rtic_internal_init_Context"]],["impl<'a> !Sync for __rtic_internal_idleSharedResources<'a>",1,["dual_iir::app::__rtic_internal_idleSharedResources"]],["impl<'a> !Sync for __rtic_internal_idle_Context<'a>",1,["dual_iir::app::__rtic_internal_idle_Context"]],["impl<'a> !Sync for __rtic_internal_processLocalResources<'a>",1,["dual_iir::app::__rtic_internal_processLocalResources"]],["impl<'a> !Sync for __rtic_internal_processSharedResources<'a>",1,["dual_iir::app::__rtic_internal_processSharedResources"]],["impl<'a> !Sync for __rtic_internal_process_Context<'a>",1,["dual_iir::app::__rtic_internal_process_Context"]],["impl Sync for __rtic_internal_eth_Context",1,["dual_iir::app::__rtic_internal_eth_Context"]],["impl Sync for __rtic_internal_spi2_Context",1,["dual_iir::app::__rtic_internal_spi2_Context"]],["impl Sync for __rtic_internal_spi3_Context",1,["dual_iir::app::__rtic_internal_spi3_Context"]],["impl Sync for __rtic_internal_spi4_Context",1,["dual_iir::app::__rtic_internal_spi4_Context"]],["impl Sync for __rtic_internal_spi5_Context",1,["dual_iir::app::__rtic_internal_spi5_Context"]],["impl<'a> !Sync for __rtic_internal_startLocalResources<'a>",1,["dual_iir::app::__rtic_internal_startLocalResources"]],["impl<'a> !Sync for __rtic_internal_start_Context<'a>",1,["dual_iir::app::__rtic_internal_start_Context"]],["impl<'a> Sync for __rtic_internal_settings_updateLocalResources<'a>",1,["dual_iir::app::__rtic_internal_settings_updateLocalResources"]],["impl<'a> !Sync for __rtic_internal_settings_updateSharedResources<'a>",1,["dual_iir::app::__rtic_internal_settings_updateSharedResources"]],["impl<'a> !Sync for __rtic_internal_settings_update_Context<'a>",1,["dual_iir::app::__rtic_internal_settings_update_Context"]],["impl<'a> Sync for __rtic_internal_telemetryLocalResources<'a>",1,["dual_iir::app::__rtic_internal_telemetryLocalResources"]],["impl<'a> !Sync for __rtic_internal_telemetrySharedResources<'a>",1,["dual_iir::app::__rtic_internal_telemetrySharedResources"]],["impl<'a> !Sync for __rtic_internal_telemetry_Context<'a>",1,["dual_iir::app::__rtic_internal_telemetry_Context"]],["impl<'a> !Sync for __rtic_internal_usbSharedResources<'a>",1,["dual_iir::app::__rtic_internal_usbSharedResources"]],["impl<'a> !Sync for __rtic_internal_usb_Context<'a>",1,["dual_iir::app::__rtic_internal_usb_Context"]],["impl<'a> !Sync for __rtic_internal_ethernet_linkSharedResources<'a>",1,["dual_iir::app::__rtic_internal_ethernet_linkSharedResources"]],["impl<'a> !Sync for __rtic_internal_ethernet_link_Context<'a>",1,["dual_iir::app::__rtic_internal_ethernet_link_Context"]],["impl !Sync for Shared",1,["dual_iir::app::Shared"]],["impl !Sync for Local",1,["dual_iir::app::Local"]],["impl Sync for Settings",1,["dual_iir::Settings"]]], +"idsp":[["impl<T> Sync for Accu<T>where\n T: Sync,",1,["idsp::accu::Accu"]],["impl Sync for Nyquist",1,["idsp::filter::Nyquist"]],["impl<const N: usize, T> Sync for Chain<N, T>where\n T: Sync,",1,["idsp::filter::Chain"]],["impl<T, U> Sync for Cascade<T, U>where\n T: Sync,\n U: Sync,",1,["idsp::filter::Cascade"]],["impl<T> Sync for IIR<T>where\n T: Sync,",1,["idsp::iir::IIR"]],["impl Sync for IIR",1,["idsp::iir_int::IIR"]],["impl<T> Sync for Lockin<T>where\n T: Sync,",1,["idsp::lockin::Lockin"]],["impl<const N: usize> Sync for Lowpass<N>",1,["idsp::lowpass::Lowpass"]],["impl Sync for PLL",1,["idsp::pll::PLL"]],["impl Sync for RPLL",1,["idsp::rpll::RPLL"]],["impl<T> Sync for Unwrapper<T>where\n T: Sync,",1,["idsp::unwrap::Unwrapper"]],["impl<'a, const M: usize, const N: usize> Sync for SymFir<'a, M, N>",1,["idsp::hbf::SymFir"]],["impl<'a, const M: usize, const N: usize> Sync for HbfDec<'a, M, N>",1,["idsp::hbf::HbfDec"]],["impl<'a, const M: usize, const N: usize> Sync for HbfInt<'a, M, N>",1,["idsp::hbf::HbfInt"]],["impl Sync for HbfDecCascade",1,["idsp::hbf::HbfDecCascade"]],["impl Sync for HbfIntCascade",1,["idsp::hbf::HbfIntCascade"]]], +"lockin":[["impl !Sync for __rtic_internal_Monotonics",1,["lockin::app::__rtic_internal_Monotonics"]],["impl<'a> !Sync for __rtic_internal_init_Context<'a>",1,["lockin::app::__rtic_internal_init_Context"]],["impl<'a> !Sync for __rtic_internal_idleSharedResources<'a>",1,["lockin::app::__rtic_internal_idleSharedResources"]],["impl<'a> !Sync for __rtic_internal_idle_Context<'a>",1,["lockin::app::__rtic_internal_idle_Context"]],["impl<'a> !Sync for __rtic_internal_processLocalResources<'a>",1,["lockin::app::__rtic_internal_processLocalResources"]],["impl<'a> !Sync for __rtic_internal_processSharedResources<'a>",1,["lockin::app::__rtic_internal_processSharedResources"]],["impl<'a> !Sync for __rtic_internal_process_Context<'a>",1,["lockin::app::__rtic_internal_process_Context"]],["impl Sync for __rtic_internal_eth_Context",1,["lockin::app::__rtic_internal_eth_Context"]],["impl<'a> !Sync for __rtic_internal_startLocalResources<'a>",1,["lockin::app::__rtic_internal_startLocalResources"]],["impl<'a> !Sync for __rtic_internal_start_Context<'a>",1,["lockin::app::__rtic_internal_start_Context"]],["impl<'a> Sync for __rtic_internal_settings_updateLocalResources<'a>",1,["lockin::app::__rtic_internal_settings_updateLocalResources"]],["impl<'a> !Sync for __rtic_internal_settings_updateSharedResources<'a>",1,["lockin::app::__rtic_internal_settings_updateSharedResources"]],["impl<'a> !Sync for __rtic_internal_settings_update_Context<'a>",1,["lockin::app::__rtic_internal_settings_update_Context"]],["impl<'a> Sync for __rtic_internal_telemetryLocalResources<'a>",1,["lockin::app::__rtic_internal_telemetryLocalResources"]],["impl<'a> !Sync for __rtic_internal_telemetrySharedResources<'a>",1,["lockin::app::__rtic_internal_telemetrySharedResources"]],["impl<'a> !Sync for __rtic_internal_telemetry_Context<'a>",1,["lockin::app::__rtic_internal_telemetry_Context"]],["impl<'a> !Sync for __rtic_internal_usbSharedResources<'a>",1,["lockin::app::__rtic_internal_usbSharedResources"]],["impl<'a> !Sync for __rtic_internal_usb_Context<'a>",1,["lockin::app::__rtic_internal_usb_Context"]],["impl<'a> !Sync for __rtic_internal_ethernet_linkSharedResources<'a>",1,["lockin::app::__rtic_internal_ethernet_linkSharedResources"]],["impl<'a> !Sync for __rtic_internal_ethernet_link_Context<'a>",1,["lockin::app::__rtic_internal_ethernet_link_Context"]],["impl !Sync for Shared",1,["lockin::app::Shared"]],["impl !Sync for Local",1,["lockin::app::Local"]],["impl Sync for Conf",1,["lockin::Conf"]],["impl Sync for LockinMode",1,["lockin::LockinMode"]],["impl Sync for Settings",1,["lockin::Settings"]]], +"miniconf":[["impl<E> Sync for Error<E>where\n E: Sync,",1,["miniconf::tree::Error"]],["impl Sync for SliceShort",1,["miniconf::tree::SliceShort"]],["impl Sync for Metadata",1,["miniconf::tree::Metadata"]],["impl<'a, M: ?Sized, const Y: usize, P> Sync for PathIter<'a, M, Y, P>where\n M: Sync,\n P: Sync,",1,["miniconf::iter::PathIter"]],["impl<'buf, Settings, Stack, Clock, Broker, const Y: usize> Sync for MqttClient<'buf, Settings, Stack, Clock, Broker, Y>where\n Broker: Sync,\n Clock: Sync,\n Settings: Sync,\n Stack: Sync,\n <Clock as Clock>::T: Sync,\n <Stack as TcpClientStack>::TcpSocket: Sync,",1,["miniconf::mqtt_client::MqttClient"]]], +"stabilizer":[["impl Sync for AdcCode",1,["stabilizer::hardware::adc::AdcCode"]],["impl !Sync for Adc0Input",1,["stabilizer::hardware::adc::Adc0Input"]],["impl !Sync for Adc1Input",1,["stabilizer::hardware::adc::Adc1Input"]],["impl Sync for Gain",1,["stabilizer::hardware::afe::Gain"]],["impl<A0, A1> Sync for ProgrammableGainAmplifier<A0, A1>where\n A0: Sync,\n A1: Sync,",1,["stabilizer::hardware::afe::ProgrammableGainAmplifier"]],["impl Sync for CpuTempSensor",1,["stabilizer::hardware::cpu_temp_sensor::CpuTempSensor"]],["impl Sync for DacCode",1,["stabilizer::hardware::dac::DacCode"]],["impl !Sync for Dac0Output",1,["stabilizer::hardware::dac::Dac0Output"]],["impl !Sync for Dac1Output",1,["stabilizer::hardware::dac::Dac1Output"]],["impl Sync for AsmDelay",1,["stabilizer::hardware::delay::AsmDelay"]],["impl Sync for InputStamper",1,["stabilizer::hardware::input_stamper::InputStamper"]],["impl !Sync for DdsOutput",1,["stabilizer::hardware::pounder::dds_output::DdsOutput"]],["impl<'a> !Sync for ProfileBuilder<'a>",1,["stabilizer::hardware::pounder::dds_output::ProfileBuilder"]],["impl Sync for Channel",1,["stabilizer::hardware::pounder::hrtimer::Channel"]],["impl !Sync for HighResTimerE",1,["stabilizer::hardware::pounder::hrtimer::HighResTimerE"]],["impl !Sync for Timestamper",1,["stabilizer::hardware::pounder::timestamp::Timestamper"]],["impl Sync for GpioPin",1,["stabilizer::hardware::pounder::GpioPin"]],["impl Sync for Error",1,["stabilizer::hardware::pounder::Error"]],["impl Sync for Channel",1,["stabilizer::hardware::pounder::Channel"]],["impl Sync for DdsChannelState",1,["stabilizer::hardware::pounder::DdsChannelState"]],["impl Sync for ChannelState",1,["stabilizer::hardware::pounder::ChannelState"]],["impl Sync for InputChannelState",1,["stabilizer::hardware::pounder::InputChannelState"]],["impl Sync for OutputChannelState",1,["stabilizer::hardware::pounder::OutputChannelState"]],["impl Sync for DdsClockConfig",1,["stabilizer::hardware::pounder::DdsClockConfig"]],["impl !Sync for QspiInterface",1,["stabilizer::hardware::pounder::QspiInterface"]],["impl !Sync for PounderDevices",1,["stabilizer::hardware::pounder::PounderDevices"]],["impl !Sync for OutputBuffer",1,["stabilizer::hardware::serial_terminal::OutputBuffer"]],["impl !Sync for SerialTerminal",1,["stabilizer::hardware::serial_terminal::SerialTerminal"]],["impl Sync for NetStorage",1,["stabilizer::hardware::setup::NetStorage"]],["impl Sync for UdpSocketStorage",1,["stabilizer::hardware::setup::UdpSocketStorage"]],["impl Sync for TcpSocketStorage",1,["stabilizer::hardware::setup::TcpSocketStorage"]],["impl !Sync for NetworkDevices",1,["stabilizer::hardware::setup::NetworkDevices"]],["impl Sync for EemGpioDevices",1,["stabilizer::hardware::setup::EemGpioDevices"]],["impl !Sync for StabilizerDevices",1,["stabilizer::hardware::setup::StabilizerDevices"]],["impl !Sync for PounderDevices",1,["stabilizer::hardware::setup::PounderDevices"]],["impl Sync for AdcError",1,["stabilizer::hardware::shared_adc::AdcError"]],["impl<'a, Adc, PIN> Sync for AdcChannel<'a, Adc, PIN>where\n Adc: Send,\n PIN: Sync,",1,["stabilizer::hardware::shared_adc::AdcChannel"]],["impl<Adc> Sync for SharedAdc<Adc>where\n Adc: Send,",1,["stabilizer::hardware::shared_adc::SharedAdc"]],["impl Sync for Signal",1,["stabilizer::hardware::signal_generator::Signal"]],["impl Sync for BasicConfig",1,["stabilizer::hardware::signal_generator::BasicConfig"]],["impl Sync for Error",1,["stabilizer::hardware::signal_generator::Error"]],["impl Sync for Config",1,["stabilizer::hardware::signal_generator::Config"]],["impl Sync for SignalGenerator",1,["stabilizer::hardware::signal_generator::SignalGenerator"]],["impl Sync for UpdateEvent",1,["stabilizer::hardware::timers::tim2::UpdateEvent"]],["impl Sync for Channels",1,["stabilizer::hardware::timers::tim2::Channels"]],["impl Sync for Channel1",1,["stabilizer::hardware::timers::tim2::Channel1"]],["impl Sync for Channel1InputCapture",1,["stabilizer::hardware::timers::tim2::Channel1InputCapture"]],["impl Sync for Channel2",1,["stabilizer::hardware::timers::tim2::Channel2"]],["impl Sync for Channel2InputCapture",1,["stabilizer::hardware::timers::tim2::Channel2InputCapture"]],["impl Sync for Channel3",1,["stabilizer::hardware::timers::tim2::Channel3"]],["impl Sync for Channel3InputCapture",1,["stabilizer::hardware::timers::tim2::Channel3InputCapture"]],["impl Sync for Channel4",1,["stabilizer::hardware::timers::tim2::Channel4"]],["impl Sync for Channel4InputCapture",1,["stabilizer::hardware::timers::tim2::Channel4InputCapture"]],["impl Sync for UpdateEvent",1,["stabilizer::hardware::timers::tim3::UpdateEvent"]],["impl Sync for Channels",1,["stabilizer::hardware::timers::tim3::Channels"]],["impl Sync for Channel1",1,["stabilizer::hardware::timers::tim3::Channel1"]],["impl Sync for Channel1InputCapture",1,["stabilizer::hardware::timers::tim3::Channel1InputCapture"]],["impl Sync for Channel2",1,["stabilizer::hardware::timers::tim3::Channel2"]],["impl Sync for Channel2InputCapture",1,["stabilizer::hardware::timers::tim3::Channel2InputCapture"]],["impl Sync for Channel3",1,["stabilizer::hardware::timers::tim3::Channel3"]],["impl Sync for Channel3InputCapture",1,["stabilizer::hardware::timers::tim3::Channel3InputCapture"]],["impl Sync for Channel4",1,["stabilizer::hardware::timers::tim3::Channel4"]],["impl Sync for Channel4InputCapture",1,["stabilizer::hardware::timers::tim3::Channel4InputCapture"]],["impl Sync for UpdateEvent",1,["stabilizer::hardware::timers::tim5::UpdateEvent"]],["impl Sync for Channels",1,["stabilizer::hardware::timers::tim5::Channels"]],["impl Sync for Channel1",1,["stabilizer::hardware::timers::tim5::Channel1"]],["impl Sync for Channel1InputCapture",1,["stabilizer::hardware::timers::tim5::Channel1InputCapture"]],["impl Sync for Channel2",1,["stabilizer::hardware::timers::tim5::Channel2"]],["impl Sync for Channel2InputCapture",1,["stabilizer::hardware::timers::tim5::Channel2InputCapture"]],["impl Sync for Channel3",1,["stabilizer::hardware::timers::tim5::Channel3"]],["impl Sync for Channel3InputCapture",1,["stabilizer::hardware::timers::tim5::Channel3InputCapture"]],["impl Sync for Channel4",1,["stabilizer::hardware::timers::tim5::Channel4"]],["impl Sync for Channel4InputCapture",1,["stabilizer::hardware::timers::tim5::Channel4InputCapture"]],["impl Sync for UpdateEvent",1,["stabilizer::hardware::timers::tim8::UpdateEvent"]],["impl Sync for Channels",1,["stabilizer::hardware::timers::tim8::Channels"]],["impl Sync for Channel1",1,["stabilizer::hardware::timers::tim8::Channel1"]],["impl Sync for Channel1InputCapture",1,["stabilizer::hardware::timers::tim8::Channel1InputCapture"]],["impl Sync for Channel2",1,["stabilizer::hardware::timers::tim8::Channel2"]],["impl Sync for Channel2InputCapture",1,["stabilizer::hardware::timers::tim8::Channel2InputCapture"]],["impl Sync for Channel3",1,["stabilizer::hardware::timers::tim8::Channel3"]],["impl Sync for Channel3InputCapture",1,["stabilizer::hardware::timers::tim8::Channel3InputCapture"]],["impl Sync for Channel4",1,["stabilizer::hardware::timers::tim8::Channel4"]],["impl Sync for Channel4InputCapture",1,["stabilizer::hardware::timers::tim8::Channel4InputCapture"]],["impl Sync for TriggerGenerator",1,["stabilizer::hardware::timers::TriggerGenerator"]],["impl Sync for TriggerSource",1,["stabilizer::hardware::timers::TriggerSource"]],["impl Sync for Prescaler",1,["stabilizer::hardware::timers::Prescaler"]],["impl Sync for SlaveMode",1,["stabilizer::hardware::timers::SlaveMode"]],["impl Sync for InputFilter",1,["stabilizer::hardware::timers::InputFilter"]],["impl !Sync for SamplingTimer",1,["stabilizer::hardware::timers::SamplingTimer"]],["impl !Sync for ShadowSamplingTimer",1,["stabilizer::hardware::timers::ShadowSamplingTimer"]],["impl !Sync for TimestampTimer",1,["stabilizer::hardware::timers::TimestampTimer"]],["impl !Sync for PounderTimestampTimer",1,["stabilizer::hardware::timers::PounderTimestampTimer"]],["impl Sync for StreamTarget",1,["stabilizer::net::data_stream::StreamTarget"]],["impl Sync for StreamFormat",1,["stabilizer::net::data_stream::StreamFormat"]],["impl !Sync for FrameGenerator",1,["stabilizer::net::data_stream::FrameGenerator"]],["impl !Sync for DataStream",1,["stabilizer::net::data_stream::DataStream"]],["impl !Sync for NetworkProcessor",1,["stabilizer::net::network_processor::NetworkProcessor"]],["impl<T> Sync for TelemetryClient<T>where\n T: Sync,",1,["stabilizer::net::telemetry::TelemetryClient"]],["impl Sync for TelemetryBuffer",1,["stabilizer::net::telemetry::TelemetryBuffer"]],["impl Sync for Telemetry",1,["stabilizer::net::telemetry::Telemetry"]],["impl Sync for MqttStorage",1,["stabilizer::net::MqttStorage"]],["impl Sync for UpdateState",1,["stabilizer::net::UpdateState"]],["impl Sync for NetworkState",1,["stabilizer::net::NetworkState"]],["impl<S, T, const Y: usize> !Sync for NetworkUsers<S, T, Y>",1,["stabilizer::net::NetworkUsers"]]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/core/marker/trait.Unpin.js b/firmware/implementors/core/marker/trait.Unpin.js new file mode 100644 index 0000000000..6a8f97cc53 --- /dev/null +++ b/firmware/implementors/core/marker/trait.Unpin.js @@ -0,0 +1,8 @@ +(function() {var implementors = { +"ad9959":[["impl<INTERFACE> Unpin for Ad9959<INTERFACE>where\n INTERFACE: Unpin,",1,["ad9959::Ad9959"]],["impl Unpin for Mode",1,["ad9959::Mode"]],["impl Unpin for Channel",1,["ad9959::Channel"]],["impl Unpin for Register",1,["ad9959::Register"]],["impl Unpin for Error",1,["ad9959::Error"]],["impl Unpin for ProfileSerializer",1,["ad9959::ProfileSerializer"]]], +"dual_iir":[["impl Unpin for __rtic_internal_Monotonics",1,["dual_iir::app::__rtic_internal_Monotonics"]],["impl<'a> Unpin for __rtic_internal_init_Context<'a>",1,["dual_iir::app::__rtic_internal_init_Context"]],["impl<'a> Unpin for __rtic_internal_idleSharedResources<'a>",1,["dual_iir::app::__rtic_internal_idleSharedResources"]],["impl<'a> Unpin for __rtic_internal_idle_Context<'a>",1,["dual_iir::app::__rtic_internal_idle_Context"]],["impl<'a> Unpin for __rtic_internal_processLocalResources<'a>",1,["dual_iir::app::__rtic_internal_processLocalResources"]],["impl<'a> Unpin for __rtic_internal_processSharedResources<'a>",1,["dual_iir::app::__rtic_internal_processSharedResources"]],["impl<'a> Unpin for __rtic_internal_process_Context<'a>",1,["dual_iir::app::__rtic_internal_process_Context"]],["impl Unpin for __rtic_internal_eth_Context",1,["dual_iir::app::__rtic_internal_eth_Context"]],["impl Unpin for __rtic_internal_spi2_Context",1,["dual_iir::app::__rtic_internal_spi2_Context"]],["impl Unpin for __rtic_internal_spi3_Context",1,["dual_iir::app::__rtic_internal_spi3_Context"]],["impl Unpin for __rtic_internal_spi4_Context",1,["dual_iir::app::__rtic_internal_spi4_Context"]],["impl Unpin for __rtic_internal_spi5_Context",1,["dual_iir::app::__rtic_internal_spi5_Context"]],["impl<'a> Unpin for __rtic_internal_startLocalResources<'a>",1,["dual_iir::app::__rtic_internal_startLocalResources"]],["impl<'a> Unpin for __rtic_internal_start_Context<'a>",1,["dual_iir::app::__rtic_internal_start_Context"]],["impl<'a> Unpin for __rtic_internal_settings_updateLocalResources<'a>",1,["dual_iir::app::__rtic_internal_settings_updateLocalResources"]],["impl<'a> Unpin for __rtic_internal_settings_updateSharedResources<'a>",1,["dual_iir::app::__rtic_internal_settings_updateSharedResources"]],["impl<'a> Unpin for __rtic_internal_settings_update_Context<'a>",1,["dual_iir::app::__rtic_internal_settings_update_Context"]],["impl<'a> Unpin for __rtic_internal_telemetryLocalResources<'a>",1,["dual_iir::app::__rtic_internal_telemetryLocalResources"]],["impl<'a> Unpin for __rtic_internal_telemetrySharedResources<'a>",1,["dual_iir::app::__rtic_internal_telemetrySharedResources"]],["impl<'a> Unpin for __rtic_internal_telemetry_Context<'a>",1,["dual_iir::app::__rtic_internal_telemetry_Context"]],["impl<'a> Unpin for __rtic_internal_usbSharedResources<'a>",1,["dual_iir::app::__rtic_internal_usbSharedResources"]],["impl<'a> Unpin for __rtic_internal_usb_Context<'a>",1,["dual_iir::app::__rtic_internal_usb_Context"]],["impl<'a> Unpin for __rtic_internal_ethernet_linkSharedResources<'a>",1,["dual_iir::app::__rtic_internal_ethernet_linkSharedResources"]],["impl<'a> Unpin for __rtic_internal_ethernet_link_Context<'a>",1,["dual_iir::app::__rtic_internal_ethernet_link_Context"]],["impl Unpin for Shared",1,["dual_iir::app::Shared"]],["impl Unpin for Local",1,["dual_iir::app::Local"]],["impl Unpin for Settings",1,["dual_iir::Settings"]]], +"idsp":[["impl<T> Unpin for Accu<T>where\n T: Unpin,",1,["idsp::accu::Accu"]],["impl Unpin for Nyquist",1,["idsp::filter::Nyquist"]],["impl<const N: usize, T> Unpin for Chain<N, T>where\n T: Unpin,",1,["idsp::filter::Chain"]],["impl<T, U> Unpin for Cascade<T, U>where\n T: Unpin,\n U: Unpin,",1,["idsp::filter::Cascade"]],["impl<T> Unpin for IIR<T>where\n T: Unpin,",1,["idsp::iir::IIR"]],["impl Unpin for IIR",1,["idsp::iir_int::IIR"]],["impl<T> Unpin for Lockin<T>where\n T: Unpin,",1,["idsp::lockin::Lockin"]],["impl<const N: usize> Unpin for Lowpass<N>",1,["idsp::lowpass::Lowpass"]],["impl Unpin for PLL",1,["idsp::pll::PLL"]],["impl Unpin for RPLL",1,["idsp::rpll::RPLL"]],["impl<T> Unpin for Unwrapper<T>where\n T: Unpin,",1,["idsp::unwrap::Unwrapper"]],["impl<'a, const M: usize, const N: usize> Unpin for SymFir<'a, M, N>",1,["idsp::hbf::SymFir"]],["impl<'a, const M: usize, const N: usize> Unpin for HbfDec<'a, M, N>",1,["idsp::hbf::HbfDec"]],["impl<'a, const M: usize, const N: usize> Unpin for HbfInt<'a, M, N>",1,["idsp::hbf::HbfInt"]],["impl Unpin for HbfDecCascade",1,["idsp::hbf::HbfDecCascade"]],["impl Unpin for HbfIntCascade",1,["idsp::hbf::HbfIntCascade"]]], +"lockin":[["impl Unpin for __rtic_internal_Monotonics",1,["lockin::app::__rtic_internal_Monotonics"]],["impl<'a> Unpin for __rtic_internal_init_Context<'a>",1,["lockin::app::__rtic_internal_init_Context"]],["impl<'a> Unpin for __rtic_internal_idleSharedResources<'a>",1,["lockin::app::__rtic_internal_idleSharedResources"]],["impl<'a> Unpin for __rtic_internal_idle_Context<'a>",1,["lockin::app::__rtic_internal_idle_Context"]],["impl<'a> Unpin for __rtic_internal_processLocalResources<'a>",1,["lockin::app::__rtic_internal_processLocalResources"]],["impl<'a> Unpin for __rtic_internal_processSharedResources<'a>",1,["lockin::app::__rtic_internal_processSharedResources"]],["impl<'a> Unpin for __rtic_internal_process_Context<'a>",1,["lockin::app::__rtic_internal_process_Context"]],["impl Unpin for __rtic_internal_eth_Context",1,["lockin::app::__rtic_internal_eth_Context"]],["impl<'a> Unpin for __rtic_internal_startLocalResources<'a>",1,["lockin::app::__rtic_internal_startLocalResources"]],["impl<'a> Unpin for __rtic_internal_start_Context<'a>",1,["lockin::app::__rtic_internal_start_Context"]],["impl<'a> Unpin for __rtic_internal_settings_updateLocalResources<'a>",1,["lockin::app::__rtic_internal_settings_updateLocalResources"]],["impl<'a> Unpin for __rtic_internal_settings_updateSharedResources<'a>",1,["lockin::app::__rtic_internal_settings_updateSharedResources"]],["impl<'a> Unpin for __rtic_internal_settings_update_Context<'a>",1,["lockin::app::__rtic_internal_settings_update_Context"]],["impl<'a> Unpin for __rtic_internal_telemetryLocalResources<'a>",1,["lockin::app::__rtic_internal_telemetryLocalResources"]],["impl<'a> Unpin for __rtic_internal_telemetrySharedResources<'a>",1,["lockin::app::__rtic_internal_telemetrySharedResources"]],["impl<'a> Unpin for __rtic_internal_telemetry_Context<'a>",1,["lockin::app::__rtic_internal_telemetry_Context"]],["impl<'a> Unpin for __rtic_internal_usbSharedResources<'a>",1,["lockin::app::__rtic_internal_usbSharedResources"]],["impl<'a> Unpin for __rtic_internal_usb_Context<'a>",1,["lockin::app::__rtic_internal_usb_Context"]],["impl<'a> Unpin for __rtic_internal_ethernet_linkSharedResources<'a>",1,["lockin::app::__rtic_internal_ethernet_linkSharedResources"]],["impl<'a> Unpin for __rtic_internal_ethernet_link_Context<'a>",1,["lockin::app::__rtic_internal_ethernet_link_Context"]],["impl Unpin for Shared",1,["lockin::app::Shared"]],["impl Unpin for Local",1,["lockin::app::Local"]],["impl Unpin for Conf",1,["lockin::Conf"]],["impl Unpin for LockinMode",1,["lockin::LockinMode"]],["impl Unpin for Settings",1,["lockin::Settings"]]], +"miniconf":[["impl<E> Unpin for Error<E>where\n E: Unpin,",1,["miniconf::tree::Error"]],["impl Unpin for SliceShort",1,["miniconf::tree::SliceShort"]],["impl Unpin for Metadata",1,["miniconf::tree::Metadata"]],["impl<'a, M: ?Sized, const Y: usize, P> Unpin for PathIter<'a, M, Y, P>where\n M: Unpin,\n P: Unpin,",1,["miniconf::iter::PathIter"]],["impl<'buf, Settings, Stack, Clock, Broker, const Y: usize> Unpin for MqttClient<'buf, Settings, Stack, Clock, Broker, Y>where\n Broker: Unpin,\n Clock: Unpin,\n Settings: Unpin,\n Stack: Unpin,\n <Clock as Clock>::T: Unpin,\n <Stack as TcpClientStack>::TcpSocket: Unpin,",1,["miniconf::mqtt_client::MqttClient"]]], +"stabilizer":[["impl Unpin for AdcCode",1,["stabilizer::hardware::adc::AdcCode"]],["impl Unpin for Adc0Input",1,["stabilizer::hardware::adc::Adc0Input"]],["impl Unpin for Adc1Input",1,["stabilizer::hardware::adc::Adc1Input"]],["impl Unpin for Gain",1,["stabilizer::hardware::afe::Gain"]],["impl<A0, A1> Unpin for ProgrammableGainAmplifier<A0, A1>where\n A0: Unpin,\n A1: Unpin,",1,["stabilizer::hardware::afe::ProgrammableGainAmplifier"]],["impl Unpin for CpuTempSensor",1,["stabilizer::hardware::cpu_temp_sensor::CpuTempSensor"]],["impl Unpin for DacCode",1,["stabilizer::hardware::dac::DacCode"]],["impl Unpin for Dac0Output",1,["stabilizer::hardware::dac::Dac0Output"]],["impl Unpin for Dac1Output",1,["stabilizer::hardware::dac::Dac1Output"]],["impl Unpin for AsmDelay",1,["stabilizer::hardware::delay::AsmDelay"]],["impl Unpin for InputStamper",1,["stabilizer::hardware::input_stamper::InputStamper"]],["impl Unpin for DdsOutput",1,["stabilizer::hardware::pounder::dds_output::DdsOutput"]],["impl<'a> Unpin for ProfileBuilder<'a>",1,["stabilizer::hardware::pounder::dds_output::ProfileBuilder"]],["impl Unpin for Channel",1,["stabilizer::hardware::pounder::hrtimer::Channel"]],["impl Unpin for HighResTimerE",1,["stabilizer::hardware::pounder::hrtimer::HighResTimerE"]],["impl Unpin for Timestamper",1,["stabilizer::hardware::pounder::timestamp::Timestamper"]],["impl Unpin for GpioPin",1,["stabilizer::hardware::pounder::GpioPin"]],["impl Unpin for Error",1,["stabilizer::hardware::pounder::Error"]],["impl Unpin for Channel",1,["stabilizer::hardware::pounder::Channel"]],["impl Unpin for DdsChannelState",1,["stabilizer::hardware::pounder::DdsChannelState"]],["impl Unpin for ChannelState",1,["stabilizer::hardware::pounder::ChannelState"]],["impl Unpin for InputChannelState",1,["stabilizer::hardware::pounder::InputChannelState"]],["impl Unpin for OutputChannelState",1,["stabilizer::hardware::pounder::OutputChannelState"]],["impl Unpin for DdsClockConfig",1,["stabilizer::hardware::pounder::DdsClockConfig"]],["impl Unpin for QspiInterface",1,["stabilizer::hardware::pounder::QspiInterface"]],["impl Unpin for PounderDevices",1,["stabilizer::hardware::pounder::PounderDevices"]],["impl Unpin for OutputBuffer",1,["stabilizer::hardware::serial_terminal::OutputBuffer"]],["impl Unpin for SerialTerminal",1,["stabilizer::hardware::serial_terminal::SerialTerminal"]],["impl Unpin for NetStorage",1,["stabilizer::hardware::setup::NetStorage"]],["impl Unpin for UdpSocketStorage",1,["stabilizer::hardware::setup::UdpSocketStorage"]],["impl Unpin for TcpSocketStorage",1,["stabilizer::hardware::setup::TcpSocketStorage"]],["impl Unpin for NetworkDevices",1,["stabilizer::hardware::setup::NetworkDevices"]],["impl Unpin for EemGpioDevices",1,["stabilizer::hardware::setup::EemGpioDevices"]],["impl Unpin for StabilizerDevices",1,["stabilizer::hardware::setup::StabilizerDevices"]],["impl Unpin for PounderDevices",1,["stabilizer::hardware::setup::PounderDevices"]],["impl Unpin for AdcError",1,["stabilizer::hardware::shared_adc::AdcError"]],["impl<'a, Adc, PIN> Unpin for AdcChannel<'a, Adc, PIN>where\n PIN: Unpin,",1,["stabilizer::hardware::shared_adc::AdcChannel"]],["impl<Adc> Unpin for SharedAdc<Adc>where\n Adc: Unpin,",1,["stabilizer::hardware::shared_adc::SharedAdc"]],["impl Unpin for Signal",1,["stabilizer::hardware::signal_generator::Signal"]],["impl Unpin for BasicConfig",1,["stabilizer::hardware::signal_generator::BasicConfig"]],["impl Unpin for Error",1,["stabilizer::hardware::signal_generator::Error"]],["impl Unpin for Config",1,["stabilizer::hardware::signal_generator::Config"]],["impl Unpin for SignalGenerator",1,["stabilizer::hardware::signal_generator::SignalGenerator"]],["impl Unpin for UpdateEvent",1,["stabilizer::hardware::timers::tim2::UpdateEvent"]],["impl Unpin for Channels",1,["stabilizer::hardware::timers::tim2::Channels"]],["impl Unpin for Channel1",1,["stabilizer::hardware::timers::tim2::Channel1"]],["impl Unpin for Channel1InputCapture",1,["stabilizer::hardware::timers::tim2::Channel1InputCapture"]],["impl Unpin for Channel2",1,["stabilizer::hardware::timers::tim2::Channel2"]],["impl Unpin for Channel2InputCapture",1,["stabilizer::hardware::timers::tim2::Channel2InputCapture"]],["impl Unpin for Channel3",1,["stabilizer::hardware::timers::tim2::Channel3"]],["impl Unpin for Channel3InputCapture",1,["stabilizer::hardware::timers::tim2::Channel3InputCapture"]],["impl Unpin for Channel4",1,["stabilizer::hardware::timers::tim2::Channel4"]],["impl Unpin for Channel4InputCapture",1,["stabilizer::hardware::timers::tim2::Channel4InputCapture"]],["impl Unpin for UpdateEvent",1,["stabilizer::hardware::timers::tim3::UpdateEvent"]],["impl Unpin for Channels",1,["stabilizer::hardware::timers::tim3::Channels"]],["impl Unpin for Channel1",1,["stabilizer::hardware::timers::tim3::Channel1"]],["impl Unpin for Channel1InputCapture",1,["stabilizer::hardware::timers::tim3::Channel1InputCapture"]],["impl Unpin for Channel2",1,["stabilizer::hardware::timers::tim3::Channel2"]],["impl Unpin for Channel2InputCapture",1,["stabilizer::hardware::timers::tim3::Channel2InputCapture"]],["impl Unpin for Channel3",1,["stabilizer::hardware::timers::tim3::Channel3"]],["impl Unpin for Channel3InputCapture",1,["stabilizer::hardware::timers::tim3::Channel3InputCapture"]],["impl Unpin for Channel4",1,["stabilizer::hardware::timers::tim3::Channel4"]],["impl Unpin for Channel4InputCapture",1,["stabilizer::hardware::timers::tim3::Channel4InputCapture"]],["impl Unpin for UpdateEvent",1,["stabilizer::hardware::timers::tim5::UpdateEvent"]],["impl Unpin for Channels",1,["stabilizer::hardware::timers::tim5::Channels"]],["impl Unpin for Channel1",1,["stabilizer::hardware::timers::tim5::Channel1"]],["impl Unpin for Channel1InputCapture",1,["stabilizer::hardware::timers::tim5::Channel1InputCapture"]],["impl Unpin for Channel2",1,["stabilizer::hardware::timers::tim5::Channel2"]],["impl Unpin for Channel2InputCapture",1,["stabilizer::hardware::timers::tim5::Channel2InputCapture"]],["impl Unpin for Channel3",1,["stabilizer::hardware::timers::tim5::Channel3"]],["impl Unpin for Channel3InputCapture",1,["stabilizer::hardware::timers::tim5::Channel3InputCapture"]],["impl Unpin for Channel4",1,["stabilizer::hardware::timers::tim5::Channel4"]],["impl Unpin for Channel4InputCapture",1,["stabilizer::hardware::timers::tim5::Channel4InputCapture"]],["impl Unpin for UpdateEvent",1,["stabilizer::hardware::timers::tim8::UpdateEvent"]],["impl Unpin for Channels",1,["stabilizer::hardware::timers::tim8::Channels"]],["impl Unpin for Channel1",1,["stabilizer::hardware::timers::tim8::Channel1"]],["impl Unpin for Channel1InputCapture",1,["stabilizer::hardware::timers::tim8::Channel1InputCapture"]],["impl Unpin for Channel2",1,["stabilizer::hardware::timers::tim8::Channel2"]],["impl Unpin for Channel2InputCapture",1,["stabilizer::hardware::timers::tim8::Channel2InputCapture"]],["impl Unpin for Channel3",1,["stabilizer::hardware::timers::tim8::Channel3"]],["impl Unpin for Channel3InputCapture",1,["stabilizer::hardware::timers::tim8::Channel3InputCapture"]],["impl Unpin for Channel4",1,["stabilizer::hardware::timers::tim8::Channel4"]],["impl Unpin for Channel4InputCapture",1,["stabilizer::hardware::timers::tim8::Channel4InputCapture"]],["impl Unpin for TriggerGenerator",1,["stabilizer::hardware::timers::TriggerGenerator"]],["impl Unpin for TriggerSource",1,["stabilizer::hardware::timers::TriggerSource"]],["impl Unpin for Prescaler",1,["stabilizer::hardware::timers::Prescaler"]],["impl Unpin for SlaveMode",1,["stabilizer::hardware::timers::SlaveMode"]],["impl Unpin for InputFilter",1,["stabilizer::hardware::timers::InputFilter"]],["impl Unpin for SamplingTimer",1,["stabilizer::hardware::timers::SamplingTimer"]],["impl Unpin for ShadowSamplingTimer",1,["stabilizer::hardware::timers::ShadowSamplingTimer"]],["impl Unpin for TimestampTimer",1,["stabilizer::hardware::timers::TimestampTimer"]],["impl Unpin for PounderTimestampTimer",1,["stabilizer::hardware::timers::PounderTimestampTimer"]],["impl Unpin for StreamTarget",1,["stabilizer::net::data_stream::StreamTarget"]],["impl Unpin for StreamFormat",1,["stabilizer::net::data_stream::StreamFormat"]],["impl Unpin for FrameGenerator",1,["stabilizer::net::data_stream::FrameGenerator"]],["impl Unpin for DataStream",1,["stabilizer::net::data_stream::DataStream"]],["impl Unpin for NetworkProcessor",1,["stabilizer::net::network_processor::NetworkProcessor"]],["impl<T> Unpin for TelemetryClient<T>where\n T: Unpin,",1,["stabilizer::net::telemetry::TelemetryClient"]],["impl Unpin for TelemetryBuffer",1,["stabilizer::net::telemetry::TelemetryBuffer"]],["impl Unpin for Telemetry",1,["stabilizer::net::telemetry::Telemetry"]],["impl Unpin for MqttStorage",1,["stabilizer::net::MqttStorage"]],["impl Unpin for UpdateState",1,["stabilizer::net::UpdateState"]],["impl Unpin for NetworkState",1,["stabilizer::net::NetworkState"]],["impl<S, T, const Y: usize> Unpin for NetworkUsers<S, T, Y>where\n S: Unpin,\n T: Unpin,",1,["stabilizer::net::NetworkUsers"]]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/core/ops/arith/trait.Sub.js b/firmware/implementors/core/ops/arith/trait.Sub.js new file mode 100644 index 0000000000..b8e5b738db --- /dev/null +++ b/firmware/implementors/core/ops/arith/trait.Sub.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"ad9959":[["impl Sub<Channel> for Channel"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/core/ops/arith/trait.SubAssign.js b/firmware/implementors/core/ops/arith/trait.SubAssign.js new file mode 100644 index 0000000000..e96ffcb976 --- /dev/null +++ b/firmware/implementors/core/ops/arith/trait.SubAssign.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"ad9959":[["impl SubAssign<Channel> for Channel"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/core/ops/bit/trait.BitAnd.js b/firmware/implementors/core/ops/bit/trait.BitAnd.js new file mode 100644 index 0000000000..675396d928 --- /dev/null +++ b/firmware/implementors/core/ops/bit/trait.BitAnd.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"ad9959":[["impl BitAnd<Channel> for Channel"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/core/ops/bit/trait.BitAndAssign.js b/firmware/implementors/core/ops/bit/trait.BitAndAssign.js new file mode 100644 index 0000000000..d9d89625cc --- /dev/null +++ b/firmware/implementors/core/ops/bit/trait.BitAndAssign.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"ad9959":[["impl BitAndAssign<Channel> for Channel"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/core/ops/bit/trait.BitOr.js b/firmware/implementors/core/ops/bit/trait.BitOr.js new file mode 100644 index 0000000000..313eeb4e91 --- /dev/null +++ b/firmware/implementors/core/ops/bit/trait.BitOr.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"ad9959":[["impl BitOr<Channel> for Channel"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/core/ops/bit/trait.BitOrAssign.js b/firmware/implementors/core/ops/bit/trait.BitOrAssign.js new file mode 100644 index 0000000000..81dd1e8a51 --- /dev/null +++ b/firmware/implementors/core/ops/bit/trait.BitOrAssign.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"ad9959":[["impl BitOrAssign<Channel> for Channel"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/core/ops/bit/trait.BitXor.js b/firmware/implementors/core/ops/bit/trait.BitXor.js new file mode 100644 index 0000000000..2ce1a9311b --- /dev/null +++ b/firmware/implementors/core/ops/bit/trait.BitXor.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"ad9959":[["impl BitXor<Channel> for Channel"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/core/ops/bit/trait.BitXorAssign.js b/firmware/implementors/core/ops/bit/trait.BitXorAssign.js new file mode 100644 index 0000000000..9830d9e762 --- /dev/null +++ b/firmware/implementors/core/ops/bit/trait.BitXorAssign.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"ad9959":[["impl BitXorAssign<Channel> for Channel"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/core/ops/bit/trait.Not.js b/firmware/implementors/core/ops/bit/trait.Not.js new file mode 100644 index 0000000000..cd8cefc7e9 --- /dev/null +++ b/firmware/implementors/core/ops/bit/trait.Not.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"ad9959":[["impl Not for Channel"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/core/panic/unwind_safe/trait.RefUnwindSafe.js b/firmware/implementors/core/panic/unwind_safe/trait.RefUnwindSafe.js new file mode 100644 index 0000000000..cdb356d165 --- /dev/null +++ b/firmware/implementors/core/panic/unwind_safe/trait.RefUnwindSafe.js @@ -0,0 +1,8 @@ +(function() {var implementors = { +"ad9959":[["impl<INTERFACE> RefUnwindSafe for Ad9959<INTERFACE>where\n INTERFACE: RefUnwindSafe,",1,["ad9959::Ad9959"]],["impl RefUnwindSafe for Mode",1,["ad9959::Mode"]],["impl RefUnwindSafe for Channel",1,["ad9959::Channel"]],["impl RefUnwindSafe for Register",1,["ad9959::Register"]],["impl RefUnwindSafe for Error",1,["ad9959::Error"]],["impl RefUnwindSafe for ProfileSerializer",1,["ad9959::ProfileSerializer"]]], +"dual_iir":[["impl RefUnwindSafe for __rtic_internal_Monotonics",1,["dual_iir::app::__rtic_internal_Monotonics"]],["impl<'a> RefUnwindSafe for __rtic_internal_init_Context<'a>",1,["dual_iir::app::__rtic_internal_init_Context"]],["impl<'a> !RefUnwindSafe for __rtic_internal_idleSharedResources<'a>",1,["dual_iir::app::__rtic_internal_idleSharedResources"]],["impl<'a> !RefUnwindSafe for __rtic_internal_idle_Context<'a>",1,["dual_iir::app::__rtic_internal_idle_Context"]],["impl<'a> !RefUnwindSafe for __rtic_internal_processLocalResources<'a>",1,["dual_iir::app::__rtic_internal_processLocalResources"]],["impl<'a> !RefUnwindSafe for __rtic_internal_processSharedResources<'a>",1,["dual_iir::app::__rtic_internal_processSharedResources"]],["impl<'a> !RefUnwindSafe for __rtic_internal_process_Context<'a>",1,["dual_iir::app::__rtic_internal_process_Context"]],["impl RefUnwindSafe for __rtic_internal_eth_Context",1,["dual_iir::app::__rtic_internal_eth_Context"]],["impl RefUnwindSafe for __rtic_internal_spi2_Context",1,["dual_iir::app::__rtic_internal_spi2_Context"]],["impl RefUnwindSafe for __rtic_internal_spi3_Context",1,["dual_iir::app::__rtic_internal_spi3_Context"]],["impl RefUnwindSafe for __rtic_internal_spi4_Context",1,["dual_iir::app::__rtic_internal_spi4_Context"]],["impl RefUnwindSafe for __rtic_internal_spi5_Context",1,["dual_iir::app::__rtic_internal_spi5_Context"]],["impl<'a> RefUnwindSafe for __rtic_internal_startLocalResources<'a>",1,["dual_iir::app::__rtic_internal_startLocalResources"]],["impl<'a> RefUnwindSafe for __rtic_internal_start_Context<'a>",1,["dual_iir::app::__rtic_internal_start_Context"]],["impl<'a> RefUnwindSafe for __rtic_internal_settings_updateLocalResources<'a>",1,["dual_iir::app::__rtic_internal_settings_updateLocalResources"]],["impl<'a> !RefUnwindSafe for __rtic_internal_settings_updateSharedResources<'a>",1,["dual_iir::app::__rtic_internal_settings_updateSharedResources"]],["impl<'a> !RefUnwindSafe for __rtic_internal_settings_update_Context<'a>",1,["dual_iir::app::__rtic_internal_settings_update_Context"]],["impl<'a> !RefUnwindSafe for __rtic_internal_telemetryLocalResources<'a>",1,["dual_iir::app::__rtic_internal_telemetryLocalResources"]],["impl<'a> !RefUnwindSafe for __rtic_internal_telemetrySharedResources<'a>",1,["dual_iir::app::__rtic_internal_telemetrySharedResources"]],["impl<'a> !RefUnwindSafe for __rtic_internal_telemetry_Context<'a>",1,["dual_iir::app::__rtic_internal_telemetry_Context"]],["impl<'a> !RefUnwindSafe for __rtic_internal_usbSharedResources<'a>",1,["dual_iir::app::__rtic_internal_usbSharedResources"]],["impl<'a> !RefUnwindSafe for __rtic_internal_usb_Context<'a>",1,["dual_iir::app::__rtic_internal_usb_Context"]],["impl<'a> !RefUnwindSafe for __rtic_internal_ethernet_linkSharedResources<'a>",1,["dual_iir::app::__rtic_internal_ethernet_linkSharedResources"]],["impl<'a> !RefUnwindSafe for __rtic_internal_ethernet_link_Context<'a>",1,["dual_iir::app::__rtic_internal_ethernet_link_Context"]],["impl !RefUnwindSafe for Shared",1,["dual_iir::app::Shared"]],["impl !RefUnwindSafe for Local",1,["dual_iir::app::Local"]],["impl RefUnwindSafe for Settings",1,["dual_iir::Settings"]]], +"idsp":[["impl<T> RefUnwindSafe for Accu<T>where\n T: RefUnwindSafe,",1,["idsp::accu::Accu"]],["impl RefUnwindSafe for Nyquist",1,["idsp::filter::Nyquist"]],["impl<const N: usize, T> RefUnwindSafe for Chain<N, T>where\n T: RefUnwindSafe,",1,["idsp::filter::Chain"]],["impl<T, U> RefUnwindSafe for Cascade<T, U>where\n T: RefUnwindSafe,\n U: RefUnwindSafe,",1,["idsp::filter::Cascade"]],["impl<T> RefUnwindSafe for IIR<T>where\n T: RefUnwindSafe,",1,["idsp::iir::IIR"]],["impl RefUnwindSafe for IIR",1,["idsp::iir_int::IIR"]],["impl<T> RefUnwindSafe for Lockin<T>where\n T: RefUnwindSafe,",1,["idsp::lockin::Lockin"]],["impl<const N: usize> RefUnwindSafe for Lowpass<N>",1,["idsp::lowpass::Lowpass"]],["impl RefUnwindSafe for PLL",1,["idsp::pll::PLL"]],["impl RefUnwindSafe for RPLL",1,["idsp::rpll::RPLL"]],["impl<T> RefUnwindSafe for Unwrapper<T>where\n T: RefUnwindSafe,",1,["idsp::unwrap::Unwrapper"]],["impl<'a, const M: usize, const N: usize> RefUnwindSafe for SymFir<'a, M, N>",1,["idsp::hbf::SymFir"]],["impl<'a, const M: usize, const N: usize> RefUnwindSafe for HbfDec<'a, M, N>",1,["idsp::hbf::HbfDec"]],["impl<'a, const M: usize, const N: usize> RefUnwindSafe for HbfInt<'a, M, N>",1,["idsp::hbf::HbfInt"]],["impl RefUnwindSafe for HbfDecCascade",1,["idsp::hbf::HbfDecCascade"]],["impl RefUnwindSafe for HbfIntCascade",1,["idsp::hbf::HbfIntCascade"]]], +"lockin":[["impl RefUnwindSafe for __rtic_internal_Monotonics",1,["lockin::app::__rtic_internal_Monotonics"]],["impl<'a> RefUnwindSafe for __rtic_internal_init_Context<'a>",1,["lockin::app::__rtic_internal_init_Context"]],["impl<'a> !RefUnwindSafe for __rtic_internal_idleSharedResources<'a>",1,["lockin::app::__rtic_internal_idleSharedResources"]],["impl<'a> !RefUnwindSafe for __rtic_internal_idle_Context<'a>",1,["lockin::app::__rtic_internal_idle_Context"]],["impl<'a> !RefUnwindSafe for __rtic_internal_processLocalResources<'a>",1,["lockin::app::__rtic_internal_processLocalResources"]],["impl<'a> !RefUnwindSafe for __rtic_internal_processSharedResources<'a>",1,["lockin::app::__rtic_internal_processSharedResources"]],["impl<'a> !RefUnwindSafe for __rtic_internal_process_Context<'a>",1,["lockin::app::__rtic_internal_process_Context"]],["impl RefUnwindSafe for __rtic_internal_eth_Context",1,["lockin::app::__rtic_internal_eth_Context"]],["impl<'a> RefUnwindSafe for __rtic_internal_startLocalResources<'a>",1,["lockin::app::__rtic_internal_startLocalResources"]],["impl<'a> RefUnwindSafe for __rtic_internal_start_Context<'a>",1,["lockin::app::__rtic_internal_start_Context"]],["impl<'a> RefUnwindSafe for __rtic_internal_settings_updateLocalResources<'a>",1,["lockin::app::__rtic_internal_settings_updateLocalResources"]],["impl<'a> !RefUnwindSafe for __rtic_internal_settings_updateSharedResources<'a>",1,["lockin::app::__rtic_internal_settings_updateSharedResources"]],["impl<'a> !RefUnwindSafe for __rtic_internal_settings_update_Context<'a>",1,["lockin::app::__rtic_internal_settings_update_Context"]],["impl<'a> !RefUnwindSafe for __rtic_internal_telemetryLocalResources<'a>",1,["lockin::app::__rtic_internal_telemetryLocalResources"]],["impl<'a> !RefUnwindSafe for __rtic_internal_telemetrySharedResources<'a>",1,["lockin::app::__rtic_internal_telemetrySharedResources"]],["impl<'a> !RefUnwindSafe for __rtic_internal_telemetry_Context<'a>",1,["lockin::app::__rtic_internal_telemetry_Context"]],["impl<'a> !RefUnwindSafe for __rtic_internal_usbSharedResources<'a>",1,["lockin::app::__rtic_internal_usbSharedResources"]],["impl<'a> !RefUnwindSafe for __rtic_internal_usb_Context<'a>",1,["lockin::app::__rtic_internal_usb_Context"]],["impl<'a> !RefUnwindSafe for __rtic_internal_ethernet_linkSharedResources<'a>",1,["lockin::app::__rtic_internal_ethernet_linkSharedResources"]],["impl<'a> !RefUnwindSafe for __rtic_internal_ethernet_link_Context<'a>",1,["lockin::app::__rtic_internal_ethernet_link_Context"]],["impl !RefUnwindSafe for Shared",1,["lockin::app::Shared"]],["impl !RefUnwindSafe for Local",1,["lockin::app::Local"]],["impl RefUnwindSafe for Conf",1,["lockin::Conf"]],["impl RefUnwindSafe for LockinMode",1,["lockin::LockinMode"]],["impl RefUnwindSafe for Settings",1,["lockin::Settings"]]], +"miniconf":[["impl<E> RefUnwindSafe for Error<E>where\n E: RefUnwindSafe,",1,["miniconf::tree::Error"]],["impl RefUnwindSafe for SliceShort",1,["miniconf::tree::SliceShort"]],["impl RefUnwindSafe for Metadata",1,["miniconf::tree::Metadata"]],["impl<'a, M: ?Sized, const Y: usize, P> RefUnwindSafe for PathIter<'a, M, Y, P>where\n M: RefUnwindSafe,\n P: RefUnwindSafe,",1,["miniconf::iter::PathIter"]],["impl<'buf, Settings, Stack, Clock, Broker, const Y: usize> RefUnwindSafe for MqttClient<'buf, Settings, Stack, Clock, Broker, Y>where\n Broker: RefUnwindSafe,\n Clock: RefUnwindSafe,\n Settings: RefUnwindSafe,\n Stack: RefUnwindSafe,\n <Clock as Clock>::T: RefUnwindSafe,\n <Stack as TcpClientStack>::TcpSocket: RefUnwindSafe,",1,["miniconf::mqtt_client::MqttClient"]]], +"stabilizer":[["impl RefUnwindSafe for AdcCode",1,["stabilizer::hardware::adc::AdcCode"]],["impl RefUnwindSafe for Adc0Input",1,["stabilizer::hardware::adc::Adc0Input"]],["impl RefUnwindSafe for Adc1Input",1,["stabilizer::hardware::adc::Adc1Input"]],["impl RefUnwindSafe for Gain",1,["stabilizer::hardware::afe::Gain"]],["impl<A0, A1> RefUnwindSafe for ProgrammableGainAmplifier<A0, A1>where\n A0: RefUnwindSafe,\n A1: RefUnwindSafe,",1,["stabilizer::hardware::afe::ProgrammableGainAmplifier"]],["impl !RefUnwindSafe for CpuTempSensor",1,["stabilizer::hardware::cpu_temp_sensor::CpuTempSensor"]],["impl RefUnwindSafe for DacCode",1,["stabilizer::hardware::dac::DacCode"]],["impl RefUnwindSafe for Dac0Output",1,["stabilizer::hardware::dac::Dac0Output"]],["impl RefUnwindSafe for Dac1Output",1,["stabilizer::hardware::dac::Dac1Output"]],["impl RefUnwindSafe for AsmDelay",1,["stabilizer::hardware::delay::AsmDelay"]],["impl RefUnwindSafe for InputStamper",1,["stabilizer::hardware::input_stamper::InputStamper"]],["impl RefUnwindSafe for DdsOutput",1,["stabilizer::hardware::pounder::dds_output::DdsOutput"]],["impl<'a> RefUnwindSafe for ProfileBuilder<'a>",1,["stabilizer::hardware::pounder::dds_output::ProfileBuilder"]],["impl RefUnwindSafe for Channel",1,["stabilizer::hardware::pounder::hrtimer::Channel"]],["impl RefUnwindSafe for HighResTimerE",1,["stabilizer::hardware::pounder::hrtimer::HighResTimerE"]],["impl RefUnwindSafe for Timestamper",1,["stabilizer::hardware::pounder::timestamp::Timestamper"]],["impl RefUnwindSafe for GpioPin",1,["stabilizer::hardware::pounder::GpioPin"]],["impl RefUnwindSafe for Error",1,["stabilizer::hardware::pounder::Error"]],["impl RefUnwindSafe for Channel",1,["stabilizer::hardware::pounder::Channel"]],["impl RefUnwindSafe for DdsChannelState",1,["stabilizer::hardware::pounder::DdsChannelState"]],["impl RefUnwindSafe for ChannelState",1,["stabilizer::hardware::pounder::ChannelState"]],["impl RefUnwindSafe for InputChannelState",1,["stabilizer::hardware::pounder::InputChannelState"]],["impl RefUnwindSafe for OutputChannelState",1,["stabilizer::hardware::pounder::OutputChannelState"]],["impl RefUnwindSafe for DdsClockConfig",1,["stabilizer::hardware::pounder::DdsClockConfig"]],["impl RefUnwindSafe for QspiInterface",1,["stabilizer::hardware::pounder::QspiInterface"]],["impl !RefUnwindSafe for PounderDevices",1,["stabilizer::hardware::pounder::PounderDevices"]],["impl !RefUnwindSafe for OutputBuffer",1,["stabilizer::hardware::serial_terminal::OutputBuffer"]],["impl !RefUnwindSafe for SerialTerminal",1,["stabilizer::hardware::serial_terminal::SerialTerminal"]],["impl RefUnwindSafe for NetStorage",1,["stabilizer::hardware::setup::NetStorage"]],["impl RefUnwindSafe for UdpSocketStorage",1,["stabilizer::hardware::setup::UdpSocketStorage"]],["impl RefUnwindSafe for TcpSocketStorage",1,["stabilizer::hardware::setup::TcpSocketStorage"]],["impl RefUnwindSafe for NetworkDevices",1,["stabilizer::hardware::setup::NetworkDevices"]],["impl RefUnwindSafe for EemGpioDevices",1,["stabilizer::hardware::setup::EemGpioDevices"]],["impl !RefUnwindSafe for StabilizerDevices",1,["stabilizer::hardware::setup::StabilizerDevices"]],["impl !RefUnwindSafe for PounderDevices",1,["stabilizer::hardware::setup::PounderDevices"]],["impl RefUnwindSafe for AdcError",1,["stabilizer::hardware::shared_adc::AdcError"]],["impl<'a, Adc, PIN> !RefUnwindSafe for AdcChannel<'a, Adc, PIN>",1,["stabilizer::hardware::shared_adc::AdcChannel"]],["impl<Adc> !RefUnwindSafe for SharedAdc<Adc>",1,["stabilizer::hardware::shared_adc::SharedAdc"]],["impl RefUnwindSafe for Signal",1,["stabilizer::hardware::signal_generator::Signal"]],["impl RefUnwindSafe for BasicConfig",1,["stabilizer::hardware::signal_generator::BasicConfig"]],["impl RefUnwindSafe for Error",1,["stabilizer::hardware::signal_generator::Error"]],["impl RefUnwindSafe for Config",1,["stabilizer::hardware::signal_generator::Config"]],["impl RefUnwindSafe for SignalGenerator",1,["stabilizer::hardware::signal_generator::SignalGenerator"]],["impl RefUnwindSafe for UpdateEvent",1,["stabilizer::hardware::timers::tim2::UpdateEvent"]],["impl RefUnwindSafe for Channels",1,["stabilizer::hardware::timers::tim2::Channels"]],["impl RefUnwindSafe for Channel1",1,["stabilizer::hardware::timers::tim2::Channel1"]],["impl RefUnwindSafe for Channel1InputCapture",1,["stabilizer::hardware::timers::tim2::Channel1InputCapture"]],["impl RefUnwindSafe for Channel2",1,["stabilizer::hardware::timers::tim2::Channel2"]],["impl RefUnwindSafe for Channel2InputCapture",1,["stabilizer::hardware::timers::tim2::Channel2InputCapture"]],["impl RefUnwindSafe for Channel3",1,["stabilizer::hardware::timers::tim2::Channel3"]],["impl RefUnwindSafe for Channel3InputCapture",1,["stabilizer::hardware::timers::tim2::Channel3InputCapture"]],["impl RefUnwindSafe for Channel4",1,["stabilizer::hardware::timers::tim2::Channel4"]],["impl RefUnwindSafe for Channel4InputCapture",1,["stabilizer::hardware::timers::tim2::Channel4InputCapture"]],["impl RefUnwindSafe for UpdateEvent",1,["stabilizer::hardware::timers::tim3::UpdateEvent"]],["impl RefUnwindSafe for Channels",1,["stabilizer::hardware::timers::tim3::Channels"]],["impl RefUnwindSafe for Channel1",1,["stabilizer::hardware::timers::tim3::Channel1"]],["impl RefUnwindSafe for Channel1InputCapture",1,["stabilizer::hardware::timers::tim3::Channel1InputCapture"]],["impl RefUnwindSafe for Channel2",1,["stabilizer::hardware::timers::tim3::Channel2"]],["impl RefUnwindSafe for Channel2InputCapture",1,["stabilizer::hardware::timers::tim3::Channel2InputCapture"]],["impl RefUnwindSafe for Channel3",1,["stabilizer::hardware::timers::tim3::Channel3"]],["impl RefUnwindSafe for Channel3InputCapture",1,["stabilizer::hardware::timers::tim3::Channel3InputCapture"]],["impl RefUnwindSafe for Channel4",1,["stabilizer::hardware::timers::tim3::Channel4"]],["impl RefUnwindSafe for Channel4InputCapture",1,["stabilizer::hardware::timers::tim3::Channel4InputCapture"]],["impl RefUnwindSafe for UpdateEvent",1,["stabilizer::hardware::timers::tim5::UpdateEvent"]],["impl RefUnwindSafe for Channels",1,["stabilizer::hardware::timers::tim5::Channels"]],["impl RefUnwindSafe for Channel1",1,["stabilizer::hardware::timers::tim5::Channel1"]],["impl RefUnwindSafe for Channel1InputCapture",1,["stabilizer::hardware::timers::tim5::Channel1InputCapture"]],["impl RefUnwindSafe for Channel2",1,["stabilizer::hardware::timers::tim5::Channel2"]],["impl RefUnwindSafe for Channel2InputCapture",1,["stabilizer::hardware::timers::tim5::Channel2InputCapture"]],["impl RefUnwindSafe for Channel3",1,["stabilizer::hardware::timers::tim5::Channel3"]],["impl RefUnwindSafe for Channel3InputCapture",1,["stabilizer::hardware::timers::tim5::Channel3InputCapture"]],["impl RefUnwindSafe for Channel4",1,["stabilizer::hardware::timers::tim5::Channel4"]],["impl RefUnwindSafe for Channel4InputCapture",1,["stabilizer::hardware::timers::tim5::Channel4InputCapture"]],["impl RefUnwindSafe for UpdateEvent",1,["stabilizer::hardware::timers::tim8::UpdateEvent"]],["impl RefUnwindSafe for Channels",1,["stabilizer::hardware::timers::tim8::Channels"]],["impl RefUnwindSafe for Channel1",1,["stabilizer::hardware::timers::tim8::Channel1"]],["impl RefUnwindSafe for Channel1InputCapture",1,["stabilizer::hardware::timers::tim8::Channel1InputCapture"]],["impl RefUnwindSafe for Channel2",1,["stabilizer::hardware::timers::tim8::Channel2"]],["impl RefUnwindSafe for Channel2InputCapture",1,["stabilizer::hardware::timers::tim8::Channel2InputCapture"]],["impl RefUnwindSafe for Channel3",1,["stabilizer::hardware::timers::tim8::Channel3"]],["impl RefUnwindSafe for Channel3InputCapture",1,["stabilizer::hardware::timers::tim8::Channel3InputCapture"]],["impl RefUnwindSafe for Channel4",1,["stabilizer::hardware::timers::tim8::Channel4"]],["impl RefUnwindSafe for Channel4InputCapture",1,["stabilizer::hardware::timers::tim8::Channel4InputCapture"]],["impl RefUnwindSafe for TriggerGenerator",1,["stabilizer::hardware::timers::TriggerGenerator"]],["impl RefUnwindSafe for TriggerSource",1,["stabilizer::hardware::timers::TriggerSource"]],["impl RefUnwindSafe for Prescaler",1,["stabilizer::hardware::timers::Prescaler"]],["impl RefUnwindSafe for SlaveMode",1,["stabilizer::hardware::timers::SlaveMode"]],["impl RefUnwindSafe for InputFilter",1,["stabilizer::hardware::timers::InputFilter"]],["impl RefUnwindSafe for SamplingTimer",1,["stabilizer::hardware::timers::SamplingTimer"]],["impl RefUnwindSafe for ShadowSamplingTimer",1,["stabilizer::hardware::timers::ShadowSamplingTimer"]],["impl RefUnwindSafe for TimestampTimer",1,["stabilizer::hardware::timers::TimestampTimer"]],["impl RefUnwindSafe for PounderTimestampTimer",1,["stabilizer::hardware::timers::PounderTimestampTimer"]],["impl RefUnwindSafe for StreamTarget",1,["stabilizer::net::data_stream::StreamTarget"]],["impl RefUnwindSafe for StreamFormat",1,["stabilizer::net::data_stream::StreamFormat"]],["impl !RefUnwindSafe for FrameGenerator",1,["stabilizer::net::data_stream::FrameGenerator"]],["impl !RefUnwindSafe for DataStream",1,["stabilizer::net::data_stream::DataStream"]],["impl !RefUnwindSafe for NetworkProcessor",1,["stabilizer::net::network_processor::NetworkProcessor"]],["impl<T> !RefUnwindSafe for TelemetryClient<T>",1,["stabilizer::net::telemetry::TelemetryClient"]],["impl RefUnwindSafe for TelemetryBuffer",1,["stabilizer::net::telemetry::TelemetryBuffer"]],["impl RefUnwindSafe for Telemetry",1,["stabilizer::net::telemetry::Telemetry"]],["impl RefUnwindSafe for MqttStorage",1,["stabilizer::net::MqttStorage"]],["impl RefUnwindSafe for UpdateState",1,["stabilizer::net::UpdateState"]],["impl RefUnwindSafe for NetworkState",1,["stabilizer::net::NetworkState"]],["impl<S, T, const Y: usize> !RefUnwindSafe for NetworkUsers<S, T, Y>",1,["stabilizer::net::NetworkUsers"]]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/core/panic/unwind_safe/trait.UnwindSafe.js b/firmware/implementors/core/panic/unwind_safe/trait.UnwindSafe.js new file mode 100644 index 0000000000..61d631da1a --- /dev/null +++ b/firmware/implementors/core/panic/unwind_safe/trait.UnwindSafe.js @@ -0,0 +1,8 @@ +(function() {var implementors = { +"ad9959":[["impl<INTERFACE> UnwindSafe for Ad9959<INTERFACE>where\n INTERFACE: UnwindSafe,",1,["ad9959::Ad9959"]],["impl UnwindSafe for Mode",1,["ad9959::Mode"]],["impl UnwindSafe for Channel",1,["ad9959::Channel"]],["impl UnwindSafe for Register",1,["ad9959::Register"]],["impl UnwindSafe for Error",1,["ad9959::Error"]],["impl UnwindSafe for ProfileSerializer",1,["ad9959::ProfileSerializer"]]], +"dual_iir":[["impl UnwindSafe for __rtic_internal_Monotonics",1,["dual_iir::app::__rtic_internal_Monotonics"]],["impl<'a> UnwindSafe for __rtic_internal_init_Context<'a>",1,["dual_iir::app::__rtic_internal_init_Context"]],["impl<'a> !UnwindSafe for __rtic_internal_idleSharedResources<'a>",1,["dual_iir::app::__rtic_internal_idleSharedResources"]],["impl<'a> !UnwindSafe for __rtic_internal_idle_Context<'a>",1,["dual_iir::app::__rtic_internal_idle_Context"]],["impl<'a> !UnwindSafe for __rtic_internal_processLocalResources<'a>",1,["dual_iir::app::__rtic_internal_processLocalResources"]],["impl<'a> !UnwindSafe for __rtic_internal_processSharedResources<'a>",1,["dual_iir::app::__rtic_internal_processSharedResources"]],["impl<'a> !UnwindSafe for __rtic_internal_process_Context<'a>",1,["dual_iir::app::__rtic_internal_process_Context"]],["impl UnwindSafe for __rtic_internal_eth_Context",1,["dual_iir::app::__rtic_internal_eth_Context"]],["impl UnwindSafe for __rtic_internal_spi2_Context",1,["dual_iir::app::__rtic_internal_spi2_Context"]],["impl UnwindSafe for __rtic_internal_spi3_Context",1,["dual_iir::app::__rtic_internal_spi3_Context"]],["impl UnwindSafe for __rtic_internal_spi4_Context",1,["dual_iir::app::__rtic_internal_spi4_Context"]],["impl UnwindSafe for __rtic_internal_spi5_Context",1,["dual_iir::app::__rtic_internal_spi5_Context"]],["impl<'a> !UnwindSafe for __rtic_internal_startLocalResources<'a>",1,["dual_iir::app::__rtic_internal_startLocalResources"]],["impl<'a> !UnwindSafe for __rtic_internal_start_Context<'a>",1,["dual_iir::app::__rtic_internal_start_Context"]],["impl<'a> !UnwindSafe for __rtic_internal_settings_updateLocalResources<'a>",1,["dual_iir::app::__rtic_internal_settings_updateLocalResources"]],["impl<'a> !UnwindSafe for __rtic_internal_settings_updateSharedResources<'a>",1,["dual_iir::app::__rtic_internal_settings_updateSharedResources"]],["impl<'a> !UnwindSafe for __rtic_internal_settings_update_Context<'a>",1,["dual_iir::app::__rtic_internal_settings_update_Context"]],["impl<'a> !UnwindSafe for __rtic_internal_telemetryLocalResources<'a>",1,["dual_iir::app::__rtic_internal_telemetryLocalResources"]],["impl<'a> !UnwindSafe for __rtic_internal_telemetrySharedResources<'a>",1,["dual_iir::app::__rtic_internal_telemetrySharedResources"]],["impl<'a> !UnwindSafe for __rtic_internal_telemetry_Context<'a>",1,["dual_iir::app::__rtic_internal_telemetry_Context"]],["impl<'a> !UnwindSafe for __rtic_internal_usbSharedResources<'a>",1,["dual_iir::app::__rtic_internal_usbSharedResources"]],["impl<'a> !UnwindSafe for __rtic_internal_usb_Context<'a>",1,["dual_iir::app::__rtic_internal_usb_Context"]],["impl<'a> !UnwindSafe for __rtic_internal_ethernet_linkSharedResources<'a>",1,["dual_iir::app::__rtic_internal_ethernet_linkSharedResources"]],["impl<'a> !UnwindSafe for __rtic_internal_ethernet_link_Context<'a>",1,["dual_iir::app::__rtic_internal_ethernet_link_Context"]],["impl !UnwindSafe for Shared",1,["dual_iir::app::Shared"]],["impl !UnwindSafe for Local",1,["dual_iir::app::Local"]],["impl UnwindSafe for Settings",1,["dual_iir::Settings"]]], +"idsp":[["impl<T> UnwindSafe for Accu<T>where\n T: UnwindSafe,",1,["idsp::accu::Accu"]],["impl UnwindSafe for Nyquist",1,["idsp::filter::Nyquist"]],["impl<const N: usize, T> UnwindSafe for Chain<N, T>where\n T: UnwindSafe,",1,["idsp::filter::Chain"]],["impl<T, U> UnwindSafe for Cascade<T, U>where\n T: UnwindSafe,\n U: UnwindSafe,",1,["idsp::filter::Cascade"]],["impl<T> UnwindSafe for IIR<T>where\n T: UnwindSafe,",1,["idsp::iir::IIR"]],["impl UnwindSafe for IIR",1,["idsp::iir_int::IIR"]],["impl<T> UnwindSafe for Lockin<T>where\n T: UnwindSafe,",1,["idsp::lockin::Lockin"]],["impl<const N: usize> UnwindSafe for Lowpass<N>",1,["idsp::lowpass::Lowpass"]],["impl UnwindSafe for PLL",1,["idsp::pll::PLL"]],["impl UnwindSafe for RPLL",1,["idsp::rpll::RPLL"]],["impl<T> UnwindSafe for Unwrapper<T>where\n T: UnwindSafe,",1,["idsp::unwrap::Unwrapper"]],["impl<'a, const M: usize, const N: usize> UnwindSafe for SymFir<'a, M, N>",1,["idsp::hbf::SymFir"]],["impl<'a, const M: usize, const N: usize> UnwindSafe for HbfDec<'a, M, N>",1,["idsp::hbf::HbfDec"]],["impl<'a, const M: usize, const N: usize> UnwindSafe for HbfInt<'a, M, N>",1,["idsp::hbf::HbfInt"]],["impl UnwindSafe for HbfDecCascade",1,["idsp::hbf::HbfDecCascade"]],["impl UnwindSafe for HbfIntCascade",1,["idsp::hbf::HbfIntCascade"]]], +"lockin":[["impl UnwindSafe for __rtic_internal_Monotonics",1,["lockin::app::__rtic_internal_Monotonics"]],["impl<'a> UnwindSafe for __rtic_internal_init_Context<'a>",1,["lockin::app::__rtic_internal_init_Context"]],["impl<'a> !UnwindSafe for __rtic_internal_idleSharedResources<'a>",1,["lockin::app::__rtic_internal_idleSharedResources"]],["impl<'a> !UnwindSafe for __rtic_internal_idle_Context<'a>",1,["lockin::app::__rtic_internal_idle_Context"]],["impl<'a> !UnwindSafe for __rtic_internal_processLocalResources<'a>",1,["lockin::app::__rtic_internal_processLocalResources"]],["impl<'a> !UnwindSafe for __rtic_internal_processSharedResources<'a>",1,["lockin::app::__rtic_internal_processSharedResources"]],["impl<'a> !UnwindSafe for __rtic_internal_process_Context<'a>",1,["lockin::app::__rtic_internal_process_Context"]],["impl UnwindSafe for __rtic_internal_eth_Context",1,["lockin::app::__rtic_internal_eth_Context"]],["impl<'a> !UnwindSafe for __rtic_internal_startLocalResources<'a>",1,["lockin::app::__rtic_internal_startLocalResources"]],["impl<'a> !UnwindSafe for __rtic_internal_start_Context<'a>",1,["lockin::app::__rtic_internal_start_Context"]],["impl<'a> !UnwindSafe for __rtic_internal_settings_updateLocalResources<'a>",1,["lockin::app::__rtic_internal_settings_updateLocalResources"]],["impl<'a> !UnwindSafe for __rtic_internal_settings_updateSharedResources<'a>",1,["lockin::app::__rtic_internal_settings_updateSharedResources"]],["impl<'a> !UnwindSafe for __rtic_internal_settings_update_Context<'a>",1,["lockin::app::__rtic_internal_settings_update_Context"]],["impl<'a> !UnwindSafe for __rtic_internal_telemetryLocalResources<'a>",1,["lockin::app::__rtic_internal_telemetryLocalResources"]],["impl<'a> !UnwindSafe for __rtic_internal_telemetrySharedResources<'a>",1,["lockin::app::__rtic_internal_telemetrySharedResources"]],["impl<'a> !UnwindSafe for __rtic_internal_telemetry_Context<'a>",1,["lockin::app::__rtic_internal_telemetry_Context"]],["impl<'a> !UnwindSafe for __rtic_internal_usbSharedResources<'a>",1,["lockin::app::__rtic_internal_usbSharedResources"]],["impl<'a> !UnwindSafe for __rtic_internal_usb_Context<'a>",1,["lockin::app::__rtic_internal_usb_Context"]],["impl<'a> !UnwindSafe for __rtic_internal_ethernet_linkSharedResources<'a>",1,["lockin::app::__rtic_internal_ethernet_linkSharedResources"]],["impl<'a> !UnwindSafe for __rtic_internal_ethernet_link_Context<'a>",1,["lockin::app::__rtic_internal_ethernet_link_Context"]],["impl !UnwindSafe for Shared",1,["lockin::app::Shared"]],["impl !UnwindSafe for Local",1,["lockin::app::Local"]],["impl UnwindSafe for Conf",1,["lockin::Conf"]],["impl UnwindSafe for LockinMode",1,["lockin::LockinMode"]],["impl UnwindSafe for Settings",1,["lockin::Settings"]]], +"miniconf":[["impl<E> UnwindSafe for Error<E>where\n E: UnwindSafe,",1,["miniconf::tree::Error"]],["impl UnwindSafe for SliceShort",1,["miniconf::tree::SliceShort"]],["impl UnwindSafe for Metadata",1,["miniconf::tree::Metadata"]],["impl<'a, M: ?Sized, const Y: usize, P> UnwindSafe for PathIter<'a, M, Y, P>where\n M: UnwindSafe,\n P: UnwindSafe,",1,["miniconf::iter::PathIter"]],["impl<'buf, Settings, Stack, Clock, Broker, const Y: usize> !UnwindSafe for MqttClient<'buf, Settings, Stack, Clock, Broker, Y>",1,["miniconf::mqtt_client::MqttClient"]]], +"stabilizer":[["impl UnwindSafe for AdcCode",1,["stabilizer::hardware::adc::AdcCode"]],["impl !UnwindSafe for Adc0Input",1,["stabilizer::hardware::adc::Adc0Input"]],["impl !UnwindSafe for Adc1Input",1,["stabilizer::hardware::adc::Adc1Input"]],["impl UnwindSafe for Gain",1,["stabilizer::hardware::afe::Gain"]],["impl<A0, A1> UnwindSafe for ProgrammableGainAmplifier<A0, A1>where\n A0: UnwindSafe,\n A1: UnwindSafe,",1,["stabilizer::hardware::afe::ProgrammableGainAmplifier"]],["impl !UnwindSafe for CpuTempSensor",1,["stabilizer::hardware::cpu_temp_sensor::CpuTempSensor"]],["impl UnwindSafe for DacCode",1,["stabilizer::hardware::dac::DacCode"]],["impl !UnwindSafe for Dac0Output",1,["stabilizer::hardware::dac::Dac0Output"]],["impl !UnwindSafe for Dac1Output",1,["stabilizer::hardware::dac::Dac1Output"]],["impl UnwindSafe for AsmDelay",1,["stabilizer::hardware::delay::AsmDelay"]],["impl UnwindSafe for InputStamper",1,["stabilizer::hardware::input_stamper::InputStamper"]],["impl UnwindSafe for DdsOutput",1,["stabilizer::hardware::pounder::dds_output::DdsOutput"]],["impl<'a> !UnwindSafe for ProfileBuilder<'a>",1,["stabilizer::hardware::pounder::dds_output::ProfileBuilder"]],["impl UnwindSafe for Channel",1,["stabilizer::hardware::pounder::hrtimer::Channel"]],["impl UnwindSafe for HighResTimerE",1,["stabilizer::hardware::pounder::hrtimer::HighResTimerE"]],["impl UnwindSafe for Timestamper",1,["stabilizer::hardware::pounder::timestamp::Timestamper"]],["impl UnwindSafe for GpioPin",1,["stabilizer::hardware::pounder::GpioPin"]],["impl UnwindSafe for Error",1,["stabilizer::hardware::pounder::Error"]],["impl UnwindSafe for Channel",1,["stabilizer::hardware::pounder::Channel"]],["impl UnwindSafe for DdsChannelState",1,["stabilizer::hardware::pounder::DdsChannelState"]],["impl UnwindSafe for ChannelState",1,["stabilizer::hardware::pounder::ChannelState"]],["impl UnwindSafe for InputChannelState",1,["stabilizer::hardware::pounder::InputChannelState"]],["impl UnwindSafe for OutputChannelState",1,["stabilizer::hardware::pounder::OutputChannelState"]],["impl UnwindSafe for DdsClockConfig",1,["stabilizer::hardware::pounder::DdsClockConfig"]],["impl UnwindSafe for QspiInterface",1,["stabilizer::hardware::pounder::QspiInterface"]],["impl !UnwindSafe for PounderDevices",1,["stabilizer::hardware::pounder::PounderDevices"]],["impl !UnwindSafe for OutputBuffer",1,["stabilizer::hardware::serial_terminal::OutputBuffer"]],["impl !UnwindSafe for SerialTerminal",1,["stabilizer::hardware::serial_terminal::SerialTerminal"]],["impl !UnwindSafe for NetStorage",1,["stabilizer::hardware::setup::NetStorage"]],["impl UnwindSafe for UdpSocketStorage",1,["stabilizer::hardware::setup::UdpSocketStorage"]],["impl UnwindSafe for TcpSocketStorage",1,["stabilizer::hardware::setup::TcpSocketStorage"]],["impl !UnwindSafe for NetworkDevices",1,["stabilizer::hardware::setup::NetworkDevices"]],["impl UnwindSafe for EemGpioDevices",1,["stabilizer::hardware::setup::EemGpioDevices"]],["impl !UnwindSafe for StabilizerDevices",1,["stabilizer::hardware::setup::StabilizerDevices"]],["impl !UnwindSafe for PounderDevices",1,["stabilizer::hardware::setup::PounderDevices"]],["impl UnwindSafe for AdcError",1,["stabilizer::hardware::shared_adc::AdcError"]],["impl<'a, Adc, PIN> !UnwindSafe for AdcChannel<'a, Adc, PIN>",1,["stabilizer::hardware::shared_adc::AdcChannel"]],["impl<Adc> UnwindSafe for SharedAdc<Adc>where\n Adc: UnwindSafe,",1,["stabilizer::hardware::shared_adc::SharedAdc"]],["impl UnwindSafe for Signal",1,["stabilizer::hardware::signal_generator::Signal"]],["impl UnwindSafe for BasicConfig",1,["stabilizer::hardware::signal_generator::BasicConfig"]],["impl UnwindSafe for Error",1,["stabilizer::hardware::signal_generator::Error"]],["impl UnwindSafe for Config",1,["stabilizer::hardware::signal_generator::Config"]],["impl UnwindSafe for SignalGenerator",1,["stabilizer::hardware::signal_generator::SignalGenerator"]],["impl UnwindSafe for UpdateEvent",1,["stabilizer::hardware::timers::tim2::UpdateEvent"]],["impl UnwindSafe for Channels",1,["stabilizer::hardware::timers::tim2::Channels"]],["impl UnwindSafe for Channel1",1,["stabilizer::hardware::timers::tim2::Channel1"]],["impl UnwindSafe for Channel1InputCapture",1,["stabilizer::hardware::timers::tim2::Channel1InputCapture"]],["impl UnwindSafe for Channel2",1,["stabilizer::hardware::timers::tim2::Channel2"]],["impl UnwindSafe for Channel2InputCapture",1,["stabilizer::hardware::timers::tim2::Channel2InputCapture"]],["impl UnwindSafe for Channel3",1,["stabilizer::hardware::timers::tim2::Channel3"]],["impl UnwindSafe for Channel3InputCapture",1,["stabilizer::hardware::timers::tim2::Channel3InputCapture"]],["impl UnwindSafe for Channel4",1,["stabilizer::hardware::timers::tim2::Channel4"]],["impl UnwindSafe for Channel4InputCapture",1,["stabilizer::hardware::timers::tim2::Channel4InputCapture"]],["impl UnwindSafe for UpdateEvent",1,["stabilizer::hardware::timers::tim3::UpdateEvent"]],["impl UnwindSafe for Channels",1,["stabilizer::hardware::timers::tim3::Channels"]],["impl UnwindSafe for Channel1",1,["stabilizer::hardware::timers::tim3::Channel1"]],["impl UnwindSafe for Channel1InputCapture",1,["stabilizer::hardware::timers::tim3::Channel1InputCapture"]],["impl UnwindSafe for Channel2",1,["stabilizer::hardware::timers::tim3::Channel2"]],["impl UnwindSafe for Channel2InputCapture",1,["stabilizer::hardware::timers::tim3::Channel2InputCapture"]],["impl UnwindSafe for Channel3",1,["stabilizer::hardware::timers::tim3::Channel3"]],["impl UnwindSafe for Channel3InputCapture",1,["stabilizer::hardware::timers::tim3::Channel3InputCapture"]],["impl UnwindSafe for Channel4",1,["stabilizer::hardware::timers::tim3::Channel4"]],["impl UnwindSafe for Channel4InputCapture",1,["stabilizer::hardware::timers::tim3::Channel4InputCapture"]],["impl UnwindSafe for UpdateEvent",1,["stabilizer::hardware::timers::tim5::UpdateEvent"]],["impl UnwindSafe for Channels",1,["stabilizer::hardware::timers::tim5::Channels"]],["impl UnwindSafe for Channel1",1,["stabilizer::hardware::timers::tim5::Channel1"]],["impl UnwindSafe for Channel1InputCapture",1,["stabilizer::hardware::timers::tim5::Channel1InputCapture"]],["impl UnwindSafe for Channel2",1,["stabilizer::hardware::timers::tim5::Channel2"]],["impl UnwindSafe for Channel2InputCapture",1,["stabilizer::hardware::timers::tim5::Channel2InputCapture"]],["impl UnwindSafe for Channel3",1,["stabilizer::hardware::timers::tim5::Channel3"]],["impl UnwindSafe for Channel3InputCapture",1,["stabilizer::hardware::timers::tim5::Channel3InputCapture"]],["impl UnwindSafe for Channel4",1,["stabilizer::hardware::timers::tim5::Channel4"]],["impl UnwindSafe for Channel4InputCapture",1,["stabilizer::hardware::timers::tim5::Channel4InputCapture"]],["impl UnwindSafe for UpdateEvent",1,["stabilizer::hardware::timers::tim8::UpdateEvent"]],["impl UnwindSafe for Channels",1,["stabilizer::hardware::timers::tim8::Channels"]],["impl UnwindSafe for Channel1",1,["stabilizer::hardware::timers::tim8::Channel1"]],["impl UnwindSafe for Channel1InputCapture",1,["stabilizer::hardware::timers::tim8::Channel1InputCapture"]],["impl UnwindSafe for Channel2",1,["stabilizer::hardware::timers::tim8::Channel2"]],["impl UnwindSafe for Channel2InputCapture",1,["stabilizer::hardware::timers::tim8::Channel2InputCapture"]],["impl UnwindSafe for Channel3",1,["stabilizer::hardware::timers::tim8::Channel3"]],["impl UnwindSafe for Channel3InputCapture",1,["stabilizer::hardware::timers::tim8::Channel3InputCapture"]],["impl UnwindSafe for Channel4",1,["stabilizer::hardware::timers::tim8::Channel4"]],["impl UnwindSafe for Channel4InputCapture",1,["stabilizer::hardware::timers::tim8::Channel4InputCapture"]],["impl UnwindSafe for TriggerGenerator",1,["stabilizer::hardware::timers::TriggerGenerator"]],["impl UnwindSafe for TriggerSource",1,["stabilizer::hardware::timers::TriggerSource"]],["impl UnwindSafe for Prescaler",1,["stabilizer::hardware::timers::Prescaler"]],["impl UnwindSafe for SlaveMode",1,["stabilizer::hardware::timers::SlaveMode"]],["impl UnwindSafe for InputFilter",1,["stabilizer::hardware::timers::InputFilter"]],["impl UnwindSafe for SamplingTimer",1,["stabilizer::hardware::timers::SamplingTimer"]],["impl UnwindSafe for ShadowSamplingTimer",1,["stabilizer::hardware::timers::ShadowSamplingTimer"]],["impl UnwindSafe for TimestampTimer",1,["stabilizer::hardware::timers::TimestampTimer"]],["impl UnwindSafe for PounderTimestampTimer",1,["stabilizer::hardware::timers::PounderTimestampTimer"]],["impl UnwindSafe for StreamTarget",1,["stabilizer::net::data_stream::StreamTarget"]],["impl UnwindSafe for StreamFormat",1,["stabilizer::net::data_stream::StreamFormat"]],["impl !UnwindSafe for FrameGenerator",1,["stabilizer::net::data_stream::FrameGenerator"]],["impl !UnwindSafe for DataStream",1,["stabilizer::net::data_stream::DataStream"]],["impl !UnwindSafe for NetworkProcessor",1,["stabilizer::net::network_processor::NetworkProcessor"]],["impl<T> !UnwindSafe for TelemetryClient<T>",1,["stabilizer::net::telemetry::TelemetryClient"]],["impl UnwindSafe for TelemetryBuffer",1,["stabilizer::net::telemetry::TelemetryBuffer"]],["impl UnwindSafe for Telemetry",1,["stabilizer::net::telemetry::Telemetry"]],["impl UnwindSafe for MqttStorage",1,["stabilizer::net::MqttStorage"]],["impl UnwindSafe for UpdateState",1,["stabilizer::net::UpdateState"]],["impl UnwindSafe for NetworkState",1,["stabilizer::net::NetworkState"]],["impl<S, T, const Y: usize> !UnwindSafe for NetworkUsers<S, T, Y>",1,["stabilizer::net::NetworkUsers"]]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/embedded_hal/blocking/delay/trait.DelayMs.js b/firmware/implementors/embedded_hal/blocking/delay/trait.DelayMs.js new file mode 100644 index 0000000000..4070764915 --- /dev/null +++ b/firmware/implementors/embedded_hal/blocking/delay/trait.DelayMs.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"stabilizer":[["impl<U> DelayMs<U> for AsmDelaywhere\n U: Into<u32>,"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/embedded_hal/blocking/delay/trait.DelayUs.js b/firmware/implementors/embedded_hal/blocking/delay/trait.DelayUs.js new file mode 100644 index 0000000000..9c776443a6 --- /dev/null +++ b/firmware/implementors/embedded_hal/blocking/delay/trait.DelayUs.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"stabilizer":[["impl<U> DelayUs<U> for AsmDelaywhere\n U: Into<u32>,"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/enum_iterator/trait.Sequence.js b/firmware/implementors/enum_iterator/trait.Sequence.js new file mode 100644 index 0000000000..2004a1c45b --- /dev/null +++ b/firmware/implementors/enum_iterator/trait.Sequence.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"stabilizer":[["impl Sequence for GpioPin"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/idsp/complex/trait.ComplexExt.js b/firmware/implementors/idsp/complex/trait.ComplexExt.js new file mode 100644 index 0000000000..37b9d5ae3e --- /dev/null +++ b/firmware/implementors/idsp/complex/trait.ComplexExt.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"idsp":[] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/idsp/complex/trait.MulScaled.js b/firmware/implementors/idsp/complex/trait.MulScaled.js new file mode 100644 index 0000000000..37b9d5ae3e --- /dev/null +++ b/firmware/implementors/idsp/complex/trait.MulScaled.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"idsp":[] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/idsp/filter/trait.Filter.js b/firmware/implementors/idsp/filter/trait.Filter.js new file mode 100644 index 0000000000..37b9d5ae3e --- /dev/null +++ b/firmware/implementors/idsp/filter/trait.Filter.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"idsp":[] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/idsp/hbf/trait.Filter.js b/firmware/implementors/idsp/hbf/trait.Filter.js new file mode 100644 index 0000000000..37b9d5ae3e --- /dev/null +++ b/firmware/implementors/idsp/hbf/trait.Filter.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"idsp":[] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/miniconf/json_core/trait.JsonCoreSlash.js b/firmware/implementors/miniconf/json_core/trait.JsonCoreSlash.js new file mode 100644 index 0000000000..9275b6060f --- /dev/null +++ b/firmware/implementors/miniconf/json_core/trait.JsonCoreSlash.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"miniconf":[] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/miniconf/tree/trait.Increment.js b/firmware/implementors/miniconf/tree/trait.Increment.js new file mode 100644 index 0000000000..9275b6060f --- /dev/null +++ b/firmware/implementors/miniconf/tree/trait.Increment.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"miniconf":[] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/miniconf/tree/trait.Key.js b/firmware/implementors/miniconf/tree/trait.Key.js new file mode 100644 index 0000000000..9275b6060f --- /dev/null +++ b/firmware/implementors/miniconf/tree/trait.Key.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"miniconf":[] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/miniconf/tree/trait.TreeDeserialize.js b/firmware/implementors/miniconf/tree/trait.TreeDeserialize.js new file mode 100644 index 0000000000..8e44194812 --- /dev/null +++ b/firmware/implementors/miniconf/tree/trait.TreeDeserialize.js @@ -0,0 +1,6 @@ +(function() {var implementors = { +"dual_iir":[["impl<'de> TreeDeserialize<'de, 3> for Settings"]], +"lockin":[["impl<'de> TreeDeserialize<'de, 2> for Settings"]], +"miniconf":[], +"stabilizer":[["impl<'de> TreeDeserialize<'de, 1> for BasicConfig"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/miniconf/tree/trait.TreeKey.js b/firmware/implementors/miniconf/tree/trait.TreeKey.js new file mode 100644 index 0000000000..598f465597 --- /dev/null +++ b/firmware/implementors/miniconf/tree/trait.TreeKey.js @@ -0,0 +1,6 @@ +(function() {var implementors = { +"dual_iir":[["impl TreeKey<3> for Settings"]], +"lockin":[["impl TreeKey<2> for Settings"]], +"miniconf":[], +"stabilizer":[["impl TreeKey<1> for BasicConfig"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/miniconf/tree/trait.TreeSerialize.js b/firmware/implementors/miniconf/tree/trait.TreeSerialize.js new file mode 100644 index 0000000000..414dd1ebce --- /dev/null +++ b/firmware/implementors/miniconf/tree/trait.TreeSerialize.js @@ -0,0 +1,6 @@ +(function() {var implementors = { +"dual_iir":[["impl TreeSerialize<3> for Settings"]], +"lockin":[["impl TreeSerialize<2> for Settings"]], +"miniconf":[], +"stabilizer":[["impl TreeSerialize<1> for BasicConfig"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/mutex_trait/trait.Mutex.js b/firmware/implementors/mutex_trait/trait.Mutex.js new file mode 100644 index 0000000000..09da651d49 --- /dev/null +++ b/firmware/implementors/mutex_trait/trait.Mutex.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"stabilizer":[["impl Mutex for Adc1Input"],["impl Mutex for Dac1Output"],["impl Mutex for Adc0Input"],["impl Mutex for Dac0Output"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/num_enum/trait.TryFromPrimitive.js b/firmware/implementors/num_enum/trait.TryFromPrimitive.js new file mode 100644 index 0000000000..46a7b20343 --- /dev/null +++ b/firmware/implementors/num_enum/trait.TryFromPrimitive.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"stabilizer":[["impl TryFromPrimitive for Gain"],["impl TryFromPrimitive for Prescaler"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/serde/de/trait.Deserialize.js b/firmware/implementors/serde/de/trait.Deserialize.js new file mode 100644 index 0000000000..2d2595607c --- /dev/null +++ b/firmware/implementors/serde/de/trait.Deserialize.js @@ -0,0 +1,5 @@ +(function() {var implementors = { +"idsp":[["impl<'de> Deserialize<'de> for IIR"],["impl<'de, T> Deserialize<'de> for IIR<T>where\n T: Deserialize<'de>,"],["impl<'de> Deserialize<'de> for PLL"],["impl<'de, T> Deserialize<'de> for Unwrapper<T>where\n T: Deserialize<'de>,"]], +"lockin":[["impl<'de> Deserialize<'de> for LockinMode"],["impl<'de> Deserialize<'de> for Conf"]], +"stabilizer":[["impl<'de> Deserialize<'de> for OutputChannelState"],["impl<'de> Deserialize<'de> for Signal"],["impl<'de> Deserialize<'de> for Gain"],["impl<'de> Deserialize<'de> for DdsClockConfig"],["impl<'de> Deserialize<'de> for ChannelState"],["impl<'de> Deserialize<'de> for InputChannelState"],["impl<'de> Deserialize<'de> for StreamTarget"],["impl<'de> Deserialize<'de> for DdsChannelState"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/serde/ser/trait.Serialize.js b/firmware/implementors/serde/ser/trait.Serialize.js new file mode 100644 index 0000000000..ea7b711629 --- /dev/null +++ b/firmware/implementors/serde/ser/trait.Serialize.js @@ -0,0 +1,5 @@ +(function() {var implementors = { +"idsp":[["impl<T> Serialize for Unwrapper<T>where\n T: Serialize,"],["impl Serialize for PLL"],["impl<T> Serialize for IIR<T>where\n T: Serialize,"],["impl Serialize for IIR"]], +"lockin":[["impl Serialize for Conf"],["impl Serialize for LockinMode"]], +"stabilizer":[["impl Serialize for DdsChannelState"],["impl Serialize for ChannelState"],["impl Serialize for Telemetry"],["impl Serialize for StreamTarget"],["impl Serialize for DdsClockConfig"],["impl Serialize for InputChannelState"],["impl Serialize for Gain"],["impl Serialize for OutputChannelState"],["impl Serialize for Signal"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/stabilizer/hardware/pounder/attenuators/trait.AttenuatorInterface.js b/firmware/implementors/stabilizer/hardware/pounder/attenuators/trait.AttenuatorInterface.js new file mode 100644 index 0000000000..58fbc3834e --- /dev/null +++ b/firmware/implementors/stabilizer/hardware/pounder/attenuators/trait.AttenuatorInterface.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"stabilizer":[] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/stabilizer/hardware/pounder/rf_power/trait.PowerMeasurementInterface.js b/firmware/implementors/stabilizer/hardware/pounder/rf_power/trait.PowerMeasurementInterface.js new file mode 100644 index 0000000000..58fbc3834e --- /dev/null +++ b/firmware/implementors/stabilizer/hardware/pounder/rf_power/trait.PowerMeasurementInterface.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"stabilizer":[] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/implementors/stm32h7xx_hal/dma/traits/trait.TargetAddress.js b/firmware/implementors/stm32h7xx_hal/dma/traits/trait.TargetAddress.js new file mode 100644 index 0000000000..2721429417 --- /dev/null +++ b/firmware/implementors/stm32h7xx_hal/dma/traits/trait.TargetAddress.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"stabilizer":[["impl TargetAddress<PeripheralToMemory> for Channel2InputCapture"],["impl TargetAddress<PeripheralToMemory> for Channel2InputCapture"],["impl TargetAddress<PeripheralToMemory> for Channel3InputCapture"],["impl TargetAddress<PeripheralToMemory> for Channel3InputCapture"],["impl TargetAddress<PeripheralToMemory> for Channel4InputCapture"],["impl TargetAddress<PeripheralToMemory> for Channel3InputCapture"],["impl TargetAddress<PeripheralToMemory> for Channel1InputCapture"],["impl TargetAddress<PeripheralToMemory> for Channel3InputCapture"],["impl TargetAddress<PeripheralToMemory> for Channel1InputCapture"],["impl TargetAddress<PeripheralToMemory> for Channel4InputCapture"],["impl TargetAddress<PeripheralToMemory> for Channel1InputCapture"],["impl TargetAddress<PeripheralToMemory> for Channel2InputCapture"],["impl TargetAddress<PeripheralToMemory> for Channel2InputCapture"],["impl TargetAddress<PeripheralToMemory> for Channel1InputCapture"],["impl TargetAddress<PeripheralToMemory> for Channel4InputCapture"],["impl TargetAddress<PeripheralToMemory> for Channel4InputCapture"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/firmware/lockin/all.html b/firmware/lockin/all.html new file mode 100644 index 0000000000..56a1b5323e --- /dev/null +++ b/firmware/lockin/all.html @@ -0,0 +1 @@ +pub struct Context {}
Execution context
+Software task
+pub use Monotonic::spawn_after;
pub use Monotonic::spawn_at;
pub use Monotonic::SpawnHandle;
ethernet_link
has access topub struct Context<'a> {
+ pub shared: SharedResources<'a>,
+}
Execution context
+Shared Resources this task has access to
+pub struct SharedResources<'a> {
+ pub network: network_that_needs_to_be_locked<'a>,
+}
Shared resources ethernet_link
has access to
network: network_that_needs_to_be_locked<'a>
Resource proxy resource network
. Use method .lock()
to gain access
pub fn __rtic_internal_ethernet_link_Monotonic_spawn_after(
+ duration: <Systick as Monotonic>::Duration
+) -> Result<SpawnHandle, ()>
Spawns the task after a set duration relative to the current time
+This will use the time Instant::new(0)
as baseline if called in #[init]
,
+so if you use a non-resetable timer use spawn_at
when in #[init]
pub fn __rtic_internal_settings_update_Monotonic_spawn_after(
+ duration: <Systick as Monotonic>::Duration
+) -> Result<SpawnHandle, ()>
Spawns the task after a set duration relative to the current time
+This will use the time Instant::new(0)
as baseline if called in #[init]
,
+so if you use a non-resetable timer use spawn_at
when in #[init]
pub fn __rtic_internal_start_Monotonic_spawn_after(
+ duration: <Systick as Monotonic>::Duration
+) -> Result<SpawnHandle, ()>
Spawns the task after a set duration relative to the current time
+This will use the time Instant::new(0)
as baseline if called in #[init]
,
+so if you use a non-resetable timer use spawn_at
when in #[init]
pub fn __rtic_internal_telemetry_Monotonic_spawn_after(
+ duration: <Systick as Monotonic>::Duration
+) -> Result<SpawnHandle, ()>
Spawns the task after a set duration relative to the current time
+This will use the time Instant::new(0)
as baseline if called in #[init]
,
+so if you use a non-resetable timer use spawn_at
when in #[init]
pub fn __rtic_internal_usb_Monotonic_spawn_after(
+ duration: <Systick as Monotonic>::Duration
+) -> Result<SpawnHandle, ()>
Spawns the task after a set duration relative to the current time
+This will use the time Instant::new(0)
as baseline if called in #[init]
,
+so if you use a non-resetable timer use spawn_at
when in #[init]
fn ethernet_link(c: Context<'_>)
User SW task ethernet_link
+fn settings_update(c: Context<'_>)
User SW task settings_update
+pub struct Context<'a> {
+ pub shared: SharedResources<'a>,
+}
Execution context
+Shared Resources this task has access to
+pub struct SharedResources<'a> {
+ pub network: network_that_needs_to_be_locked<'a>,
+ pub usb_terminal: usb_terminal_that_needs_to_be_locked<'a>,
+}
Shared resources idle
has access to
network: network_that_needs_to_be_locked<'a>
Resource proxy resource network
. Use method .lock()
to gain access
usb_terminal: usb_terminal_that_needs_to_be_locked<'a>
Resource proxy resource usb_terminal
. Use method .lock()
to gain access
App module
+The RTIC application module
+pub use rtic::Monotonic as _;
ethernet_link
has access toidle
has access toprocess
has access toprocess
has access tosettings_update
has access tosettings_update
has access tostart
has access totelemetry
has access totelemetry
has access tousb
has access topub struct Context<'a> {
+ pub core: Peripherals,
+ pub device: Peripherals,
+ pub cs: CriticalSection<'a>,
+}
Execution context
+core: Peripherals
Core (Cortex-M) peripherals
+device: Peripherals
Device peripherals
+cs: CriticalSection<'a>
Critical section token for init
+pub struct Monotonics(pub Systick);
Monotonics used by the system
+0: Systick
Holds static methods for each monotonic.
+pub use Monotonic::now;
Monotonic::now()
Hardware task
+process
has access toprocess
has access topub struct Context<'a> {
+ pub local: LocalResources<'a>,
+ pub shared: SharedResources<'a>,
+}
Execution context
+local: LocalResources<'a>
Local Resources this task has access to
+Shared Resources this task has access to
+pub struct LocalResources<'a> {
+ pub adcs: &'a mut (Adc0Input, Adc1Input),
+ pub dacs: &'a mut (Dac0Output, Dac1Output),
+ pub lockin: &'a mut Lockin<Chain<2, Lowpass<2>>>,
+ pub timestamper: &'a mut InputStamper,
+ pub pll: &'a mut RPLL,
+ pub generator: &'a mut FrameGenerator,
+ pub signal_generator: &'a mut SignalGenerator,
+}
Local resources process
has access to
adcs: &'a mut (Adc0Input, Adc1Input)
Local resource adcs
dacs: &'a mut (Dac0Output, Dac1Output)
Local resource dacs
lockin: &'a mut Lockin<Chain<2, Lowpass<2>>>
Local resource lockin
timestamper: &'a mut InputStamper
Local resource timestamper
pll: &'a mut RPLL
Local resource pll
generator: &'a mut FrameGenerator
Local resource generator
signal_generator: &'a mut SignalGenerator
Local resource signal_generator
pub struct SharedResources<'a> {
+ pub settings: settings_that_needs_to_be_locked<'a>,
+ pub telemetry: telemetry_that_needs_to_be_locked<'a>,
+}
Shared resources process
has access to
settings: settings_that_needs_to_be_locked<'a>
Resource proxy resource settings
. Use method .lock()
to gain access
telemetry: telemetry_that_needs_to_be_locked<'a>
Resource proxy resource telemetry
. Use method .lock()
to gain access
Software task
+pub use Monotonic::spawn_after;
pub use Monotonic::spawn_at;
pub use Monotonic::SpawnHandle;
settings_update
has access tosettings_update
has access topub struct Context<'a> {
+ pub local: LocalResources<'a>,
+ pub shared: SharedResources<'a>,
+}
Execution context
+local: LocalResources<'a>
Local Resources this task has access to
+Shared Resources this task has access to
+pub struct LocalResources<'a> {
+ pub afes: &'a mut (AFE0, AFE1),
+}
Local resources settings_update
has access to
afes: &'a mut (AFE0, AFE1)
Local resource afes
pub struct SharedResources<'a> {
+ pub network: network_that_needs_to_be_locked<'a>,
+ pub settings: settings_that_needs_to_be_locked<'a>,
+}
Shared resources settings_update
has access to
network: network_that_needs_to_be_locked<'a>
Resource proxy resource network
. Use method .lock()
to gain access
settings: settings_that_needs_to_be_locked<'a>
Resource proxy resource settings
. Use method .lock()
to gain access
Software task
+pub use Monotonic::spawn_after;
pub use Monotonic::spawn_at;
pub use Monotonic::SpawnHandle;
start
has access topub struct Context<'a> {
+ pub local: LocalResources<'a>,
+}
Execution context
+local: LocalResources<'a>
Local Resources this task has access to
+pub struct LocalResources<'a> {
+ pub sampling_timer: &'a mut SamplingTimer,
+}
Local resources start
has access to
sampling_timer: &'a mut SamplingTimer
Local resource sampling_timer
struct Local {
+ sampling_timer: SamplingTimer,
+ digital_inputs: (DigitalInput0, DigitalInput1),
+ timestamper: InputStamper,
+ afes: (AFE0, AFE1),
+ adcs: (Adc0Input, Adc1Input),
+ dacs: (Dac0Output, Dac1Output),
+ pll: RPLL,
+ lockin: Lockin<Chain<2, Lowpass<2>>>,
+ signal_generator: SignalGenerator,
+ generator: FrameGenerator,
+ cpu_temp_sensor: CpuTempSensor,
+}
RTIC local resource struct
+sampling_timer: SamplingTimer
§digital_inputs: (DigitalInput0, DigitalInput1)
§timestamper: InputStamper
§afes: (AFE0, AFE1)
§adcs: (Adc0Input, Adc1Input)
§dacs: (Dac0Output, Dac1Output)
§pll: RPLL
§lockin: Lockin<Chain<2, Lowpass<2>>>
§signal_generator: SignalGenerator
§generator: FrameGenerator
§cpu_temp_sensor: CpuTempSensor
struct Shared {
+ usb_terminal: SerialTerminal,
+ network: NetworkUsers<Settings, Telemetry, 2>,
+ settings: Settings,
+ telemetry: TelemetryBuffer,
+}
RTIC shared resource struct
+usb_terminal: SerialTerminal
§network: NetworkUsers<Settings, Telemetry, 2>
§settings: Settings
§telemetry: TelemetryBuffer
pub struct __rtic_internal_Monotonics(pub Systick);
Monotonics used by the system
+0: Systick
pub struct __rtic_internal_eth_Context {}
Execution context
+pub struct __rtic_internal_ethernet_linkSharedResources<'a> {
+ pub network: network_that_needs_to_be_locked<'a>,
+}
Shared resources ethernet_link
has access to
network: network_that_needs_to_be_locked<'a>
Resource proxy resource network
. Use method .lock()
to gain access
pub struct __rtic_internal_ethernet_link_Context<'a> {
+ pub shared: SharedResources<'a>,
+}
Execution context
+Shared Resources this task has access to
+pub struct __rtic_internal_idleSharedResources<'a> {
+ pub network: network_that_needs_to_be_locked<'a>,
+ pub usb_terminal: usb_terminal_that_needs_to_be_locked<'a>,
+}
Shared resources idle
has access to
network: network_that_needs_to_be_locked<'a>
Resource proxy resource network
. Use method .lock()
to gain access
usb_terminal: usb_terminal_that_needs_to_be_locked<'a>
Resource proxy resource usb_terminal
. Use method .lock()
to gain access
App module
+pub struct __rtic_internal_idle_Context<'a> {
+ pub shared: SharedResources<'a>,
+}
Execution context
+Shared Resources this task has access to
+pub struct __rtic_internal_init_Context<'a> {
+ pub core: Peripherals,
+ pub device: Peripherals,
+ pub cs: CriticalSection<'a>,
+}
Execution context
+core: Peripherals
Core (Cortex-M) peripherals
+device: Peripherals
Device peripherals
+cs: CriticalSection<'a>
Critical section token for init
+pub struct __rtic_internal_processLocalResources<'a> {
+ pub adcs: &'a mut (Adc0Input, Adc1Input),
+ pub dacs: &'a mut (Dac0Output, Dac1Output),
+ pub lockin: &'a mut Lockin<Chain<2, Lowpass<2>>>,
+ pub timestamper: &'a mut InputStamper,
+ pub pll: &'a mut RPLL,
+ pub generator: &'a mut FrameGenerator,
+ pub signal_generator: &'a mut SignalGenerator,
+}
Local resources process
has access to
adcs: &'a mut (Adc0Input, Adc1Input)
Local resource adcs
dacs: &'a mut (Dac0Output, Dac1Output)
Local resource dacs
lockin: &'a mut Lockin<Chain<2, Lowpass<2>>>
Local resource lockin
timestamper: &'a mut InputStamper
Local resource timestamper
pll: &'a mut RPLL
Local resource pll
generator: &'a mut FrameGenerator
Local resource generator
signal_generator: &'a mut SignalGenerator
Local resource signal_generator
pub struct __rtic_internal_processSharedResources<'a> {
+ pub settings: settings_that_needs_to_be_locked<'a>,
+ pub telemetry: telemetry_that_needs_to_be_locked<'a>,
+}
Shared resources process
has access to
settings: settings_that_needs_to_be_locked<'a>
Resource proxy resource settings
. Use method .lock()
to gain access
telemetry: telemetry_that_needs_to_be_locked<'a>
Resource proxy resource telemetry
. Use method .lock()
to gain access
pub struct __rtic_internal_process_Context<'a> {
+ pub local: LocalResources<'a>,
+ pub shared: SharedResources<'a>,
+}
Execution context
+local: LocalResources<'a>
Local Resources this task has access to
+Shared Resources this task has access to
+pub struct __rtic_internal_settings_updateLocalResources<'a> {
+ pub afes: &'a mut (AFE0, AFE1),
+}
Local resources settings_update
has access to
afes: &'a mut (AFE0, AFE1)
Local resource afes
pub struct __rtic_internal_settings_updateSharedResources<'a> {
+ pub network: network_that_needs_to_be_locked<'a>,
+ pub settings: settings_that_needs_to_be_locked<'a>,
+}
Shared resources settings_update
has access to
network: network_that_needs_to_be_locked<'a>
Resource proxy resource network
. Use method .lock()
to gain access
settings: settings_that_needs_to_be_locked<'a>
Resource proxy resource settings
. Use method .lock()
to gain access
pub struct __rtic_internal_settings_update_Context<'a> {
+ pub local: LocalResources<'a>,
+ pub shared: SharedResources<'a>,
+}
Execution context
+local: LocalResources<'a>
Local Resources this task has access to
+Shared Resources this task has access to
+pub struct __rtic_internal_startLocalResources<'a> {
+ pub sampling_timer: &'a mut SamplingTimer,
+}
Local resources start
has access to
sampling_timer: &'a mut SamplingTimer
Local resource sampling_timer
pub struct __rtic_internal_start_Context<'a> {
+ pub local: LocalResources<'a>,
+}
Execution context
+local: LocalResources<'a>
Local Resources this task has access to
+pub struct __rtic_internal_telemetryLocalResources<'a> {
+ pub digital_inputs: &'a mut (DigitalInput0, DigitalInput1),
+ pub cpu_temp_sensor: &'a mut CpuTempSensor,
+}
Local resources telemetry
has access to
digital_inputs: &'a mut (DigitalInput0, DigitalInput1)
Local resource digital_inputs
cpu_temp_sensor: &'a mut CpuTempSensor
Local resource cpu_temp_sensor
pub struct __rtic_internal_telemetrySharedResources<'a> {
+ pub network: network_that_needs_to_be_locked<'a>,
+ pub settings: settings_that_needs_to_be_locked<'a>,
+ pub telemetry: telemetry_that_needs_to_be_locked<'a>,
+}
Shared resources telemetry
has access to
network: network_that_needs_to_be_locked<'a>
Resource proxy resource network
. Use method .lock()
to gain access
settings: settings_that_needs_to_be_locked<'a>
Resource proxy resource settings
. Use method .lock()
to gain access
telemetry: telemetry_that_needs_to_be_locked<'a>
Resource proxy resource telemetry
. Use method .lock()
to gain access
pub struct __rtic_internal_telemetry_Context<'a> {
+ pub local: LocalResources<'a>,
+ pub shared: SharedResources<'a>,
+}
Execution context
+local: LocalResources<'a>
Local Resources this task has access to
+Shared Resources this task has access to
+pub struct __rtic_internal_usbSharedResources<'a> {
+ pub usb_terminal: usb_terminal_that_needs_to_be_locked<'a>,
+}
Shared resources usb
has access to
usb_terminal: usb_terminal_that_needs_to_be_locked<'a>
Resource proxy resource usb_terminal
. Use method .lock()
to gain access
pub struct __rtic_internal_usb_Context<'a> {
+ pub shared: SharedResources<'a>,
+}
Execution context
+Shared Resources this task has access to
+Software task
+pub use Monotonic::spawn_after;
pub use Monotonic::spawn_at;
pub use Monotonic::SpawnHandle;
telemetry
has access totelemetry
has access topub struct Context<'a> {
+ pub local: LocalResources<'a>,
+ pub shared: SharedResources<'a>,
+}
Execution context
+local: LocalResources<'a>
Local Resources this task has access to
+Shared Resources this task has access to
+pub struct LocalResources<'a> {
+ pub digital_inputs: &'a mut (DigitalInput0, DigitalInput1),
+ pub cpu_temp_sensor: &'a mut CpuTempSensor,
+}
Local resources telemetry
has access to
digital_inputs: &'a mut (DigitalInput0, DigitalInput1)
Local resource digital_inputs
cpu_temp_sensor: &'a mut CpuTempSensor
Local resource cpu_temp_sensor
pub struct SharedResources<'a> {
+ pub network: network_that_needs_to_be_locked<'a>,
+ pub settings: settings_that_needs_to_be_locked<'a>,
+ pub telemetry: telemetry_that_needs_to_be_locked<'a>,
+}
Shared resources telemetry
has access to
network: network_that_needs_to_be_locked<'a>
Resource proxy resource network
. Use method .lock()
to gain access
settings: settings_that_needs_to_be_locked<'a>
Resource proxy resource settings
. Use method .lock()
to gain access
telemetry: telemetry_that_needs_to_be_locked<'a>
Resource proxy resource telemetry
. Use method .lock()
to gain access
Software task
+pub use Monotonic::spawn_after;
pub use Monotonic::spawn_at;
pub use Monotonic::SpawnHandle;
usb
has access topub struct Context<'a> {
+ pub shared: SharedResources<'a>,
+}
Execution context
+Shared Resources this task has access to
+pub struct SharedResources<'a> {
+ pub usb_terminal: usb_terminal_that_needs_to_be_locked<'a>,
+}
Shared resources usb
has access to
usb_terminal: usb_terminal_that_needs_to_be_locked<'a>
Resource proxy resource usb_terminal
. Use method .lock()
to gain access
pub(crate) const BATCH_SIZE: usize = _; // 8usize
pub(crate) const BATCH_SIZE_LOG2: u32 = 3;
pub(crate) const SAMPLE_TICKS: u32 = _; // 128u32
pub(crate) const SAMPLE_TICKS_LOG2: u32 = 7;
pub(crate) enum Conf {
+ Magnitude,
+ Phase,
+ ReferenceFrequency,
+ LogPower,
+ InPhase,
+ Quadrature,
+ Modulation,
+}
Output the lockin magnitude.
+Output the phase of the lockin
+Output the lockin reference frequency as a sinusoid
+Output the logarithmic power of the lockin
+Output the in-phase component of the lockin signal.
+Output the quadrature component of the lockin signal.
+Output the lockin internal modulation frequency as a sinusoid
+pub(crate) enum LockinMode {
+ Internal,
+ External,
+}
Utilize an internally generated reference for demodulation
+Utilize an external modulation signal supplied to DI0
+source
. Read moreself
and other
values to be equal, and is used
+by ==
.The lockin
application implements a lock-in amplifier using either an external or internally
+generated reference.
Refer to the Settings structure for documentation of run-time configurable settings for this +application.
+Refer to Telemetry for information about telemetry reported by this application.
+This application streams raw ADC and DAC data over UDP. Refer to +stabilizer::net::data_stream for more information.
+pub struct Settings {
+ pub(crate) afe: [Gain; 2],
+ pub(crate) lockin_mode: LockinMode,
+ pub(crate) pll_tc: [u32; 2],
+ pub(crate) lockin_k: <Lowpass<2> as Filter>::Config,
+ pub(crate) lockin_harmonic: i32,
+ pub(crate) lockin_phase: i32,
+ pub(crate) output_conf: [Conf; 2],
+ pub(crate) telemetry_period: u16,
+ pub(crate) stream_target: StreamTarget,
+}
afe: [Gain; 2]
Configure the Analog Front End (AFE) gain.
+afe/<n>
<n>
specifies which channel to configure. <n>
:= [0, 1]Any of the variants of Gain enclosed in double quotes.
+lockin_mode: LockinMode
Specifies the operational mode of the lockin.
+lockin_mode
One of the variants of LockinMode enclosed in double quotes.
+pll_tc: [u32; 2]
Specifis the PLL time constant.
+pll_tc/<n>
<n>
specifies which channel to configure. <n>
:= [0, 1]The PLL time constant exponent (1-31).
+lockin_k: <Lowpass<2> as Filter>::Config
Specifies the lockin lowpass gains.
+lockin_k
The lockin low-pass coefficients. See idsp::Lowpass
for determining them.
lockin_harmonic: i32
Specifies which harmonic to use for the lockin.
+lockin_harmonic
Harmonic index of the LO. -1 to _de_modulate the fundamental (complex conjugate)
+lockin_phase: i32
Specifies the LO phase offset.
+lockin_phase
Demodulation LO phase offset. Units are in terms of i32, where i32::MIN is equivalent to +-pi and i32::MAX is equivalent to +pi.
+output_conf: [Conf; 2]
Specifies DAC output mode.
+output_conf/<n>
<n>
specifies which channel to configure. <n>
:= [0, 1]One of the variants of Conf enclosed in double quotes.
+telemetry_period: u16
Specifies the telemetry output period in seconds.
+telemetry_period
Any non-zero value less than 65536.
+stream_target: StreamTarget
indices
. Read more#[derive(TreeDeserialize)]
+{
+ // Attributes available to this derive:
+ #[tree]
+}
+
Derive the TreeDeserialize
trait for a struct.
#[derive(TreeSerialize)]
+{
+ // Attributes available to this derive:
+ #[tree]
+}
+
Derive the TreeSerialize
trait for a struct.
#[non_exhaustive]pub enum Error<E> {
+ Absent(usize),
+ TooShort(usize),
+ NotFound(usize),
+ TooLong(usize),
+ Inner(E),
+ PostDeserialization(E),
+}
Errors that can occur when using the Tree traits.
+A usize
member indicates the key depth where the error occurred.
+The depth here is the number of names or indices consumed.
+It is also the number of separators in a path or the length
+of an indices slice.
If multiple errors are applicable simultaneously the precedence +is from high to low:
+Absent > TooShort > NotFound > TooLong > Inner > PostDeserialization
+before any Ok
.
The key is valid, but does not exist at runtime.
+This is the case if an Option
using the Tree*
traits
+is None
at runtime. See also TreeKey
.
The key ends early and does not reach a leaf node.
+The key was not found (index unparsable or too large, name not found or invalid).
+The key is too long and goes beyond a leaf node.
+The value provided could not be serialized or deserialized +or the traversal function returned an error.
+There was an error after deserializing a value.
+The Deserializer
has encountered an error only after successfully
+deserializing a value. This is the case if there is additional unexpected data.
+The TreeDeserialize::deserialize_by_key()
update takes place but this
+error will be returned.
Miniconf
enables lightweight (no_std
) partial serialization (retrieval) and deserialization
+(updates, modification) within a tree by key. The tree is backed by
+structs/arrays/Options of serializable types.
See below for a comprehensive example showing the features of the Tree
traits.
+See also the documentation of the TreeKey
trait for a detailed description.
use miniconf::{Error, JsonCoreSlash, Tree, TreeKey};
+use serde::{Deserialize, Serialize};
+
+#[derive(Deserialize, Serialize, Copy, Clone, Default)]
+enum Either {
+ #[default]
+ Bad,
+ Good,
+}
+
+#[derive(Deserialize, Serialize, Copy, Clone, Default, Tree)]
+struct Inner {
+ a: i32,
+ b: i32,
+}
+
+#[derive(Tree, Default)]
+struct Settings {
+ // Atomic updtes by field name
+ foo: bool,
+ enum_: Either,
+ struct_: Inner,
+ array: [i32; 2],
+ option: Option<i32>,
+
+ // Exclude an element (not Deserialize/Serialize)
+ #[tree(skip)]
+ skipped: (),
+
+ // Exposing elements of containers
+ // ... by field name
+ #[tree]
+ struct_tree: Inner,
+ // ... or by index
+ #[tree]
+ array_tree: [i32; 2],
+ // ... or exposing two levels (array index and then inner field name)
+ #[tree(depth(2))]
+ array_tree2: [Inner; 2],
+
+ // Hiding paths by setting the Option to `None` at runtime
+ #[tree]
+ option_tree: Option<i32>,
+ // Hiding a path and descending into the inner `Tree`
+ #[tree(depth(2))]
+ option_tree2: Option<Inner>,
+ // Hiding elements of an array of `Tree`s
+ #[tree(depth(3))]
+ array_option_tree: [Option<Inner>; 2],
+}
+
+let mut settings = Settings::default();
+let mut buf = [0; 64];
+
+// Atomic updates by field name
+settings.set_json("/foo", b"true")?;
+assert_eq!(settings.foo, true);
+settings.set_json("/enum_", br#""Good""#)?;
+settings.set_json("/struct_", br#"{"a": 3, "b": 3}"#)?;
+settings.set_json("/array", b"[6, 6]")?;
+settings.set_json("/option", b"12")?;
+settings.set_json("/option", b"null")?;
+
+// Deep access by field name in a struct
+settings.set_json("/struct_tree/a", b"4")?;
+// ... or by index in an array
+settings.set_json("/array_tree/0", b"7")?;
+// ... or by index and then struct field name
+settings.set_json("/array_tree2/1/b", b"11")?;
+
+// If a `Tree`-Option is `None` it is hidden at runtime and can't be serialized/deserialized.
+settings.option_tree = None;
+assert_eq!(settings.set_json("/option_tree", b"13"), Err(Error::Absent(1)));
+settings.option_tree = Some(0);
+settings.set_json("/option_tree", b"13")?;
+settings.option_tree2 = Some(Inner::default());
+settings.set_json("/option_tree2/a", b"14")?;
+settings.array_option_tree[1] = Some(Inner::default());
+settings.set_json("/array_option_tree/1/a", b"15")?;
+
+// Serializing elements by path
+let len = settings.get_json("/struct_", &mut buf).unwrap();
+assert_eq!(&buf[..len], br#"{"a":3,"b":3}"#);
+
+// Iterating over all paths
+for path in Settings::iter_paths::<String>("/") {
+ let path = path.unwrap();
+ // Serializing each
+ match settings.get_json(&path, &mut buf) {
+ // Full round-trip: deserialize and set again
+ Ok(len) => { settings.set_json(&path, &buf[..len])?; }
+ // Some settings are still `None` and thus their paths are expected to be absent
+ Err(Error::Absent(_)) => {}
+ e => { e.unwrap(); }
+ }
+}
+
One possible use of Miniconf
is a backend for run-time settings management in embedded devices.
+It was originally designed to work with JSON (serde_json_core
)
+payloads over MQTT (minimq
) and provides a MQTT settings management
+client and a Python reference implementation to interact with it.
Miniconf
can be used with any serde::Serializer
/serde::Deserializer
backend, path
+hierarchy separator, and key lookup algorithm.
Currently support for /
as the path hierarchy separator and JSON (serde_json_core
) is implemented
+through the JsonCoreSlash
super trait.
Miniconf is also protocol-agnostic. Any means that can receive key-value input from +some external source can be used to access nodes by path.
+The MqttClient
implements settings management over the MQTT
+protocol with JSON payloads. A Python reference library is provided that
+interfaces with it. This example discovers the unique prefix of an application listening to messages
+under the topic quartiq/application/12345
and set its foo
setting to true
.
python -m miniconf -d quartiq/application/+ foo=true
+
For structs Miniconf offers derive macros for TreeKey
, TreeSerialize
, and TreeDeserialize
.
+The macros implements the TreeKey
, TreeSerialize
, and TreeDeserialize
traits.
+Fields/items that form internal nodes (non-leaf) need to implement the respective Tree{Key,Serialize,Deserialize}
trait.
+Leaf fields/items need to support the respective serde
trait (and the desired serde::Serializer
/serde::Deserializer
+backend).
Structs, arrays, and Options can then be cascaded to construct more complex trees.
+When using the derive macro, the behavior and tree recursion depth can be configured for each
+struct field using the #[tree(depth(Y))]
attribute.
See also the TreeKey
trait documentation for details.
Lookup into the tree is done using an iterator over Key
items. usize
indices or &str
names are supported.
Path iteration is supported with arbitrary separator between names.
+Deferred/deep/non-atomic access to inner elements of some types is not yet supported, e.g. enums
+other than Option
. These are still however usable in their atomic serde
form as leaves.
mqtt-client
Enable the MQTT client feature. See the example in MqttClient
.json-core
Enable the JsonCoreSlash
implementation of serializing from and
+into json slices (using serde_json_core
).The mqtt-client
and json-core
features are enabled by default.
pub use minimq;
TreeKey
.TreeKey::indices()
.Result
up one hierarchy level, incrementing its usize member./
”.M: TreeKey
.TreeKey
, TreeSerialize
, and TreeDeserialize
traits for a struct.TreeDeserialize
trait for a struct.TreeKey
trait for a struct.TreeSerialize
trait for a struct.Redirecting to ../../miniconf/struct.PathIter.html...
+ + + \ No newline at end of file diff --git a/firmware/miniconf/json_core/trait.JsonCoreSlash.html b/firmware/miniconf/json_core/trait.JsonCoreSlash.html new file mode 100644 index 0000000000..0a7572a19c --- /dev/null +++ b/firmware/miniconf/json_core/trait.JsonCoreSlash.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../miniconf/trait.JsonCoreSlash.html...
+ + + \ No newline at end of file diff --git a/firmware/miniconf/mqtt_client/struct.MqttClient.html b/firmware/miniconf/mqtt_client/struct.MqttClient.html new file mode 100644 index 0000000000..aaf80d5d17 --- /dev/null +++ b/firmware/miniconf/mqtt_client/struct.MqttClient.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../miniconf/struct.MqttClient.html...
+ + + \ No newline at end of file diff --git a/firmware/miniconf/sidebar-items.js b/firmware/miniconf/sidebar-items.js new file mode 100644 index 0000000000..b00d805fa6 --- /dev/null +++ b/firmware/miniconf/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"derive":["Tree","TreeDeserialize","TreeKey","TreeSerialize"],"enum":["Error"],"struct":["Metadata","MqttClient","PathIter","SliceShort"],"trait":["Increment","JsonCoreSlash","Key","TreeDeserialize","TreeKey","TreeSerialize"]}; \ No newline at end of file diff --git a/firmware/miniconf/struct.Metadata.html b/firmware/miniconf/struct.Metadata.html new file mode 100644 index 0000000000..05f590e1b4 --- /dev/null +++ b/firmware/miniconf/struct.Metadata.html @@ -0,0 +1,31 @@ +#[non_exhaustive]pub struct Metadata {
+ pub max_length: usize,
+ pub max_depth: usize,
+ pub count: usize,
+}
Metadata about a TreeKey namespace.
+Struct { .. }
syntax; cannot be matched against without a wildcard ..
; and struct update syntax will not work.max_length: usize
The maximum length of a path in bytes.
+This is the concatenation of all names in a path
+and does not include separators.
+It includes paths that may be Error::Absent
at runtime.
max_depth: usize
The maximum path depth.
+This is equal to the maximum number of path hierarchy separators.
+It may be smaller than the Tree recursion depth const generic paramerter Y
.
+It includes paths that may be Error::Absent
at runtime.
count: usize
The total number of paths.
+This includes paths that may be Error::Absent
at runtime.
pub struct MqttClient<'buf, Settings, Stack, Clock, Broker, const Y: usize>where
+ Settings: TreeKey<Y> + Clone,
+ Stack: TcpClientStack,
+ Clock: Clock,
+ Broker: Broker,{ /* private fields */ }
MQTT settings interface.
+The MQTT client places the TreeKey paths <path>
at the MQTT <prefix>/settings/<path>
topic,
+where <prefix>
is provided in the client constructor.
It publishes its alive-ness as a 1
to <prefix>/alive
and sets a will to publish 0
there when
+it is disconnected.
The MQTT client logs failures to subscribe to the settings topic, but does not re-attempt to +connect to it when errors occur.
+The client only supports paths up to MAX_TOPIC_LENGTH = 128
byte length.
+Re-publication timeout is fixed to REPUBLISH_TIMEOUT_SECONDS = 2
seconds.
use miniconf::{MqttClient, Tree};
+
+#[derive(Tree, Clone, Default)]
+struct Settings {
+ foo: bool,
+}
+
+let mut buffer = [0u8; 1024];
+let localhost: minimq::embedded_nal::IpAddr = "127.0.0.1".parse().unwrap();
+let mut client: MqttClient<'_, _, _, _, minimq::broker::IpBroker, 1> = MqttClient::new(
+ std_embedded_nal::Stack::default(),
+ "quartiq/application/12345", // prefix
+ std_embedded_time::StandardClock::default(),
+ Settings::default(),
+ minimq::ConfigBuilder::new(localhost.into(), &mut buffer).keepalive_interval(60),
+)
+.unwrap();
+
+client
+ .handled_update(|path, old_settings, new_settings| {
+ if new_settings.foo {
+ return Err("Foo!");
+ }
+ *old_settings = new_settings.clone();
+ Ok(())
+ })
+ .unwrap();
Construct a new MQTT settings interface.
+stack
- The network stack to use for communication.prefix
- The MQTT device prefix to use for this device.clock
- The clock for managing the MQTT connection.settings
- The initial settings values.config
- The configuration of the MQTT client.Update the MQTT interface and service the network. Pass any settings changes to the handler +supplied.
+handler
- A closure called with updated settings that can be used to apply current
+settings or validate the configuration. Arguments are (path, old_settings, new_settings).True if the settings changed. False otherwise.
+Update the settings from the network stack without any specific handling.
+True if the settings changed. False otherwise
+Force republication of the current settings.
+This is intended to be used if modification of a setting had side effects that affected +another setting.
+pub struct PathIter<'a, M: ?Sized, const Y: usize, P> { /* private fields */ }
An iterator over the paths in a TreeKey
.
iter_next_chunk
)N
values. Read moreiter_advance_by
)n
elements. Read moren
th element of the iterator. Read moreiter_intersperse
)separator
+between adjacent items of the original iterator. Read moren
elements. Read moren
elements, or fewer
+if the underlying iterator ends sooner. Read moreiter_map_windows
)f
for each contiguous window of size N
over
+self
and returns an iterator over the outputs of f
. Like slice::windows()
,
+the windows during mapping overlap as well. Read moreiter_collect_into
)iter_is_partitioned
)true
precede all those that return false
. Read moreiterator_try_reduce
)try_find
)iter_array_chunks
)N
elements of the iterator at a time. Read moreiter_order_by
)Iterator
with those
+of another with respect to the specified comparison function. Read morePartialOrd
elements of
+this Iterator
with those of another. The comparison works like short-circuit
+evaluation, returning a result without comparing the remaining elements.
+As soon as an order can be determined, the evaluation stops and a result is returned. Read moreiter_order_by
)Iterator
with those
+of another with respect to the specified comparison function. Read moreiter_order_by
)Iterator
are lexicographically
+less than those of another. Read moreIterator
are lexicographically
+less or equal to those of another. Read moreIterator
are lexicographically
+greater than those of another. Read moreIterator
are lexicographically
+greater than or equal to those of another. Read moreis_sorted
)is_sorted
)pub struct SliceShort;
Unit struct to indicate a short indices iterator in TreeKey::indices()
.
source
. Read moreself
and other
values to be equal, and is used
+by ==
.pub trait JsonCoreSlash<'de, const Y: usize = 1>: TreeSerialize<Y> + TreeDeserialize<'de, Y> {
+ // Required methods
+ fn set_json(
+ &mut self,
+ path: &str,
+ data: &'de [u8]
+ ) -> Result<usize, Error<Error>>;
+ fn get_json(
+ &self,
+ path: &str,
+ data: &mut [u8]
+ ) -> Result<usize, Error<Error>>;
+ fn set_json_by_index(
+ &mut self,
+ indices: &[usize],
+ data: &'de [u8]
+ ) -> Result<usize, Error<Error>>;
+ fn get_json_by_index(
+ &self,
+ indices: &[usize],
+ data: &mut [u8]
+ ) -> Result<usize, Error<Error>>;
+}
Miniconf with “JSON and /
”.
Access items with '/'
as path separator and JSON (from serde-json-core
)
+as serialization/deserialization payload format.
pub trait TreeDeserialize<'de, const Y: usize = 1>: TreeKey<Y> {
+ // Required method
+ fn deserialize_by_key<K, D>(
+ &mut self,
+ keys: K,
+ de: D
+ ) -> Result<usize, Error<D::Error>>
+ where K: Iterator,
+ K::Item: Key,
+ D: Deserializer<'de>;
+}
Deserialize a leaf node by its keys.
+See also crate::JsonCoreSlash
for a convenient blanket implementation using this trait.
Deserialize an node by keys.
+ +#[derive(TreeKey, TreeDeserialize)]
+struct S {
+ foo: u32,
+ bar: u16,
+};
+let mut s = S { foo: 9, bar: 11 };
+let mut de = serde_json_core::de::Deserializer::new(b"7");
+s.deserialize_by_key(["bar"].into_iter(), &mut de).unwrap();
+de.end().unwrap();
+assert_eq!(s.bar, 7);
keys
: An Iterator
of Key
s identifying the node.de
: A Deserializer
to deserialize the value.Node depth on success
+pub trait TreeKey<const Y: usize = 1> {
+ // Required methods
+ fn name_to_index(name: &str) -> Option<usize>;
+ fn traverse_by_key<K, F, E>(keys: K, func: F) -> Result<usize, Error<E>>
+ where K: Iterator,
+ K::Item: Key,
+ F: FnMut(usize, &str) -> Result<(), E>;
+ fn metadata() -> Metadata;
+
+ // Provided methods
+ fn path<K, P>(keys: K, path: P, sep: &str) -> Result<usize, Error<Error>>
+ where K: IntoIterator,
+ K::Item: Key,
+ P: Write { ... }
+ fn indices<'a, K, I>(
+ keys: K,
+ indices: I
+ ) -> Result<usize, Error<SliceShort>>
+ where K: IntoIterator,
+ K::Item: Key,
+ I: IntoIterator<Item = &'a mut usize> { ... }
+ fn iter_paths<P: Write>(sep: &str) -> PathIter<'_, Self, Y, P> ⓘ { ... }
+ fn iter_paths_unchecked<P: Write>(sep: &str) -> PathIter<'_, Self, Y, P> ⓘ { ... }
+}
Traversal, iteration, and serialization/deserialization of nodes in a tree.
+The following documentation sections on TreeKey<Y>
apply analogously to TreeSerialize<Y>
+and TreeDeserialize<Y>
.
The TreeKey
trait (and the TreeSerialize
/TreeDeserialize
traits as well)
+are meant to be implemented
+recursively on nested data structures. Recursion here means that a container
+that implements TreeKey
, may call on the TreeKey
implementations of
+inner types.
The const parameter Y
in the traits here is the recursion depth and determines the
+maximum nesting of TreeKey
layers. It’s at least 1
and defaults to 1
.
The recursion depth Y
doubles as an upper bound to the key length
+(the depth/height of the tree):
+An implementor of TreeKey<Y>
may consume at most Y
items from the
+keys
iterator argument in the recursive methods (TreeSerialize::serialize_by_key()
,
+TreeDeserialize::deserialize_by_key()
, TreeKey::traverse_by_key()
). This includes
+both the items consumed directly before recursing and those consumed indirectly
+by recursing into inner types. In the same way it may call func
in
+TreeKey::traverse_by_key()
at most Y
times, again including those calls due
+to recursion into inner Miniconf
types.
This implies that if an implementor T
of TreeKey<Y>
(with Y >= 1
) contains and recurses into
+an inner type using that type’s TreeKey<Z>
implementation, then 1 <= Z <= Y
must
+hold and T
may consume at most Y - Z
items from the keys
iterators and call
+func
at most Y - Z
times.
The keys used to locate nodes can be either iterators over usize
or iterators
+over &str
names.
usize
may appear like ASN.1 Object Identifiers.
+&str
keys are sequences of names, like path names. When concatenated, they are separated by
+path hierarchy separators, e.g. '/'
.
Derive macros to automatically implement the correct TreeKey<Y>
traits on a struct S
are available through
+crate::TreeKey
, crate::TreeSerialize
, and crate::TreeDeserialize
.
+A shorthand derive macro that derives all three trait implementations is also available at crate::Tree
.
To derive TreeSerialize
/TreeDeserialize
, each field in the struct must either implement
+serde::Serialize
/serde::de::DeserializeOwned
+(and ultimately also be supported by the intended serde::Serializer
/serde::Deserializer
backend)
+or implement the respective TreeSerialize
/TreeDeserialize
trait themselves for the required remaining
+recursion depth.
For each field, the remaining recursion depth is configured through the #[tree(depth(Y))]
+attribute, with Y = 1
being the implied default when using #[tree()]
and Y = 0
invalid.
+If the attribute is not present, the field is a leaf and accessed only through its
+serde::Serialize
/serde::Deserialize
implementation.
+With the attribute present the field is accessed through its TreeKey<Y>
implementation with the given
+remaining recursion depth.
Blanket implementations of the TreeKey
traits are provided for homogeneous arrays [T; N]
+up to recursion depth Y = 8
.
When a [T; N]
is used as TreeKey<Y>
(i.e. marked as #[tree(depth(Y))]
in a struct)
+and Y > 1
each item of the array is accessed as a TreeKey
tree.
+For a depth Y = 0
(attribute absent), the entire array is accessed as one atomic
+value. For Y = 1
each index of the array is is instead accessed as
+one atomic value.
The type to use depends on the desired semantics of the data contained in the array. If the array
+contains TreeKey
items, one can (and often wants to) use Y >= 2
.
+However, if each element in the array should be individually configurable as a single value (e.g. a list
+of u32
), then Y = 1
can be used. With Y = 0
all items are to be accessed simultaneously and atomically.
+For e.g. [[T; 2]; 3] where T: TreeKey<3>
the recursion depth is Y = 5
. It automatically implements
+TreeKey<5>
.
+For [[T; 2]; 3] where T: Serialize + DeserializeOwned
, any Y <= 2
is available.
Blanket implementations of the TreeKey
traits are provided for Option<T>
+up to recursion depth Y = 8
.
These implementation do not alter the path hierarchy and do not consume any items from the keys
+iterators. The TreeKey
behavior of an Option
is such that the None
variant makes the corresponding part
+of the tree inaccessible at run-time. It will still be iterated over by TreeKey::iter_paths()
but attempts
+to TreeSerialize::serialize_by_key()
or TreeDeserialize::deserialize_by_key()
them
+return Error::Absent
.
+This is intended as a mechanism to provide run-time construction of the namespace. In some
+cases, run-time detection may indicate that some component is not present. In this case,
+namespaces will not be exposed for it.
If the depth specified by the #[tree(depth(Y))]
attribute exceeds 1,
+the Option
can be used to access within the inner type using its TreeKey
trait.
+If there is no tree
attribute on an Option
field in a struct or in an array, JSON
nullcorresponds to
Noneas usual and the
TreeKey` trait is not used.
The following example shows potential usage of arrays and Option
:
#[derive(TreeKey)]
+struct S {
+ // "/b/1/2" = 5
+ #[tree(depth(2))]
+ b: [[u32; 3]; 3],
+ // "/c/0" = [3,4], optionally absent at runtime
+ #[tree(depth(2))]
+ c: [Option<[u32; 2]>; 2],
+}
The macros add bounds to generic types of the struct they are acting on.
+If a generic type parameter T
of the struct S<T>
is used as a type parameter to a
+field type a: F1<F2<T>>
the type T
will be considered to reside at type depth X = 2
(as it is
+within F2
which is within F1
) and the following bounds will be applied:
#[tree()]
attribute not present on a
, T
will receive bounds Serialize
/DeserializeOwned
when
+TreeSerialize
/TreeDeserialize
is derived.#[tree(depth(Y))]
, and Y - X < 1
it will also receive bounds Serialize + DeserializeOwned
.Y - X >= 1
it will receive the bound T: TreeKey<Y - X>
.E.g. In the following T
resides at depth 2
and T: TreeKey<1>
will be inferred:
#[derive(TreeKey)]
+struct S<T> {
+ #[tree(depth(3))]
+ a: [Option<T>; 2],
+};
+// This works as [u32; N] implements TreeKey<1>:
+S::<[u32; 5]>::metadata();
+// This does not compile as u32 does not implement TreeKey<1>:
+// S::<u32>::metadata();
This behavior is upheld by and compatible with all implementations in this crate. It is only violated
+when deriving TreeKey
for a struct that (a) forwards its own type parameters as type
+parameters to its field types, (b) uses TreeKey
on those fields, and (c) those field
+types use their type parameters at other levels than TreeKey<Y - 1>
. See the
+test_derive_macro_bound_failure
test in tests/generics.rs
.
See the crate
documentation for an example showing how the traits and the derive macros work.
Convert a node name to a node index.
+The details of the mapping and the usize
index values
+are an implementation detail and only need to be stable for at runtime.
#[derive(TreeKey)]
+struct S {
+ foo: u32,
+ bar: u16,
+}
+assert_eq!(S::name_to_index("bar"), Some(1));
Call a function for each node on the path described by keys.
+Traversal is aborted once func
returns an Err(E)
.
May not exhaust keys
if a leaf is found early. i.e. keys
+may be longer than required.
+If Self
is a leaf, nothing will be consumed from keys
+and Ok(0)
will be returned.
+If Self
is non-leaf (internal node) and the iterator is
+exhausted (empty),
+Err(Error::TooShort(0))
will be returned.
#[derive(TreeKey)]
+struct S {
+ foo: u32,
+ bar: u16,
+};
+assert_eq!(
+ S::traverse_by_key(["bar"].into_iter(), |index, name| {
+ assert_eq!((1, "bar"), (index, name));
+ Ok::<_, ()>(())
+ }),
+ Ok(1)
+);
keys
: An Iterator
of Key
s identifying the node.func
: A FnMut
to be called for each node on the path. Its arguments are
+the index and the name of the node at the given depth. Returning Err()
aborts
+the traversal.Final node depth on success
+Convert keys to path.
+This is typically called through a PathIter returned by TreeKey::iter_paths.
+keys
may be longer than required. Extra items are ignored.
#[derive(TreeKey)]
+struct S {
+ foo: u32,
+ bar: u16,
+};
+let mut s = String::new();
+S::path([1], &mut s, "/").unwrap();
+assert_eq!(s, "/bar");
keys
: An Iterator
of Key
s identifying the node.path
: A string to write the separators and node names into.
+See also TreeKey::metadata() for upper bounds on path length.sep
: The path hierarchy separator to be inserted before each name.Final node depth on success
+Convert keys to indices
.
This determines the indices
of the item specified by keys
.
See also TreeKey::path()
for the analogous function.
Entries in indices
at and beyond the depth
returned are unaffected.
#[derive(TreeKey)]
+struct S {
+ foo: u32,
+ bar: u16,
+};
+let mut i = [0; 2];
+let depth = S::indices(["bar"], &mut i).unwrap();
+assert_eq!(&i[..depth], &[1]);
keys
: An Iterator
of Key
s identifying the node.indices
: An iterator of &mut usize
to write the node indices into.
+If indices
is shorter than the node depth, Error<SliceShort>
is returned
+See also TreeKey::metadata() for upper bounds on depth.Final node depth on success
+Create an iterator of all possible paths.
+This is a depth-first walk. +The iterator will walk all paths, including those that may be absent at +run-time (see Option). +The iterator has an exact and trusted Iterator::size_hint.
+ +#[derive(TreeKey)]
+struct S {
+ foo: u32,
+ bar: u16,
+};
+let paths: Vec<String> = S::iter_paths("/").map(|p| p.unwrap()).collect();
+assert_eq!(paths, ["/foo", "/bar"]);
P
- The type to hold the path. Needs to be core::fmt::Write + Default
sep
- The path hierarchy separatorAn iterator of paths with a trusted and exact Iterator::size_hint()
.
Create an unchecked iterator of all possible paths.
+See also TreeKey::iter_paths.
+ +#[derive(TreeKey)]
+struct S {
+ foo: u32,
+ bar: u16,
+};
+let paths: Vec<String> = S::iter_paths_unchecked("/").map(|p| p.unwrap()).collect();
+assert_eq!(paths, ["/foo", "/bar"]);
P
- The type to hold the path. Needs to be core::fmt::Write + Default
.sep
- The path hierarchy separatorA iterator of paths.
+pub trait TreeSerialize<const Y: usize = 1>: TreeKey<Y> {
+ // Required method
+ fn serialize_by_key<K, S>(
+ &self,
+ keys: K,
+ ser: S
+ ) -> Result<usize, Error<S::Error>>
+ where K: Iterator,
+ K::Item: Key,
+ S: Serializer;
+}
Serialize a leaf node by its keys.
+See also crate::JsonCoreSlash
for a convenient blanket implementation using this trait.
Serialize a node by keys.
+ +#[derive(TreeKey, TreeSerialize)]
+struct S {
+ foo: u32,
+ bar: u16,
+};
+let s = S { foo: 9, bar: 11 };
+let mut buf = [0u8; 10];
+let mut ser = serde_json_core::ser::Serializer::new(&mut buf);
+s.serialize_by_key(["bar"].into_iter(), &mut ser).unwrap();
+let length = ser.end();
+assert_eq!(&buf[..length], b"11");
keys
: An Iterator
of Key
s identifying the node.ser
: A Serializer
to to serialize the value.Node depth on success.
+Redirecting to ../../miniconf/enum.Error.html...
+ + + \ No newline at end of file diff --git a/firmware/miniconf/tree/struct.Metadata.html b/firmware/miniconf/tree/struct.Metadata.html new file mode 100644 index 0000000000..d39ad1a910 --- /dev/null +++ b/firmware/miniconf/tree/struct.Metadata.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../miniconf/struct.Metadata.html...
+ + + \ No newline at end of file diff --git a/firmware/miniconf/tree/struct.SliceShort.html b/firmware/miniconf/tree/struct.SliceShort.html new file mode 100644 index 0000000000..e31490873b --- /dev/null +++ b/firmware/miniconf/tree/struct.SliceShort.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../miniconf/struct.SliceShort.html...
+ + + \ No newline at end of file diff --git a/firmware/miniconf/tree/trait.Increment.html b/firmware/miniconf/tree/trait.Increment.html new file mode 100644 index 0000000000..caecb5af9c --- /dev/null +++ b/firmware/miniconf/tree/trait.Increment.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../miniconf/trait.Increment.html...
+ + + \ No newline at end of file diff --git a/firmware/miniconf/tree/trait.Key.html b/firmware/miniconf/tree/trait.Key.html new file mode 100644 index 0000000000..721b4becd7 --- /dev/null +++ b/firmware/miniconf/tree/trait.Key.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../miniconf/trait.Key.html...
+ + + \ No newline at end of file diff --git a/firmware/miniconf/tree/trait.TreeDeserialize.html b/firmware/miniconf/tree/trait.TreeDeserialize.html new file mode 100644 index 0000000000..6ff6d41bfe --- /dev/null +++ b/firmware/miniconf/tree/trait.TreeDeserialize.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../miniconf/trait.TreeDeserialize.html...
+ + + \ No newline at end of file diff --git a/firmware/miniconf/tree/trait.TreeKey.html b/firmware/miniconf/tree/trait.TreeKey.html new file mode 100644 index 0000000000..296a024a5d --- /dev/null +++ b/firmware/miniconf/tree/trait.TreeKey.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../miniconf/trait.TreeKey.html...
+ + + \ No newline at end of file diff --git a/firmware/miniconf/tree/trait.TreeSerialize.html b/firmware/miniconf/tree/trait.TreeSerialize.html new file mode 100644 index 0000000000..557af020a0 --- /dev/null +++ b/firmware/miniconf/tree/trait.TreeSerialize.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../miniconf/trait.TreeSerialize.html...
+ + + \ No newline at end of file diff --git a/firmware/search-index.js b/firmware/search-index.js new file mode 100644 index 0000000000..f34f530795 --- /dev/null +++ b/firmware/search-index.js @@ -0,0 +1,10 @@ +var searchIndex = JSON.parse('{\ +"ad9959":{"doc":"","t":"NSDNNNNNNNNNNNNNNNNNNNNDNEQNSNNNNINNESNDNENNSSNLLLLLLLLLLLLLLLLLLLLLLLKLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLK","n":["ACR","ALL","Ad9959","Bounds","CFR","CFTW0","CPOW0","CSR","CW1","CW10","CW11","CW12","CW13","CW14","CW15","CW2","CW3","CW4","CW5","CW6","CW7","CW8","CW9","Channel","Check","Error","Error","FDW","FOUR","FR1","FR2","FourBitSerial","Frequency","Interface","Interface","LSRR","Mode","ONE","Pin","ProfileSerializer","RDW","Register","SingleBitThreeWire","SingleBitTwoWire","THREE","TWO","TwoBitSerial","all","bitand","bitand_assign","bitor","bitor_assign","bits","bits","bitxor","bitxor_assign","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","complement","configure_mode","contains","difference","empty","eq","extend","finalize","fmt","fmt","fmt","fmt","fmt","freeze","from","from","from","from","from","from","from_bits","from_bits_retain","from_bits_retain","from_bits_truncate","from_iter","from_name","get_amplitude","get_frequency","get_phase","get_reference_clock_frequency","get_reference_clock_multiplier","insert","intersection","intersects","into","into","into","into","into","into","into_iter","is_all","is_empty","iter","iter_names","new","new","not","read","remove","self_test","set","set_amplitude","set_frequency","set_phase","sub","sub_assign","symmetric_difference","toggle","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","union","update_channels","write"],"q":[[0,"ad9959"]],"d":["","","A device driver for the AD9959 direct digital synthesis …","","","","","","","","","","","","","","","","","","","","","Specifies an output channel of the AD9959 DDS chip.","","Possible errors generated by the AD9959 driver.","","","","","","","","A trait that allows a HAL to provide a means of …","","","Indicates various communication modes of the DDS. The …","","","Represents a means of serializing a DDS profile for …","","The configuration registers within the AD9959 DDS device. …","","","","","","Get a flags value with all known bits set.","The bitwise and (&
) of the bits in two flags values.","The bitwise and (&
) of the bits in two flags values.","The bitwise or (|
) of the bits in two flags values.","The bitwise or (|
) of the bits in two flags values.","Get the underlying bits value.","","The bitwise exclusive-or (^
) of the bits in two flags …","The bitwise exclusive-or (^
) of the bits in two flags …","","","","","","","","","","","","","","The bitwise negation (!
) of the bits in a flags value, …","","Whether all set bits in a source flags value are also set …","The intersection of a source flags value with the …","Get a flags value with all bits unset.","","The bitwise or (|
) of the bits in each flags value.","Get the serialized profile as a slice of 32-bit words.","","","","","","Finalize DDS configuration","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Convert from a bits value.","","Convert from a bits value exactly.","Convert from a bits value, unsetting any unknown bits.","The bitwise or (|
) of the bits in each flags value.","Get a flags value with the bits of a flag with the given …","Get the configured amplitude of a channel.","Get the frequency of a channel.","Get the current phase of a specified channel.","Get the current reference clock frequency in Hz.","Get the current reference clock multiplier.","The bitwise or (|
) of the bits in two flags values.","The bitwise and (&
) of the bits in two flags values.","Whether any set bits in a source flags value are also set …","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","","Whether all known bits in this flags value are set.","Whether all bits in this flags value are unset.","Yield a set of contained flags values.","Yield a set of contained named flags values.","Construct and initialize the DDS.","Construct a new serializer.","The bitwise negation (!
) of the bits in a flags value, …","","The intersection of a source flags value with the …","Perform a self-test of the communication interface.","Call insert
when value
is true
or remove
when value
is …","Configure the amplitude of a specified channel.","Configure the frequency of a specified channel.","Configure the phase of a specified channel.","The intersection of a source flags value with the …","The intersection of a source flags value with the …","The bitwise exclusive-or (^
) of the bits in two flags …","The bitwise exclusive-or (^
) of the bits in two flags …","","","","","","","","","","","","","","","","","","","The bitwise or (|
) of the bits in two flags values.","Update a number of channels with the requested profile.",""],"i":[24,1,0,12,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,0,12,0,13,24,1,24,24,3,12,0,12,24,0,1,12,0,24,0,3,3,1,1,3,1,1,1,1,1,1,1,1,1,14,24,7,3,1,12,14,24,7,3,1,12,3,1,13,1,1,1,3,1,7,1,1,1,1,12,14,14,24,7,3,1,12,1,1,1,1,1,1,14,14,14,14,14,1,1,1,14,24,7,3,1,12,1,1,1,1,1,14,7,1,13,1,14,1,14,14,14,1,1,1,1,14,24,7,3,1,12,14,24,7,3,1,12,14,24,7,3,1,12,1,7,13],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],1],[[1,1],1],[[1,1]],[[1,1],1],[[1,1]],[1,2],[1,2],[[1,1],1],[[1,1]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[3,3],[1,1],[3,4],[[1,1],5],[[1,1],1],[[],1],[[3,3],5],[[1,6]],[7,[[9,[8]]]],[[1,10],11],[[1,10],11],[[1,10],11],[[1,10],11],[[12,10],11],[[[14,[13]]]],[[]],[[]],[[]],[[]],[[]],[[]],[2,[[15,[1]]]],[2,1],[2,1],[2,1],[6,1],[16,[[15,[1]]]],[[[14,[13]],1],[[4,[17,12]]]],[[[14,[13]],1],[[4,[17,12]]]],[[[14,[13]],1],[[4,[17,12]]]],[[[14,[13]]],17],[[[14,[13]]],[[4,[2,12]]]],[[1,1]],[[1,1],1],[[1,1],5],[[]],[[]],[[]],[[]],[[]],[[]],[1],[1,5],[1,5],[1,[[18,[1]]]],[1,[[19,[1]]]],[[13,20,20,[21,[2]],3,17,2],[[4,[[14,[13]],12]]]],[3,7],[1,1],[[2,[9,[2]]],4],[[1,1]],[[[14,[13]]],[[4,[5,12]]]],[[1,1,5]],[[[14,[13]],1,17],[[4,[17,12]]]],[[[14,[13]],1,17],[[4,[17,12]]]],[[[14,[13]],1,17],[[4,[17,12]]]],[[1,1],1],[[1,1]],[[1,1],1],[[1,1]],[[],4],[[],4],[[],4],[[],4],[[],4],[[],4],[[],4],[[],4],[[],4],[[],4],[[],4],[[],4],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[1,1],1],[[7,1,[15,[8]],[15,[23]],[15,[8]]]],[[2,[9,[2]]],4]],"c":[],"p":[[3,"Channel"],[15,"u8"],[4,"Mode"],[4,"Result"],[15,"bool"],[8,"IntoIterator"],[3,"ProfileSerializer"],[15,"u32"],[15,"slice"],[3,"Formatter"],[6,"Result"],[4,"Error"],[8,"Interface"],[3,"Ad9959"],[4,"Option"],[15,"str"],[15,"f32"],[3,"Iter"],[3,"IterNames"],[8,"OutputPin"],[8,"DelayUs"],[3,"TypeId"],[15,"u16"],[4,"Register"]]},\
+"dual_iir":{"doc":"Dual IIR","t":"RRRRRRDSSMMALLLLLLMLLLMLLLLLLMMMLLLLFFFDGFFFFDFCDDDDFFFDDDDDDDDDFFFDDDDDDFFFDDDFFFDDFFFMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMMMMMMMAFAFLLLLLLLLLLLLLLLLLLLLLLLLLLMMAFMMAFLLLLLLLLLLLLLLLLLLLLLLLLLLMMMMAMMMMMAFMMMMMMAFMMMMMMAMMMAFAFAFAFAFAFMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLAFMMMDDDCMMFCCDDMMMDDMMMACFDDDMMMMMMMMMMDDDCMMMMMMFCCDDDDDDCMMFCCDDDCMMMMMFCCMDDCMFCCM","n":["BATCH_SIZE","IIR_CASCADE_LENGTH","SAMPLE_PERIOD","SAMPLE_TICKS","SAMPLE_TICKS_LOG2","SCALE","Settings","__MINICONF_DEFERS","__MINICONF_NAMES","afe","allow_hold","app","borrow","borrow_mut","clone","default","deserialize_by_key","fmt","force_hold","from","get_json","get_json_by_index","iir_ch","into","metadata","name_to_index","serialize_by_key","set_json","set_json_by_index","signal_generator","stream_target","telemetry_period","traverse_by_key","try_from","try_into","type_id","DCMI","DMA1_STR4","ETH","Local","Monotonic","SPI2","SPI3","SPI4","SPI5","Shared","SysTick","_","__rtic_internal_Monotonics","__rtic_internal_eth_Context","__rtic_internal_ethernet_linkSharedResources","__rtic_internal_ethernet_link_Context","__rtic_internal_ethernet_link_Monotonic_spawn_after","__rtic_internal_ethernet_link_Monotonic_spawn_at","__rtic_internal_ethernet_link_spawn","__rtic_internal_idleSharedResources","__rtic_internal_idle_Context","__rtic_internal_init_Context","__rtic_internal_processLocalResources","__rtic_internal_processSharedResources","__rtic_internal_process_Context","__rtic_internal_settings_updateLocalResources","__rtic_internal_settings_updateSharedResources","__rtic_internal_settings_update_Context","__rtic_internal_settings_update_Monotonic_spawn_after","__rtic_internal_settings_update_Monotonic_spawn_at","__rtic_internal_settings_update_spawn","__rtic_internal_spi2_Context","__rtic_internal_spi3_Context","__rtic_internal_spi4_Context","__rtic_internal_spi5_Context","__rtic_internal_startLocalResources","__rtic_internal_start_Context","__rtic_internal_start_Monotonic_spawn_after","__rtic_internal_start_Monotonic_spawn_at","__rtic_internal_start_spawn","__rtic_internal_telemetryLocalResources","__rtic_internal_telemetrySharedResources","__rtic_internal_telemetry_Context","__rtic_internal_telemetry_Monotonic_spawn_after","__rtic_internal_telemetry_Monotonic_spawn_at","__rtic_internal_telemetry_spawn","__rtic_internal_usbSharedResources","__rtic_internal_usb_Context","__rtic_internal_usb_Monotonic_spawn_after","__rtic_internal_usb_Monotonic_spawn_at","__rtic_internal_usb_spawn","adcs","adcs","afes","afes","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","core","cpu_temp_sensor","cpu_temp_sensor","cs","dacs","dacs","device","digital_inputs","digital_inputs","eth","eth","ethernet_link","ethernet_link","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","generator","generator","idle","idle","iir_state","iir_state","init","init","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","local","local","local","local","monotonics","network","network","network","network","network","process","process","sampling_timer","sampling_timer","settings","settings","settings","settings","settings_update","settings_update","shared","shared","shared","shared","shared","shared","shared_resources","signal_generator","signal_generator","signal_generator","spi2","spi2","spi3","spi3","spi4","spi4","spi5","spi5","start","start","telemetry","telemetry","telemetry","telemetry","telemetry","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","usb","usb","usb_terminal","usb_terminal","usb_terminal","Context","Context","SharedResources","SpawnHandle","network","shared","spawn","spawn_after","spawn_at","Context","SharedResources","network","shared","usb_terminal","Context","Monotonics","core","cs","device","Monotonic","now","now","Context","LocalResources","SharedResources","adcs","dacs","digital_inputs","generator","iir_state","local","settings","shared","signal_generator","telemetry","Context","LocalResources","SharedResources","SpawnHandle","afes","local","network","settings","shared","signal_generator","spawn","spawn_after","spawn_at","Context","Context","Context","Context","Context","LocalResources","SpawnHandle","local","sampling_timer","spawn","spawn_after","spawn_at","Context","LocalResources","SharedResources","SpawnHandle","cpu_temp_sensor","local","network","settings","shared","spawn","spawn_after","spawn_at","telemetry","Context","SharedResources","SpawnHandle","shared","spawn","spawn_after","spawn_at","usb_terminal"],"q":[[0,"dual_iir"],[36,"dual_iir::app"],[344,"dual_iir::app::eth"],[345,"dual_iir::app::ethernet_link"],[353,"dual_iir::app::idle"],[358,"dual_iir::app::init"],[363,"dual_iir::app::monotonics"],[365,"dual_iir::app::monotonics::Monotonic"],[366,"dual_iir::app::process"],[379,"dual_iir::app::settings_update"],[392,"dual_iir::app::spi2"],[393,"dual_iir::app::spi3"],[394,"dual_iir::app::spi4"],[395,"dual_iir::app::spi5"],[396,"dual_iir::app::start"],[404,"dual_iir::app::telemetry"],[417,"dual_iir::app::usb"]],"d":["","","","","","","","","","Configure the Analog Front End (AFE) gain.","Specified true if DI1 should be used as a “hold” input.","The RTIC application module","","","","","","","Specified true if “hold” should be forced regardless …","Returns the argument unchanged.","","","Configure the IIR filter parameters.","Calls U::from(self)
.","","","","","","Specifies the config for signal generators to add on to …","Specifies the target for data livestreaming.","Specifies the telemetry output period in seconds.","","","","","Interrupt handler to dispatch tasks at priority 1","User HW task ISR trampoline for process","User HW task ISR trampoline for eth","RTIC local resource struct","User code from within the module","User HW task ISR trampoline for spi2","User HW task ISR trampoline for spi3","User HW task ISR trampoline for spi4","User HW task ISR trampoline for spi5","RTIC shared resource struct","","","Monotonics used by the system","Execution context","Shared resources ethernet_link
has access to","Execution context","Spawns the task after a set duration relative to the …","Spawns the task at a fixed time instant","Spawns the task directly","Shared resources idle
has access to","Execution context","Execution context","Local resources process
has access to","Shared resources process
has access to","Execution context","Local resources settings_update
has access to","Shared resources settings_update
has access to","Execution context","Spawns the task after a set duration relative to the …","Spawns the task at a fixed time instant","Spawns the task directly","Execution context","Execution context","Execution context","Execution context","Local resources start
has access to","Execution context","Spawns the task after a set duration relative to the …","Spawns the task at a fixed time instant","Spawns the task directly","Local resources telemetry
has access to","Shared resources telemetry
has access to","Execution context","Spawns the task after a set duration relative to the …","Spawns the task at a fixed time instant","Spawns the task directly","Shared resources usb
has access to","Execution context","Spawns the task after a set duration relative to the …","Spawns the task at a fixed time instant","Spawns the task directly","","Local resource adcs
","","Local resource afes
","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Core (Cortex-M) peripherals","","Local resource cpu_temp_sensor
","Critical section token for init","","Local resource dacs
","Device peripherals","","Local resource digital_inputs
","Hardware task","User HW task: eth","Software task","User SW task ethernet_link","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Local resource generator
","Idle loop","User provided idle function","","Local resource iir_state
","Initialization function","User code end User provided init function","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Local Resources this task has access to","Local Resources this task has access to","Local Resources this task has access to","Local Resources this task has access to","Holds static methods for each monotonic.","","Resource proxy resource network
. Use method .lock()
to …","Resource proxy resource network
. Use method .lock()
to …","Resource proxy resource network
. Use method .lock()
to …","Resource proxy resource network
. Use method .lock()
to …","Hardware task","User HW task: process","","Local resource sampling_timer
","","Resource proxy resource settings
. Use method .lock()
to …","Resource proxy resource settings
. Use method .lock()
to …","Resource proxy resource settings
. Use method .lock()
to …","Software task","User SW task settings_update","Shared Resources this task has access to","Shared Resources this task has access to","Shared Resources this task has access to","Shared Resources this task has access to","Shared Resources this task has access to","Shared Resources this task has access to","","","Resource proxy resource signal_generator
. Use method …","Resource proxy resource signal_generator
. Use method …","Hardware task","User HW task: spi2","Hardware task","User HW task: spi3","Hardware task","User HW task: spi4","Hardware task","User HW task: spi5","Software task","User SW task start","Software task","User SW task telemetry","","Resource proxy resource telemetry
. Use method .lock()
to …","Resource proxy resource telemetry
. Use method .lock()
to …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Software task","User SW task usb","","Resource proxy resource usb_terminal
. Use method .lock()
…","Resource proxy resource usb_terminal
. Use method .lock()
…","Execution context","Execution context","Shared resources ethernet_link
has access to","","Resource proxy resource network
. Use method .lock()
to …","Shared Resources this task has access to","Spawns the task directly","","","Execution context","Shared resources idle
has access to","Resource proxy resource network
. Use method .lock()
to …","Shared Resources this task has access to","Resource proxy resource usb_terminal
. Use method .lock()
…","Execution context","Monotonics used by the system","Core (Cortex-M) peripherals","Critical section token for init","Device peripherals","This module holds the static implementation for …","","Read the current time from this monotonic","Execution context","Local resources process
has access to","Shared resources process
has access to","Local resource adcs
","Local resource dacs
","Local resource digital_inputs
","Local resource generator
","Local resource iir_state
","Local Resources this task has access to","Resource proxy resource settings
. Use method .lock()
to …","Shared Resources this task has access to","Resource proxy resource signal_generator
. Use method …","Resource proxy resource telemetry
. Use method .lock()
to …","Execution context","Local resources settings_update
has access to","Shared resources settings_update
has access to","","Local resource afes
","Local Resources this task has access to","Resource proxy resource network
. Use method .lock()
to …","Resource proxy resource settings
. Use method .lock()
to …","Shared Resources this task has access to","Resource proxy resource signal_generator
. Use method …","Spawns the task directly","","","Execution context","Execution context","Execution context","Execution context","Execution context","Local resources start
has access to","","Local Resources this task has access to","Local resource sampling_timer
","Spawns the task directly","","","Execution context","Local resources telemetry
has access to","Shared resources telemetry
has access to","","Local resource cpu_temp_sensor
","Local Resources this task has access to","Resource proxy resource network
. Use method .lock()
to …","Resource proxy resource settings
. Use method .lock()
to …","Shared Resources this task has access to","Spawns the task directly","","","Resource proxy resource telemetry
. Use method .lock()
to …","Execution context","Shared resources usb
has access to","","Shared Resources this task has access to","Spawns the task directly","","","Resource proxy resource usb_terminal
. Use method .lock()
…"],"i":[0,0,0,0,0,0,0,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,33,34,33,35,36,33,37,23,38,21,34,39,24,19,26,27,28,29,40,30,35,41,25,42,43,31,44,32,45,20,36,33,37,23,38,21,34,39,24,19,26,27,28,29,40,30,35,41,25,42,43,31,44,32,45,20,23,33,42,23,33,34,23,33,34,0,0,0,0,36,33,37,23,38,21,34,39,24,19,26,27,28,29,40,30,35,41,25,42,43,31,44,32,45,20,33,34,0,0,33,34,0,0,36,33,37,23,38,21,34,39,24,19,26,27,28,29,40,30,35,41,25,42,43,31,44,32,45,20,24,30,25,31,0,36,38,41,43,45,0,0,33,40,36,39,41,43,0,0,21,24,25,31,32,20,0,36,39,41,0,0,0,0,0,0,0,0,0,0,0,0,36,39,43,36,33,37,23,38,21,34,39,24,19,26,27,28,29,40,30,35,41,25,42,43,31,44,32,45,20,36,33,37,23,38,21,34,39,24,19,26,27,28,29,40,30,35,41,25,42,43,31,44,32,45,20,36,33,37,23,38,21,34,39,24,19,26,27,28,29,40,30,35,41,25,42,43,31,44,32,45,20,0,0,36,38,44,0,0,0,0,45,20,0,0,0,0,0,38,21,38,0,0,23,23,23,0,0,0,0,0,0,34,34,34,34,34,24,39,24,39,39,0,0,0,0,35,25,41,41,25,41,0,0,0,0,0,0,0,0,0,0,30,40,0,0,0,0,0,0,0,42,31,43,43,31,0,0,0,43,0,0,0,32,0,0,0,44],"f":[0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[1,1],[[],1],[[1,2,3],[[6,[4,5]]]],[[1,7],8],0,[[]],[[9,[11,[10]]],[[6,[4,[5,[12]]]]]],[[[11,[4]],[11,[10]]],[[6,[4,[5,[12]]]]]],0,[[]],[[],13],[9,[[14,[4]]]],[[1,2,15],[[6,[4,5]]]],[[9,[11,[10]]],[[6,[4,[5,[16]]]]]],[[[11,[4]],[11,[10]]],[[6,[4,[5,[16]]]]]],0,0,0,[[2,17],[[6,[4,5]]]],[[],6],[[],6],[[],18],[[]],[[]],[[]],0,0,[[]],[[]],[[]],[[]],0,[[]],0,0,0,0,0,[[],[[6,[0]]]],[[],[[6,[0]]]],[[],6],0,0,0,0,0,0,0,0,0,[[],[[6,[0]]]],[[],[[6,[0]]]],[[],6],0,0,0,0,0,0,[[],[[6,[0]]]],[[],[[6,[0]]]],[[],6],0,0,0,[[],[[6,[0]]]],[[],[[6,[0]]]],[[],6],0,0,[[],[[6,[0]]]],[[],[[6,[0]]]],[[],6],0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,0,0,0,0,0,0,[19],0,[20],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,[21,22],0,0,0,[23],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,0,0,0,0,0,0,0,[24],0,0,0,0,0,0,0,[25],0,0,0,0,0,0,0,0,0,0,0,[26],0,[27],0,[28],0,[29],0,[30],0,[31],0,0,0,[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],0,[32],0,0,0,0,0,0,0,0,0,[[],6],0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],6],0,0,0,0,0,0,0,0,0,0,0,[[],6],0,0,0,0,0,0,0,0,0,0,0,[[],6],0,0,0,0,0,0,0,[[],6],0,0,0],"c":[],"p":[[3,"Settings"],[8,"Iterator"],[8,"Deserializer"],[15,"usize"],[4,"Error"],[4,"Result"],[3,"Formatter"],[6,"Result"],[15,"str"],[15,"u8"],[15,"slice"],[4,"Error"],[3,"Metadata"],[4,"Option"],[8,"Serializer"],[4,"Error"],[8,"FnMut"],[3,"TypeId"],[3,"__rtic_internal_eth_Context"],[3,"__rtic_internal_ethernet_link_Context"],[3,"__rtic_internal_idle_Context"],[15,"never"],[3,"__rtic_internal_init_Context"],[3,"__rtic_internal_process_Context"],[3,"__rtic_internal_settings_update_Context"],[3,"__rtic_internal_spi2_Context"],[3,"__rtic_internal_spi3_Context"],[3,"__rtic_internal_spi4_Context"],[3,"__rtic_internal_spi5_Context"],[3,"__rtic_internal_start_Context"],[3,"__rtic_internal_telemetry_Context"],[3,"__rtic_internal_usb_Context"],[3,"Local"],[3,"__rtic_internal_processLocalResources"],[3,"__rtic_internal_settings_updateLocalResources"],[3,"Shared"],[3,"__rtic_internal_Monotonics"],[3,"__rtic_internal_idleSharedResources"],[3,"__rtic_internal_processSharedResources"],[3,"__rtic_internal_startLocalResources"],[3,"__rtic_internal_settings_updateSharedResources"],[3,"__rtic_internal_telemetryLocalResources"],[3,"__rtic_internal_telemetrySharedResources"],[3,"__rtic_internal_usbSharedResources"],[3,"__rtic_internal_ethernet_linkSharedResources"]]},\
+"idsp":{"doc":"","t":"DDDDIQIDDGGIDDDDFKLLLLLLLLLLLLLKLLFLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLFFLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKLLLLLLLLLLLLLLLLLKLLLLLALAAMLLLLLLLLLLLLLLLLLLLLLKLFFLLLLLLLLLLLLLLLLKLLLLLLLLLLLFLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMLLLLLLLLLLLLKLFKLLLLLKLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKLLLLLLLLLLLIRRRRDDDDQDKLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKLLLLKLLLLLLMLLLLLLLLLLLLLLLDGMLLLLLLLLLLLLLLLLLLMMMDSGMLLLLLLLLLLLLLMMM","n":["Accu","Cascade","Chain","Complex","ComplexExt","Config","Filter","Lockin","Lowpass","Lowpass1","Lowpass2","MulScaled","Nyquist","PLL","RPLL","Unwrapper","abs","abs_sqr","abs_sqr","add","add","add","add","add","add","add","add","add_assign","add_assign","add_assign","add_assign","arg","arg","as_","atan2","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","conj","copysign","cossin","default","default","default","default","default","default","default","default","default","default","deserialize","deserialize","deserialize","div","div","div","div","div","div","div","div","div_assign","div_assign","div_assign","div_assign","eq","eq","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","frequency","frequency","from","from","from","from","from","from","from","from","from","from","from","from","from","from_angle","from_angle","from_f32","from_f64","from_i128","from_i16","from_i32","from_i64","from_i8","from_isize","from_str","from_str_radix","from_u128","from_u16","from_u32","from_u64","from_u8","from_usize","get","get","get","get","get","hash","hbf","i","iir","iir_int","im","into","into","into","into","into","into","into","into","into","into","into_iter","inv","inv","inv","is_finite","is_infinite","is_nan","is_normal","is_one","is_zero","l1_norm","log2","log2","macc","macc_i32","mul","mul","mul","mul","mul","mul","mul","mul","mul_add","mul_add","mul_add_assign","mul_add_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_scaled","mul_scaled","mul_scaled","mul_scaled","neg","neg","new","new","new","next","norm_sqr","one","overflowing_sub","phase","phase","phase","pow","pow","pow","pow","pow","pow","pow","pow","pow","pow","pow","pow","pow","pow","pow","pow","pow","pow","pow","pow","pow","pow","pow","pow","powi","powu","product","product","re","rem","rem","rem","rem","rem","rem","rem","rem","rem_assign","rem_assign","rem_assign","rem_assign","saturating_add","saturating_add","saturating_scale","saturating_sub","saturating_sub","scale","serialize","serialize","serialize","set","set","set","set","set","set_one","set_zero","sub","sub","sub","sub","sub","sub","sub","sub","sub_assign","sub_assign","sub_assign","sub_assign","sum","sum","to_f32","to_f64","to_i128","to_i16","to_i32","to_i64","to_i8","to_isize","to_u128","to_u16","to_u32","to_u64","to_u8","to_usize","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","unscale","update","update","update","update","update","update","update","update","update","update_iq","wraps","zero","Filter","HBF_CASCADE_BLOCK","HBF_PASSBAND","HBF_TAPS","HBF_TAPS_98","HbfDec","HbfDecCascade","HbfInt","HbfIntCascade","Item","SymFir","block_size","block_size","block_size","block_size","block_size","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","buf_mut","buf_mut","clone","clone","clone","clone","clone","default","default","depth","depth","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","get","into","into","into","into","into","keep_state","new","new","new","process_block","process_block","process_block","process_block","process_block","response_length","response_length","response_length","response_length","response_length","set_depth","set_depth","stages","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","IIR","Vec5","ba","borrow","borrow_mut","clone","default","deserialize","fmt","from","get_k","get_x_offset","into","new","serialize","set_pi","set_x_offset","try_from","try_into","type_id","update","y_max","y_min","y_offset","IIR","SHIFT","Vec5","ba","borrow","borrow_mut","clone","default","deserialize","fmt","from","into","serialize","try_from","try_into","type_id","update","y_max","y_min","y_offset"],"q":[[0,"idsp"],[333,"idsp::hbf"],[418,"idsp::iir"],[442,"idsp::iir_int"]],"d":["","","","A complex number in Cartesian form.","Complex extension trait offering DSP (fast, good accuracy) …","","","","Arbitrary order, high dynamic range, wide coefficient …","First order lowpass","Second order lowpass","Full scale fixed point multiplication.","","Type-II, sampled phase, discrete time PLL","Reciprocal PLL.","Overflow unwrapper.","","","Return the absolute square (the squared magnitude).","","","","","","","","","","","","","","Return the angle.","","2-argument arctangent function.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the complex conjugate. i.e. re - i im
","","Compute the cosine and sine of an angle. This is ported …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Return the current frequency estimate","Return the current frequency estimate","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Return a Complex on the unit circle given an angle.","","","","","","","","","Parses a +/- bi
; ai +/- b
; a
; or bi
where a
and b
are of …","Parses a +/- bi
; ai +/- b
; a
; or bi
where a
and b
are of …","","","","","","","Return the current filter output","","","","","","","Returns imaginary unit","","","Imaginary portion of the complex number","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","","Returns 1/self
","","","Checks if the given complex number is finite","Checks if the given complex number is infinite","Checks if the given complex number is NaN","Checks if the given complex number is normal","","","Returns the L1 norm |re| + |im|
– the Manhattan distance …","","log2(power) re full scale approximation","","","","","","","","","","","","","","","","","","","","","","","","","","Create a new Complex","Create a new RPLL instance.","","Returns the square of the norm (since T
doesn’t …","","Subtract y - x
with signed overflow.","Return the current phase estimate","Return the current phase estimate","Return the last known phase","","","","","","","","","","","","","","","","","","","","","","","","","Raises self
to a signed integer power.","Raises self
to an unsigned integer power.","","","Real portion of the complex number","","","","","","","","","","","","","","","Combine high and low i32 into a single downscaled i32, …","","","Multiplies self
by the scalar t
.","","","","Update the filter so that it outputs the provided value. …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Divides self
by the scalar t
.","Update the filter with a new sample.","","","","Update the lockin with a sample taken at a given phase.","","Update the PLL with a new phase sample. This needs to be …","Advance the RPLL and optionally supply a new timestamp.","Unwrap a new sample from a sequence and update the …","Update the lockin with a sample taken at a local …","Return the current number of wraps","","Filter input items into output items.","Max low-rate block size (HbfIntCascade input, …","Passband width in units of lowest sample rate","137 dB stopband, 2 µdB passband rippleotherwise like …","Standard/optimal half-band filter cascade taps","Half band decimator (decimate by two)","Half-band decimation filter cascade with optimal taps","Half band interpolator (interpolation rate 2)","Half-band interpolation filter cascade with optimal taps.","Input/output item type.","Symmetric FIR filter prototype.","Return the block size granularity and the maximum block …","","","","","","","","","","","","","","","Obtain a mutable reference to the input items buffer space.","Obtain a mutable reference to the input items buffer space","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Perform the FIR convolution and yield results iteratively.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Move items as new filter state.","Create a new SymFir
.","Create a new HbfDec
.","Non-zero (odd) taps from oldest to one-before-center. …","Process a block of items.","","","","","Finite impulse response length in numer of output items …","","","","","","","","","","","","","","","","","","","","","","","IIR configuration.","IIR state and coefficients type.","","","","","","","","Returns the argument unchanged.","Compute the overall (DC feed-forward) gain.","","Calls U::from(self)
.","","","Configures IIR filter coefficients for …","Convert input (x
) offset to equivalent output (y
) offset …","","","","Feed a new input value into the filter, update the filter …","","","","Integer biquad IIR","Coefficient fixed point format: signed Q2.30. Tailored to …","Generic vector for integer IIR filter. This struct is used …","","","","","","","","Returns the argument unchanged.","Calls U::from(self)
.","","","","","Feed a new input value into the filter, update the filter …","","",""],"i":[0,0,0,0,0,55,0,0,0,0,0,0,0,0,0,0,0,84,5,5,5,5,5,5,5,5,5,5,5,5,5,84,5,5,0,12,13,14,15,5,16,17,18,19,20,12,13,14,15,5,16,17,18,19,20,12,13,14,15,5,16,17,18,19,20,5,0,0,12,13,14,15,5,16,17,18,19,20,5,18,20,5,5,5,5,5,5,5,5,5,5,5,5,12,5,12,5,5,5,5,5,5,5,5,18,19,12,13,14,15,5,5,5,5,16,17,18,19,20,84,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,55,13,14,15,17,5,0,5,0,0,5,12,13,14,15,5,16,17,18,19,20,12,5,5,5,5,5,5,5,5,5,5,84,5,0,0,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,85,5,5,5,5,5,12,5,19,12,5,5,0,18,19,20,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,84,5,0,84,5,5,5,18,20,55,13,14,15,17,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,12,13,14,15,5,16,17,18,19,20,12,13,14,15,5,16,17,18,19,20,12,13,14,15,5,16,17,18,19,20,5,55,13,14,15,16,17,18,19,20,16,20,5,0,0,0,0,0,0,0,0,0,86,0,86,72,73,74,75,76,72,73,74,75,76,72,73,74,75,76,73,76,72,73,74,75,74,75,74,75,76,72,73,74,75,76,72,73,74,75,76,76,72,73,74,75,76,76,72,73,86,72,73,74,75,86,72,73,74,75,74,75,75,76,72,73,74,75,76,72,73,74,75,76,72,73,74,75,0,0,78,78,78,78,78,78,78,78,78,78,78,78,78,78,78,78,78,78,78,78,78,78,0,82,0,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82,82],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[[0,[1,2,3]]],[[0,[1,2,3]]]],[[]],[[[5,[4]]],6],[[[5,[[0,[7,8]]]],[0,[7,8]]]],[[[5,[[0,[7,8]]]],[0,[7,8]]]],[[[5,[[0,[7,8]]]],[0,[7,8]]]],[[[5,[[0,[7,8]]]],[5,[[0,[7,8]]]]]],[[[5,[[0,[7,8]]]],[5,[[0,[7,8]]]]]],[[[5,[[0,[7,8]]]],[5,[[0,[7,8]]]]]],[[[5,[[0,[7,8]]]],[5,[[0,[7,8]]]]]],[[[5,[[0,[7,8]]]],[0,[7,8]]]],[[[5,[[0,[7,9]]]],[5,[[0,[7,9]]]]]],[[[5,[[0,[7,9]]]],[5,[[0,[7,9]]]]]],[[[5,[[0,[7,9]]]],[0,[7,9]]]],[[[5,[[0,[7,9]]]],[0,[7,9]]]],[[]],[[[5,[4]]],4],[[[5,[[11,[10]]]]],10],[[4,4],4],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[12,[7]]],[[12,[7]]]],[13,13],[[[14,[7]]],[[14,[7]]]],[[[15,[7,7]]],[[15,[7,7]]]],[[[5,[7]]],[[5,[7]]]],[[[16,[7]]],[[16,[7]]]],[17,17],[18,18],[19,19],[[[20,[7]]],[[20,[7]]]],[[[5,[[0,[7,8,3]]]]],[[5,[[0,[7,8,3]]]]]],[[[0,[1,2,3]],[0,[1,2,3]]],[[0,[1,2,3]]]],[4],[[],[[12,[2]]]],[[],13],[[],[[14,[[0,[2,10]]]]]],[[],[[15,[2,2]]]],[[],[[5,[2]]]],[[],[[16,[2]]]],[[],17],[[],18],[[],19],[[],[[20,[2]]]],[21,[[23,[[5,[[0,[22,8,7]]]]]]]],[21,[[23,[18]]]],[21,[[23,[[20,[22]]]]]],[[[5,[[0,[7,8]]]],[0,[7,8]]]],[[[5,[[0,[7,8]]]],[0,[7,8]]]],[[[5,[[0,[7,8]]]],[0,[7,8]]]],[[[5,[[0,[7,8]]]],[5,[[0,[7,8]]]]]],[[[5,[[0,[7,8]]]],[5,[[0,[7,8]]]]]],[[[5,[[0,[7,8]]]],[5,[[0,[7,8]]]]]],[[[5,[[0,[7,8]]]],[0,[7,8]]]],[[[5,[[0,[7,8]]]],[5,[[0,[7,8]]]]]],[[[5,[[0,[7,9]]]],[0,[7,9]]]],[[[5,[[0,[7,9]]]],[5,[[0,[7,9]]]]]],[[[5,[[0,[7,9]]]],[5,[[0,[7,9]]]]]],[[[5,[[0,[7,9]]]],[0,[7,9]]]],[[[12,[24]],[12,[24]]],25],[[[5,[[24,[[24,[[24,[24]]]]]]]],[5,[[24,[[24,[[24,[24]]]]]]]]],25],[[[12,[26]],27],28],[[[5,[[0,[29,8,[1,[[0,[29,8,[1,[[0,[29,8,[1,[[0,[29,8,1,7]]]],7]]]],7]]]],7]]]],27],[[23,[30]]]],[[[5,[26]],27],[[23,[30]]]],[[[5,[[0,[31,8,[1,[[0,[31,8,[1,[[0,[31,8,[1,[[0,[31,8,1,7]]]],7]]]],7]]]],7]]]],27],[[23,[30]]]],[[[5,[[0,[32,8,[1,[[0,[32,8,[1,[[0,[32,8,[1,[[0,[32,8,1,7]]]],7]]]],7]]]],7]]]],27],[[23,[30]]]],[[[5,[[0,[33,8,[1,[[0,[33,8,[1,[[0,[33,8,[1,[[0,[33,8,1,7]]]],7]]]],7]]]],7]]]],27],[[23,[30]]]],[[[5,[[0,[34,8,[1,[[0,[34,8,[1,[[0,[34,8,[1,[[0,[34,8,1,7]]]],7]]]],7]]]],7]]]],27],[[23,[30]]]],[[[5,[[0,[35,8,[1,[[0,[35,8,[1,[[0,[35,8,[1,[[0,[35,8,1,7]]]],7]]]],7]]]],7]]]],27],[[23,[30]]]],[[[5,[[0,[36,8,[1,[[0,[36,8,[1,[[0,[36,8,[1,[[0,[36,8,1,7]]]],7]]]],7]]]],7]]]],27],[[23,[30]]]],[18,4],[19,6],[[]],[[]],[[]],[[]],[[[0,[7,8]]],[[5,[[0,[7,8]]]]]],[[[0,[7,8]]],[[5,[[0,[7,8]]]]]],[37,[[39,[[5,[[0,[38,8]]]]]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[4,[[5,[4]]]],[40,[[39,[[5,[[0,[41,8]]]]]]]],[42,[[39,[[5,[[0,[41,8]]]]]]]],[43,[[39,[[5,[[0,[41,8]]]]]]]],[44,[[39,[[5,[[0,[41,8]]]]]]]],[4,[[39,[[5,[[0,[41,8]]]]]]]],[45,[[39,[[5,[[0,[41,8]]]]]]]],[46,[[39,[[5,[[0,[41,8]]]]]]]],[47,[[39,[[5,[[0,[41,8]]]]]]]],[48,[[23,[[5,[[0,[49,8,7]]]]]]]],[[48,6],[[23,[[5,[[0,[8,7]]]]]]]],[50,[[39,[[5,[[0,[41,8]]]]]]]],[51,[[39,[[5,[[0,[41,8]]]]]]]],[6,[[39,[[5,[[0,[41,8]]]]]]]],[52,[[39,[[5,[[0,[41,8]]]]]]]],[53,[[39,[[5,[[0,[41,8]]]]]]]],[54,[[39,[[5,[[0,[41,8]]]]]]]],[[],4],[13,4],[[[14,[55]]],4],[[[15,[55,55]]],4],[17,4],[[[5,[56]],57]],0,[[],[[5,[[0,[7,8]]]]]],0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[5,[[0,[7,8,3]]]]],[[5,[[0,[7,8,3]]]]]],[[[5,[[0,[7,8,3]]]]]],[[[5,[[0,[7,8,3]]]]]],[[[5,[58]]],25],[[[5,[58]]],25],[[[5,[58]]],25],[[[5,[58]]],25],[[[5,[[0,[7,8]]]]],25],[[[5,[[0,[7,8]]]]],25],[[[5,[[0,[7,59]]]]],[[0,[7,59]]]],[[]],[[[5,[4]]],4],[[[0,[60,61,10]],[62,[[0,[60,61,10]]]],[62,[[0,[60,61,10]]]]],[[0,[60,61,10]]]],[[4,[62,[4]],[62,[4]],6],4],[[[5,[[0,[7,8]]]],[0,[7,8]]]],[[[5,[[0,[7,8]]]],[5,[[0,[7,8]]]]]],[[[5,[[0,[7,8]]]],[5,[[0,[7,8]]]]]],[[[5,[[0,[7,8]]]],[0,[7,8]]]],[[[5,[[0,[7,8]]]],[0,[7,8]]]],[[[5,[[0,[7,8]]]],[5,[[0,[7,8]]]]]],[[[5,[[0,[7,8]]]],[5,[[0,[7,8]]]]]],[[[5,[[0,[7,8]]]],[0,[7,8]]]],[[[5,[[0,[7,8,[63,[[0,[7,8,[63,[[0,[7,8,[63,[[0,[7,8,63]],[0,[7,8,63]]]]]],[0,[7,8,[63,[[0,[7,8,63]],[0,[7,8,63]]]]]]]]]],[0,[7,8,[63,[[0,[7,8,[63,[[0,[7,8,63]],[0,[7,8,63]]]]]],[0,[7,8,[63,[[0,[7,8,63]],[0,[7,8,63]]]]]]]]]]]]]]]],[5,[[0,[7,8,[63,[[0,[7,8,[63,[[0,[7,8,[63,[[0,[7,8,63]],[0,[7,8,63]]]]]],[0,[7,8,[63,[[0,[7,8,63]],[0,[7,8,63]]]]]]]]]],[0,[7,8,[63,[[0,[7,8,[63,[[0,[7,8,63]],[0,[7,8,63]]]]]],[0,[7,8,[63,[[0,[7,8,63]],[0,[7,8,63]]]]]]]]]]]]]]]],[5,[[0,[7,8,[63,[[0,[7,8,[63,[[0,[7,8,[63,[[0,[7,8,63]],[0,[7,8,63]]]]]],[0,[7,8,[63,[[0,[7,8,63]],[0,[7,8,63]]]]]]]]]],[0,[7,8,[63,[[0,[7,8,[63,[[0,[7,8,63]],[0,[7,8,63]]]]]],[0,[7,8,[63,[[0,[7,8,63]],[0,[7,8,63]]]]]]]]]]]]]]]]],[[5,[[0,[7,8,[63,[[0,[7,8,[63,[[0,[7,8,[63,[[0,[7,8,63]],[0,[7,8,63]]]]]],[0,[7,8,[63,[[0,[7,8,63]],[0,[7,8,63]]]]]]]]]],[0,[7,8,[63,[[0,[7,8,[63,[[0,[7,8,63]],[0,[7,8,63]]]]]],[0,[7,8,[63,[[0,[7,8,63]],[0,[7,8,63]]]]]]]]]]]]]]]]]],[[[5,[[0,[7,8,[63,[[0,[7,8,[63,[[0,[7,8,[63,[[0,[7,8,63]],[0,[7,8,63]]]]]],[0,[7,8,[63,[[0,[7,8,63]],[0,[7,8,63]]]]]]]]]],[0,[7,8,[63,[[0,[7,8,[63,[[0,[7,8,63]],[0,[7,8,63]]]]]],[0,[7,8,[63,[[0,[7,8,63]],[0,[7,8,63]]]]]]]]]]]]]]]],[5,[[0,[7,8,[63,[[0,[7,8,[63,[[0,[7,8,[63,[[0,[7,8,63]],[0,[7,8,63]]]]]],[0,[7,8,[63,[[0,[7,8,63]],[0,[7,8,63]]]]]]]]]],[0,[7,8,[63,[[0,[7,8,[63,[[0,[7,8,63]],[0,[7,8,63]]]]]],[0,[7,8,[63,[[0,[7,8,63]],[0,[7,8,63]]]]]]]]]]]]]]]],[5,[[0,[7,8,[63,[[0,[7,8,[63,[[0,[7,8,[63,[[0,[7,8,63]],[0,[7,8,63]]]]]],[0,[7,8,[63,[[0,[7,8,63]],[0,[7,8,63]]]]]]]]]],[0,[7,8,[63,[[0,[7,8,[63,[[0,[7,8,63]],[0,[7,8,63]]]]]],[0,[7,8,[63,[[0,[7,8,63]],[0,[7,8,63]]]]]]]]]]]]]]]]],[[5,[[0,[7,8,[63,[[0,[7,8,[63,[[0,[7,8,[63,[[0,[7,8,63]],[0,[7,8,63]]]]]],[0,[7,8,[63,[[0,[7,8,63]],[0,[7,8,63]]]]]]]]]],[0,[7,8,[63,[[0,[7,8,[63,[[0,[7,8,63]],[0,[7,8,63]]]]]],[0,[7,8,[63,[[0,[7,8,63]],[0,[7,8,63]]]]]]]]]]]]]]]]]],[[[5,[[0,[7,9,[64,[[0,[7,9,[64,[[0,[7,9,[64,[[0,[7,9,64]],[0,[7,9,64]]]]]],[0,[7,9,[64,[[0,[7,9,64]],[0,[7,9,64]]]]]]]]]],[0,[7,9,[64,[[0,[7,9,[64,[[0,[7,9,64]],[0,[7,9,64]]]]]],[0,[7,9,[64,[[0,[7,9,64]],[0,[7,9,64]]]]]]]]]]]]]]]],[5,[[0,[7,9,[64,[[0,[7,9,[64,[[0,[7,9,[64,[[0,[7,9,64]],[0,[7,9,64]]]]]],[0,[7,9,[64,[[0,[7,9,64]],[0,[7,9,64]]]]]]]]]],[0,[7,9,[64,[[0,[7,9,[64,[[0,[7,9,64]],[0,[7,9,64]]]]]],[0,[7,9,[64,[[0,[7,9,64]],[0,[7,9,64]]]]]]]]]]]]]]]],[5,[[0,[7,9,[64,[[0,[7,9,[64,[[0,[7,9,[64,[[0,[7,9,64]],[0,[7,9,64]]]]]],[0,[7,9,[64,[[0,[7,9,64]],[0,[7,9,64]]]]]]]]]],[0,[7,9,[64,[[0,[7,9,[64,[[0,[7,9,64]],[0,[7,9,64]]]]]],[0,[7,9,[64,[[0,[7,9,64]],[0,[7,9,64]]]]]]]]]]]]]]]]]],[[[5,[[0,[7,9,[64,[[0,[7,9,[64,[[0,[7,9,[64,[[0,[7,9,64]],[0,[7,9,64]]]]]],[0,[7,9,[64,[[0,[7,9,64]],[0,[7,9,64]]]]]]]]]],[0,[7,9,[64,[[0,[7,9,[64,[[0,[7,9,64]],[0,[7,9,64]]]]]],[0,[7,9,[64,[[0,[7,9,64]],[0,[7,9,64]]]]]]]]]]]]]]]],[5,[[0,[7,9,[64,[[0,[7,9,[64,[[0,[7,9,[64,[[0,[7,9,64]],[0,[7,9,64]]]]]],[0,[7,9,[64,[[0,[7,9,64]],[0,[7,9,64]]]]]]]]]],[0,[7,9,[64,[[0,[7,9,[64,[[0,[7,9,64]],[0,[7,9,64]]]]]],[0,[7,9,[64,[[0,[7,9,64]],[0,[7,9,64]]]]]]]]]]]]]]]],[5,[[0,[7,9,[64,[[0,[7,9,[64,[[0,[7,9,[64,[[0,[7,9,64]],[0,[7,9,64]]]]]],[0,[7,9,[64,[[0,[7,9,64]],[0,[7,9,64]]]]]]]]]],[0,[7,9,[64,[[0,[7,9,[64,[[0,[7,9,64]],[0,[7,9,64]]]]]],[0,[7,9,[64,[[0,[7,9,64]],[0,[7,9,64]]]]]]]]]]]]]]]]]],[[[5,[[0,[7,9]]]],[0,[7,9]]]],[[[5,[[0,[7,9]]]],[5,[[0,[7,9]]]]]],[[[5,[[0,[7,9]]]],[5,[[0,[7,9]]]]]],[[[5,[[0,[7,9]]]],[0,[7,9]]]],[[]],[[[5,[4]],[5,[4]]],[[5,[4]]]],[[[5,[4]],4],[[5,[4]]]],[[[5,[4]],44],[[5,[4]]]],[[[5,[[0,[7,8,3]]]]]],[[[5,[[0,[7,8,3]]]]]],[[],12],[[],5],[6,19],[[[12,[[0,[65,10]]]]],[[39,[[0,[65,10]]]]]],[[[5,[[0,[7,8]]]]],[[0,[7,8]]]],[[],[[5,[[0,[7,8]]]]]],[[[0,[66,67,1]],[0,[66,67,1]]]],[18,4],[19,4],[[[20,[[0,[66,67,1,10]]]]],[[0,[66,67,1,10]]]],[[[5,[[0,[7,8]]]],52]],[[[5,[[0,[7,8,3]]]],44]],[[[5,[[0,[7,8]]]],54]],[[[5,[[0,[7,8,3]]]],43]],[[[5,[[0,[7,8]]]],51]],[[[5,[[0,[7,8]]]],50]],[[[5,[[0,[7,8,3]]]],45]],[[[5,[[0,[7,8]]]],50]],[[[5,[[0,[7,8,3]]]],44]],[[[5,[[0,[7,8]]]],51]],[[[5,[[0,[7,8,3]]]],46]],[[[5,[[0,[7,8,3]]]],4]],[[[5,[[0,[7,8]]]],53]],[[[5,[[0,[7,8]]]],6]],[[[5,[[0,[7,8,3]]]],47]],[[[5,[[0,[7,8]]]],52]],[[[5,[[0,[7,8,3]]]],46]],[[[5,[[0,[7,8,3]]]],43]],[[[5,[[0,[7,8,3]]]],45]],[[[5,[[0,[7,8,3]]]],47]],[[[5,[[0,[7,8]]]],54]],[[[5,[[0,[7,8,3]]]],4]],[[[5,[[0,[7,8]]]],6]],[[[5,[[0,[7,8]]]],53]],[[[5,[[0,[7,8,3]]]],4],[[5,[[0,[7,8,3]]]]]],[[[5,[[0,[7,8]]]],6],[[5,[[0,[7,8]]]]]],[68,[[5,[[0,[8,7]]]]]],[68,[[5,[[0,[8,7]]]]]],0,[[[5,[[0,[7,8]]]],[0,[7,8]]]],[[[5,[[0,[7,8]]]],[5,[[0,[7,8]]]]]],[[[5,[[0,[7,8]]]],[0,[7,8]]]],[[[5,[[0,[7,8]]]],[5,[[0,[7,8]]]]]],[[[5,[[0,[7,8]]]],[0,[7,8]]]],[[[5,[[0,[7,8]]]],[5,[[0,[7,8]]]]]],[[[5,[[0,[7,8]]]],[5,[[0,[7,8]]]]]],[[[5,[[0,[7,8]]]],[0,[7,8]]]],[[[5,[[0,[7,9]]]],[0,[7,9]]]],[[[5,[[0,[7,9]]]],[0,[7,9]]]],[[[5,[[0,[7,9]]]],[5,[[0,[7,9]]]]]],[[[5,[[0,[7,9]]]],[5,[[0,[7,9]]]]]],[[]],[[[5,[4]],[5,[4]]],[[5,[4]]]],[[4,4,6],4],[[]],[[[5,[4]],[5,[4]]],[[5,[4]]]],[[[5,[[0,[7,8]]]],[0,[7,8]]],[[5,[[0,[7,8]]]]]],[[[5,[69]],70],23],[[18,70],23],[[[20,[69]],70],23],[4],[[13,4]],[[[14,[55]],4]],[[[15,[55,55]],4]],[[17,4]],[[[5,[[0,[7,8]]]]]],[[[5,[[0,[7,8]]]]]],[[[5,[[0,[7,8]]]],[0,[7,8]]]],[[[5,[[0,[7,8]]]],[5,[[0,[7,8]]]]]],[[[5,[[0,[7,8]]]],[5,[[0,[7,8]]]]]],[[[5,[[0,[7,8]]]],[0,[7,8]]]],[[[5,[[0,[7,8]]]],[0,[7,8]]]],[[[5,[[0,[7,8]]]],[5,[[0,[7,8]]]]]],[[[5,[[0,[7,8]]]],[0,[7,8]]]],[[[5,[[0,[7,8]]]],[5,[[0,[7,8]]]]]],[[[5,[[0,[7,9]]]],[5,[[0,[7,9]]]]]],[[[5,[[0,[7,9]]]],[0,[7,9]]]],[[[5,[[0,[7,9]]]],[0,[7,9]]]],[[[5,[[0,[7,9]]]],[5,[[0,[7,9]]]]]],[68,[[5,[[0,[8,7]]]]]],[68,[[5,[[0,[8,7]]]]]],[[[5,[[0,[37,8]]]]],[[39,[40]]]],[[[5,[[0,[37,8]]]]],[[39,[42]]]],[[[5,[[0,[37,8]]]]],[[39,[43]]]],[[[5,[[0,[37,8]]]]],[[39,[44]]]],[[[5,[[0,[37,8]]]]],[[39,[4]]]],[[[5,[[0,[37,8]]]]],[[39,[45]]]],[[[5,[[0,[37,8]]]]],[[39,[46]]]],[[[5,[[0,[37,8]]]]],[[39,[47]]]],[[[5,[[0,[37,8]]]]],[[39,[50]]]],[[[5,[[0,[37,8]]]]],[[39,[51]]]],[[[5,[[0,[37,8]]]]],[[39,[6]]]],[[[5,[[0,[37,8]]]]],[[39,[52]]]],[[[5,[[0,[37,8]]]]],[[39,[53]]]],[[[5,[[0,[37,8]]]]],[[39,[54]]]],[[],23],[[],23],[[],23],[[],23],[[],23],[[],23],[[],23],[[],23],[[],23],[[],23],[[],23],[[],23],[[],23],[[],23],[[],23],[[],23],[[],23],[[],23],[[],23],[[],23],[[],71],[[],71],[[],71],[[],71],[[],71],[[],71],[[],71],[[],71],[[],71],[[],71],[[[5,[[0,[7,8]]]],[0,[7,8]]],[[5,[[0,[7,8]]]]]],[4,4],[[13,4],4],[[[14,[55]],4],4],[[[15,[55,55]],4],4],[[[16,[55]],4,4],[[5,[4]]]],[[17,4],4],[[18,[39,[4]],4]],[[19,[39,[4]],6,6]],[[[20,[[0,[66,67,1,10]]]],[0,[66,67,1,10]]]],[[[16,[55]],4,[5,[4]]],[[5,[4]]]],[[[20,[[0,[66,67,1,10]]]]],4],[[],[[5,[[0,[7,8]]]]]],0,0,0,0,0,0,0,0,0,0,0,[[]],[72],[73],[74],[75],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[76,[[62,[40]]]],[73,[[62,[40]]]],[76,76],[72,72],[73,73],[74,74],[75,75],[[],74],[[],75],[74,54],[75,54],[[76,27],28],[[72,27],28],[[73,27],28],[[74,27],28],[[75,27],28],[[]],[[]],[[]],[[]],[[]],[76,68],[[]],[[]],[[]],[[]],[[]],[[76,54]],[[[77,[40]]],76],[[[77,[40]]],72],[[[77,[40]]],73],[[[39,[62]],62],62],[[72,[39,[62]],62],62],[[73,[39,[62]],62],62],[[74,[39,[62]],62],62],[[75,[39,[62]],62],62],[[],54],[72,54],[73,54],[74,54],[75,54],[[74,54]],[[75,54]],0,[[],23],[[],23],[[],23],[[],23],[[],23],[[],23],[[],23],[[],23],[[],23],[[],23],[[],71],[[],71],[[],71],[[],71],[[],71],0,0,0,[[]],[[]],[[[78,[7]]],[[78,[7]]]],[[],[[78,[2]]]],[21,[[23,[[78,[22]]]]]],[[[78,[26]],27],28],[[]],[[[78,[[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,80]]]]]]]]]]]]]]]]],[[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,80]]]]]]]]]]]]]]]]]]]],[[[78,[[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,80]]]]]]]]]]]]]]]]],[[23,[[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,80]]]]]]]]]]]]]],48]]]],[[]],[[[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,80]]]]]]]]]]]]]]]]]],[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,80]]]]]]]]]]]]]]]]]],[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,80]]]]]]]]]]]]]]]]]]],[[78,[[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,80]]]]]]]]]]]]]]]]]],[[[78,[69]],70],23],[[[78,[[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,80]]]]]]]]]]]]]]]],[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,80]]]]]]]]]]]]]]]]]],[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,80]]]]]]]]]]]]]]]]]],[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,80]]]]]]]]]]]]]]]]]]],[[23,[48]]]],[[[78,[[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,80]]]]]]]]]]]]]]]],[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,80]]]]]]]]]]]]]]]]]]]],[[],23],[[],23],[[],71],[[[78,[[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,80]]]]]]]]]]]]]]]],[81,[[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,80]]]]]]]]]]]]]]]],[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,80]]]]]]]]]]]]]]]]]],25],[[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,[80,[[0,[79,2,80]]]]]]]]]]]]]]]]]]]],0,0,0,0,0,0,0,[[]],[[]],[82,82],[[],82],[21,[[23,[82]]]],[[82,27],28],[[]],[[]],[[82,70],23],[[],23],[[],23],[[],71],[[82,83,4],4],0,0,0],"c":[],"p":[[8,"PartialOrd"],[8,"Default"],[8,"Neg"],[15,"i32"],[3,"Complex"],[15,"u32"],[8,"Clone"],[8,"Num"],[8,"NumAssign"],[8,"Copy"],[8,"AsPrimitive"],[3,"Accu"],[3,"Nyquist"],[3,"Chain"],[3,"Cascade"],[3,"Lockin"],[3,"Lowpass"],[3,"PLL"],[3,"RPLL"],[3,"Unwrapper"],[8,"Deserializer"],[8,"Deserialize"],[4,"Result"],[8,"PartialEq"],[15,"bool"],[8,"Debug"],[3,"Formatter"],[6,"Result"],[8,"UpperExp"],[3,"Error"],[8,"Binary"],[8,"LowerHex"],[8,"Display"],[8,"LowerExp"],[8,"Octal"],[8,"UpperHex"],[8,"ToPrimitive"],[8,"NumCast"],[4,"Option"],[15,"f32"],[8,"FromPrimitive"],[15,"f64"],[15,"i128"],[15,"i16"],[15,"i64"],[15,"i8"],[15,"isize"],[15,"str"],[8,"FromStr"],[15,"u128"],[15,"u16"],[15,"u64"],[15,"u8"],[15,"usize"],[8,"Filter"],[8,"Hash"],[8,"Hasher"],[8,"FloatCore"],[8,"Signed"],[8,"Add"],[8,"Mul"],[15,"slice"],[8,"MulAdd"],[8,"MulAddAssign"],[8,"WrappingAdd"],[8,"WrappingSub"],[8,"Zero"],[8,"Iterator"],[8,"Serialize"],[8,"Serializer"],[3,"TypeId"],[3,"HbfDec"],[3,"HbfInt"],[3,"HbfDecCascade"],[3,"HbfIntCascade"],[3,"SymFir"],[15,"array"],[3,"IIR"],[8,"Float"],[8,"Sum"],[6,"Vec5"],[3,"IIR"],[6,"Vec5"],[8,"ComplexExt"],[8,"MulScaled"],[8,"Filter"]]},\
+"lockin":{"doc":"Lockin","t":"RRENNNENNNNNNRRDSSMALLLLLLLLLLLLLLLLLLLLLLLLLMMMMLLMMLLLLLMMLLLLLLLLLLFFFDGDFCDDDDFFFDDDDDDDDDFFFDDFFFDDDFFFDDFFFMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMMMMMMMAFAFLLLLLLLLLLLLLLLLLLLLLLMMAFAFLLLLLLLLLLLLLLLLLLLLLLMMMMMMAMMMMMMMAFMMMMMMAFMMMMMMAMMAFAFMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLAFMMMDDDCMMFCCDDMMMDDMMMACFDDDMMMMMMMMMMMDDDCMMMMMFCCDDCMMFCCDDDCMMMMMMFCCMDDCMFCCM","n":["BATCH_SIZE","BATCH_SIZE_LOG2","Conf","External","InPhase","Internal","LockinMode","LogPower","Magnitude","Modulation","Phase","Quadrature","ReferenceFrequency","SAMPLE_TICKS","SAMPLE_TICKS_LOG2","Settings","__MINICONF_DEFERS","__MINICONF_NAMES","afe","app","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","default","deserialize","deserialize","deserialize_by_key","eq","fmt","fmt","fmt","from","from","from","get_json","get_json_by_index","into","into","into","lockin_harmonic","lockin_k","lockin_mode","lockin_phase","metadata","name_to_index","output_conf","pll_tc","serialize","serialize","serialize_by_key","set_json","set_json_by_index","stream_target","telemetry_period","traverse_by_key","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","DCMI","DMA1_STR4","ETH","Local","Monotonic","Shared","SysTick","_","__rtic_internal_Monotonics","__rtic_internal_eth_Context","__rtic_internal_ethernet_linkSharedResources","__rtic_internal_ethernet_link_Context","__rtic_internal_ethernet_link_Monotonic_spawn_after","__rtic_internal_ethernet_link_Monotonic_spawn_at","__rtic_internal_ethernet_link_spawn","__rtic_internal_idleSharedResources","__rtic_internal_idle_Context","__rtic_internal_init_Context","__rtic_internal_processLocalResources","__rtic_internal_processSharedResources","__rtic_internal_process_Context","__rtic_internal_settings_updateLocalResources","__rtic_internal_settings_updateSharedResources","__rtic_internal_settings_update_Context","__rtic_internal_settings_update_Monotonic_spawn_after","__rtic_internal_settings_update_Monotonic_spawn_at","__rtic_internal_settings_update_spawn","__rtic_internal_startLocalResources","__rtic_internal_start_Context","__rtic_internal_start_Monotonic_spawn_after","__rtic_internal_start_Monotonic_spawn_at","__rtic_internal_start_spawn","__rtic_internal_telemetryLocalResources","__rtic_internal_telemetrySharedResources","__rtic_internal_telemetry_Context","__rtic_internal_telemetry_Monotonic_spawn_after","__rtic_internal_telemetry_Monotonic_spawn_at","__rtic_internal_telemetry_spawn","__rtic_internal_usbSharedResources","__rtic_internal_usb_Context","__rtic_internal_usb_Monotonic_spawn_after","__rtic_internal_usb_Monotonic_spawn_at","__rtic_internal_usb_spawn","adcs","adcs","afes","afes","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","core","cpu_temp_sensor","cpu_temp_sensor","cs","dacs","dacs","device","digital_inputs","digital_inputs","eth","eth","ethernet_link","ethernet_link","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","generator","generator","idle","idle","init","init","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","local","local","local","local","lockin","lockin","monotonics","network","network","network","network","network","pll","pll","process","process","sampling_timer","sampling_timer","settings","settings","settings","settings","settings_update","settings_update","shared","shared","shared","shared","shared","shared","shared_resources","signal_generator","signal_generator","start","start","telemetry","telemetry","telemetry","telemetry","telemetry","timestamper","timestamper","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","usb","usb","usb_terminal","usb_terminal","usb_terminal","Context","Context","SharedResources","SpawnHandle","network","shared","spawn","spawn_after","spawn_at","Context","SharedResources","network","shared","usb_terminal","Context","Monotonics","core","cs","device","Monotonic","now","now","Context","LocalResources","SharedResources","adcs","dacs","generator","local","lockin","pll","settings","shared","signal_generator","telemetry","timestamper","Context","LocalResources","SharedResources","SpawnHandle","afes","local","network","settings","shared","spawn","spawn_after","spawn_at","Context","LocalResources","SpawnHandle","local","sampling_timer","spawn","spawn_after","spawn_at","Context","LocalResources","SharedResources","SpawnHandle","cpu_temp_sensor","digital_inputs","local","network","settings","shared","spawn","spawn_after","spawn_at","telemetry","Context","SharedResources","SpawnHandle","shared","spawn","spawn_after","spawn_at","usb_terminal"],"q":[[0,"lockin"],[70,"lockin::app"],[337,"lockin::app::eth"],[338,"lockin::app::ethernet_link"],[346,"lockin::app::idle"],[351,"lockin::app::init"],[356,"lockin::app::monotonics"],[358,"lockin::app::monotonics::Monotonic"],[359,"lockin::app::process"],[373,"lockin::app::settings_update"],[385,"lockin::app::start"],[393,"lockin::app::telemetry"],[407,"lockin::app::usb"]],"d":["","","","Utilize an external modulation signal supplied to DI0","Output the in-phase component of the lockin signal.","Utilize an internally generated reference for demodulation","","Output the logarithmic power of the lockin","Output the lockin magnitude.","Output the lockin internal modulation frequency as a …","Output the phase of the lockin","Output the quadrature component of the lockin signal.","Output the lockin reference frequency as a sinusoid","","","","","","Configure the Analog Front End (AFE) gain.","The RTIC application module","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Specifies which harmonic to use for the lockin.","Specifies the lockin lowpass gains.","Specifies the operational mode of the lockin.","Specifies the LO phase offset.","","","Specifies DAC output mode.","Specifis the PLL time constant.","","","","","","Specifies the target for data livestreaming.","Specifies the telemetry output period in seconds.","","","","","","","","","","","Interrupt handler to dispatch tasks at priority 1","User HW task ISR trampoline for process","User HW task ISR trampoline for eth","RTIC local resource struct","User code from within the module","RTIC shared resource struct","","","Monotonics used by the system","Execution context","Shared resources ethernet_link
has access to","Execution context","Spawns the task after a set duration relative to the …","Spawns the task at a fixed time instant","Spawns the task directly","Shared resources idle
has access to","Execution context","Execution context","Local resources process
has access to","Shared resources process
has access to","Execution context","Local resources settings_update
has access to","Shared resources settings_update
has access to","Execution context","Spawns the task after a set duration relative to the …","Spawns the task at a fixed time instant","Spawns the task directly","Local resources start
has access to","Execution context","Spawns the task after a set duration relative to the …","Spawns the task at a fixed time instant","Spawns the task directly","Local resources telemetry
has access to","Shared resources telemetry
has access to","Execution context","Spawns the task after a set duration relative to the …","Spawns the task at a fixed time instant","Spawns the task directly","Shared resources usb
has access to","Execution context","Spawns the task after a set duration relative to the …","Spawns the task at a fixed time instant","Spawns the task directly","","Local resource adcs
","","Local resource afes
","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Core (Cortex-M) peripherals","","Local resource cpu_temp_sensor
","Critical section token for init","","Local resource dacs
","Device peripherals","","Local resource digital_inputs
","Hardware task","User HW task: eth","Software task","User SW task ethernet_link","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Local resource generator
","Idle loop","User provided idle function","Initialization function","User code end User provided init function","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Local Resources this task has access to","Local Resources this task has access to","Local Resources this task has access to","Local Resources this task has access to","","Local resource lockin
","Holds static methods for each monotonic.","","Resource proxy resource network
. Use method .lock()
to …","Resource proxy resource network
. Use method .lock()
to …","Resource proxy resource network
. Use method .lock()
to …","Resource proxy resource network
. Use method .lock()
to …","","Local resource pll
","Hardware task","User HW task: process","","Local resource sampling_timer
","","Resource proxy resource settings
. Use method .lock()
to …","Resource proxy resource settings
. Use method .lock()
to …","Resource proxy resource settings
. Use method .lock()
to …","Software task","User SW task settings_update","Shared Resources this task has access to","Shared Resources this task has access to","Shared Resources this task has access to","Shared Resources this task has access to","Shared Resources this task has access to","Shared Resources this task has access to","","","Local resource signal_generator
","Software task","User SW task start","Software task","User SW task telemetry","","Resource proxy resource telemetry
. Use method .lock()
to …","Resource proxy resource telemetry
. Use method .lock()
to …","","Local resource timestamper
","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Software task","User SW task usb","","Resource proxy resource usb_terminal
. Use method .lock()
…","Resource proxy resource usb_terminal
. Use method .lock()
…","Execution context","Execution context","Shared resources ethernet_link
has access to","","Resource proxy resource network
. Use method .lock()
to …","Shared Resources this task has access to","Spawns the task directly","","","Execution context","Shared resources idle
has access to","Resource proxy resource network
. Use method .lock()
to …","Shared Resources this task has access to","Resource proxy resource usb_terminal
. Use method .lock()
…","Execution context","Monotonics used by the system","Core (Cortex-M) peripherals","Critical section token for init","Device peripherals","This module holds the static implementation for …","","Read the current time from this monotonic","Execution context","Local resources process
has access to","Shared resources process
has access to","Local resource adcs
","Local resource dacs
","Local resource generator
","Local Resources this task has access to","Local resource lockin
","Local resource pll
","Resource proxy resource settings
. Use method .lock()
to …","Shared Resources this task has access to","Local resource signal_generator
","Resource proxy resource telemetry
. Use method .lock()
to …","Local resource timestamper
","Execution context","Local resources settings_update
has access to","Shared resources settings_update
has access to","","Local resource afes
","Local Resources this task has access to","Resource proxy resource network
. Use method .lock()
to …","Resource proxy resource settings
. Use method .lock()
to …","Shared Resources this task has access to","Spawns the task directly","","","Execution context","Local resources start
has access to","","Local Resources this task has access to","Local resource sampling_timer
","Spawns the task directly","","","Execution context","Local resources telemetry
has access to","Shared resources telemetry
has access to","","Local resource cpu_temp_sensor
","Local resource digital_inputs
","Local Resources this task has access to","Resource proxy resource network
. Use method .lock()
to …","Resource proxy resource settings
. Use method .lock()
to …","Shared Resources this task has access to","Spawns the task directly","","","Resource proxy resource telemetry
. Use method .lock()
to …","Execution context","Shared resources usb
has access to","","Shared Resources this task has access to","Spawns the task directly","","","Resource proxy resource usb_terminal
. Use method .lock()
…"],"i":[0,0,0,2,1,2,0,1,1,1,1,1,1,0,0,0,3,3,3,0,1,2,3,1,2,3,1,2,3,3,1,2,3,2,1,2,3,1,2,3,3,3,1,2,3,3,3,3,3,3,3,3,3,1,2,3,3,3,3,3,3,1,2,3,1,2,3,1,2,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,33,32,34,35,32,36,26,37,24,33,38,27,22,39,29,34,40,28,41,42,30,43,31,44,23,35,32,36,26,37,24,33,38,27,22,39,29,34,40,28,41,42,30,43,31,44,23,26,32,41,26,32,33,26,32,41,0,0,0,0,35,32,36,26,37,24,33,38,27,22,39,29,34,40,28,41,42,30,43,31,44,23,32,33,0,0,0,0,35,32,36,26,37,24,33,38,27,22,39,29,34,40,28,41,42,30,43,31,44,23,27,29,28,30,32,33,0,35,37,40,42,44,32,33,0,0,32,39,35,38,40,42,0,0,24,27,28,30,31,23,0,32,33,0,0,0,0,35,38,42,32,33,35,32,36,26,37,24,33,38,27,22,39,29,34,40,28,41,42,30,43,31,44,23,35,32,36,26,37,24,33,38,27,22,39,29,34,40,28,41,42,30,43,31,44,23,35,32,36,26,37,24,33,38,27,22,39,29,34,40,28,41,42,30,43,31,44,23,0,0,35,37,43,0,0,0,0,44,23,0,0,0,0,0,37,24,37,0,0,26,26,26,0,0,0,0,0,0,33,33,33,27,33,33,38,27,33,38,33,0,0,0,0,34,28,40,40,28,0,0,0,0,0,0,29,39,0,0,0,0,0,0,0,41,41,30,42,42,30,0,0,0,42,0,0,0,31,0,0,0,43],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[1,1],[2,2],[3,3],[[],3],[4,[[5,[1]]]],[4,[[5,[2]]]],[[3,6,4],[[5,[7,8]]]],[[2,2],9],[[1,10],11],[[2,10],11],[[3,10],11],[[]],[[]],[[]],[[12,[14,[13]]],[[5,[7,[8,[15]]]]]],[[[14,[7]],[14,[13]]],[[5,[7,[8,[15]]]]]],[[]],[[]],[[]],0,0,0,0,[[],16],[12,[[17,[7]]]],0,0,[[1,18],5],[[2,18],5],[[3,6,18],[[5,[7,8]]]],[[12,[14,[13]]],[[5,[7,[8,[19]]]]]],[[[14,[7]],[14,[13]]],[[5,[7,[8,[19]]]]]],0,0,[[6,20],[[5,[7,8]]]],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],21],[[],21],[[],21],[[]],[[]],[[]],0,0,0,[[]],0,0,0,0,0,[[],[[5,[0]]]],[[],[[5,[0]]]],[[],5],0,0,0,0,0,0,0,0,0,[[],[[5,[0]]]],[[],[[5,[0]]]],[[],5],0,0,[[],[[5,[0]]]],[[],[[5,[0]]]],[[],5],0,0,0,[[],[[5,[0]]]],[[],[[5,[0]]]],[[],5],0,0,[[],[[5,[0]]]],[[],[[5,[0]]]],[[],5],0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,0,0,0,0,0,0,[22],0,[23],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,[24,25],0,[26],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[27],0,0,0,0,0,0,0,[28],0,0,0,0,0,0,0,0,0,0,[29],0,[30],0,0,0,0,0,[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],0,[31],0,0,0,0,0,0,0,0,0,[[],5],0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],5],0,0,0,0,0,0,0,[[],5],0,0,0,0,0,0,0,0,0,0,0,0,[[],5],0,0,0,0,0,0,0,[[],5],0,0,0],"c":[],"p":[[4,"Conf"],[4,"LockinMode"],[3,"Settings"],[8,"Deserializer"],[4,"Result"],[8,"Iterator"],[15,"usize"],[4,"Error"],[15,"bool"],[3,"Formatter"],[6,"Result"],[15,"str"],[15,"u8"],[15,"slice"],[4,"Error"],[3,"Metadata"],[4,"Option"],[8,"Serializer"],[4,"Error"],[8,"FnMut"],[3,"TypeId"],[3,"__rtic_internal_eth_Context"],[3,"__rtic_internal_ethernet_link_Context"],[3,"__rtic_internal_idle_Context"],[15,"never"],[3,"__rtic_internal_init_Context"],[3,"__rtic_internal_process_Context"],[3,"__rtic_internal_settings_update_Context"],[3,"__rtic_internal_start_Context"],[3,"__rtic_internal_telemetry_Context"],[3,"__rtic_internal_usb_Context"],[3,"Local"],[3,"__rtic_internal_processLocalResources"],[3,"__rtic_internal_settings_updateLocalResources"],[3,"Shared"],[3,"__rtic_internal_Monotonics"],[3,"__rtic_internal_idleSharedResources"],[3,"__rtic_internal_processSharedResources"],[3,"__rtic_internal_startLocalResources"],[3,"__rtic_internal_settings_updateSharedResources"],[3,"__rtic_internal_telemetryLocalResources"],[3,"__rtic_internal_telemetrySharedResources"],[3,"__rtic_internal_usbSharedResources"],[3,"__rtic_internal_ethernet_linkSharedResources"]]},\
+"miniconf":{"doc":"Miniconf","t":"NEINIIDDNDNDNNYIYIYIYLLLLLLLLLLLLLLMLKLLLLKLLLLLLLLLLLLLKKLKLLLLLLLLLLLLMMKCKLLLLLKKKLLKLLLLLLLLLLLLLLLL","n":["Absent","Error","Increment","Inner","JsonCoreSlash","Key","Metadata","MqttClient","NotFound","PathIter","PostDeserialization","SliceShort","TooLong","TooShort","Tree","TreeDeserialize","TreeDeserialize","TreeKey","TreeKey","TreeSerialize","TreeSerialize","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","count","default","deserialize_by_key","eq","eq","eq","eq","find","fmt","fmt","fmt","fmt","fmt","force_republish","from","from","from","from","from","from","from","get_json","get_json_by_index","handled_update","increment","indices","indices","into","into","into","into","into","into_iter","iter_paths","iter_paths","iter_paths_unchecked","iter_paths_unchecked","max_depth","max_length","metadata","minimq","name_to_index","new","next","path","path","separator","serialize_by_key","set_json","set_json_by_index","settings","size_hint","traverse_by_key","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","update"],"q":[[0,"miniconf"]],"d":["The key is valid, but does not exist at runtime.","Errors that can occur when using the Tree traits.","Pass a Result
up one hierarchy level, incrementing its …","The value provided could not be serialized or deserialized …","Miniconf with “JSON and /
”.","Capability to convert a key into a node index for a given …","Metadata about a TreeKey namespace.","MQTT settings interface.","The key was not found (index unparsable or too large, name …","An iterator over the paths in a TreeKey
.","There was an error after deserializing a value.","Unit struct to indicate a short indices iterator in …","The key is too long and goes beyond a leaf node.","The key ends early and does not reach a leaf node.","Shorthand to derive the TreeKey
, TreeSerialize
, and …","Deserialize a leaf node by its keys.","Derive the TreeDeserialize
trait for a struct.","Traversal, iteration, and serialization/deserialization of …","Derive the TreeKey
trait for a struct.","Serialize a leaf node by its keys.","Derive the TreeSerialize
trait for a struct.","","","","","","","","","","","","","","","The total number of paths.","","Deserialize an node by keys.","","","","","Convert the key self
to a usize
index.","","","","","","Force republication of the current settings.","Returns the argument unchanged.","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Retrieve a serialized value by path.","Retrieve a serialized value by indices.","Update the MQTT interface and service the network. Pass …","Increment the depth
member by one.","Convert keys to indices
.","Convert keys to indices
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","","Create an iterator of all possible paths.","Create an iterator of all possible paths.","Create an unchecked iterator of all possible paths.","Create an unchecked iterator of all possible paths.","The maximum path depth.","The maximum length of a path in bytes.","Get metadata about the paths in the namespace.","","Convert a node name to a node index.","Construct a new MQTT settings interface.","","Convert keys to path.","Convert keys to path.","Add separator length to the maximum path length.","Serialize a node by keys.","Update an element by path.","Update an element by indices.","Get the current settings from miniconf.","","Call a function for each node on the path described by …","","","","","","","","","","","","","","","","Update the settings from the network stack without any …"],"i":[2,0,0,2,0,0,0,0,2,0,2,0,2,2,0,0,0,0,0,0,0,22,2,3,4,6,22,2,3,4,6,2,3,4,6,4,4,40,2,3,4,6,41,2,2,3,4,6,22,22,2,2,2,3,4,6,18,18,22,42,34,34,22,2,3,4,6,6,34,34,34,34,4,4,34,0,34,22,6,34,34,4,43,18,18,22,6,34,22,2,3,4,6,22,2,3,4,6,22,2,3,4,6,22],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[2,[1]]],[[2,[1]]]],[3,3],[4,4],[[[6,[[0,[1,5]],1]]],[[6,[[0,[1,5]],1]]]],0,[[],4],[[7,8],[[10,[9,2]]]],[[[2,[11]],[2,[11]]],12],[[3,3],12],[[4,4],12],[[[6,[[0,[11,5]],11]],[6,[[0,[11,5]],11]]],12],[[],[[13,[9]]]],[[[2,[14]],15],16],[[[2,[17]],15],16],[[3,15],16],[[4,15],16],[[[6,[[0,[17,5]],17]],15],16],[[[22,[[0,[18,1]],19,[0,[20,1]],21]]]],[[]],[[],2],[[]],[23],[[]],[[]],[[]],[[24,[26,[25]]],[[10,[9,[2,[27]]]]]],[[[26,[9]],[26,[25]]],[[10,[9,[2,[27]]]]]],[[[22,[[0,[18,1]],19,[0,[20,1]],21]],28],[[10,[12,29]]]],[[]],[[30,30],[[10,[9,[2,[3]]]]]],[[30,30],[[10,[9,[2,[3]]]]]],[[]],[[]],[[]],[[]],[[]],[[]],[24,[[6,[31]]]],[24,[[6,[31]]]],[24,[[6,[31]]]],[24,[[6,[31]]]],0,0,[[],4],0,[24,[[13,[9]]]],[[19,24,[0,[20,1]],[0,[18,1]],[32,[21]]],[[10,[[22,[[0,[18,1]],19,[0,[20,1]],21]],33]]]],[[[6,[[0,[34,5]],[0,[31,35]]]]],13],[[30,31,24],[[10,[9,[2,[36]]]]]],[[30,31,24],[[10,[9,[2,[36]]]]]],[[4,24],4],[[7,37],[[10,[9,2]]]],[[24,[26,[25]]],[[10,[9,[2,[38]]]]]],[[[26,[9]],[26,[25]]],[[10,[9,[2,[38]]]]]],[[[22,[[0,[18,1]],19,[0,[20,1]],21]]],[[0,[18,1]]]],[[[6,[[0,[34,5]],[0,[31,35]]]]]],[[7,28],[[10,[9,2]]]],[[],10],[[],10],[[],10],[[],10],[[],10],[[],10],[[],10],[[],10],[[],10],[[],10],[[],39],[[],39],[[],39],[[],39],[[],39],[[[22,[[0,[18,1]],19,[0,[20,1]],21]]],[[10,[12,29]]]]],"c":[],"p":[[8,"Clone"],[4,"Error"],[3,"SliceShort"],[3,"Metadata"],[8,"Sized"],[3,"PathIter"],[8,"Iterator"],[8,"Deserializer"],[15,"usize"],[4,"Result"],[8,"PartialEq"],[15,"bool"],[4,"Option"],[8,"Display"],[3,"Formatter"],[6,"Result"],[8,"Debug"],[8,"JsonCoreSlash"],[8,"TcpClientStack"],[8,"Clock"],[8,"Broker"],[3,"MqttClient"],[15,"never"],[15,"str"],[15,"u8"],[15,"slice"],[4,"Error"],[8,"FnMut"],[4,"Error"],[8,"IntoIterator"],[8,"Write"],[3,"ConfigBuilder"],[4,"ProtocolError"],[8,"TreeKey"],[8,"Default"],[3,"Error"],[8,"Serializer"],[4,"Error"],[3,"TypeId"],[8,"TreeDeserialize"],[8,"Key"],[8,"Increment"],[8,"TreeSerialize"]]},\
+"stabilizer":{"doc":"","t":"AAGGGGGGGGGGGRGGGGGAAAAAACCAAAAAAADDDLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNEDLLLLLLLLLLLLLLLLLLLLLLLLDLLLLLLLLLDDDSSSLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLDLLLLLLLLLLRRRRRRRRRRRGRRDLLLLLLLLLLNNNNNNNEDDDENENNNDNNNNNNNNNNNNDDNDNMMMMALLLLLLLLLLLLLLLLLLLLMLLLLLLLLLALLLLLMMLLLLLLLLLMLLLLLLLLLLLLALLLLLLLLLLLLMMMLLLMMMLMLMLALLLLLLLLLLALLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLILKKLKDDLLLLLLLLLLLLLLLLLLLEDNNLLLLLLLLLLLLLLLLLILKDLLLLLLLLLLLDDLLLLLLLLLLLLLLLLLLDDDDDDDMMMLLLLLLLLLLLLLLLLMMLMMMLLLLLLLLLLLLLLMMMMMMMMMFMMMMMMMMLLLLLLLLLLLLLLLLLLLLLMMDENDLLLLLLLLLLLLLLLLLLLLLLLLLLLDDNENNNEDNNNMMLLLLLLLLLLLLLLLLLLLLLLLLMLLLLLLLLLLLLLLLLLMMMLLLLMMMLLLLLLLLLLLLLLLLLLNNNNNNNNNNNNNEDENDDEDNNNNNEENLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLAAAALLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLEEEEDDDDDDDDDNNNNNNNNNNNNDLLLLLLLLLLLLLLLLLLLLLLLLMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLEEEEDDDDDDDDDNNNNNNNNNNNNDLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLEEEEDDDDDDDDDNNNNNNNNNNNNDLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLEEEEDDDDDDDDDNNNNNNNNNNNNDLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLDGEDNNNENNLLLLLLLLLALLLLLLFCLLLLCMALMCAMLLLLLLLLLLLLLNDNDEDNLLLLLLLLLLLLLLLLLLLLLLLLMMLLLFLLLLLLLLLLLLDLLLLLLMLLLLDDDMMLLLLLLLMMMLMMLLLLLLLLLLLLLLLLLLLL","n":["hardware","net","AFE0","AFE1","DigitalInput0","DigitalInput1","EemDigitalInput0","EemDigitalInput1","EemDigitalOutput0","EemDigitalOutput1","EthernetPhy","I2c1","I2c1Proxy","MONOTONIC_FREQUENCY","NetworkManager","NetworkStack","SystemTimer","Systick","UsbBus","adc","afe","cpu_temp_sensor","dac","delay","design_parameters","embedded_hal","hal","input_stamper","pounder","serial_terminal","setup","shared_adc","signal_generator","timers","Adc0Input","Adc1Input","AdcCode","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","from","from","from","from","from","into","into","into","lock","lock","new","new","start","start","try_from","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","with_buffer","with_buffer","G1","G10","G2","G5","Gain","ProgrammableGainAmplifier","as_multiplier","borrow","borrow","borrow_mut","borrow_mut","clone","deserialize","fmt","from","from","get_gain","into","into","new","serialize","set_gain","try_from","try_from","try_from","try_from_primitive","try_into","try_into","type_id","type_id","CpuTempSensor","borrow","borrow_mut","from","get_temperature","into","new","try_from","try_into","type_id","Dac0Output","Dac1Output","DacCode","FULL_SCALE","LSB_PER_VOLT","VOLT_PER_LSB","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","from","from","from","from","from","into","into","into","lock","lock","new","new","start","start","try_from","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","with_buffer","with_buffer","AsmDelay","borrow","borrow_mut","delay_ms","delay_us","from","into","new","try_from","try_into","type_id","ADC_DAC_SCK_MAX","ADC_SETUP_TIME","DDS_MULTIPLIER","DDS_REF_CLK","DDS_SYNC_CLK_DIV","DDS_SYSTEM_CLK","MAX_SAMPLE_BUFFER_SIZE","POUNDER_IO_UPDATE_DELAY","POUNDER_IO_UPDATE_DURATION","POUNDER_QSPI_FREQUENCY","SYSCLK","SampleBuffer","TIMER_FREQUENCY","TIMER_PERIOD","InputStamper","borrow","borrow_mut","from","into","latest_timestamp","new","start","try_from","try_into","type_id","Adc","AttLe0","AttLe1","AttLe2","AttLe3","AttRstN","Bounds","Channel","ChannelState","DdsChannelState","DdsClockConfig","Error","ExtClkSel","GpioPin","I2c","In0","In1","InputChannelState","InvalidAddress","InvalidChannel","InvalidState","Led4Green","Led5Red","Led6Green","Led7Red","Led8Green","Led9Red","OscEnN","Out0","Out1","OutputChannelState","PounderDevices","Qspi","QspiInterface","Spi","amplitude","attenuation","attenuation","attenuation","attenuators","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","channel","clone","clone","clone","clone","clone","clone","clone","clone","configure_mode","dds_output","deserialize","deserialize","deserialize","deserialize","deserialize","enabled","external_clock","first","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","frequency","from","from","from","from","from","from","from","from","from","from","from","from","hrtimer","into","into","into","into","into","into","into","into","into","into","last","latch_attenuator","lm75","mixer","multiplier","new","new","next","parameters","phase_offset","power","previous","qspi","read","reference_clock","reset_attenuators","rf_power","sample_aux_adc","sample_converter","serialize","serialize","serialize","serialize","serialize","set_ext_clk","set_gpio_pin","start_stream","timestamp","transfer_attenuators","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","write","AttenuatorInterface","get_attenuation","latch_attenuator","reset_attenuators","set_attenuation","transfer_attenuators","DdsOutput","ProfileBuilder","borrow","borrow","borrow_mut","borrow_mut","builder","from","from","into","into","new","try_from","try_from","try_into","try_into","type_id","type_id","update_channels","write","write","Channel","HighResTimerE","One","Two","borrow","borrow","borrow_mut","borrow_mut","configure_single_shot","from","from","into","into","new","trigger","try_from","try_from","try_into","try_into","type_id","type_id","PowerMeasurementInterface","measure_power","sample_converter","Timestamper","borrow","borrow_mut","from","into","latest_timestamp","new","start","try_from","try_into","type_id","update_period","OutputBuffer","SerialTerminal","borrow","borrow","borrow_mut","borrow_mut","from","from","into","into","new","process","try_from","try_from","try_into","try_into","type_id","type_id","usb_is_suspended","write_str","EemGpioDevices","NetStorage","NetworkDevices","PounderDevices","StabilizerDevices","TcpSocketStorage","UdpSocketStorage","adc_dac_timer","adcs","afes","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","dacs","dds_output","default","digital_inputs","dns_storage","eem_gpio","from","from","from","from","from","from","from","into","into","into","into","into","into","into","ip_addrs","lvds4","lvds5","lvds6","lvds7","mac_address","net","phy","pounder","setup","sockets","stack","systick","tcp_socket_storage","temperature_sensor","timestamp_timer","timestamper","timestamper","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","udp_socket_storage","usb_serial","AdcChannel","AdcError","InUse","SharedAdc","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","create_channel","fmt","from","from","from","into","into","into","new","read_normalized","read_raw","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","BasicConfig","Config","Cosine","Error","InvalidAmplitude","InvalidFrequency","InvalidSymmetry","Signal","SignalGenerator","Square","Triangle","WhiteNoise","amplitude","amplitude","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clear_phase_accumulator","clone","clone","clone","clone","default","default","deserialize","deserialize_by_key","fmt","fmt","fmt","fmt","fmt","frequency","from","from","from","from","from","get_json","get_json_by_index","into","into","into","into","into","into_iter","metadata","name_to_index","new","next","phase","phase_increment","phase_offset","serialize","serialize_by_key","set_json","set_json_by_index","signal","signal","symmetry","traverse_by_key","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into_config","type_id","type_id","type_id","type_id","type_id","update_waveform","Ch1Compare","Ch2Compare","Ch3Compare","Ch4Compare","ComparePulse","Disabled","Div1","Div1N1","Div1N8","Div2","Div4","Div8","Enable","InputFilter","PounderTimestampTimer","Prescaler","Reset","SamplingTimer","ShadowSamplingTimer","SlaveMode","TimestampTimer","Trigger","Trigger0","Trigger1","Trigger2","Trigger3","TriggerGenerator","TriggerSource","Update","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","channels","channels","channels","channels","from","from","from","from","from","from","from","from","from","generate_trigger","generate_trigger","generate_trigger","generate_trigger","get_period","get_period","get_period","get_period","into","into","into","into","into","into","into","into","into","new","new","new","new","set_external_clock","set_external_clock","set_external_clock","set_external_clock","set_period_ticks","set_period_ticks","set_period_ticks","set_period_ticks","set_slave_mode","set_slave_mode","set_slave_mode","set_slave_mode","set_trigger_source","set_trigger_source","set_trigger_source","set_trigger_source","start","start","start","start","tim2","tim3","tim5","tim8","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from_primitive","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","update_event","update_event","update_event","update_event","CaptureSource1","CaptureSource2","CaptureSource3","CaptureSource4","Channel1","Channel1InputCapture","Channel2","Channel2InputCapture","Channel3","Channel3InputCapture","Channel4","Channel4InputCapture","Channels","Ti1","Ti1","Ti2","Ti2","Ti3","Ti3","Ti4","Ti4","Trc","Trc","Trc","Trc","UpdateEvent","address","address","address","address","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","ch1","ch2","ch3","ch4","check_overcapture","check_overcapture","check_overcapture","check_overcapture","configure_filter","configure_filter","configure_filter","configure_filter","configure_prescaler","configure_prescaler","configure_prescaler","configure_prescaler","enable","enable","enable","enable","from","from","from","from","from","from","from","from","from","from","into","into","into","into","into","into","into","into","into","into","into_input_capture","into_input_capture","into_input_capture","into_input_capture","latest_capture","latest_capture","latest_capture","latest_capture","listen_dma","listen_dma","listen_dma","listen_dma","listen_dma","listen_dma","listen_dma","listen_dma","listen_dma","new","new","to_output_compare","to_output_compare","to_output_compare","to_output_compare","trigger","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","CaptureSource1","CaptureSource2","CaptureSource3","CaptureSource4","Channel1","Channel1InputCapture","Channel2","Channel2InputCapture","Channel3","Channel3InputCapture","Channel4","Channel4InputCapture","Channels","Ti1","Ti1","Ti2","Ti2","Ti3","Ti3","Ti4","Ti4","Trc","Trc","Trc","Trc","UpdateEvent","address","address","address","address","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","ch1","ch2","ch3","ch4","check_overcapture","check_overcapture","check_overcapture","check_overcapture","clone","clone","clone","clone","configure_filter","configure_filter","configure_filter","configure_filter","configure_prescaler","configure_prescaler","configure_prescaler","configure_prescaler","enable","enable","enable","enable","eq","eq","eq","eq","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into_input_capture","into_input_capture","into_input_capture","into_input_capture","latest_capture","latest_capture","latest_capture","latest_capture","listen_dma","listen_dma","listen_dma","listen_dma","listen_dma","listen_dma","listen_dma","listen_dma","listen_dma","new","new","to_output_compare","to_output_compare","to_output_compare","to_output_compare","trigger","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","CaptureSource1","CaptureSource2","CaptureSource3","CaptureSource4","Channel1","Channel1InputCapture","Channel2","Channel2InputCapture","Channel3","Channel3InputCapture","Channel4","Channel4InputCapture","Channels","Ti1","Ti1","Ti2","Ti2","Ti3","Ti3","Ti4","Ti4","Trc","Trc","Trc","Trc","UpdateEvent","address","address","address","address","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","ch1","ch2","ch3","ch4","check_overcapture","check_overcapture","check_overcapture","check_overcapture","clone","clone","clone","clone","configure_filter","configure_filter","configure_filter","configure_filter","configure_prescaler","configure_prescaler","configure_prescaler","configure_prescaler","enable","enable","enable","enable","eq","eq","eq","eq","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into_input_capture","into_input_capture","into_input_capture","into_input_capture","latest_capture","latest_capture","latest_capture","latest_capture","listen_dma","listen_dma","listen_dma","listen_dma","listen_dma","listen_dma","listen_dma","listen_dma","listen_dma","new","new","to_output_compare","to_output_compare","to_output_compare","to_output_compare","trigger","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","CaptureSource1","CaptureSource2","CaptureSource3","CaptureSource4","Channel1","Channel1InputCapture","Channel2","Channel2InputCapture","Channel3","Channel3InputCapture","Channel4","Channel4InputCapture","Channels","Ti1","Ti1","Ti2","Ti2","Ti3","Ti3","Ti4","Ti4","Trc","Trc","Trc","Trc","UpdateEvent","address","address","address","address","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","ch1","ch2","ch3","ch4","check_overcapture","check_overcapture","check_overcapture","check_overcapture","clone","clone","clone","clone","configure_filter","configure_filter","configure_filter","configure_filter","configure_prescaler","configure_prescaler","configure_prescaler","configure_prescaler","enable","enable","enable","enable","eq","eq","eq","eq","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into_input_capture","into_input_capture","into_input_capture","into_input_capture","latest_capture","latest_capture","latest_capture","latest_capture","listen_dma","listen_dma","listen_dma","listen_dma","listen_dma","listen_dma","listen_dma","listen_dma","listen_dma","new","new","to_output_compare","to_output_compare","to_output_compare","to_output_compare","trigger","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","MqttStorage","NetworkReference","NetworkState","NetworkUsers","NoChange","NoChange","SettingsChanged","UpdateState","Updated","Updated","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","configure_streaming","data_stream","default","direct_stream","from","from","from","from","get_device_prefix","heapless","into","into","into","into","miniconf","miniconf","network_processor","new","processor","serde","telemetry","telemetry","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","update","AdcDacData","DataStream","Fls","FrameGenerator","StreamFormat","StreamTarget","Unknown","add","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","default","deserialize","eq","fmt","fmt","from","from","from","from","into","into","into","into","ip","port","process","serialize","set_remote","setup_streaming","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","NetworkProcessor","borrow","borrow_mut","from","handle_link","into","new","stack","try_from","try_into","type_id","update","Telemetry","TelemetryBuffer","TelemetryClient","adcs","adcs","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","cpu_temp","dacs","dacs","default","digital_inputs","digital_inputs","finalize","from","from","from","into","into","into","new","publish","serialize","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","update"],"q":[[0,"stabilizer"],[2,"stabilizer::hardware"],[34,"stabilizer::hardware::adc"],[70,"stabilizer::hardware::afe"],[100,"stabilizer::hardware::cpu_temp_sensor"],[110,"stabilizer::hardware::dac"],[149,"stabilizer::hardware::delay"],[160,"stabilizer::hardware::design_parameters"],[174,"stabilizer::hardware::input_stamper"],[185,"stabilizer::hardware::pounder"],[356,"stabilizer::hardware::pounder::attenuators"],[362,"stabilizer::hardware::pounder::dds_output"],[383,"stabilizer::hardware::pounder::hrtimer"],[404,"stabilizer::hardware::pounder::rf_power"],[407,"stabilizer::hardware::pounder::timestamp"],[419,"stabilizer::hardware::serial_terminal"],[439,"stabilizer::hardware::setup"],[526,"stabilizer::hardware::shared_adc"],[557,"stabilizer::hardware::signal_generator"],[641,"stabilizer::hardware::timers"],[779,"stabilizer::hardware::timers::tim2"],[923,"stabilizer::hardware::timers::tim3"],[1107,"stabilizer::hardware::timers::tim5"],[1291,"stabilizer::hardware::timers::tim8"],[1475,"stabilizer::net"],[1528,"stabilizer::net::data_stream"],[1577,"stabilizer::net::network_processor"],[1589,"stabilizer::net::telemetry"]],"d":["Module for all hardware-specific setup of Stabilizer","Stabilizer network management module","","","","","","","","","","","","System timer (RTIC Monotonic) tick frequency","","","","","","Stabilizer ADC management interface","","STM32 Temperature Sensor Driver","Stabilizer DAC management interface","Basic blocking delay","","","","Digital Input 0 (DI0) reference clock timestamper","","","Stabilizer hardware configuration","","","The sampling timer is used for managing ADC sampling and …","Represents data associated with ADC.","Represents data associated with ADC.","A type representing an ADC sample.","","","","","","","","Construct an ADC code from a provided binary …","Returns the argument unchanged.","Construct an ADC code from the stabilizer-defined code …","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","","","Construct the ADC input channel.","Construct the ADC input channel.","Enable the ADC DMA transfer sequence.","Enable the ADC DMA transfer sequence.","","","","","","","","","","","Wait for the transfer of the currently active buffer to …","Wait for the transfer of the currently active buffer to …","","","","","","A programmable gain amplifier that allows for setting the …","Get the AFE gain as a numerical value.","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Get the programmed gain of the analog front-end.","Calls U::from(self)
.","Calls U::from(self)
.","Construct a new programmable gain driver.","","Set the gain of the front-end.","","","","","","","","","A driver to access the CPU temeprature sensor.","","","Returns the argument unchanged.","Get the temperature of the CPU in degrees Celsius.","Calls U::from(self)
.","Construct the temperature sensor.","","","","Represents data associated with DAC.","Represents data associated with DAC.","Custom type for referencing DAC output codes. The internal …","","","","","","","","","","","Encode signed 16-bit values into DAC offset binary for a …","Returns the argument unchanged.","Create a dac code from the provided DAC output code.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","","","Construct the DAC output channel.","Construct the DAC output channel.","","","","","","","","","","","","","Wait for the transfer of the currently active buffer to …","Wait for the transfer of the currently active buffer to …","A basic delay implementation.","","","","","Returns the argument unchanged.","Calls U::from(self)
.","Create a new delay.","","","","The maximum DAC/ADC serial clock line frequency. This is a …","The ADC setup time is the number of seconds after the CSn …","The multiplier used for the DDS reference clock PLL.","The DDS reference clock frequency in MHz.","The divider from the DDS system clock to the SYNC_CLK …","The DDS system clock frequency after the internal PLL …","The maximum ADC/DAC sample processing buffer size.","The delay after initiating a QSPI transfer before …","The duration to assert IO_Update for the pounder DDS.","The QSPI frequency for communicating with the pounder DDS.","The system clock, used in various timer calculations","","The optimal counting frequency of the hardware timers used …","","The timestamper for DI0 reference clock inputs.","","","Returns the argument unchanged.","Calls U::from(self)
.","Get the latest timestamp that has occurred.","Construct the DI0 input timestamper.","Start to capture timestamps on DI0.","","","","","","","","","","","The numerical value (discriminant) of the Channel enum is …","","","","","","","","","","","","","","","","","","","","","","","","A structure containing implementation for Pounder hardware.","","A structure for the QSPI interface for the DDS.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Configure the operations mode of the interface.","The DdsOutput is used as an output stream to the pounder …","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","The HRTimer (High Resolution Timer) is used to generate …","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","","Latch a configuration into a digital attenuator.","","","","Initialize the QSPI interface.","Construct and initialize pounder-specific hardware.","","","","","","","","","Reset all of the attenuators to a power-on default state.","","Sample one of the two auxiliary ADC channels associated …","Sample an ADC channel.","","","","","","Select external reference clock input.","Set the state (its electrical level) of the given GPIO pin …","","ADC sample timestamper using external Pounder reference …","Read the raw attenuation codes stored in the attenuator …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Write data over QSPI to the DDS.","Provide an interface for managing digital attenuators on …","Get the attenuation of a channel.","","","Set the attenuation of a single channel.","","The DDS profile update stream.","A temporary builder for serializing and writing profiles.","","","","","Get a builder for serializing a Pounder DDS profile.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self)
.","Calls U::from(self)
.","Construct a new DDS output stream.","","","","","","","Update a number of channels with the provided configuration","Write a profile to the stream.","Write the profile to the DDS asynchronously.","A HRTimer output channel.","The high resolution timer. Currently, only Timer E is …","","","","","","","Configure the timer to operate in single-shot mode.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self)
.","Calls U::from(self)
.","Construct a new high resolution timer for generating …","Generate a single trigger of the timer to start the output …","","","","","","","Provide an interface to measure RF input power in dBm.","Measure the power of an input channel in dBm.","","Software unit to timestamp stabilizer ADC samples using an …","","","Returns the argument unchanged.","Calls U::from(self)
.","Obtain a timestamp.","Construct the pounder sample timestamper.","Start collecting timestamps.","","","","Update the period of the underlying timestamp timer.","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self)
.","Calls U::from(self)
.","","","","","","","","","","","The GPIO pins available on the EEM connector, if Pounder …","","The available networking devices on Stabilizer.","The available Pounder-specific hardware interfaces.","The available hardware interfaces on Stabilizer.","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","","","","","","","","","","Configure the stabilizer hardware for operation.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","A single channel on an ADC peripheral.","","Indicates that the ADC is already in use","An ADC peripheral that can provide ownership of individual …","","","","","","","","Allocate an ADC channel for usage.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Construct a new shared ADC driver.","Read the ADC channel and normalize the result.","Read the raw ADC sample for the channel.","","","","","","","","","","Basic configuration for a generated signal.","","","Represents the errors that can occur when attempting to …","The provided amplitude is out-of-range.","The provided frequency is out of range.","The provided symmetry is out of range.","Types of signals that can be generated.","","","","","The amplitude of the output signal in volts.","The full-scale output code of the signal","","","","","","","","","","","Clear the phase accumulator.","","","","","","","","","","","","","","The frequency of the generated signal in Hertz.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","","","","Construct a new signal generator with some specific config.","Get the next value in the generator sequence.","The phase of the output signal in turns.","The frequency tuning word of the signal. Phase is …","The phase offset","","","","","The signal type that should be generated. See Signal …","The type of signal being generated","The normalized symmetry of the signal. At 0% symmetry, the …","","","","","","","","","","","","Convert configuration into signal generator values.","","","","","","Update waveform generation settings.","","","","","","","","","","","","","","Optional input capture preconditioning filter …","The timer used for managing ADC sampling.","Prescalers for externally-supplied reference clocks.","","The timer used for managing ADC sampling.","The timer used for managing ADC sampling.","Optional slave operation modes of a timer.","The timer used for managing ADC sampling.","","","","","","The event that should generate an external trigger from …","Selects the trigger source for the timer peripheral.","","","","","","","","","","","","","","","","","","","","Get the timer capture/compare channels.","Get the timer capture/compare channels.","Get the timer capture/compare channels.","Get the timer capture/compare channels.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Configure the timer peripheral to generate a trigger based …","Configure the timer peripheral to generate a trigger based …","Configure the timer peripheral to generate a trigger based …","Configure the timer peripheral to generate a trigger based …","Get the period of the timer.","Get the period of the timer.","Get the period of the timer.","Get the period of the timer.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Construct the sampling timer.","Construct the sampling timer.","Construct the sampling timer.","Construct the sampling timer.","Clock the timer from an external source.","Clock the timer from an external source.","Clock the timer from an external source.","Clock the timer from an external source.","Manually set the period of the timer.","Manually set the period of the timer.","Manually set the period of the timer.","Manually set the period of the timer.","","","","","Select a trigger source for the timer peripheral.","Select a trigger source for the timer peripheral.","Select a trigger source for the timer peripheral.","Select a trigger source for the timer peripheral.","Start the timer.","Start the timer.","Start the timer.","Start the timer.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Get the timer update event.","Get the timer update event.","Get the timer update event.","Get the timer update event.","Capture/Compare 1 selection","Capture/Compare 2 selection","Capture/compare 3 selection","Capture/Compare 4 selection","A capture/compare channel of the timer.","A capture channel of the timer.","A capture/compare channel of the timer.","A capture channel of the timer.","A capture/compare channel of the timer.","A capture channel of the timer.","A capture/compare channel of the timer.","A capture channel of the timer.","The channels representing the timer.","2: CC2 channel is configured as input, IC2 is mapped on TI1","1: CC1 channel is configured as input, IC1 is mapped on TI1","1: CC2 channel is configured as input, IC2 is mapped on TI2","2: CC1 channel is configured as input, IC1 is mapped on TI2","2: CC4 channel is configured as input, IC4 is mapped on TI3","1: CC3 channel is configured as input, IC3 is mapped on TI3","1: CC4 channel is configured as input, IC4 is mapped on TI4","2: CC3 channel is configured as input, IC3 is mapped on TI4","3: CC2 channel is configured as input, IC2 is mapped on TRC","3: CC1 channel is configured as input, IC1 is mapped on TRC","3: CC4 channel is configured as input, IC4 is mapped on TRC","3: CC3 channel is configured as input, IC3 is mapped on TRC","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Check if an over-capture event has occurred.","Check if an over-capture event has occurred.","Check if an over-capture event has occurred.","Check if an over-capture event has occurred.","Configure the input capture input pre-filter.","Configure the input capture input pre-filter.","Configure the input capture input pre-filter.","Configure the input capture input pre-filter.","Configure the input capture prescaler.","Configure the input capture prescaler.","Configure the input capture prescaler.","Configure the input capture prescaler.","Enable the input capture to begin capturing timer values.","Enable the input capture to begin capturing timer values.","Enable the input capture to begin capturing timer values.","Enable the input capture to begin capturing timer values.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Operate the channel in input-capture mode.","Operate the channel in input-capture mode.","Operate the channel in input-capture mode.","Operate the channel in input-capture mode.","Get the latest capture from the channel.","Get the latest capture from the channel.","Get the latest capture from the channel.","Get the latest capture from the channel.","Enable DMA requests upon timer updates.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Create a new update event","Construct a new set of channels.","Operate the channel as an output-compare.","Operate the channel as an output-compare.","Operate the channel as an output-compare.","Operate the channel as an output-compare.","Trigger a DMA request manually","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Capture/Compare 1 selection","Capture/Compare 2 selection","Capture/compare 3 selection","Capture/Compare 4 selection","A capture/compare channel of the timer.","A capture channel of the timer.","A capture/compare channel of the timer.","A capture channel of the timer.","A capture/compare channel of the timer.","A capture channel of the timer.","A capture/compare channel of the timer.","A capture channel of the timer.","The channels representing the timer.","2: CC2 channel is configured as input, IC2 is mapped on TI1","1: CC1 channel is configured as input, IC1 is mapped on TI1","1: CC2 channel is configured as input, IC2 is mapped on TI2","2: CC1 channel is configured as input, IC1 is mapped on TI2","2: CC4 channel is configured as input, IC4 is mapped on TI3","1: CC3 channel is configured as input, IC3 is mapped on TI3","1: CC4 channel is configured as input, IC4 is mapped on TI4","2: CC3 channel is configured as input, IC3 is mapped on TI4","3: CC2 channel is configured as input, IC2 is mapped on TRC","3: CC1 channel is configured as input, IC1 is mapped on TRC","3: CC4 channel is configured as input, IC4 is mapped on TRC","3: CC3 channel is configured as input, IC3 is mapped on TRC","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Check if an over-capture event has occurred.","Check if an over-capture event has occurred.","Check if an over-capture event has occurred.","Check if an over-capture event has occurred.","","","","","Configure the input capture input pre-filter.","Configure the input capture input pre-filter.","Configure the input capture input pre-filter.","Configure the input capture input pre-filter.","Configure the input capture prescaler.","Configure the input capture prescaler.","Configure the input capture prescaler.","Configure the input capture prescaler.","Enable the input capture to begin capturing timer values.","Enable the input capture to begin capturing timer values.","Enable the input capture to begin capturing timer values.","Enable the input capture to begin capturing timer values.","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Operate the channel in input-capture mode.","Operate the channel in input-capture mode.","Operate the channel in input-capture mode.","Operate the channel in input-capture mode.","Get the latest capture from the channel.","Get the latest capture from the channel.","Get the latest capture from the channel.","Get the latest capture from the channel.","Enable DMA requests upon timer updates.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Create a new update event","Construct a new set of channels.","Operate the channel as an output-compare.","Operate the channel as an output-compare.","Operate the channel as an output-compare.","Operate the channel as an output-compare.","Trigger a DMA request manually","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Capture/Compare 1 selection","Capture/Compare 2 selection","Capture/compare 3 selection","Capture/Compare 4 selection","A capture/compare channel of the timer.","A capture channel of the timer.","A capture/compare channel of the timer.","A capture channel of the timer.","A capture/compare channel of the timer.","A capture channel of the timer.","A capture/compare channel of the timer.","A capture channel of the timer.","The channels representing the timer.","2: CC2 channel is configured as input, IC2 is mapped on TI1","1: CC1 channel is configured as input, IC1 is mapped on TI1","1: CC2 channel is configured as input, IC2 is mapped on TI2","2: CC1 channel is configured as input, IC1 is mapped on TI2","2: CC4 channel is configured as input, IC4 is mapped on TI3","1: CC3 channel is configured as input, IC3 is mapped on TI3","1: CC4 channel is configured as input, IC4 is mapped on TI4","2: CC3 channel is configured as input, IC3 is mapped on TI4","3: CC2 channel is configured as input, IC2 is mapped on TRC","3: CC1 channel is configured as input, IC1 is mapped on TRC","3: CC4 channel is configured as input, IC4 is mapped on TRC","3: CC3 channel is configured as input, IC3 is mapped on TRC","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Check if an over-capture event has occurred.","Check if an over-capture event has occurred.","Check if an over-capture event has occurred.","Check if an over-capture event has occurred.","","","","","Configure the input capture input pre-filter.","Configure the input capture input pre-filter.","Configure the input capture input pre-filter.","Configure the input capture input pre-filter.","Configure the input capture prescaler.","Configure the input capture prescaler.","Configure the input capture prescaler.","Configure the input capture prescaler.","Enable the input capture to begin capturing timer values.","Enable the input capture to begin capturing timer values.","Enable the input capture to begin capturing timer values.","Enable the input capture to begin capturing timer values.","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Operate the channel in input-capture mode.","Operate the channel in input-capture mode.","Operate the channel in input-capture mode.","Operate the channel in input-capture mode.","Get the latest capture from the channel.","Get the latest capture from the channel.","Get the latest capture from the channel.","Get the latest capture from the channel.","Enable DMA requests upon timer updates.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Create a new update event","Construct a new set of channels.","Operate the channel as an output-compare.","Operate the channel as an output-compare.","Operate the channel as an output-compare.","Operate the channel as an output-compare.","Trigger a DMA request manually","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Capture/Compare 1 selection","Capture/Compare 2 selection","Capture/compare 3 selection","Capture/Compare 4 selection","A capture/compare channel of the timer.","A capture channel of the timer.","A capture/compare channel of the timer.","A capture channel of the timer.","A capture/compare channel of the timer.","A capture channel of the timer.","A capture/compare channel of the timer.","A capture channel of the timer.","The channels representing the timer.","2: CC2 channel is configured as input, IC2 is mapped on TI1","1: CC1 channel is configured as input, IC1 is mapped on TI1","1: CC2 channel is configured as input, IC2 is mapped on TI2","2: CC1 channel is configured as input, IC1 is mapped on TI2","2: CC4 channel is configured as input, IC4 is mapped on TI3","1: CC3 channel is configured as input, IC3 is mapped on TI3","1: CC4 channel is configured as input, IC4 is mapped on TI4","2: CC3 channel is configured as input, IC3 is mapped on TI4","3: CC2 channel is configured as input, IC2 is mapped on TRC","3: CC1 channel is configured as input, IC1 is mapped on TRC","3: CC4 channel is configured as input, IC4 is mapped on TRC","3: CC3 channel is configured as input, IC3 is mapped on TRC","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Check if an over-capture event has occurred.","Check if an over-capture event has occurred.","Check if an over-capture event has occurred.","Check if an over-capture event has occurred.","","","","","Configure the input capture input pre-filter.","Configure the input capture input pre-filter.","Configure the input capture input pre-filter.","Configure the input capture input pre-filter.","Configure the input capture prescaler.","Configure the input capture prescaler.","Configure the input capture prescaler.","Configure the input capture prescaler.","Enable the input capture to begin capturing timer values.","Enable the input capture to begin capturing timer values.","Enable the input capture to begin capturing timer values.","Enable the input capture to begin capturing timer values.","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Operate the channel in input-capture mode.","Operate the channel in input-capture mode.","Operate the channel in input-capture mode.","Operate the channel in input-capture mode.","Get the latest capture from the channel.","Get the latest capture from the channel.","Get the latest capture from the channel.","Get the latest capture from the channel.","Enable DMA requests upon timer updates.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Create a new update event","Construct a new set of channels.","Operate the channel as an output-compare.","Operate the channel as an output-compare.","Operate the channel as an output-compare.","Operate the channel as an output-compare.","Trigger a DMA request manually","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","A structure of Stabilizer’s default network users.","","","","","","","","","","","","","","","Enable live data streaming.","Stabilizer data stream capabilities","","Direct the stream to the provided remote target.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Get the MQTT prefix of a device.","","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","","","Task to process network hardware.","Construct Stabilizer’s default network users.","","","Stabilizer Telemetry Capabilities","","","","","","","","","","","","","","Update and process all of the network users state.","Streamed data contains ADC0, ADC1, DAC0, and DAC1 …","The “consumer” portion of the data stream.","Streamed data in FLS (fiber length stabilization) format. …","The data generator for a stream.","Specifies the format of streamed data","Represents the destination for the UDP stream to send data …","Reserved, unused format specifier.","Add a batch to the current stream frame.","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","","","Process any data for transmission.","","Configure the remote endpoint of the stream.","Configure streaming on a device.","","","","","","","","","","","","","Processor for managing network hardware.","","","Returns the argument unchanged.","Handle ethernet link connection status.","Calls U::from(self)
.","Construct a new network processor.","","","","","Process and update the state of the network.","The telemetry structure is data that is ultimately …","The telemetry buffer is used for storing sample values …","The telemetry client for reporting telemetry data over …","The latest input sample on ADC0/ADC1.","Most recent input voltage measurement.","","","","","","","","The CPU temperature in degrees Celsius.","The latest output code on DAC0/DAC1.","Most recent output voltage.","","The latest digital input states during processing.","Most recent digital input assertion state.","Convert the telemetry buffer to finalized, SI-unit …","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Construct a new telemetry client.","Publish telemetry over MQTT","","","","","","","","","","","Update the telemetry client"],"i":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,4,6,1,4,6,1,1,1,1,4,6,1,4,6,4,6,4,6,4,6,1,1,4,6,1,4,6,1,4,6,4,6,27,27,27,27,0,0,27,32,27,32,27,27,27,27,32,27,32,32,27,32,27,32,32,27,27,27,32,27,32,27,0,36,36,36,36,36,36,36,36,36,0,0,0,41,41,41,41,42,43,41,42,43,41,41,41,41,42,43,41,42,43,42,43,42,43,42,43,41,41,42,43,41,42,43,41,42,43,42,43,0,50,50,50,50,50,50,50,50,50,50,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,53,53,53,53,53,53,53,53,53,53,59,58,58,58,58,58,59,0,0,0,0,0,58,0,59,60,60,0,59,59,59,58,58,58,58,58,58,58,60,60,0,0,59,0,59,61,62,63,64,0,66,69,58,59,60,61,62,63,64,65,66,69,58,59,60,61,62,63,64,65,64,58,59,60,61,62,63,64,65,66,0,61,62,63,64,65,61,65,58,58,59,60,61,62,63,64,65,61,66,69,58,58,59,59,60,61,62,63,64,65,0,66,69,58,59,60,61,62,63,64,65,58,69,69,63,65,66,69,58,62,61,63,58,66,66,65,69,0,69,69,61,62,63,64,65,69,69,66,0,69,66,69,58,59,60,61,62,63,64,65,66,69,58,59,60,61,62,63,64,65,66,69,58,59,60,61,62,63,64,65,66,0,212,212,212,212,212,0,0,89,90,89,90,89,89,90,89,90,89,89,90,89,90,89,90,90,89,90,0,0,93,93,93,91,93,91,91,93,91,93,91,91,91,93,91,93,91,93,91,0,213,213,0,99,99,99,99,99,99,99,99,99,99,99,0,0,108,107,108,107,108,107,108,107,107,107,108,107,108,107,108,107,107,108,0,0,0,0,0,0,0,214,214,214,112,215,216,214,217,110,111,112,215,216,214,217,110,111,110,111,214,217,112,214,112,214,112,215,216,214,217,110,111,112,215,216,214,217,110,111,112,216,216,216,216,215,214,215,217,0,112,215,214,112,214,214,214,217,112,215,216,214,217,110,111,112,215,216,214,217,110,111,112,215,216,214,217,110,111,112,214,0,0,37,0,40,116,37,40,116,37,37,116,37,40,116,37,40,116,37,116,40,40,40,116,37,40,116,37,40,116,37,0,0,121,0,123,123,123,0,0,121,121,121,122,124,121,122,123,124,120,121,122,123,124,120,120,121,122,123,124,122,124,121,122,121,122,123,124,120,122,121,122,123,124,120,122,122,121,122,123,124,120,120,122,122,120,120,122,124,124,121,122,122,122,122,124,122,122,121,122,123,124,120,121,122,123,124,120,122,121,122,123,124,120,120,137,137,137,137,137,145,143,154,154,143,143,143,137,0,0,0,137,0,0,0,0,145,144,144,144,144,0,0,137,137,144,145,154,143,102,132,134,100,137,144,145,154,143,102,132,134,100,102,132,134,100,137,144,145,154,143,102,132,134,100,102,132,134,100,102,132,134,100,137,144,145,154,143,102,132,134,100,102,132,134,100,102,132,134,100,102,132,134,100,102,132,134,100,102,132,134,100,102,132,134,100,0,0,0,0,137,144,145,154,143,143,102,132,134,100,143,137,144,145,154,143,102,132,134,100,137,144,145,154,143,102,132,134,100,102,132,134,100,0,0,0,0,0,0,0,0,0,0,0,0,0,156,155,156,155,158,157,158,157,156,155,158,157,0,150,151,152,153,146,131,14,150,21,151,46,152,49,153,146,131,14,150,21,151,46,152,49,153,131,131,131,131,150,151,152,153,150,151,152,153,150,151,152,153,150,151,152,153,146,131,14,150,21,151,46,152,49,153,146,131,14,150,21,151,46,152,49,153,14,21,46,49,150,151,152,153,146,14,150,21,151,46,152,49,153,146,131,14,21,46,49,146,146,131,14,150,21,151,46,152,49,153,146,131,14,150,21,151,46,152,49,153,146,131,14,150,21,151,46,152,49,153,0,0,0,0,0,0,0,0,0,0,0,0,0,163,164,163,164,165,166,165,166,163,164,165,166,0,159,160,161,162,147,133,15,159,22,160,168,161,169,162,163,164,165,166,147,133,15,159,22,160,168,161,169,162,163,164,165,166,133,133,133,133,159,160,161,162,163,164,165,166,159,160,161,162,159,160,161,162,159,160,161,162,163,164,165,166,163,164,165,166,147,133,15,159,22,160,168,161,169,162,163,164,165,166,147,133,15,159,22,160,168,161,169,162,163,164,165,166,15,22,168,169,159,160,161,162,147,15,159,22,160,168,161,169,162,147,133,15,22,168,169,147,147,133,15,159,22,160,168,161,169,162,163,164,165,166,147,133,15,159,22,160,168,161,169,162,163,164,165,166,147,133,15,159,22,160,168,161,169,162,163,164,165,166,0,0,0,0,0,0,0,0,0,0,0,0,0,156,155,156,155,158,157,158,157,156,155,158,157,0,170,171,172,173,148,135,174,170,175,171,176,172,57,173,156,155,158,157,148,135,174,170,175,171,176,172,57,173,156,155,158,157,135,135,135,135,170,171,172,173,156,155,158,157,170,171,172,173,170,171,172,173,170,171,172,173,156,155,158,157,156,155,158,157,148,135,174,170,175,171,176,172,57,173,156,155,158,157,148,135,174,170,175,171,176,172,57,173,156,155,158,157,174,175,176,57,170,171,172,173,148,174,170,175,171,176,172,57,173,148,135,174,175,176,57,148,148,135,174,170,175,171,176,172,57,173,156,155,158,157,148,135,174,170,175,171,176,172,57,173,156,155,158,157,148,135,174,170,175,171,176,172,57,173,156,155,158,157,0,0,0,0,0,0,0,0,0,0,0,0,0,181,182,181,182,183,184,183,184,181,182,183,184,0,177,178,179,180,149,136,101,177,185,178,186,179,187,180,181,182,183,184,149,136,101,177,185,178,186,179,187,180,181,182,183,184,136,136,136,136,177,178,179,180,181,182,183,184,177,178,179,180,177,178,179,180,177,178,179,180,181,182,183,184,181,182,183,184,149,136,101,177,185,178,186,179,187,180,181,182,183,184,149,136,101,177,185,178,186,179,187,180,181,182,183,184,101,185,186,187,177,178,179,180,149,101,177,185,178,186,179,187,180,149,136,101,185,186,187,149,149,136,101,177,185,178,186,179,187,180,181,182,183,184,149,136,101,177,185,178,186,179,187,180,181,182,183,184,149,136,101,177,185,178,186,179,187,180,181,182,183,184,0,0,0,0,206,200,200,0,206,200,194,206,200,192,194,206,200,192,192,0,194,192,194,206,200,192,0,0,194,206,200,192,0,192,0,192,192,0,0,192,194,206,200,192,194,206,200,192,194,206,200,192,192,202,0,202,0,0,0,202,193,193,203,201,202,193,203,201,202,201,202,201,201,202,201,202,193,203,201,202,193,203,201,202,201,201,203,201,203,0,193,203,201,202,193,203,201,202,193,203,201,202,0,205,205,205,205,205,205,205,205,205,205,205,0,0,0,207,208,211,207,208,211,207,208,207,208,207,208,207,207,208,207,211,207,208,211,207,208,211,211,208,211,207,208,211,207,208,211,207,208,211],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[1,1],[2,1],[[]],[3,1],[[]],[[]],[[]],[[]],[[]],[[4,5]],[[6,5]],[[[9,[7,8,2]],[11,[10]],[12,[10]],[13,[10]],14,15,16],4],[[[9,[17,8,2]],[18,[10]],[19,[10]],[20,[10]],21,22,16],6],[4],[6],[23,[[24,[1]]]],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],25],[[],25],[[],25],[[4,5],[[24,[26]]]],[[6,5],[[24,[26]]]],0,0,0,0,0,0,[27,23],[[]],[[]],[[]],[[]],[27,27],[28,[[24,[27]]]],[[27,29],30],[[]],[[]],[[[32,[31,31]]],27],[[]],[[]],[[31,31],[[32,[31,31]]]],[[27,33],24],[[[32,[31,31]],27]],[[],24],[34,[[24,[27,[35,[27]]]]]],[[],24],[[],[[24,[27,[35,[27]]]]]],[[],24],[[],24],[[],25],[[],25],0,[[]],[[]],[[]],[36,[[24,[23,37]]]],[[]],[[[40,[38,39]]],36],[[],24],[[],24],[[],25],0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[41,41],[3,41],[[]],[2,41],[[]],[[]],[[]],[[]],[[]],[[42,5]],[[43,5]],[[[9,[44,8,2]],[45,[10]],46,16],42],[[[9,[47,8,2]],[48,[10]],49,16],43],[42],[43],[[],24],[23,[[24,[41]]]],[[],24],[[],24],[[],24],[[],24],[[],24],[[],25],[[],25],[[],25],[[42,5],[[24,[26]]]],[[43,5],[[24,[26]]]],0,[[]],[[]],[[50,[52,[51]]]],[[50,[52,[51]]]],[[]],[[]],[51,50],[[],24],[[],24],[[],25],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[53,[[24,[[54,[51]],[54,[51]]]]]],[[[56,[55]],57],53],[53],[[],24],[[],24],[[],25],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,[58,58],[59,59],[60,60],[61,61],[62,62],[63,63],[64,64],[65,65],[[66,67],[[24,[59]]]],0,[28,[[24,[61]]]],[28,[[24,[62]]]],[28,[[24,[63]]]],[28,[[24,[64]]]],[28,[[24,[65]]]],0,0,[[],[[54,[58]]]],[[58,29],30],[[59,29],30],[[60,29],30],[[61,29],30],[[62,29],30],[[63,29],30],[[64,29],30],[[65,29],30],0,[[]],[[]],[[]],[60,58],[68,59],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],[[54,[58]]]],[[69,60],[[24,[59]]]],0,0,0,[[[71,[70]]],[[24,[66,59]]]],[[[74,[72,73]],[76,[72,75]],[9,[77,8,34]],[40,[78,[80,[79]]]],[40,[81,[82,[79]]]],[40,[38,[83,[79]]]],[40,[38,[84,[79]]]]],[[24,[69,59]]]],[58,[[54,[58]]]],0,0,0,[58,[[54,[58]]]],0,[[66,34,[85,[34]]],[[24,[59]]]],0,[69,[[24,[59]]]],0,[[69,60],[[24,[23,59]]]],[[69,60],[[24,[23,59]]]],[[61,33],24],[[62,33],24],[[63,33],24],[[64,33],24],[[65,33],24],[[69,86],[[24,[59]]]],[[69,58,87],[[24,[59]]]],[66,[[24,[59]]]],0,[[69,[88,[34]]],[[24,[59]]]],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],[[66,34,[85,[34]]],[[24,[59]]]],0,[60,[[24,[23,59]]]],[60,[[24,[59]]]],[[],[[24,[59]]]],[[60,23],[[24,[23,59]]]],[[[88,[34]]],[[24,[59]]]],0,0,[[]],[[]],[[]],[[]],[89,90],[[]],[[]],[[]],[[]],[[66,91,67],89],[[],24],[[],24],[[],24],[[],24],[[],25],[[],25],[[90,92,[54,[51]],[54,[2]],[54,[51]]],90],[[89,[85,[51]]]],[90],0,0,0,0,[[]],[[]],[[]],[[]],[[91,93,23,23]],[[]],[[]],[[]],[[]],[[94,95,96,97,98],91],[91],[[],24],[[],24],[[],24],[[],24],[[],25],[[],25],0,[60,[[24,[23,59]]]],[60,[[24,[23,59]]]],0,[[]],[[]],[[]],[[]],[99,[[24,[[54,[2]],[54,[2]]]]]],[[100,101,102,[103,[55]],16],99],[99],[[],24],[[],24],[[],25],[[99,2]],0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[105,[104]],[106,[104]]],107],[107],[[],24],[[],24],[[],24],[[],24],[[],25],[[],25],[107,86],[[108,109],30],0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[110,110],[111,111],0,0,[[],112],0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,0,0,0,0,0,[[113,114,115,16,51]],0,0,0,0,0,0,0,0,[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[37,37],[[116,117],[[40,[117]]]],[[37,29],30],[[]],[[]],[[]],[[]],[[]],[[]],[[23,[119,[118]]],116],[[[40,[117]]],[[24,[23,37]]]],[[[40,[117]]],[[24,[51,37]]]],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],25],[[],25],[[],25],0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[120],[121,121],[122,122],[123,123],[124,124],[[],122],[[],124],[28,[[24,[121]]]],[[122,125,28],[[24,[16,126]]]],[[121,29],30],[[122,29],30],[[123,29],30],[[124,29],30],[[120,29],30],0,[[]],[[]],[[]],[[]],[[]],[[109,[85,[34]]],[[24,[16,[126,[127]]]]]],[[[85,[16]],[85,[34]]],[[24,[16,[126,[127]]]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[],128],[109,[[54,[16]]]],[124,120],[120,[[54,[3]]]],0,0,0,[[121,33],24],[[122,125,33],[[24,[16,126]]]],[[109,[85,[34]]],[[24,[16,[126,[129]]]]]],[[[85,[16]],[85,[34]]],[[24,[16,[126,[129]]]]]],0,0,0,[[125,130],[[24,[16,126]]]],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[122,23,23],[[24,[124,123]]]],[[],25],[[],25],[[],25],[[],25],[[],25],[[120,124]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[102,131],[132,133],[134,135],[100,136],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[102,137]],[[132,137]],[[134,137]],[[100,137]],[102,51],[132,2],[134,51],[100,2],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[139,[138]]],102],[[[139,[140]]],132],[[[139,[141]]],134],[[[139,[142]]],100],[[102,143]],[[132,143]],[[134,143]],[[100,143]],[[102,51]],[[132,2]],[[134,51]],[[100,2]],[[102,144,145]],[[132,144,145]],[[134,144,145]],[[100,144,145]],[[102,144]],[[132,144]],[[134,144]],[[100,144]],[102],[132],[134],[100],0,0,0,0,[[],24],[[],24],[[],24],[[],24],[[],24],[34,[[24,[143,[35,[143]]]]]],[[],24],[[],24],[[],24],[[],24],[[],[[24,[143,[35,[143]]]]]],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],[102,146],[132,147],[134,148],[100,149],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[150,16],[151,16],[152,16],[153,16],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,[150,86],[151,86],[152,86],[153,86],[[150,154]],[[151,154]],[[152,154]],[[153,154]],[[150,143]],[[151,143]],[[152,143]],[[153,143]],[150],[151],[152],[153],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[14,155],150],[[21,156],151],[[46,157],152],[[49,158],153],[150,[[24,[[54,[51]],[54,[51]]]]]],[151,[[24,[[54,[51]],[54,[51]]]]]],[152,[[24,[[54,[51]],[54,[51]]]]]],[153,[[24,[[54,[51]],[54,[51]]]]]],[146],[14],[150],[21],[151],[46],[152],[49],[153],[[],146],[[],131],[[14,51]],[[21,51]],[[46,51]],[[49,51]],[146],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[159,16],[160,16],[161,16],[162,16],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,[159,86],[160,86],[161,86],[162,86],[163,163],[164,164],[165,165],[166,166],[[159,154]],[[160,154]],[[161,154]],[[162,154]],[[159,143]],[[160,143]],[[161,143]],[[162,143]],[159],[160],[161],[162],[[163,163],86],[[164,164],86],[[165,165],86],[[166,166],86],[[163,29],[[24,[167]]]],[[164,29],[[24,[167]]]],[[165,29],[[24,[167]]]],[[166,29],[[24,[167]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[15,164],159],[[22,163],160],[[168,166],161],[[169,165],162],[159,[[24,[[54,[2]],[54,[2]]]]]],[160,[[24,[[54,[2]],[54,[2]]]]]],[161,[[24,[[54,[2]],[54,[2]]]]]],[162,[[24,[[54,[2]],[54,[2]]]]]],[147],[15],[159],[22],[160],[168],[161],[169],[162],[[],147],[[],133],[[15,2]],[[22,2]],[[168,2]],[[169,2]],[147],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[170,16],[171,16],[172,16],[173,16],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,[170,86],[171,86],[172,86],[173,86],[156,156],[155,155],[158,158],[157,157],[[170,154]],[[171,154]],[[172,154]],[[173,154]],[[170,143]],[[171,143]],[[172,143]],[[173,143]],[170],[171],[172],[173],[[156,156],86],[[155,155],86],[[158,158],86],[[157,157],86],[[156,29],[[24,[167]]]],[[155,29],[[24,[167]]]],[[158,29],[[24,[167]]]],[[157,29],[[24,[167]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[174,155],170],[[175,156],171],[[176,157],172],[[57,158],173],[170,[[24,[[54,[51]],[54,[51]]]]]],[171,[[24,[[54,[51]],[54,[51]]]]]],[172,[[24,[[54,[51]],[54,[51]]]]]],[173,[[24,[[54,[51]],[54,[51]]]]]],[148],[174],[170],[175],[171],[176],[172],[57],[173],[[],148],[[],135],[[174,51]],[[175,51]],[[176,51]],[[57,51]],[148],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[177,16],[178,16],[179,16],[180,16],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,[177,86],[178,86],[179,86],[180,86],[181,181],[182,182],[183,183],[184,184],[[177,154]],[[178,154]],[[179,154]],[[180,154]],[[177,143]],[[178,143]],[[179,143]],[[180,143]],[177],[178],[179],[180],[[181,181],86],[[182,182],86],[[183,183],86],[[184,184],86],[[181,29],[[24,[167]]]],[[182,29],[[24,[167]]]],[[183,29],[[24,[167]]]],[[184,29],[[24,[167]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[101,182],177],[[185,181],178],[[186,184],179],[[187,183],180],[177,[[24,[[54,[2]],[54,[2]]]]]],[178,[[24,[[54,[2]],[54,[2]]]]]],[179,[[24,[[54,[2]],[54,[2]]]]]],[180,[[24,[[54,[2]],[54,[2]]]]]],[149],[101],[177],[185],[178],[186],[179],[187],[180],[[],149],[[],136],[[101,2]],[[185,2]],[[186,2]],[[187,2]],[149],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[192,[[0,[188,189,190]],191]],[52,[34]]],193],0,[[],194],[[[192,[[0,[188,189,190]],191]],195]],[[]],[[]],[[]],[[]],[[109,196],197],0,[[]],[[]],[[]],[[]],0,0,0,[[198,199,115,109,196,109],[[192,[[0,[188,189,190]],191]]]],0,0,0,0,[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],25],[[],25],[[],25],[[],25],[[[192,[[0,[188,189,190]],191]]],200],0,0,0,0,0,0,0,[[193,130]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[201,201],[202,202],[[],201],[28,[[24,[201]]]],[[202,202],86],[[201,29],30],[[202,29],30],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,[203],[[201,33],24],[[203,195]],[204],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],25],[[],25],[[],25],[[],25],0,[[]],[[]],[[]],[205],[[]],[[204,199],205],0,[[],24],[[],24],[[],25],[205,206],0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[207,207],0,0,0,[[],207],0,0,[[207,27,27,23],208],[[]],[[]],[[]],[[]],[[]],[[]],[[[210,[204,115,[209,[204]]]],109],[[211,[191]]]],[[[211,[191]],191]],[[208,33],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],25],[[],25],[[],25],[[[211,[191]]]]],"c":[],"p":[[3,"AdcCode"],[15,"u16"],[15,"i16"],[3,"Adc0Input"],[8,"FnOnce"],[3,"Adc1Input"],[3,"SPI2"],[3,"Enabled"],[3,"Spi"],[3,"DMA1"],[6,"Stream0"],[6,"Stream1"],[6,"Stream2"],[3,"Channel1"],[3,"Channel1"],[15,"usize"],[3,"SPI3"],[6,"Stream3"],[6,"Stream4"],[6,"Stream5"],[3,"Channel2"],[3,"Channel2"],[15,"f32"],[4,"Result"],[3,"TypeId"],[4,"DMAError"],[4,"Gain"],[8,"Deserializer"],[3,"Formatter"],[6,"Result"],[8,"StatefulOutputPin"],[3,"ProgrammableGainAmplifier"],[8,"Serializer"],[15,"u8"],[3,"TryFromPrimitiveError"],[3,"CpuTempSensor"],[4,"AdcError"],[3,"ADC3"],[3,"Temperature"],[3,"AdcChannel"],[3,"DacCode"],[3,"Dac0Output"],[3,"Dac1Output"],[3,"SPI4"],[6,"Stream6"],[3,"Channel3"],[3,"SPI5"],[6,"Stream7"],[3,"Channel4"],[3,"AsmDelay"],[15,"u32"],[8,"Into"],[3,"InputStamper"],[4,"Option"],[3,"Alternate"],[6,"PA3"],[3,"Channel4"],[4,"GpioPin"],[4,"Error"],[4,"Channel"],[3,"DdsChannelState"],[3,"ChannelState"],[3,"InputChannelState"],[3,"OutputChannelState"],[3,"DdsClockConfig"],[3,"QspiInterface"],[4,"Mode"],[4,"XspiError"],[3,"PounderDevices"],[3,"QUADSPI"],[3,"Xspi"],[6,"I2c1Proxy"],[3,"Lm75"],[3,"Lm75"],[4,"Mcp23017"],[3,"Mcp230xx"],[3,"SPI1"],[3,"ADC1"],[3,"Analog"],[6,"PF11"],[3,"ADC2"],[6,"PF14"],[6,"PF3"],[6,"PF4"],[15,"slice"],[15,"bool"],[4,"Level"],[15,"array"],[3,"DdsOutput"],[3,"ProfileBuilder"],[3,"HighResTimerE"],[3,"Channel"],[4,"Channel"],[3,"HRTIM_TIME"],[3,"HRTIM_MASTER"],[3,"HRTIM_COMMON"],[3,"CoreClocks"],[3,"Hrtim"],[3,"Timestamper"],[3,"PounderTimestampTimer"],[3,"Channel1"],[3,"SamplingTimer"],[6,"PA0"],[6,"UsbBus"],[3,"UsbDevice"],[3,"SerialPort"],[3,"SerialTerminal"],[3,"OutputBuffer"],[15,"str"],[3,"UdpSocketStorage"],[3,"TcpSocketStorage"],[3,"NetStorage"],[3,"Peripherals"],[3,"Peripherals"],[6,"SystemTimer"],[3,"SharedAdc"],[8,"Channel"],[3,"Enabled"],[3,"Adc"],[3,"SignalGenerator"],[4,"Signal"],[3,"BasicConfig"],[4,"Error"],[3,"Config"],[8,"Iterator"],[4,"Error"],[4,"Error"],[3,"Metadata"],[4,"Error"],[8,"FnMut"],[3,"Channels"],[3,"ShadowSamplingTimer"],[3,"Channels"],[3,"TimestampTimer"],[3,"Channels"],[3,"Channels"],[4,"TriggerGenerator"],[3,"TIM2"],[3,"Timer"],[3,"TIM3"],[3,"TIM5"],[3,"TIM8"],[4,"Prescaler"],[4,"TriggerSource"],[4,"SlaveMode"],[3,"UpdateEvent"],[3,"UpdateEvent"],[3,"UpdateEvent"],[3,"UpdateEvent"],[3,"Channel1InputCapture"],[3,"Channel2InputCapture"],[3,"Channel3InputCapture"],[3,"Channel4InputCapture"],[4,"InputFilter"],[4,"CaptureSource1"],[4,"CaptureSource2"],[4,"CaptureSource3"],[4,"CaptureSource4"],[3,"Channel1InputCapture"],[3,"Channel2InputCapture"],[3,"Channel3InputCapture"],[3,"Channel4InputCapture"],[4,"CaptureSource2"],[4,"CaptureSource1"],[4,"CaptureSource4"],[4,"CaptureSource3"],[3,"Error"],[3,"Channel3"],[3,"Channel4"],[3,"Channel1InputCapture"],[3,"Channel2InputCapture"],[3,"Channel3InputCapture"],[3,"Channel4InputCapture"],[3,"Channel1"],[3,"Channel2"],[3,"Channel3"],[3,"Channel1InputCapture"],[3,"Channel2InputCapture"],[3,"Channel3InputCapture"],[3,"Channel4InputCapture"],[4,"CaptureSource2"],[4,"CaptureSource1"],[4,"CaptureSource4"],[4,"CaptureSource3"],[3,"Channel2"],[3,"Channel3"],[3,"Channel4"],[8,"Default"],[8,"JsonCoreSlash"],[8,"Clone"],[8,"Serialize"],[3,"NetworkUsers"],[3,"FrameGenerator"],[3,"MqttStorage"],[4,"SocketAddr"],[3,"Address"],[3,"String"],[6,"NetworkStack"],[6,"EthernetPhy"],[4,"NetworkState"],[3,"StreamTarget"],[4,"StreamFormat"],[3,"DataStream"],[6,"NetworkReference"],[3,"NetworkProcessor"],[4,"UpdateState"],[3,"TelemetryBuffer"],[3,"Telemetry"],[3,"NamedBroker"],[3,"Minimq"],[3,"TelemetryClient"],[8,"AttenuatorInterface"],[8,"PowerMeasurementInterface"],[3,"StabilizerDevices"],[3,"NetworkDevices"],[3,"EemGpioDevices"],[3,"PounderDevices"]]}\
+}');
+if (typeof window !== 'undefined' && window.initSearch) {window.initSearch(searchIndex)};
+if (typeof exports !== 'undefined') {exports.searchIndex = searchIndex};
diff --git a/firmware/settings.html b/firmware/settings.html
new file mode 100644
index 0000000000..457549b754
--- /dev/null
+++ b/firmware/settings.html
@@ -0,0 +1 @@
+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 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499 +500 +501 +502 +503 +504 +505 +506 +507 +508 +509 +510 +511 +512 +513 +514 +515 +516 +517 +518 +519 +520 +521 +522 +523 +524 +525 +526 +527 +528 +529 +530 +531 +532 +533 +534 +535 +536 +537 +538 +539 +540 +541 +542 +543 +544 +545 +546 +547 +548 +549 +550 +551 +552 +553 +554 +555 +556 +557 +558 +559 +560 +561 +562 +563 +564 +565 +566 +567 +568 +569 +570 +571 +572 +573 +574 +575 +576 +577 +578 +579 +580 +581 +582 +583 +584 +585 +586 +587 +588 +589 +590 +591 +592 +593 +594 +595 +596 +597 +598 +599 +600 +601 +602 +603 +604 +605 +606 +
#![no_std]
+
+use bit_field::BitField;
+use bitflags::bitflags;
+use embedded_hal::{blocking::delay::DelayUs, digital::v2::OutputPin};
+
+/// A device driver for the AD9959 direct digital synthesis (DDS) chip.
+///
+/// This chip provides four independently controllable digital-to-analog output sinusoids with
+/// configurable phase, amplitude, and frequency. All channels are inherently synchronized as they
+/// are derived off a common system clock.
+///
+/// The chip contains a configurable PLL and supports system clock frequencies up to 500 MHz.
+///
+/// The chip supports a number of serial interfaces to improve data throughput, including normal,
+/// dual, and quad SPI configurations.
+pub struct Ad9959<INTERFACE> {
+ interface: INTERFACE,
+ reference_clock_frequency: f32,
+ system_clock_multiplier: u8,
+ communication_mode: Mode,
+}
+
+/// A trait that allows a HAL to provide a means of communicating with the AD9959.
+pub trait Interface {
+ type Error;
+
+ fn configure_mode(&mut self, mode: Mode) -> Result<(), Self::Error>;
+
+ fn write(&mut self, addr: u8, data: &[u8]) -> Result<(), Self::Error>;
+
+ fn read(&mut self, addr: u8, dest: &mut [u8]) -> Result<(), Self::Error>;
+}
+
+/// Indicates various communication modes of the DDS. The value of this enumeration is equivalent to
+/// the configuration bits of the DDS CSR register.
+#[derive(Copy, Clone, PartialEq, Eq)]
+#[repr(u8)]
+pub enum Mode {
+ SingleBitTwoWire = 0b000,
+ SingleBitThreeWire = 0b010,
+ TwoBitSerial = 0b100,
+ FourBitSerial = 0b110,
+}
+
+bitflags! {
+ /// Specifies an output channel of the AD9959 DDS chip.
+ pub struct Channel: u8 {
+ const ONE = 0b00010000;
+ const TWO = 0b00100000;
+ const THREE = 0b01000000;
+ const FOUR = 0b10000000;
+ const ALL = Self::ONE.bits() | Self::TWO.bits() | Self::THREE.bits() | Self::FOUR.bits();
+ }
+}
+
+/// The configuration registers within the AD9959 DDS device. The values of each register are
+/// equivalent to the address.
+#[allow(clippy::upper_case_acronyms)]
+#[repr(u8)]
+pub enum Register {
+ CSR = 0x00,
+ FR1 = 0x01,
+ FR2 = 0x02,
+ CFR = 0x03,
+ CFTW0 = 0x04,
+ CPOW0 = 0x05,
+ ACR = 0x06,
+ LSRR = 0x07,
+ RDW = 0x08,
+ FDW = 0x09,
+ CW1 = 0x0a,
+ CW2 = 0x0b,
+ CW3 = 0x0c,
+ CW4 = 0x0d,
+ CW5 = 0x0e,
+ CW6 = 0x0f,
+ CW7 = 0x10,
+ CW8 = 0x11,
+ CW9 = 0x12,
+ CW10 = 0x13,
+ CW11 = 0x14,
+ CW12 = 0x15,
+ CW13 = 0x16,
+ CW14 = 0x17,
+ CW15 = 0x18,
+}
+
+/// Possible errors generated by the AD9959 driver.
+#[derive(Debug)]
+pub enum Error {
+ Interface,
+ Check,
+ Bounds,
+ Pin,
+ Frequency,
+}
+
+impl<I: Interface> Ad9959<I> {
+ /// Construct and initialize the DDS.
+ ///
+ /// Args:
+ /// * `interface` - An interface to the DDS.
+ /// * `reset_pin` - A pin connected to the DDS reset input.
+ /// * `io_update` - A pin connected to the DDS io_update input.
+ /// * `delay` - A delay implementation for blocking operation for specific amounts of time.
+ /// * `desired_mode` - The desired communication mode of the interface to the DDS.
+ /// * `clock_frequency` - The clock frequency of the reference clock input.
+ /// * `multiplier` - The desired clock multiplier for the system clock. This multiplies
+ /// `clock_frequency` to generate the system clock.
+ pub fn new(
+ interface: I,
+ mut reset_pin: impl OutputPin,
+ io_update: &mut impl OutputPin,
+ delay: &mut impl DelayUs<u8>,
+ desired_mode: Mode,
+ clock_frequency: f32,
+ multiplier: u8,
+ ) -> Result<Self, Error> {
+ let mut ad9959 = Ad9959 {
+ interface,
+ reference_clock_frequency: clock_frequency,
+ system_clock_multiplier: 1,
+ communication_mode: desired_mode,
+ };
+
+ io_update.set_low().or(Err(Error::Pin))?;
+
+ // Reset the AD9959
+ reset_pin.set_high().or(Err(Error::Pin))?;
+
+ // Delay for at least 1 SYNC_CLK period for the reset to occur. The SYNC_CLK is guaranteed
+ // to be at least 250KHz (1/4 of 1MHz minimum REF_CLK). We use 5uS instead of 4uS to
+ // guarantee conformance with datasheet requirements.
+ delay.delay_us(5);
+
+ reset_pin.set_low().or(Err(Error::Pin))?;
+
+ ad9959
+ .interface
+ .configure_mode(Mode::SingleBitTwoWire)
+ .or(Err(Error::Interface))?;
+
+ // Program the interface configuration in the AD9959. Default to all channels enabled.
+ let csr = [Channel::ALL.bits() | desired_mode as u8];
+ ad9959.write(Register::CSR, &csr)?;
+
+ // Latch the new interface configuration.
+ io_update.set_high().or(Err(Error::Pin))?;
+
+ // Delay for at least 1 SYNC_CLK period for the update to occur. The SYNC_CLK is guaranteed
+ // to be at least 250KHz (1/4 of 1MHz minimum REF_CLK). We use 5uS instead of 4uS to
+ // guarantee conformance with datasheet requirements.
+ delay.delay_us(5);
+
+ io_update.set_low().or(Err(Error::Pin))?;
+
+ ad9959
+ .interface
+ .configure_mode(desired_mode)
+ .or(Err(Error::Interface))?;
+
+ // Empirical evidence indicates a delay is necessary here for the IO update to become
+ // active. This is likely due to needing to wait at least 1 clock cycle of the DDS for the
+ // interface update to occur.
+ // Delay for at least 1 SYNC_CLK period for the update to occur. The SYNC_CLK is guaranteed
+ // to be at least 250KHz (1/4 of 1MHz minimum REF_CLK). We use 5uS instead of 4uS to
+ // guarantee conformance with datasheet requirements.
+ delay.delay_us(5);
+
+ // Read back the CSR to ensure it specifies the mode correctly.
+ let mut updated_csr: [u8; 1] = [0];
+ ad9959.read(Register::CSR, &mut updated_csr)?;
+ if updated_csr[0] != csr[0] {
+ return Err(Error::Check);
+ }
+
+ // Set the clock frequency to configure the device as necessary.
+ ad9959.configure_system_clock(clock_frequency, multiplier)?;
+
+ // Latch the new clock configuration.
+ io_update.set_high().or(Err(Error::Pin))?;
+
+ // Delay for at least 1 SYNC_CLK period for the update to occur. The SYNC_CLK is guaranteed
+ // to be at least 250KHz (1/4 of 1MHz minimum REF_CLK). We use 5uS instead of 4uS to
+ // guarantee conformance with datasheet requirements.
+ delay.delay_us(5);
+
+ io_update.set_low().or(Err(Error::Pin))?;
+
+ Ok(ad9959)
+ }
+
+ fn read(&mut self, reg: Register, data: &mut [u8]) -> Result<(), Error> {
+ self.interface
+ .read(reg as u8, data)
+ .or(Err(Error::Interface))
+ }
+
+ fn write(&mut self, reg: Register, data: &[u8]) -> Result<(), Error> {
+ self.interface
+ .write(reg as u8, data)
+ .or(Err(Error::Interface))
+ }
+
+ /// Configure the internal system clock of the chip.
+ ///
+ /// Arguments:
+ /// * `reference_clock_frequency` - The reference clock frequency provided to the AD9959 core.
+ /// * `multiplier` - The frequency multiplier of the system clock. Must be 1 or 4-20.
+ ///
+ /// Returns:
+ /// The actual frequency configured for the internal system clock.
+ fn configure_system_clock(
+ &mut self,
+ reference_clock_frequency: f32,
+ multiplier: u8,
+ ) -> Result<f32, Error> {
+ self.reference_clock_frequency = reference_clock_frequency;
+
+ if multiplier != 1 && !(4..=20).contains(&multiplier) {
+ return Err(Error::Bounds);
+ }
+
+ let frequency = multiplier as f32 * self.reference_clock_frequency;
+ if frequency > 500_000_000.0f32 {
+ return Err(Error::Frequency);
+ }
+
+ // TODO: Update / disable any enabled channels?
+ let mut fr1: [u8; 3] = [0, 0, 0];
+ self.read(Register::FR1, &mut fr1)?;
+ fr1[0].set_bits(2..=6, multiplier);
+
+ let vco_range = frequency > 255e6;
+ fr1[0].set_bit(7, vco_range);
+
+ self.write(Register::FR1, &fr1)?;
+ self.system_clock_multiplier = multiplier;
+
+ Ok(self.system_clock_frequency())
+ }
+
+ /// Get the current reference clock frequency in Hz.
+ pub fn get_reference_clock_frequency(&self) -> f32 {
+ self.reference_clock_frequency
+ }
+
+ /// Get the current reference clock multiplier.
+ pub fn get_reference_clock_multiplier(&mut self) -> Result<u8, Error> {
+ let mut fr1: [u8; 3] = [0, 0, 0];
+ self.read(Register::FR1, &mut fr1)?;
+
+ Ok(fr1[0].get_bits(2..=6))
+ }
+
+ /// Perform a self-test of the communication interface.
+ ///
+ /// Note:
+ /// This modifies the existing channel enables. They are restored upon exit.
+ ///
+ /// Returns:
+ /// True if the self test succeeded. False otherwise.
+ pub fn self_test(&mut self) -> Result<bool, Error> {
+ let mut csr: [u8; 1] = [0];
+ self.read(Register::CSR, &mut csr)?;
+ let old_csr = csr[0];
+
+ // Enable all channels.
+ csr[0].set_bits(4..8, 0xF);
+ self.write(Register::CSR, &csr)?;
+
+ // Read back the enable.
+ csr[0] = 0;
+ self.read(Register::CSR, &mut csr)?;
+ if csr[0].get_bits(4..8) != 0xF {
+ return Ok(false);
+ }
+
+ // Clear all channel enables.
+ csr[0].set_bits(4..8, 0x0);
+ self.write(Register::CSR, &csr)?;
+
+ // Read back the enable.
+ csr[0] = 0xFF;
+ self.read(Register::CSR, &mut csr)?;
+ if csr[0].get_bits(4..8) != 0 {
+ return Ok(false);
+ }
+
+ // Restore the CSR.
+ csr[0] = old_csr;
+ self.write(Register::CSR, &csr)?;
+
+ Ok(true)
+ }
+
+ /// Get the current system clock frequency in Hz.
+ fn system_clock_frequency(&self) -> f32 {
+ self.system_clock_multiplier as f32 * self.reference_clock_frequency
+ }
+
+ /// Update an output channel configuration register.
+ ///
+ /// Args:
+ /// * `channel` - The channel to configure.
+ /// * `register` - The register to update.
+ /// * `data` - The contents to write to the provided register.
+ fn modify_channel(
+ &mut self,
+ channel: Channel,
+ register: Register,
+ data: &[u8],
+ ) -> Result<(), Error> {
+ // Disable all other outputs so that we can update the configuration register of only the
+ // specified channel.
+ let csr = [self.communication_mode as u8 | channel.bits()];
+
+ self.write(Register::CSR, &csr)?;
+ self.write(register, data)?;
+
+ Ok(())
+ }
+
+ /// Read a configuration register of a specific channel.
+ ///
+ /// Args:
+ /// * `channel` - The channel to read.
+ /// * `register` - The register to read.
+ /// * `data` - A location to store the read register contents.
+ fn read_channel(
+ &mut self,
+ channel: Channel,
+ register: Register,
+ data: &mut [u8],
+ ) -> Result<(), Error> {
+ // Disable all other channels in the CSR so that we can read the configuration register of
+ // only the desired channel.
+ let mut csr = [0];
+ self.read(Register::CSR, &mut csr)?;
+ let new_csr = [self.communication_mode as u8 | channel.bits()];
+
+ self.write(Register::CSR, &new_csr)?;
+ self.read(register, data)?;
+
+ // Restore the previous CSR. Note that the re-enable of the channel happens immediately, so
+ // the CSR update does not need to be latched.
+ self.write(Register::CSR, &csr)?;
+
+ Ok(())
+ }
+
+ /// Configure the phase of a specified channel.
+ ///
+ /// Arguments:
+ /// * `channel` - The channel to configure the frequency of.
+ /// * `phase_turns` - The desired phase offset in turns.
+ ///
+ /// Returns:
+ /// The actual programmed phase offset of the channel in turns.
+ pub fn set_phase(
+ &mut self,
+ channel: Channel,
+ phase_turns: f32,
+ ) -> Result<f32, Error> {
+ let phase_offset: u16 =
+ (phase_turns * (1 << 14) as f32) as u16 & 0x3FFFu16;
+
+ self.modify_channel(
+ channel,
+ Register::CPOW0,
+ &phase_offset.to_be_bytes(),
+ )?;
+
+ Ok((phase_offset as f32) / ((1 << 14) as f32))
+ }
+
+ /// Get the current phase of a specified channel.
+ ///
+ /// Args:
+ /// * `channel` - The channel to get the phase of.
+ ///
+ /// Returns:
+ /// The phase of the channel in turns.
+ pub fn get_phase(&mut self, channel: Channel) -> Result<f32, Error> {
+ let mut phase_offset: [u8; 2] = [0; 2];
+ self.read_channel(channel, Register::CPOW0, &mut phase_offset)?;
+
+ let phase_offset = u16::from_be_bytes(phase_offset) & 0x3FFFu16;
+
+ Ok((phase_offset as f32) / ((1 << 14) as f32))
+ }
+
+ /// Configure the amplitude of a specified channel.
+ ///
+ /// Arguments:
+ /// * `channel` - The channel to configure the frequency of.
+ /// * `amplitude` - A normalized amplitude setting [0, 1].
+ ///
+ /// Returns:
+ /// The actual normalized amplitude of the channel relative to full-scale range.
+ pub fn set_amplitude(
+ &mut self,
+ channel: Channel,
+ amplitude: f32,
+ ) -> Result<f32, Error> {
+ if !(0.0..=1.0).contains(&litude) {
+ return Err(Error::Bounds);
+ }
+
+ let amplitude_control: u16 = (amplitude * (1 << 10) as f32) as u16;
+
+ let mut acr: [u8; 3] = [0; 3];
+
+ // Enable the amplitude multiplier for the channel if required. The amplitude control has
+ // full-scale at 0x3FF (amplitude of 1), so the multiplier should be disabled whenever
+ // full-scale is used.
+ if amplitude_control < (1 << 10) {
+ let masked_control = amplitude_control & 0x3FF;
+ acr[1] = masked_control.to_be_bytes()[0];
+ acr[2] = masked_control.to_be_bytes()[1];
+
+ // Enable the amplitude multiplier
+ acr[1].set_bit(4, true);
+ }
+
+ self.modify_channel(channel, Register::ACR, &acr)?;
+
+ Ok(amplitude_control as f32 / (1 << 10) as f32)
+ }
+
+ /// Get the configured amplitude of a channel.
+ ///
+ /// Args:
+ /// * `channel` - The channel to get the amplitude of.
+ ///
+ /// Returns:
+ /// The normalized amplitude of the channel.
+ pub fn get_amplitude(&mut self, channel: Channel) -> Result<f32, Error> {
+ let mut acr: [u8; 3] = [0; 3];
+ self.read_channel(channel, Register::ACR, &mut acr)?;
+
+ if acr[1].get_bit(4) {
+ let amplitude_control: u16 =
+ (((acr[1] as u16) << 8) | (acr[2] as u16)) & 0x3FF;
+ Ok(amplitude_control as f32 / (1 << 10) as f32)
+ } else {
+ Ok(1.0)
+ }
+ }
+
+ /// Configure the frequency of a specified channel.
+ ///
+ /// Arguments:
+ /// * `channel` - The channel to configure the frequency of.
+ /// * `frequency` - The desired output frequency in Hz.
+ ///
+ /// Returns:
+ /// The actual programmed frequency of the channel.
+ pub fn set_frequency(
+ &mut self,
+ channel: Channel,
+ frequency: f32,
+ ) -> Result<f32, Error> {
+ if frequency < 0.0 || frequency > self.system_clock_frequency() {
+ return Err(Error::Bounds);
+ }
+
+ // The function for channel frequency is `f_out = FTW * f_s / 2^32`, where FTW is the
+ // frequency tuning word and f_s is the system clock rate.
+ let tuning_word: u32 = ((frequency / self.system_clock_frequency())
+ * 1u64.wrapping_shl(32) as f32)
+ as u32;
+
+ self.modify_channel(
+ channel,
+ Register::CFTW0,
+ &tuning_word.to_be_bytes(),
+ )?;
+ Ok((tuning_word as f32 / 1u64.wrapping_shl(32) as f32)
+ * self.system_clock_frequency())
+ }
+
+ /// Get the frequency of a channel.
+ ///
+ /// Arguments:
+ /// * `channel` - The channel to get the frequency of.
+ ///
+ /// Returns:
+ /// The frequency of the channel in Hz.
+ pub fn get_frequency(&mut self, channel: Channel) -> Result<f32, Error> {
+ // Read the frequency tuning word for the channel.
+ let mut tuning_word: [u8; 4] = [0; 4];
+ self.read_channel(channel, Register::CFTW0, &mut tuning_word)?;
+ let tuning_word = u32::from_be_bytes(tuning_word);
+
+ // Convert the tuning word into a frequency.
+ Ok((tuning_word as f32 * self.system_clock_frequency())
+ / (1u64 << 32) as f32)
+ }
+
+ /// Finalize DDS configuration
+ ///
+ /// # Note
+ /// This is intended for when the DDS profiles will be written as a stream of data to the DDS.
+ ///
+ /// # Returns
+ /// (i, mode) where `i` is the interface to the DDS and `mode` is the frozen `Mode`.
+ pub fn freeze(self) -> (I, Mode) {
+ (self.interface, self.communication_mode)
+ }
+}
+
+/// Represents a means of serializing a DDS profile for writing to a stream.
+pub struct ProfileSerializer {
+ // heapless::Vec<u8, 32>, especially its extend_from_slice() is slow
+ data: [u8; 32],
+ index: usize,
+ // make mode u32 to work around https://github.com/japaric/heapless/issues/305
+ mode: u32,
+}
+
+impl ProfileSerializer {
+ /// Construct a new serializer.
+ ///
+ /// # Args
+ /// * `mode` - The communication mode of the DDS.
+ pub fn new(mode: Mode) -> Self {
+ Self {
+ mode: mode as _,
+ data: [0; 32],
+ index: 0,
+ }
+ }
+
+ /// Update a number of channels with the requested profile.
+ ///
+ /// # Args
+ /// * `channels` - A set of channels to apply the configuration to.
+ /// * `ftw` - If provided, indicates a frequency tuning word for the channels.
+ /// * `pow` - If provided, indicates a phase offset word for the channels.
+ /// * `acr` - If provided, indicates the amplitude control register for the channels. The ACR
+ /// should be stored in the 3 LSB of the word. Note that if amplitude scaling is to be used,
+ /// the "Amplitude multiplier enable" bit must be set.
+ #[inline]
+ pub fn update_channels(
+ &mut self,
+ channels: Channel,
+ ftw: Option<u32>,
+ pow: Option<u16>,
+ acr: Option<u32>,
+ ) {
+ let csr = [self.mode as u8 | channels.bits()];
+ self.add_write(Register::CSR, &csr);
+
+ if let Some(ftw) = ftw {
+ self.add_write(Register::CFTW0, &ftw.to_be_bytes());
+ }
+
+ if let Some(pow) = pow {
+ self.add_write(Register::CPOW0, &pow.to_be_bytes());
+ }
+
+ if let Some(acr) = acr {
+ self.add_write(Register::ACR, &acr.to_be_bytes()[1..]);
+ }
+ }
+
+ /// Add a register write to the serialization data.
+ fn add_write(&mut self, register: Register, value: &[u8]) {
+ let data = &mut self.data[self.index..];
+ data[0] = register as u8;
+ data[1..][..value.len()].copy_from_slice(value);
+ self.index += value.len() + 1;
+ }
+
+ #[inline]
+ fn pad(&mut self) {
+ // Pad the buffer to 32-bit (4 byte) alignment by adding dummy writes to CSR and LSRR.
+ // In the case of 1 byte padding, this instead pads with 5 bytes as there is no
+ // valid single-byte write that could be used.
+ if self.index & 1 != 0 {
+ // Pad with 3 bytes
+ self.add_write(Register::LSRR, &[0, 0]);
+ }
+ if self.index & 2 != 0 {
+ // Pad with 2 bytes
+ self.add_write(Register::CSR, &[self.mode as _]);
+ }
+ debug_assert_eq!(self.index & 3, 0);
+ }
+
+ /// Get the serialized profile as a slice of 32-bit words.
+ ///
+ /// # Note
+ /// The serialized profile will be padded to the next 32-bit word boundary by adding dummy
+ /// writes to the CSR or LSRR registers.
+ ///
+ /// # Returns
+ /// A slice of `u32` words representing the serialized profile.
+ #[inline]
+ pub fn finalize(&mut self) -> &[u32] {
+ self.pad();
+ bytemuck::cast_slice(&self.data[..self.index])
+ }
+}
+
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 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499 +500 +501 +502 +503 +504 +505 +506 +
//! # Dual IIR
+//!
+//! The Dual IIR application exposes two configurable channels. Stabilizer samples input at a fixed
+//! rate, digitally filters the data, and then generates filtered output signals on the respective
+//! channel outputs.
+//!
+//! ## Features
+//! * Two indpenendent channels
+//! * up to 800 kHz rate, timed sampling
+//! * Run-time filter configuration
+//! * Input/Output data streaming
+//! * Down to 2 µs latency
+//! * f32 IIR math
+//! * Generic biquad (second order) IIR filter
+//! * Anti-windup
+//! * Derivative kick avoidance
+//!
+//! ## Settings
+//! Refer to the [Settings] structure for documentation of run-time configurable settings for this
+//! application.
+//!
+//! ## Telemetry
+//! Refer to [Telemetry] for information about telemetry reported by this application.
+//!
+//! ## Livestreaming
+//! This application streams raw ADC and DAC data over UDP. Refer to
+//! [stabilizer::net::data_stream](../stabilizer/net/data_stream/index.html) for more information.
+#![deny(warnings)]
+#![no_std]
+#![no_main]
+
+use core::mem::MaybeUninit;
+use core::sync::atomic::{fence, Ordering};
+
+use fugit::ExtU64;
+use mutex_trait::prelude::*;
+
+use idsp::iir;
+
+use stabilizer::{
+ hardware::{
+ self,
+ adc::{Adc0Input, Adc1Input, AdcCode},
+ afe::Gain,
+ dac::{Dac0Output, Dac1Output, DacCode},
+ hal,
+ serial_terminal::SerialTerminal,
+ signal_generator::{self, SignalGenerator},
+ timers::SamplingTimer,
+ DigitalInput0, DigitalInput1, SystemTimer, Systick, AFE0, AFE1,
+ },
+ net::{
+ data_stream::{FrameGenerator, StreamFormat, StreamTarget},
+ miniconf::Tree,
+ telemetry::{Telemetry, TelemetryBuffer},
+ NetworkState, NetworkUsers,
+ },
+};
+
+const SCALE: f32 = i16::MAX as _;
+
+// The number of cascaded IIR biquads per channel. Select 1 or 2!
+const IIR_CASCADE_LENGTH: usize = 1;
+
+// The number of samples in each batch process
+const BATCH_SIZE: usize = 8;
+
+// The logarithm of the number of 100MHz timer ticks between each sample. With a value of 2^7 =
+// 128, there is 1.28uS per sample, corresponding to a sampling frequency of 781.25 KHz.
+const SAMPLE_TICKS_LOG2: u8 = 7;
+const SAMPLE_TICKS: u32 = 1 << SAMPLE_TICKS_LOG2;
+const SAMPLE_PERIOD: f32 =
+ SAMPLE_TICKS as f32 * hardware::design_parameters::TIMER_PERIOD;
+
+#[derive(Clone, Copy, Debug, Tree)]
+pub struct Settings {
+ /// Configure the Analog Front End (AFE) gain.
+ ///
+ /// # Path
+ /// `afe/<n>`
+ ///
+ /// * `<n>` specifies which channel to configure. `<n>` := [0, 1]
+ ///
+ /// # Value
+ /// Any of the variants of [Gain] enclosed in double quotes.
+ #[tree]
+ afe: [Gain; 2],
+
+ /// Configure the IIR filter parameters.
+ ///
+ /// # Path
+ /// `iir_ch/<n>/<m>`
+ ///
+ /// * `<n>` specifies which channel to configure. `<n>` := [0, 1]
+ /// * `<m>` specifies which cascade to configure. `<m>` := [0, 1], depending on [IIR_CASCADE_LENGTH]
+ ///
+ /// # Value
+ /// See [iir::IIR#miniconf]
+ #[tree(depth(2))]
+ iir_ch: [[iir::IIR<f32>; IIR_CASCADE_LENGTH]; 2],
+
+ /// Specified true if DI1 should be used as a "hold" input.
+ ///
+ /// # Path
+ /// `allow_hold`
+ ///
+ /// # Value
+ /// "true" or "false"
+ allow_hold: bool,
+
+ /// Specified true if "hold" should be forced regardless of DI1 state and hold allowance.
+ ///
+ /// # Path
+ /// `force_hold`
+ ///
+ /// # Value
+ /// "true" or "false"
+ force_hold: bool,
+
+ /// Specifies the telemetry output period in seconds.
+ ///
+ /// # Path
+ /// `telemetry_period`
+ ///
+ /// # Value
+ /// Any non-zero value less than 65536.
+ telemetry_period: u16,
+
+ /// Specifies the target for data livestreaming.
+ ///
+ /// # Path
+ /// `stream_target`
+ ///
+ /// # Value
+ /// See [StreamTarget#miniconf]
+ stream_target: StreamTarget,
+
+ /// Specifies the config for signal generators to add on to DAC0/DAC1 outputs.
+ ///
+ /// # Path
+ /// `signal_generator/<n>`
+ ///
+ /// * `<n>` specifies which channel to configure. `<n>` := [0, 1]
+ ///
+ /// # Value
+ /// See [signal_generator::BasicConfig#miniconf]
+ #[tree(depth(2))]
+ signal_generator: [signal_generator::BasicConfig; 2],
+}
+
+impl Default for Settings {
+ fn default() -> Self {
+ Self {
+ // Analog frontend programmable gain amplifier gains (G1, G2, G5, G10)
+ afe: [Gain::G1, Gain::G1],
+ // IIR filter tap gains are an array `[b0, b1, b2, a1, a2]` such that the
+ // new output is computed as `y0 = a1*y1 + a2*y2 + b0*x0 + b1*x1 + b2*x2`.
+ // The array is `iir_state[channel-index][cascade-index][coeff-index]`.
+ // The IIR coefficients can be mapped to other transfer function
+ // representations, for example as described in https://arxiv.org/abs/1508.06319
+ iir_ch: [[iir::IIR::new(1., -SCALE, SCALE); IIR_CASCADE_LENGTH]; 2],
+
+ // Permit the DI1 digital input to suppress filter output updates.
+ allow_hold: false,
+ // Force suppress filter output updates.
+ force_hold: false,
+ // The default telemetry period in seconds.
+ telemetry_period: 10,
+
+ signal_generator: [signal_generator::BasicConfig::default(); 2],
+
+ stream_target: StreamTarget::default(),
+ }
+ }
+}
+
+#[rtic::app(device = stabilizer::hardware::hal::stm32, peripherals = true, dispatchers=[DCMI, JPEG, LTDC, SDMMC])]
+mod app {
+ use super::*;
+
+ #[monotonic(binds = SysTick, default = true, priority = 2)]
+ type Monotonic = Systick;
+
+ #[shared]
+ struct Shared {
+ usb_terminal: SerialTerminal,
+ network: NetworkUsers<Settings, Telemetry, 3>,
+
+ settings: Settings,
+ telemetry: TelemetryBuffer,
+ signal_generator: [SignalGenerator; 2],
+ }
+
+ #[local]
+ struct Local {
+ sampling_timer: SamplingTimer,
+ digital_inputs: (DigitalInput0, DigitalInput1),
+ afes: (AFE0, AFE1),
+ adcs: (Adc0Input, Adc1Input),
+ dacs: (Dac0Output, Dac1Output),
+ iir_state: [[iir::Vec5<f32>; IIR_CASCADE_LENGTH]; 2],
+ generator: FrameGenerator,
+ cpu_temp_sensor: stabilizer::hardware::cpu_temp_sensor::CpuTempSensor,
+ }
+
+ #[init]
+ fn init(c: init::Context) -> (Shared, Local, init::Monotonics) {
+ let clock = SystemTimer::new(|| monotonics::now().ticks() as u32);
+
+ // Configure the microcontroller
+ let (stabilizer, _pounder) = hardware::setup::setup(
+ c.core,
+ c.device,
+ clock,
+ BATCH_SIZE,
+ SAMPLE_TICKS,
+ );
+
+ let mut network = NetworkUsers::new(
+ stabilizer.net.stack,
+ stabilizer.net.phy,
+ clock,
+ env!("CARGO_BIN_NAME"),
+ stabilizer.net.mac_address,
+ option_env!("BROKER").unwrap_or("mqtt"),
+ );
+
+ let generator = network.configure_streaming(StreamFormat::AdcDacData);
+
+ let settings = Settings::default();
+
+ let shared = Shared {
+ usb_terminal: stabilizer.usb_serial,
+ network,
+ settings,
+ telemetry: TelemetryBuffer::default(),
+ signal_generator: [
+ SignalGenerator::new(
+ settings.signal_generator[0]
+ .try_into_config(SAMPLE_PERIOD, DacCode::FULL_SCALE)
+ .unwrap(),
+ ),
+ SignalGenerator::new(
+ settings.signal_generator[1]
+ .try_into_config(SAMPLE_PERIOD, DacCode::FULL_SCALE)
+ .unwrap(),
+ ),
+ ],
+ };
+
+ let mut local = Local {
+ sampling_timer: stabilizer.adc_dac_timer,
+ digital_inputs: stabilizer.digital_inputs,
+ afes: stabilizer.afes,
+ adcs: stabilizer.adcs,
+ dacs: stabilizer.dacs,
+ iir_state: [[[0.; 5]; IIR_CASCADE_LENGTH]; 2],
+ generator,
+ cpu_temp_sensor: stabilizer.temperature_sensor,
+ };
+
+ // Enable ADC/DAC events
+ local.adcs.0.start();
+ local.adcs.1.start();
+ local.dacs.0.start();
+ local.dacs.1.start();
+
+ // Spawn a settings update for default settings.
+ settings_update::spawn().unwrap();
+ telemetry::spawn().unwrap();
+ ethernet_link::spawn().unwrap();
+ usb::spawn().unwrap();
+ start::spawn_after(100.millis()).unwrap();
+
+ (shared, local, init::Monotonics(stabilizer.systick))
+ }
+
+ #[task(priority = 1, local=[sampling_timer])]
+ fn start(c: start::Context) {
+ // Start sampling ADCs and DACs.
+ c.local.sampling_timer.start();
+ }
+
+ /// Main DSP processing routine.
+ ///
+ /// # Note
+ /// Processing time for the DSP application code is bounded by the following constraints:
+ ///
+ /// DSP application code starts after the ADC has generated a batch of samples and must be
+ /// completed by the time the next batch of ADC samples has been acquired (plus the FIFO buffer
+ /// time). If this constraint is not met, firmware will panic due to an ADC input overrun.
+ ///
+ /// The DSP application code must also fill out the next DAC output buffer in time such that the
+ /// DAC can switch to it when it has completed the current buffer. If this constraint is not met
+ /// it's possible that old DAC codes will be generated on the output and the output samples will
+ /// be delayed by 1 batch.
+ ///
+ /// Because the ADC and DAC operate at the same rate, these two constraints actually implement
+ /// the same time bounds, meeting one also means the other is also met.
+ #[task(binds=DMA1_STR4, local=[digital_inputs, adcs, dacs, iir_state, generator], shared=[settings, signal_generator, telemetry], priority=3)]
+ #[link_section = ".itcm.process"]
+ fn process(c: process::Context) {
+ let process::SharedResources {
+ settings,
+ telemetry,
+ signal_generator,
+ } = c.shared;
+
+ let process::LocalResources {
+ digital_inputs,
+ adcs: (adc0, adc1),
+ dacs: (dac0, dac1),
+ iir_state,
+ generator,
+ } = c.local;
+
+ (settings, telemetry, signal_generator).lock(
+ |settings, telemetry, signal_generator| {
+ let digital_inputs =
+ [digital_inputs.0.is_high(), digital_inputs.1.is_high()];
+ telemetry.digital_inputs = digital_inputs;
+
+ let hold = settings.force_hold
+ || (digital_inputs[1] && settings.allow_hold);
+
+ (adc0, adc1, dac0, dac1).lock(|adc0, adc1, dac0, dac1| {
+ let adc_samples = [adc0, adc1];
+ let dac_samples = [dac0, dac1];
+
+ // Preserve instruction and data ordering w.r.t. DMA flag access.
+ fence(Ordering::SeqCst);
+
+ for channel in 0..adc_samples.len() {
+ adc_samples[channel]
+ .iter()
+ .zip(dac_samples[channel].iter_mut())
+ .zip(&mut signal_generator[channel])
+ .map(|((ai, di), signal)| {
+ let x = f32::from(*ai as i16);
+ let y = settings.iir_ch[channel]
+ .iter()
+ .zip(iir_state[channel].iter_mut())
+ .fold(x, |yi, (ch, state)| {
+ ch.update(state, yi, hold)
+ });
+
+ // Note(unsafe): The filter limits must ensure that the value is in range.
+ // The truncation introduces 1/2 LSB distortion.
+ let y: i16 = unsafe { y.to_int_unchecked() };
+
+ let y = y.saturating_add(signal);
+
+ // Convert to DAC code
+ *di = DacCode::from(y).0;
+ })
+ .last();
+ }
+
+ // Stream the data.
+ const N: usize = BATCH_SIZE * core::mem::size_of::<i16>();
+ generator.add(|buf| {
+ for (data, buf) in adc_samples
+ .iter()
+ .chain(dac_samples.iter())
+ .zip(buf.chunks_exact_mut(N))
+ {
+ let data = unsafe {
+ core::slice::from_raw_parts(
+ data.as_ptr() as *const MaybeUninit<u8>,
+ N,
+ )
+ };
+ buf.copy_from_slice(data)
+ }
+ N * 4
+ });
+ // Update telemetry measurements.
+ telemetry.adcs = [
+ AdcCode(adc_samples[0][0]),
+ AdcCode(adc_samples[1][0]),
+ ];
+
+ telemetry.dacs = [
+ DacCode(dac_samples[0][0]),
+ DacCode(dac_samples[1][0]),
+ ];
+
+ // Preserve instruction and data ordering w.r.t. DMA flag access.
+ fence(Ordering::SeqCst);
+ });
+ },
+ );
+ }
+
+ #[idle(shared=[network, usb_terminal])]
+ fn idle(mut c: idle::Context) -> ! {
+ loop {
+ match c.shared.network.lock(|net| net.update()) {
+ NetworkState::SettingsChanged(_path) => {
+ settings_update::spawn().unwrap()
+ }
+ NetworkState::Updated => {}
+ NetworkState::NoChange => {
+ // We can't sleep if USB is not in suspend.
+ if c.shared
+ .usb_terminal
+ .lock(|terminal| terminal.usb_is_suspended())
+ {
+ cortex_m::asm::wfi();
+ }
+ }
+ }
+ }
+ }
+
+ #[task(priority = 1, local=[afes], shared=[network, settings, signal_generator])]
+ fn settings_update(mut c: settings_update::Context) {
+ let settings = c.shared.network.lock(|net| *net.miniconf.settings());
+ c.shared.settings.lock(|current| *current = settings);
+
+ c.local.afes.0.set_gain(settings.afe[0]);
+ c.local.afes.1.set_gain(settings.afe[1]);
+
+ // Update the signal generators
+ for (i, &config) in settings.signal_generator.iter().enumerate() {
+ match config.try_into_config(SAMPLE_PERIOD, DacCode::FULL_SCALE) {
+ Ok(config) => {
+ c.shared
+ .signal_generator
+ .lock(|generator| generator[i].update_waveform(config));
+ }
+ Err(err) => log::error!(
+ "Failed to update signal generation on DAC{}: {:?}",
+ i,
+ err
+ ),
+ }
+ }
+
+ let target = settings.stream_target.into();
+ c.shared.network.lock(|net| net.direct_stream(target));
+ }
+
+ #[task(priority = 1, shared=[network, settings, telemetry], local=[cpu_temp_sensor])]
+ fn telemetry(mut c: telemetry::Context) {
+ let telemetry: TelemetryBuffer =
+ c.shared.telemetry.lock(|telemetry| *telemetry);
+
+ let (gains, telemetry_period) = c
+ .shared
+ .settings
+ .lock(|settings| (settings.afe, settings.telemetry_period));
+
+ c.shared.network.lock(|net| {
+ net.telemetry.publish(&telemetry.finalize(
+ gains[0],
+ gains[1],
+ c.local.cpu_temp_sensor.get_temperature().unwrap(),
+ ))
+ });
+
+ // Schedule the telemetry task in the future.
+ telemetry::Monotonic::spawn_after((telemetry_period as u64).secs())
+ .unwrap();
+ }
+
+ #[task(priority = 1, shared=[usb_terminal])]
+ fn usb(mut c: usb::Context) {
+ // Handle the USB serial terminal.
+ c.shared.usb_terminal.lock(|usb| usb.process());
+
+ // Schedule to run this task every 10 milliseconds.
+ usb::spawn_after(10u64.millis()).unwrap();
+ }
+
+ #[task(priority = 1, shared=[network])]
+ fn ethernet_link(mut c: ethernet_link::Context) {
+ c.shared.network.lock(|net| net.processor.handle_link());
+ ethernet_link::Monotonic::spawn_after(1.secs()).unwrap();
+ }
+
+ #[task(binds = ETH, priority = 1)]
+ fn eth(_: eth::Context) {
+ unsafe { hal::ethernet::interrupt_handler() }
+ }
+
+ #[task(binds = SPI2, priority = 4)]
+ fn spi2(_: spi2::Context) {
+ panic!("ADC0 SPI error");
+ }
+
+ #[task(binds = SPI3, priority = 4)]
+ fn spi3(_: spi3::Context) {
+ panic!("ADC1 SPI error");
+ }
+
+ #[task(binds = SPI4, priority = 4)]
+ fn spi4(_: spi4::Context) {
+ panic!("DAC0 SPI error");
+ }
+
+ #[task(binds = SPI5, priority = 4)]
+ fn spi5(_: spi5::Context) {
+ panic!("DAC1 SPI error");
+ }
+}
+
use num_traits::ops::wrapping::WrappingAdd;
+
+#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
+pub struct Accu<T> {
+ state: T,
+ step: T,
+}
+
+impl<T> Accu<T> {
+ pub fn new(state: T, step: T) -> Self {
+ Self { state, step }
+ }
+}
+
+impl<T> Iterator for Accu<T>
+where
+ T: WrappingAdd + Copy,
+{
+ type Item = T;
+ fn next(&mut self) -> Option<T> {
+ let s = self.state;
+ self.state = s.wrapping_add(&self.step);
+ Some(s)
+ }
+}
+
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 +
fn divi(mut y: u32, mut x: u32) -> u32 {
+ debug_assert!(y <= x);
+ let z = y.leading_zeros().min(15);
+ y <<= z;
+ x += (1 << (15 - z)) - 1;
+ x >>= 16 - z;
+ if x == 0 {
+ 0 // x == y == 0
+ } else {
+ ((y / x) << 15) + (1 << 14)
+ }
+}
+
+fn atani(x: u32) -> u32 {
+ const A: [i32; 6] = [
+ 0x0517c2cd,
+ -0x06c6496b,
+ 0x0fbdb021,
+ -0x25b32e0a,
+ 0x43b34c81,
+ -0x3bc823dd,
+ ];
+ let x = x as i64;
+ let x2 = ((x * x) >> 32) as i32 as i64;
+ let r = A
+ .iter()
+ .rev()
+ .fold(0, |r, a| ((r as i64 * x2) >> 32) as i32 + a);
+ ((r as i64 * x) >> 28) as _
+}
+
+/// 2-argument arctangent function.
+///
+/// This implementation uses all integer arithmetic for fast
+/// computation.
+///
+/// # Arguments
+///
+/// * `y` - Y-axis component.
+/// * `x` - X-axis component.
+///
+/// # Returns
+///
+/// The angle between the x-axis and the ray to the point (x,y). The
+/// result range is from i32::MIN to i32::MAX, where i32::MIN
+/// represents -pi and, equivalently, +pi. i32::MAX represents one
+/// count less than +pi.
+pub fn atan2(mut y: i32, mut x: i32) -> i32 {
+ let mut k = 0u32;
+ if y < 0 {
+ y = y.saturating_neg();
+ k ^= u32::MAX;
+ }
+ if x < 0 {
+ x = x.saturating_neg();
+ k ^= u32::MAX >> 1;
+ }
+ if y > x {
+ (y, x) = (x, y);
+ k ^= u32::MAX >> 2;
+ }
+ let r = atani(divi(y as _, x as _));
+ (r ^ k) as _
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use core::f64::consts::PI;
+
+ #[test]
+ fn atan2_error() {
+ const N: isize = 201;
+ for i in 0..N {
+ let p = ((1. - 2. * i as f64 / N as f64) * i32::MIN as f64) as i32;
+ let pf = p as f64 / i32::MIN as f64 * -PI;
+ let y = (pf.sin() * i32::MAX as f64) as i32;
+ let x = (pf.cos() * i32::MAX as f64) as i32;
+ let _p0 = (y as f64).atan2(x as f64);
+ let pp = atan2(y, x);
+ let pe = -(pp as f64 / i32::MIN as f64);
+ println!(
+ "y:{:.5e}, x:{:.5e}, p/PI:{:.5e}: pe:{:.5e}, pe*PI-p0:{:.5e}",
+ y as f64 / i32::MAX as f64,
+ x as f64 / i32::MAX as f64,
+ pf / PI,
+ pe,
+ pe * PI - pf
+ );
+ }
+ }
+
+ fn angle_to_axis(angle: f64) -> f64 {
+ let angle = angle % (PI / 2.);
+ (PI / 2. - angle).min(angle)
+ }
+
+ #[test]
+ fn atan2_absolute_error() {
+ const N: usize = 321;
+ let mut test_vals = [0i32; N + 2];
+ let scale = (1i64 << 31) as f64;
+ for i in 0..N {
+ test_vals[i] = (scale * (-1. + 2. * i as f64 / N as f64)) as i32;
+ }
+
+ assert!(test_vals.contains(&i32::MIN));
+ test_vals[N] = i32::MAX;
+ test_vals[N + 1] = 0;
+
+ let mut rms_err = 0f64;
+ let mut abs_err = 0f64;
+ let mut rel_err = 0f64;
+
+ for &x in test_vals.iter() {
+ for &y in test_vals.iter() {
+ let want = (y as f64).atan2(x as f64);
+ let have = atan2(y, x) as f64 * (PI / scale);
+ let err = (have - want).abs();
+ abs_err = abs_err.max(err);
+ rms_err += err * err;
+ if err > 3e-5 {
+ println!("{:.5e}/{:.5e}: {:.5e} vs {:.5e}", y, x, have, want);
+ println!("y/x {} {}", y, x);
+ rel_err = rel_err.max(err / angle_to_axis(want));
+ }
+ }
+ }
+ rms_err = rms_err.sqrt() / test_vals.len() as f64;
+ println!("max abs err: {:.2e}", abs_err);
+ println!("rms abs err: {:.2e}", rms_err);
+ println!("max rel err: {:.2e}", rel_err);
+ assert!(abs_err < 1.2e-5);
+ assert!(rms_err < 4.2e-6);
+ assert!(rel_err < 1e-12);
+ }
+}
+
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 +
pub use num_complex::Complex;
+
+use super::{atan2, cossin};
+
+/// Complex extension trait offering DSP (fast, good accuracy) functionality.
+pub trait ComplexExt<T, U> {
+ fn from_angle(angle: T) -> Self;
+ fn abs_sqr(&self) -> U;
+ fn log2(&self) -> T;
+ fn arg(&self) -> T;
+ fn saturating_add(&self, other: Self) -> Self;
+ fn saturating_sub(&self, other: Self) -> Self;
+}
+
+impl ComplexExt<i32, u32> for Complex<i32> {
+ /// Return a Complex on the unit circle given an angle.
+ ///
+ /// Example:
+ ///
+ /// ```
+ /// use idsp::{Complex, ComplexExt};
+ /// Complex::<i32>::from_angle(0);
+ /// Complex::<i32>::from_angle(1 << 30); // pi/2
+ /// Complex::<i32>::from_angle(-1 << 30); // -pi/2
+ /// ```
+ fn from_angle(angle: i32) -> Self {
+ let (c, s) = cossin(angle);
+ Self::new(c, s)
+ }
+
+ /// Return the absolute square (the squared magnitude).
+ ///
+ /// Note: Normalization is `1 << 32`, i.e. U0.32.
+ ///
+ /// Note(panic): This will panic for `Complex(i32::MIN, i32::MIN)`
+ ///
+ /// Example:
+ ///
+ /// ```
+ /// use idsp::{Complex, ComplexExt};
+ /// assert_eq!(Complex::new(i32::MIN, 0).abs_sqr(), 1 << 31);
+ /// assert_eq!(Complex::new(i32::MAX, i32::MAX).abs_sqr(), u32::MAX - 3);
+ /// ```
+ fn abs_sqr(&self) -> u32 {
+ (((self.re as i64) * (self.re as i64) + (self.im as i64) * (self.im as i64)) >> 31) as u32
+ }
+
+ /// log2(power) re full scale approximation
+ ///
+ /// TODO: scale up, interpolate
+ ///
+ /// Panic:
+ /// This will panic for `Complex(i32::MIN, i32::MIN)`
+ ///
+ /// Example:
+ ///
+ /// ```
+ /// use idsp::{Complex, ComplexExt};
+ /// assert_eq!(Complex::new(i32::MAX, i32::MAX).log2(), -1);
+ /// assert_eq!(Complex::new(i32::MAX, 0).log2(), -2);
+ /// assert_eq!(Complex::new(1, 0).log2(), -63);
+ /// assert_eq!(Complex::new(0, 0).log2(), -64);
+ /// ```
+ fn log2(&self) -> i32 {
+ let a = (self.re as i64) * (self.re as i64) + (self.im as i64) * (self.im as i64);
+ -(a.leading_zeros() as i32)
+ }
+
+ /// Return the angle.
+ ///
+ /// Note: Normalization is `1 << 31 == pi`.
+ ///
+ /// Example:
+ ///
+ /// ```
+ /// use idsp::{Complex, ComplexExt};
+ /// assert_eq!(Complex::new(0, 0).arg(), 0);
+ /// ```
+ fn arg(&self) -> i32 {
+ atan2(self.im, self.re)
+ }
+
+ fn saturating_add(&self, other: Self) -> Self {
+ Self::new(
+ self.re.saturating_add(other.re),
+ self.im.saturating_add(other.im),
+ )
+ }
+
+ fn saturating_sub(&self, other: Self) -> Self {
+ Self::new(
+ self.re.saturating_sub(other.re),
+ self.im.saturating_sub(other.im),
+ )
+ }
+}
+
+/// Full scale fixed point multiplication.
+pub trait MulScaled<T> {
+ fn mul_scaled(self, other: T) -> Self;
+}
+
+impl MulScaled<Complex<i32>> for Complex<i32> {
+ fn mul_scaled(self, other: Self) -> Self {
+ let a = self.re as i64;
+ let b = self.im as i64;
+ let c = other.re as i64;
+ let d = other.im as i64;
+ Complex {
+ re: ((a * c - b * d) >> 31) as i32,
+ im: ((b * c + a * d) >> 31) as i32,
+ }
+ }
+}
+
+impl MulScaled<i32> for Complex<i32> {
+ fn mul_scaled(self, other: i32) -> Self {
+ Complex {
+ re: ((other as i64 * self.re as i64) >> 31) as i32,
+ im: ((other as i64 * self.im as i64) >> 31) as i32,
+ }
+ }
+}
+
+impl MulScaled<i16> for Complex<i32> {
+ fn mul_scaled(self, other: i16) -> Self {
+ Complex {
+ re: (other as i32 * (self.re >> 16) + (1 << 14)) >> 15,
+ im: (other as i32 * (self.im >> 16) + (1 << 14)) >> 15,
+ }
+ }
+}
+
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 +
include!(concat!(env!("OUT_DIR"), "/cossin_table.rs"));
+
+/// Compute the cosine and sine of an angle.
+/// This is ported from the MiSoC cossin core.
+/// <https://github.com/m-labs/misoc/blob/master/misoc/cores/cossin.py>
+///
+/// # Arguments
+/// * `phase` - 32-bit phase.
+///
+/// # Returns
+/// The cos and sin values of the provided phase as a `(i32, i32)`
+/// tuple. With a 7-bit deep LUT there is 9e-6 max and 4e-6 RMS error
+/// in each quadrature over 20 bit phase.
+pub fn cossin(mut phase: i32) -> (i32, i32) {
+ let mut octant = phase as u32;
+ if octant & (1 << 29) != 0 {
+ // phase = pi/4 - phase
+ phase = !phase;
+ }
+
+ // 16 + 1 bits for cos/sin and 15 for dphi to saturate the i32 range.
+ const ALIGN_MSB: usize = 32 - 16 - 1;
+
+ // Mask off octant bits. This leaves the angle in the range [0, pi/4).
+ phase = (((phase as u32) << 3) >> (32 - COSSIN_DEPTH - ALIGN_MSB)) as _;
+
+ let lookup = COSSIN[(phase >> ALIGN_MSB) as usize];
+ phase &= (1 << ALIGN_MSB) - 1;
+
+ // The phase values used for the LUT are at midpoint for the truncated phase.
+ // Interpolate relative to the LUT entry midpoint.
+ phase -= 1 << (ALIGN_MSB - 1);
+
+ // Cancel the -1 bias that was conditionally introduced above.
+ // This lowers the DC spur from 2e-8 to 2e-10 magnitude.
+ // phase += (octant & 1) as i32;
+
+ // Fixed point pi/4.
+ const PI4: i32 = (core::f64::consts::FRAC_PI_4 * (1 << 16) as f64) as _;
+ // No rounding bias necessary here since we keep enough low bits.
+ let dphi = (phase * PI4) >> 16;
+
+ // 1/2 < cos(0 <= x <= pi/4) <= 1: Shift the cos
+ // values and scale the sine values as encoded in the LUT.
+ let mut cos = (lookup & 0xffff) as i32 + (1 << 16);
+ let mut sin = (lookup >> 16) as i32;
+
+ let dcos = (sin * dphi) >> COSSIN_DEPTH;
+ let dsin = (cos * dphi) >> (COSSIN_DEPTH + 1);
+
+ cos = (cos << (ALIGN_MSB - 1)) - dcos;
+ sin = (sin << ALIGN_MSB) + dsin;
+
+ // Unmap using octant bits.
+ octant ^= octant >> 1;
+ if octant & (1 << 29) != 0 {
+ (cos, sin) = (sin, cos);
+ }
+ if octant & (1 << 30) != 0 {
+ cos = -cos;
+ }
+ if octant & (1 << 31) != 0 {
+ sin = -sin;
+ }
+
+ (cos, sin)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use core::f64::consts::PI;
+
+ #[test]
+ fn cossin_error_max_rms_all_phase() {
+ // Constant amplitude error due to LUT data range.
+ const AMPLITUDE: f64 = (1i64 << 31) as f64 - 0.85 * (1i64 << 15) as f64;
+ const MAX_PHASE: f64 = (1i64 << 32) as _;
+ let mut rms_err = (0f64, 0f64);
+ let mut sum_err = (0f64, 0f64);
+ let mut max_err = (0f64, 0f64);
+ let mut sum = (0f64, 0f64);
+ let mut demod = (0f64, 0f64);
+
+ // use std::{fs::File, io::{BufWriter, prelude::*}, path::Path};
+ // let mut file = BufWriter::new(File::create(Path::new("data.bin")).unwrap());
+
+ // log2 of the number of phase values to check
+ const PHASE_DEPTH: usize = 20;
+
+ for phase in 0..(1 << PHASE_DEPTH) {
+ let phase = (phase << (32 - PHASE_DEPTH)) as i32;
+ let have = cossin(phase);
+ // file.write(&have.0.to_le_bytes()).unwrap();
+ // file.write(&have.1.to_le_bytes()).unwrap();
+
+ let have = (have.0 as f64 / AMPLITUDE, have.1 as f64 / AMPLITUDE);
+
+ let radian_phase = 2. * PI * phase as f64 / MAX_PHASE;
+ let want = (radian_phase.cos(), radian_phase.sin());
+
+ sum.0 += have.0;
+ sum.1 += have.1;
+
+ demod.0 += have.0 * want.0 - have.1 * want.1;
+ demod.1 += have.1 * want.0 + have.0 * want.1;
+
+ let err = (have.0 - want.0, have.1 - want.1);
+
+ sum_err.0 += err.0;
+ sum_err.1 += err.1;
+
+ rms_err.0 += err.0 * err.0;
+ rms_err.1 += err.1 * err.1;
+
+ max_err.0 = max_err.0.max(err.0.abs());
+ max_err.1 = max_err.1.max(err.1.abs());
+ }
+ rms_err.0 /= (1 << PHASE_DEPTH) as f64;
+ rms_err.1 /= (1 << PHASE_DEPTH) as f64;
+
+ println!("sum: {:.2e} {:.2e}", sum.0, sum.1);
+ println!("demod: {:.2e} {:.2e}", demod.0, demod.1);
+ println!("sum_err: {:.2e} {:.2e}", sum_err.0, sum_err.1);
+ println!("rms: {:.2e} {:.2e}", rms_err.0.sqrt(), rms_err.1.sqrt());
+ println!("max: {:.2e} {:.2e}", max_err.0, max_err.1);
+
+ assert!(sum.0.abs() < 4e-10);
+ assert!(sum.1.abs() < 3e-8);
+
+ assert!(demod.0.abs() < 4e-10);
+ assert!(demod.1.abs() < 1e-8);
+
+ assert!(sum_err.0.abs() < 4e-10);
+ assert!(sum_err.1.abs() < 4e-10);
+
+ assert!(rms_err.0.sqrt() < 4e-6);
+ assert!(rms_err.1.sqrt() < 4e-6);
+
+ assert!(max_err.0 < 1e-5);
+ assert!(max_err.1 < 1e-5);
+ }
+}
+
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 +
pub trait Filter {
+ type Config;
+ /// Update the filter with a new sample.
+ ///
+ /// # Args
+ /// * `x`: Input data.
+ /// * `k`: Filter configuration.
+ ///
+ /// # Return
+ /// Filtered output y.
+ fn update(&mut self, x: i32, k: &Self::Config) -> i32;
+ /// Return the current filter output
+ fn get(&self) -> i32;
+ /// Update the filter so that it outputs the provided value.
+ /// This does not completely define the state of the filter.
+ fn set(&mut self, x: i32);
+}
+
+#[derive(Copy, Clone, Default)]
+pub struct Nyquist(pub(crate) i32);
+impl Filter for Nyquist {
+ type Config = ();
+ fn update(&mut self, x: i32, _k: &Self::Config) -> i32 {
+ let x = x >> 1; // x/2 for less bias but more distortion
+ let y = x.wrapping_add(self.0);
+ self.0 = x;
+ y
+ }
+ fn get(&self) -> i32 {
+ self.0
+ }
+ fn set(&mut self, x: i32) {
+ self.0 = x;
+ }
+}
+
+#[derive(Copy, Clone)]
+pub struct Chain<const N: usize, T>(pub(crate) [T; N]);
+impl<const N: usize, T: Filter> Filter for Chain<N, T> {
+ type Config = T::Config;
+ fn update(&mut self, x: i32, k: &Self::Config) -> i32 {
+ self.0.iter_mut().fold(x, |x, stage| stage.update(x, k))
+ }
+ fn get(&self) -> i32 {
+ self.0[N - 1].get()
+ }
+ fn set(&mut self, x: i32) {
+ self.0.iter_mut().for_each(|stage| stage.set(x));
+ }
+}
+impl<const N: usize, T: Default + Copy> Default for Chain<N, T> {
+ fn default() -> Self {
+ Self([T::default(); N])
+ }
+}
+
+#[derive(Copy, Clone, Default)]
+pub struct Cascade<T, U>(pub(crate) T, U);
+impl<T: Filter, U: Filter> Filter for Cascade<T, U> {
+ type Config = (T::Config, U::Config);
+ fn update(&mut self, x: i32, k: &Self::Config) -> i32 {
+ self.1.update(self.0.update(x, &k.0), &k.1)
+ }
+ fn get(&self) -> i32 {
+ self.1.get()
+ }
+ fn set(&mut self, x: i32) {
+ self.1.set(x)
+ }
+}
+
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 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499 +500 +501 +502 +503 +504 +505 +506 +507 +508 +509 +510 +511 +512 +513 +514 +515 +516 +517 +518 +519 +520 +521 +522 +523 +524 +525 +526 +527 +528 +529 +530 +531 +532 +533 +534 +535 +536 +537 +538 +539 +540 +541 +542 +543 +544 +545 +546 +547 +548 +549 +550 +551 +552 +553 +554 +555 +556 +557 +558 +559 +560 +561 +562 +563 +564 +565 +566 +567 +568 +569 +570 +571 +572 +573 +574 +575 +576 +577 +578 +579 +580 +581 +582 +583 +584 +585 +586 +587 +588 +589 +590 +591 +592 +593 +594 +595 +596 +597 +598 +599 +600 +601 +602 +603 +604 +605 +606 +607 +608 +609 +610 +611 +612 +613 +614 +615 +616 +617 +618 +619 +620 +621 +622 +623 +624 +625 +626 +627 +628 +629 +630 +631 +632 +633 +634 +635 +636 +637 +638 +639 +640 +641 +642 +643 +644 +645 +646 +647 +648 +649 +650 +651 +652 +653 +654 +655 +656 +657 +658 +659 +660 +661 +662 +663 +664 +665 +666 +667 +668 +669 +670 +671 +672 +673 +674 +675 +676 +677 +678 +679 +680 +681 +682 +683 +684 +685 +686 +687 +688 +689 +690 +691 +692 +693 +694 +695 +696 +697 +698 +699 +700 +701 +702 +703 +704 +705 +706 +707 +708 +709 +710 +711 +712 +713 +714 +715 +716 +717 +718 +719 +720 +721 +722 +723 +724 +725 +726 +727 +728 +729 +730 +731 +732 +733 +734 +735 +736 +737 +738 +739 +740 +741 +742 +743 +744 +745 +746 +747 +748 +749 +750 +751 +752 +753 +754 +755 +756 +757 +758 +759 +
/// Filter input items into output items.
+pub trait Filter {
+ /// Input/output item type.
+ // TODO: impl with generic item type
+ type Item;
+
+ /// Process a block of items.
+ ///
+ /// Input items can be either in `x` or in `y`.
+ /// In the latter case the filtering operation is done in-place.
+ /// Output is always written into `y`.
+ /// The slice of items written into `y` is returned.
+ /// Input and output size relations must match the filter requirements
+ /// (decimation/interpolation and maximum block size).
+ /// When using in-place operation, `y` needs to contain the input items
+ /// (fewer than `y.len()` in the case of interpolation) and must be able to
+ /// contain the output items.
+ fn process_block<'a>(
+ &mut self,
+ x: Option<&[Self::Item]>,
+ y: &'a mut [Self::Item],
+ ) -> &'a mut [Self::Item];
+
+ /// Return the block size granularity and the maximum block size.
+ ///
+ /// For in-place processing, this refers to constraints on `y`.
+ /// Otherwise this refers to the larger of `x` and `y` (`x` for decimation and `y` for interpolation).
+ /// The granularity is also the rate change in the case of interpolation/decimation filters.
+ fn block_size(&self) -> (usize, usize);
+
+ /// Finite impulse response length in numer of output items minus one
+ /// Get this many to drain all previous memory
+ fn response_length(&self) -> usize;
+
+ // TODO: process items with automatic blocks
+ // fn process(&mut self, x: Option<&[Self::Item]>, y: &mut [Self::Item]) -> usize {}
+}
+
+/// Symmetric FIR filter prototype.
+///
+/// # Generics
+/// * `M`: number of taps, one-sided. The filter has effectively 2*M DSP taps
+/// * `N`: state size: N = 2*M - 1 + {input/output}.len()
+///
+/// # Half band decimation/interpolation filters
+///
+/// Half-band filters (rate change of 2) and cascades of HBFs are implemented in
+/// [`HbfDec`] and [`HbfInt`] etc.
+/// The half-band filter has unique properties that make it preferrable in many cases:
+///
+/// * only needs M multiplications (fused multiply accumulate) for 4*M taps
+/// * HBF decimator stores less state than a generic FIR filter
+/// * as a FIR filter has linear phase/flat group delay
+/// * very small passband ripple and excellent stopband attenuation
+/// * as a cascade of decimation/interpolation filters, the higher-rate filters
+/// need successively fewer taps, allowing the filtering to be dominated by
+/// only the highest rate filter with the fewest taps
+/// * In a cascade of HBF the overall latency, group delay, and impulse response
+/// length are dominated by the lowest-rate filter which, due to its manageable transition
+/// band width (compared to single-stage filters) can be smaller, shorter, and faster.
+/// * high dynamic range and inherent stability compared with an IIR filter
+/// * can be combined with a CIC filter for non-power-of-two or even higher rate changes
+///
+/// The implementations here are all `no_std` and `no-alloc`.
+/// They support (but don't require) in-place filtering to reduce memory usage.
+/// They unroll and optimmize extremely well targetting current architectures,
+/// e.g. requiring less than 4 instructions per input item for the full `HbfDecCascade` on Skylake.
+/// The filters are optimized for decent block sizes and perform best (i.e. with negligible
+/// overhead) for blocks of 32 high-rate items or more, depending very much on architecture.
+
+#[derive(Clone, Debug, Copy)]
+pub struct SymFir<'a, const M: usize, const N: usize> {
+ x: [f32; N],
+ taps: &'a [f32; M],
+}
+
+impl<'a, const M: usize, const N: usize> SymFir<'a, M, N> {
+ /// Create a new `SymFir`.
+ ///
+ /// # Args
+ /// * `taps`: one-sided FIR coefficients, expluding center tap, oldest to one-before-center
+ pub fn new(taps: &'a [f32; M]) -> Self {
+ debug_assert!(N >= M * 2);
+ Self { x: [0.0; N], taps }
+ }
+
+ /// Obtain a mutable reference to the input items buffer space.
+ #[inline]
+ pub fn buf_mut(&mut self) -> &mut [f32] {
+ &mut self.x[2 * M - 1..]
+ }
+
+ /// Perform the FIR convolution and yield results iteratively.
+ #[inline]
+ pub fn get(&self) -> impl Iterator<Item = f32> + '_ {
+ self.x.windows(2 * M).map(|x| {
+ let (old, new) = x.split_at(M);
+ old.iter()
+ .zip(new.iter().rev())
+ .zip(self.taps.iter())
+ .map(|((xo, xn), tap)| (xo + xn) * tap)
+ .sum()
+ })
+ }
+
+ /// Move items as new filter state.
+ ///
+ /// # Args
+ /// * `offset`: Keep the `2*M-1` items at `offset` as the new filter state.
+ #[inline]
+ pub fn keep_state(&mut self, offset: usize) {
+ self.x.copy_within(offset..offset + 2 * M - 1, 0);
+ }
+}
+
+// TODO: pub struct SymFirInt<R>, SymFirDec<R>
+
+/// Half band decimator (decimate by two)
+///
+/// The effective number of DSP taps is 4*M - 1.
+///
+/// M: number of taps
+/// N: state size: N = 2*M - 1 + output.len()
+#[derive(Clone, Debug, Copy)]
+pub struct HbfDec<'a, const M: usize, const N: usize> {
+ even: [f32; N], // This is an upper bound to N - M (unstable const expr)
+ odd: SymFir<'a, M, N>,
+}
+
+impl<'a, const M: usize, const N: usize> HbfDec<'a, M, N> {
+ /// Create a new `HbfDec`.
+ ///
+ /// # Args
+ /// * `taps`: The FIR filter coefficients. Only the non-zero (odd) taps
+ /// from oldest to one-before-center. Normalized such that center tap is 1.
+ pub fn new(taps: &'a [f32; M]) -> Self {
+ Self {
+ even: [0.0; N],
+ odd: SymFir::new(taps),
+ }
+ }
+}
+
+impl<'a, const M: usize, const N: usize> Filter for HbfDec<'a, M, N> {
+ type Item = f32;
+
+ #[inline]
+ fn block_size(&self) -> (usize, usize) {
+ (2, 2 * (N - (2 * M - 1)))
+ }
+
+ #[inline]
+ fn response_length(&self) -> usize {
+ 2 * M - 1
+ }
+
+ fn process_block<'b>(
+ &mut self,
+ x: Option<&[Self::Item]>,
+ y: &'b mut [Self::Item],
+ ) -> &'b mut [Self::Item] {
+ let x = x.unwrap_or(y);
+ debug_assert_eq!(x.len() & 1, 0);
+ let k = x.len() / 2;
+ // load input
+ for (xi, (even, odd)) in x.chunks_exact(2).zip(
+ self.even[M - 1..][..k]
+ .iter_mut()
+ .zip(self.odd.buf_mut()[..k].iter_mut()),
+ ) {
+ *even = xi[0];
+ *odd = xi[1];
+ }
+ // compute output
+ for (yi, (even, odd)) in y[..k]
+ .iter_mut()
+ .zip(self.even[..k].iter().zip(self.odd.get()))
+ {
+ *yi = 0.5 * (even + odd);
+ }
+ // keep state
+ self.even.copy_within(k..k + M - 1, 0);
+ self.odd.keep_state(k);
+ &mut y[..k]
+ }
+}
+
+/// Half band interpolator (interpolation rate 2)
+///
+/// The effective number of DSP taps is 4*M - 1.
+///
+/// M: number of taps
+/// N: state size: N = 2*M - 1 + input.len()
+#[derive(Clone, Debug, Copy)]
+pub struct HbfInt<'a, const M: usize, const N: usize> {
+ fir: SymFir<'a, M, N>,
+}
+
+impl<'a, const M: usize, const N: usize> HbfInt<'a, M, N> {
+ /// Non-zero (odd) taps from oldest to one-before-center.
+ /// Normalized such that center tap is 1.
+ pub fn new(taps: &'a [f32; M]) -> Self {
+ Self {
+ fir: SymFir::new(taps),
+ }
+ }
+
+ /// Obtain a mutable reference to the input items buffer space
+ pub fn buf_mut(&mut self) -> &mut [f32] {
+ self.fir.buf_mut()
+ }
+}
+
+impl<'a, const M: usize, const N: usize> Filter for HbfInt<'a, M, N> {
+ type Item = f32;
+
+ #[inline]
+ fn block_size(&self) -> (usize, usize) {
+ (2, 2 * (N - (2 * M - 1)))
+ }
+
+ #[inline]
+ fn response_length(&self) -> usize {
+ 4 * M - 2
+ }
+
+ fn process_block<'b>(
+ &mut self,
+ x: Option<&[Self::Item]>,
+ y: &'b mut [Self::Item],
+ ) -> &'b mut [Self::Item] {
+ debug_assert_eq!(y.len() & 1, 0);
+ let k = y.len() / 2;
+ let x = x.unwrap_or(&y[..k]);
+ // load input
+ self.fir.buf_mut()[..k].copy_from_slice(x);
+ // compute output
+ for (yi, (even, &odd)) in y
+ .chunks_exact_mut(2)
+ .zip(self.fir.get().zip(self.fir.x[M..][..k].iter()))
+ {
+ // Choose the even item to be the interpolated one.
+ // The alternative would have the same response length
+ // but larger latency.
+ yi[0] = even; // interpolated
+ yi[1] = odd; // center tap: identity
+ }
+ // keep state
+ self.fir.keep_state(k);
+ y
+ }
+}
+
+/// Standard/optimal half-band filter cascade taps
+///
+/// * obtained with `2*signal.remez(4*n - 1, bands=(0, .5-df/2, .5+df/2, 1), desired=(1, 0), fs=2, grid_density=512)[:2*n:2]`
+/// * more than 98 dB stop band attenuation (>16 bit)
+/// * 0.4 pass band (relative to lowest sample rate)
+/// * less than 0.001 dB ripple
+/// * linear phase/flat group delay
+/// * rate change up to 2**5 = 32
+/// * lowest rate filter is at 0 index
+/// * use taps 0..n for 2**n interpolation/decimation
+#[allow(clippy::excessive_precision, clippy::type_complexity)]
+pub const HBF_TAPS_98: ([f32; 15], [f32; 6], [f32; 3], [f32; 3], [f32; 2]) = (
+ // n=15 coefficients (effective number of DSP taps 4*15-1 = 59), transition band width df=.2 fs
+ [
+ 7.02144012e-05,
+ -2.43279582e-04,
+ 6.35026936e-04,
+ -1.39782541e-03,
+ 2.74613582e-03,
+ -4.96403839e-03,
+ 8.41806912e-03,
+ -1.35827601e-02,
+ 2.11004053e-02,
+ -3.19267647e-02,
+ 4.77024289e-02,
+ -7.18014345e-02,
+ 1.12942004e-01,
+ -2.03279594e-01,
+ 6.33592923e-01,
+ ],
+ // 6, .47
+ [
+ -0.00086943,
+ 0.00577837,
+ -0.02201674,
+ 0.06357869,
+ -0.16627679,
+ 0.61979312,
+ ],
+ // 3, .754
+ [0.01414651, -0.10439639, 0.59026742],
+ // 3, .877
+ [0.01227974, -0.09930782, 0.58702834],
+ // 2, .94
+ [-0.06291796, 0.5629161],
+);
+
+/// * 137 dB stopband, 2 µdB passband ripple
+/// * otherwise like [`HBF_TAPS_98`].
+#[allow(clippy::excessive_precision, clippy::type_complexity)]
+pub const HBF_TAPS: ([f32; 23], [f32; 9], [f32; 5], [f32; 4], [f32; 3]) = (
+ [
+ 7.60373837e-07,
+ -3.77493552e-06,
+ 1.26458399e-05,
+ -3.43187930e-05,
+ 8.10686834e-05,
+ -1.72971355e-04,
+ 3.40844883e-04,
+ -6.29522605e-04,
+ 1.10128790e-03,
+ -1.83933240e-03,
+ 2.95124855e-03,
+ -4.57290886e-03,
+ 6.87374081e-03,
+ -1.00656245e-02,
+ 1.44199822e-02,
+ -2.03025080e-02,
+ 2.82462314e-02,
+ -3.91128510e-02,
+ 5.44795655e-02,
+ -7.77002648e-02,
+ 1.17523454e-01,
+ -2.06185386e-01,
+ 6.34588718e-01,
+ ],
+ [
+ 3.16797814e-05,
+ -2.92647950e-04,
+ 1.46750943e-03,
+ -5.24297496e-03,
+ 1.49256140e-02,
+ -3.62773016e-02,
+ 8.02858546e-02,
+ -1.80063710e-01,
+ 6.25166059e-01,
+ ],
+ [
+ 7.49291794e-04,
+ -7.57137313e-03,
+ 3.83604467e-02,
+ -1.39644265e-01,
+ 6.08105898e-01,
+ ],
+ [
+ -2.60655954e-03,
+ 2.47360896e-02,
+ -1.21069372e-01,
+ 5.98939836e-01,
+ ],
+ [1.18496474e-02, -9.80471969e-02, 5.86197555e-01],
+);
+
+/// Passband width in units of lowest sample rate
+pub const HBF_PASSBAND: f32 = 0.4;
+
+/// Max low-rate block size (HbfIntCascade input, HbfDecCascade output)
+pub const HBF_CASCADE_BLOCK: usize = 1 << 6;
+
+/// Half-band decimation filter cascade with optimal taps
+///
+/// See [HBF_TAPS].
+/// Only in-place processing is implemented.
+/// Supports rate changes of 1, 2, 4, 8, and 16.
+#[derive(Copy, Clone, Debug)]
+pub struct HbfDecCascade {
+ depth: usize,
+ stages: (
+ HbfDec<'static, { HBF_TAPS.0.len() }, { 2 * HBF_TAPS.0.len() - 1 + HBF_CASCADE_BLOCK }>,
+ HbfDec<'static, { HBF_TAPS.1.len() }, { 2 * HBF_TAPS.1.len() - 1 + HBF_CASCADE_BLOCK * 2 }>,
+ HbfDec<'static, { HBF_TAPS.2.len() }, { 2 * HBF_TAPS.2.len() - 1 + HBF_CASCADE_BLOCK * 4 }>,
+ HbfDec<'static, { HBF_TAPS.3.len() }, { 2 * HBF_TAPS.3.len() - 1 + HBF_CASCADE_BLOCK * 8 }>,
+ ),
+}
+
+impl Default for HbfDecCascade {
+ fn default() -> Self {
+ Self {
+ depth: 0,
+ stages: (
+ HbfDec::new(&HBF_TAPS.0),
+ HbfDec::new(&HBF_TAPS.1),
+ HbfDec::new(&HBF_TAPS.2),
+ HbfDec::new(&HBF_TAPS.3),
+ ),
+ }
+ }
+}
+
+impl HbfDecCascade {
+ #[inline]
+ pub fn set_depth(&mut self, n: usize) {
+ assert!(n <= 4);
+ self.depth = n;
+ }
+
+ #[inline]
+ pub fn depth(&self) -> usize {
+ self.depth
+ }
+}
+
+impl Filter for HbfDecCascade {
+ type Item = f32;
+
+ #[inline]
+ fn block_size(&self) -> (usize, usize) {
+ (
+ 1 << self.depth,
+ match self.depth {
+ 0 => usize::MAX,
+ 1 => self.stages.0.block_size().1,
+ 2 => self.stages.1.block_size().1,
+ 3 => self.stages.2.block_size().1,
+ _ => self.stages.3.block_size().1,
+ },
+ )
+ }
+
+ #[inline]
+ fn response_length(&self) -> usize {
+ let mut n = 0;
+ if self.depth > 3 {
+ n = n / 2 + self.stages.3.response_length();
+ }
+ if self.depth > 2 {
+ n = n / 2 + self.stages.2.response_length();
+ }
+ if self.depth > 1 {
+ n = n / 2 + self.stages.1.response_length();
+ }
+ if self.depth > 0 {
+ n = n / 2 + self.stages.0.response_length();
+ }
+ n
+ }
+
+ fn process_block<'a>(
+ &mut self,
+ x: Option<&[Self::Item]>,
+ mut y: &'a mut [Self::Item],
+ ) -> &'a mut [Self::Item] {
+ if x.is_some() {
+ unimplemented!(); // TODO: pair of intermediate buffers
+ }
+ let n = y.len();
+
+ if self.depth > 3 {
+ y = self.stages.3.process_block(None, y);
+ }
+ if self.depth > 2 {
+ y = self.stages.2.process_block(None, y);
+ }
+ if self.depth > 1 {
+ y = self.stages.1.process_block(None, y);
+ }
+ if self.depth > 0 {
+ y = self.stages.0.process_block(None, y);
+ }
+ debug_assert_eq!(y.len(), n >> self.depth);
+ y
+ }
+}
+
+/// Half-band interpolation filter cascade with optimal taps.
+///
+/// This is a no_alloc version without trait objects.
+/// The price to pay is fixed and maximal memory usage independent
+/// of block size and cascade length.
+///
+/// See [HBF_TAPS].
+/// Only in-place processing is implemented.
+/// Supports rate changes of 1, 2, 4, 8, and 16.
+#[derive(Copy, Clone, Debug)]
+pub struct HbfIntCascade {
+ depth: usize,
+ pub stages: (
+ HbfInt<'static, { HBF_TAPS.0.len() }, { 2 * HBF_TAPS.0.len() - 1 + HBF_CASCADE_BLOCK }>,
+ HbfInt<'static, { HBF_TAPS.1.len() }, { 2 * HBF_TAPS.1.len() - 1 + HBF_CASCADE_BLOCK * 2 }>,
+ HbfInt<'static, { HBF_TAPS.2.len() }, { 2 * HBF_TAPS.2.len() - 1 + HBF_CASCADE_BLOCK * 4 }>,
+ HbfInt<'static, { HBF_TAPS.3.len() }, { 2 * HBF_TAPS.3.len() - 1 + HBF_CASCADE_BLOCK * 8 }>,
+ ),
+}
+
+impl Default for HbfIntCascade {
+ fn default() -> Self {
+ Self {
+ depth: 4,
+ stages: (
+ HbfInt::new(&HBF_TAPS.0),
+ HbfInt::new(&HBF_TAPS.1),
+ HbfInt::new(&HBF_TAPS.2),
+ HbfInt::new(&HBF_TAPS.3),
+ ),
+ }
+ }
+}
+
+impl HbfIntCascade {
+ pub fn set_depth(&mut self, n: usize) {
+ assert!(n <= 4);
+ self.depth = n;
+ }
+
+ pub fn depth(&self) -> usize {
+ self.depth
+ }
+}
+
+impl Filter for HbfIntCascade {
+ type Item = f32;
+
+ #[inline]
+ fn block_size(&self) -> (usize, usize) {
+ (
+ 1 << self.depth,
+ match self.depth {
+ 0 => usize::MAX,
+ 1 => self.stages.0.block_size().1,
+ 2 => self.stages.1.block_size().1,
+ 3 => self.stages.2.block_size().1,
+ _ => self.stages.3.block_size().1,
+ },
+ )
+ }
+
+ #[inline]
+ fn response_length(&self) -> usize {
+ let mut n = 0;
+ if self.depth > 0 {
+ n = 2 * n + self.stages.0.response_length();
+ }
+ if self.depth > 1 {
+ n = 2 * n + self.stages.1.response_length();
+ }
+ if self.depth > 2 {
+ n = 2 * n + self.stages.2.response_length();
+ }
+ if self.depth > 3 {
+ n = 2 * n + self.stages.3.response_length();
+ }
+ n
+ }
+
+ fn process_block<'a>(
+ &mut self,
+ x: Option<&[Self::Item]>,
+ y: &'a mut [Self::Item],
+ ) -> &'a mut [Self::Item] {
+ if x.is_some() {
+ unimplemented!(); // TODO: one intermediate buffer and `y`
+ }
+ // TODO: use buf_mut() and write directly into next filters' input buffer
+
+ let mut n = y.len() >> self.depth;
+ if self.depth > 0 {
+ n = self.stages.0.process_block(None, &mut y[..2 * n]).len();
+ }
+ if self.depth > 1 {
+ n = self.stages.1.process_block(None, &mut y[..2 * n]).len();
+ }
+ if self.depth > 2 {
+ n = self.stages.2.process_block(None, &mut y[..2 * n]).len();
+ }
+ if self.depth > 3 {
+ n = self.stages.3.process_block(None, &mut y[..2 * n]).len();
+ }
+ debug_assert_eq!(n, y.len());
+ &mut y[..n]
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use rustfft::{num_complex::Complex, FftPlanner};
+
+ #[test]
+ fn test() {
+ let mut h = HbfDec::<1, 5>::new(&[0.5]);
+ assert_eq!(h.process_block(None, &mut []), &[]);
+
+ let mut x = [1.0; 8];
+ assert_eq!((2, x.len()), h.block_size());
+ let x = h.process_block(None, &mut x);
+ assert_eq!(x, [0.75, 1.0, 1.0, 1.0]);
+
+ let mut h = HbfDec::<{ HBF_TAPS.3.len() }, 11>::new(&HBF_TAPS.3);
+ let mut x: Vec<_> = (0..8).map(|i| i as f32).collect();
+ assert_eq!((2, x.len()), h.block_size());
+ let x = h.process_block(None, &mut x);
+ println!("{:?}", x);
+ }
+
+ #[test]
+ fn decim() {
+ let mut h = HbfDecCascade::default();
+ h.set_depth(4);
+ assert_eq!(
+ h.block_size(),
+ (1 << h.depth(), HBF_CASCADE_BLOCK << h.depth())
+ );
+ let mut x: Vec<_> = (0..2 << h.depth()).map(|i| i as f32).collect();
+ let x = h.process_block(None, &mut x);
+ println!("{:?}", x);
+ }
+
+ #[test]
+ fn response_length_dec() {
+ let mut h = HbfDecCascade::default();
+ h.set_depth(4);
+ let mut y: Vec<f32> = (0..1 << 10).map(|_| rand::random()).collect();
+ h.process_block(None, &mut y);
+ let mut y = vec![0.0; 1 << 10];
+ let z = h.process_block(None, &mut y);
+ let n = h.response_length();
+ assert!(z[n - 1] != 0.0);
+ assert_eq!(z[n], 0.0);
+ }
+
+ #[test]
+ fn interp() {
+ let mut h = HbfIntCascade::default();
+ h.set_depth(4);
+ assert_eq!(
+ h.block_size(),
+ (1 << h.depth(), HBF_CASCADE_BLOCK << h.depth())
+ );
+ let k = h.block_size().0;
+ let r = h.response_length();
+ let mut x = vec![0.0; (r + 1 + k - 1) / k * k];
+ x[0] = 1.0;
+ let x = h.process_block(None, &mut x);
+ println!("{:?}", x); // interpolator impulse response
+ assert!(x[r] != 0.0);
+ assert_eq!(x[r + 1..], vec![0.0; x.len() - r - 1]);
+
+ let g = (1 << h.depth()) as f32;
+ let mut y = Vec::from_iter(x.iter().map(|&x| Complex { re: x / g, im: 0.0 }));
+ // pad
+ y.resize(5 << 10, Complex::default());
+ FftPlanner::new().plan_fft_forward(y.len()).process(&mut y);
+ // transfer function
+ let p = Vec::from_iter(y.iter().map(|y| 10.0 * y.norm_sqr().log10()));
+ let f = p.len() as f32 / g;
+ // pass band ripple
+ let p_pass = p[..(f * HBF_PASSBAND).floor() as _]
+ .iter()
+ .fold(0.0, |m, p| p.abs().max(m));
+ assert!(p_pass < 2e-6);
+ // stop band attenuation
+ let p_stop = p[(f * (1.0 - HBF_PASSBAND)).ceil() as _..p.len() / 2]
+ .iter()
+ .fold(-200.0, |m, p| p.max(m));
+ assert!(p_stop < -137.0, "{}", p_stop);
+ }
+
+ /// small 32 block size, single stage, 3 mul (11 tap) decimator
+ /// 3.5 insn per input item, > 1 GS/s per core on Skylake
+ #[test]
+ #[ignore]
+ fn insn_dec() {
+ const N: usize = HBF_TAPS.3.len();
+ let mut h = HbfDec::<N, { 2 * N - 1 + (1 << 4) }>::new(&HBF_TAPS.3);
+ let mut x = [9.0; 1 << 5];
+ for _ in 0..1 << 25 {
+ h.process_block(None, &mut x);
+ }
+ }
+
+ /// 1k block size, single stage, 15 mul (59 tap) decimator
+ /// 4.9 insn: > 1 GS/s
+ #[test]
+ #[ignore]
+ fn insn_dec2() {
+ const N: usize = HBF_TAPS.0.len();
+ assert_eq!(N, 15);
+ const M: usize = 1 << 10;
+ let mut h = HbfDec::<N, { 2 * N - 1 + M }>::new(&HBF_TAPS.0);
+ let mut x = [9.0; M];
+ for _ in 0..1 << 20 {
+ h.process_block(None, &mut x);
+ }
+ }
+
+ /// full block size full decimator cascade (depth 4, 1024 items per input block)
+ /// 4.1 insn: > 1 GS/s
+ #[test]
+ #[ignore]
+ fn insn_casc() {
+ let mut x = [9.0; 1 << 10];
+ let mut h = HbfDecCascade::default();
+ h.set_depth(4);
+ for _ in 0..1 << 20 {
+ h.process_block(None, &mut x);
+ }
+ }
+
+ // // sdr crate, setup like insn_dec2()
+ // // 187 insn
+ // #[test]
+ // #[ignore]
+ // fn insn_sdr() {
+ // use sdr::fir;
+ // const N: usize = HBF_TAPS.0.len();
+ // const M: usize = 1 << 10;
+ // let mut taps = [0.0f64; { 4 * N - 1 }];
+ // let (old, new) = taps.split_at_mut(2 * N - 1);
+ // for (tap, (old, new)) in HBF_TAPS.0.iter().zip(
+ // old.iter_mut()
+ // .step_by(2)
+ // .zip(new.iter_mut().rev().step_by(2)),
+ // ) {
+ // *old = (*tap * 0.5).into();
+ // *new = *old;
+ // }
+ // taps[2 * N - 1] = 0.5;
+ // let mut h = fir::FIR::new(&taps, 2, 1);
+ // let x = [9.0; M];
+ // // let mut h1 = HbfDec::<N, { 2 * N - 1 + M }>::new(&HBF_TAPS.0);
+ // // let mut y1 = [0.0; M / 2];
+ // for _ in 0..1 << 16 {
+ // let _y = h.process(&x);
+ // // h1.process_block(Some(&x), &mut y1);
+ // // assert_eq!(y1.len(), y.len());
+ // // assert!(y1.iter().zip(y.iter()).all(|(y1, y)| (y1 - y).abs() < 1e-6));
+ // }
+ // }
+
+ // // // futuredsp crate, setup like insn_dec2()
+ // // // 315 insn
+ // #[test]
+ // #[ignore]
+ // fn insn_futuredsp() {
+ // use futuredsp::{fir::PolyphaseResamplingFirKernel, UnaryKernel};
+ // const N: usize = HBF_TAPS.0.len();
+ // const M: usize = 1 << 10;
+ // let mut taps = [0.0f32; { 4 * N - 1 }];
+ // let (old, new) = taps.split_at_mut(2 * N - 1);
+ // for (tap, (old, new)) in HBF_TAPS.0.iter().zip(
+ // old.iter_mut()
+ // .step_by(2)
+ // .zip(new.iter_mut().rev().step_by(2)),
+ // ) {
+ // *old = *tap * 0.5;
+ // *new = *old;
+ // }
+ // taps[2 * N - 1] = 0.5;
+ // let x = [9.0f32; M];
+ // let mut y = [0.0f32; M];
+ // let fir = PolyphaseResamplingFirKernel::<_, _, _, _>::new(1, 2, taps);
+ // for _ in 0..1 << 14 {
+ // fir.work(&x, &mut y);
+ // }
+ // }
+}
+
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 +159 +
use serde::{Deserialize, Serialize};
+
+use super::{abs, copysign, macc};
+use core::iter::Sum;
+use num_traits::{clamp, Float, NumCast};
+
+/// IIR state and coefficients type.
+///
+/// To represent the IIR state (input and output memory) during the filter update
+/// this contains the three inputs (x0, x1, x2) and the two outputs (y1, y2)
+/// concatenated. Lower indices correspond to more recent samples.
+/// To represent the IIR coefficients, this contains the feed-forward
+/// coefficients (b0, b1, b2) followd by the negated feed-back coefficients
+/// (-a1, -a2), all five normalized such that a0 = 1.
+pub type Vec5<T> = [T; 5];
+
+/// IIR configuration.
+///
+/// Contains the coeeficients `ba`, the output offset `y_offset`, and the
+/// output limits `y_min` and `y_max`. Data is represented in variable precision
+/// floating-point. The dataformat is the same for all internal signals, input
+/// and output.
+///
+/// This implementation achieves several important properties:
+///
+/// * Its transfer function is universal in the sense that any biquadratic
+/// transfer function can be implemented (high-passes, gain limits, second
+/// order integrators with inherent anti-windup, notches etc) without code
+/// changes preserving all features.
+/// * It inherits a universal implementation of "integrator anti-windup", also
+/// and especially in the presence of set-point changes and in the presence
+/// of proportional or derivative gain without any back-off that would reduce
+/// steady-state output range.
+/// * It has universal derivative-kick (undesired, unlimited, and un-physical
+/// amplification of set-point changes by the derivative term) avoidance.
+/// * An offset at the input of an IIR filter (a.k.a. "set-point") is
+/// equivalent to an offset at the output. They are related by the
+/// overall (DC feed-forward) gain of the filter.
+/// * It stores only previous outputs and inputs. These have direct and
+/// invariant interpretation (independent of gains and offsets).
+/// Therefore it can trivially implement bump-less transfer.
+/// * Cascading multiple IIR filters allows stable and robust
+/// implementation of transfer functions beyond bequadratic terms.
+///
+/// # Serialization/Deserialization/Miniconf
+///
+/// `{"y_offset": y_offset, "y_min": y_min, "y_max": y_max, "ba": [b0, b1, b2, a1, a2]}`
+///
+/// * `y0` is the output offset code
+/// * `ym` is the lower saturation limit
+/// * `yM` is the upper saturation limit
+///
+/// IIR filter tap gains (`ba`) are an array `[b0, b1, b2, a1, a2]` such that the
+/// new output is computed as `y0 = a1*y1 + a2*y2 + b0*x0 + b1*x1 + b2*x2`.
+/// The IIR coefficients can be mapped to other transfer function
+/// representations, for example as described in <https://arxiv.org/abs/1508.06319>
+#[derive(Copy, Clone, Debug, Default, Deserialize, Serialize)]
+pub struct IIR<T> {
+ pub ba: Vec5<T>,
+ pub y_offset: T,
+ pub y_min: T,
+ pub y_max: T,
+}
+
+impl<T: Float + Default + Sum<T>> IIR<T> {
+ pub fn new(gain: T, y_min: T, y_max: T) -> Self {
+ Self {
+ ba: [gain, T::default(), T::default(), T::default(), T::default()],
+ y_offset: T::default(),
+ y_min,
+ y_max,
+ }
+ }
+
+ /// Configures IIR filter coefficients for proportional-integral behavior
+ /// with gain limit.
+ ///
+ /// # Arguments
+ ///
+ /// * `kp` - Proportional gain. Also defines gain sign.
+ /// * `ki` - Integral gain at Nyquist. Sign taken from `kp`.
+ /// * `g` - Gain limit.
+ pub fn set_pi(&mut self, kp: T, ki: T, g: T) -> Result<(), &str> {
+ let zero: T = T::default();
+ let one: T = NumCast::from(1.0).unwrap();
+ let two: T = NumCast::from(2.0).unwrap();
+ let ki = copysign(ki, kp);
+ let g = copysign(g, kp);
+ let (a1, b0, b1) = if abs(ki) < T::epsilon() {
+ (zero, kp, zero)
+ } else {
+ let c = if abs(g) < T::epsilon() {
+ one
+ } else {
+ one / (one + ki / g)
+ };
+ let a1 = two * c - one;
+ let b0 = ki * c + kp;
+ let b1 = ki * c - a1 * kp;
+ if abs(b0 + b1) < T::epsilon() {
+ return Err("low integrator gain and/or gain limit");
+ }
+ (a1, b0, b1)
+ };
+ self.ba.copy_from_slice(&[b0, b1, zero, a1, zero]);
+ Ok(())
+ }
+
+ /// Compute the overall (DC feed-forward) gain.
+ pub fn get_k(&self) -> T {
+ self.ba[..3].iter().copied().sum()
+ }
+
+ // /// Compute input-referred (`x`) offset from output (`y`) offset.
+ pub fn get_x_offset(&self) -> Result<T, &str> {
+ let k = self.get_k();
+ if abs(k) < T::epsilon() {
+ Err("k is zero")
+ } else {
+ Ok(self.y_offset / k)
+ }
+ }
+ /// Convert input (`x`) offset to equivalent output (`y`) offset and apply.
+ ///
+ /// # Arguments
+ /// * `xo`: Input (`x`) offset.
+ pub fn set_x_offset(&mut self, xo: T) {
+ self.y_offset = xo * self.get_k();
+ }
+
+ /// Feed a new input value into the filter, update the filter state, and
+ /// return the new output. Only the state `xy` is modified.
+ ///
+ /// # Arguments
+ /// * `xy` - Current filter state.
+ /// * `x0` - New input.
+ pub fn update(&self, xy: &mut Vec5<T>, x0: T, hold: bool) -> T {
+ let n = self.ba.len();
+ debug_assert!(xy.len() == n);
+ // `xy` contains x0 x1 y0 y1 y2
+ // Increment time x1 x2 y1 y2 y3
+ // Shift x1 x1 x2 y1 y2
+ // This unrolls better than xy.rotate_right(1)
+ xy.copy_within(0..n - 1, 1);
+ // Store x0 x0 x1 x2 y1 y2
+ xy[0] = x0;
+ // Compute y0 by multiply-accumulate
+ let y0 = if hold {
+ xy[n / 2 + 1]
+ } else {
+ macc(self.y_offset, xy, &self.ba)
+ };
+ // Limit y0
+ let y0 = clamp(y0, self.y_min, self.y_max);
+ // Store y0 x0 x1 y0 y1 y2
+ xy[n / 2] = y0;
+ y0
+ }
+}
+
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 +
use super::tools::macc_i32;
+use core::f64::consts::PI;
+use serde::{Deserialize, Serialize};
+
+/// Generic vector for integer IIR filter.
+/// This struct is used to hold the x/y input/output data vector or the b/a coefficient
+/// vector.
+pub type Vec5 = [i32; 5];
+
+trait Coeff {
+ /// Lowpass biquad filter using cutoff and sampling frequencies. Taken from:
+ /// https://webaudio.github.io/Audio-EQ-Cookbook/audio-eq-cookbook.html
+ ///
+ /// # Args
+ /// * `f` - Corner frequency, or 3dB cutoff frequency (in units of sample rate).
+ /// This is only accurate for low corner frequencies less than ~0.01.
+ /// * `q` - Quality factor (1/sqrt(2) for critical).
+ /// * `k` - DC gain.
+ ///
+ /// # Returns
+ /// 2nd-order IIR filter coefficients in the form [b0,b1,b2,a1,a2]. a0 is set to -1.
+ fn lowpass(f: f64, q: f64, k: f64) -> Self;
+}
+
+impl Coeff for Vec5 {
+ fn lowpass(f: f64, q: f64, k: f64) -> Self {
+ // 3rd order Taylor approximation of sin and cos.
+ let f = f * 2. * PI;
+ let f2 = f * f * 0.5;
+ let fcos = 1. - f2;
+ let fsin = f * (1. - f2 / 3.);
+ let alpha = fsin / (2. * q);
+ // IIR uses Q2.30 fixed point
+ let a0 = (1. + alpha) / (1 << IIR::SHIFT) as f64;
+ let b0 = (k / 2. * (1. - fcos) / a0 + 0.5) as _;
+ let a1 = (2. * fcos / a0 + 0.5) as _;
+ let a2 = ((alpha - 1.) / a0 + 0.5) as _;
+
+ [b0, 2 * b0, b0, a1, a2]
+ }
+}
+
+/// Integer biquad IIR
+///
+/// See `dsp::iir::IIR` for general implementation details.
+/// Offset and limiting disabled to suit lowpass applications.
+/// Coefficient scaling fixed and optimized.
+#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize)]
+pub struct IIR {
+ pub ba: Vec5,
+ pub y_offset: i32,
+ pub y_min: i32,
+ pub y_max: i32,
+}
+
+impl IIR {
+ /// Coefficient fixed point format: signed Q2.30.
+ /// Tailored to low-passes, PI, II etc.
+ pub const SHIFT: u32 = 30;
+
+ /// Feed a new input value into the filter, update the filter state, and
+ /// return the new output. Only the state `xy` is modified.
+ ///
+ /// # Arguments
+ /// * `xy` - Current filter state.
+ /// * `x0` - New input.
+ pub fn update(&self, xy: &mut Vec5, x0: i32) -> i32 {
+ let n = self.ba.len();
+ debug_assert!(xy.len() == n);
+ // `xy` contains x0 x1 y0 y1 y2
+ // Increment time x1 x2 y1 y2 y3
+ // Shift x1 x1 x2 y1 y2
+ // This unrolls better than xy.rotate_right(1)
+ xy.copy_within(0..n - 1, 1);
+ // Store x0 x0 x1 x2 y1 y2
+ xy[0] = x0;
+ // Compute y0 by multiply-accumulate
+ let y0 = macc_i32(self.y_offset, xy, &self.ba, IIR::SHIFT);
+ // Limit y0
+ let y0 = y0.max(self.y_min).min(self.y_max);
+ // Store y0 x0 x1 y0 y1 y2
+ xy[n / 2] = y0;
+ y0
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::{Coeff, Vec5};
+
+ #[test]
+ fn lowpass_gen() {
+ let ba = Vec5::lowpass(1e-5, 1. / 2f64.sqrt(), 2.);
+ println!("{:?}", ba);
+ }
+}
+
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 +
#![cfg_attr(not(test), no_std)]
+
+mod tools;
+pub use tools::*;
+mod atan2;
+pub use atan2::*;
+mod accu;
+pub use accu::*;
+mod filter;
+pub use filter::*;
+mod complex;
+pub use complex::*;
+mod cossin;
+pub use cossin::*;
+pub mod iir;
+pub mod iir_int;
+mod lockin;
+pub use lockin::*;
+mod lowpass;
+pub use lowpass::*;
+mod pll;
+pub use pll::*;
+mod rpll;
+pub use rpll::*;
+mod unwrap;
+pub use unwrap::*;
+pub mod hbf;
+
+#[cfg(test)]
+pub mod testing;
+
use super::{Complex, ComplexExt, Filter, MulScaled};
+
+#[derive(Copy, Clone, Default)]
+pub struct Lockin<T> {
+ state: [T; 2],
+}
+
+impl<T: Filter> Lockin<T> {
+ /// Update the lockin with a sample taken at a local oscillator IQ value.
+ pub fn update_iq(&mut self, sample: i32, lo: Complex<i32>, k: &T::Config) -> Complex<i32> {
+ let mix = lo.mul_scaled(sample);
+
+ // Filter with the IIR lowpass,
+ // return IQ (in-phase and quadrature) data.
+ Complex {
+ re: self.state[0].update(mix.re, k),
+ im: self.state[1].update(mix.im, k),
+ }
+ }
+
+ /// Update the lockin with a sample taken at a given phase.
+ pub fn update(&mut self, sample: i32, phase: i32, k: &T::Config) -> Complex<i32> {
+ // Get the LO signal for demodulation and mix the sample;
+ self.update_iq(sample, Complex::from_angle(phase), k)
+ }
+}
+
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 +
use crate::Filter;
+
+/// Arbitrary order, high dynamic range, wide coefficient range,
+/// lowpass filter implementation. DC gain is 1.
+///
+/// Type argument N is the filter order. N must be `1` or `2`.
+///
+/// The filter will cleanly saturate towards the `i32` range.
+///
+///
+/// Both filters have been optimized for accuracy, dynamic range, and
+/// speed on Cortex-M7.
+#[derive(Copy, Clone)]
+pub struct Lowpass<const N: usize>(pub(crate) [i64; N]);
+impl<const N: usize> Filter for Lowpass<N> {
+ /// The filter configuration `Config` contains the filter gains.
+ ///
+ /// For the first-order lowpass this is a single element array `[k]` with
+ /// the corner frequency in scaled Q31:
+ /// `k = pi*(1 << 31)*f0/fn` where
+ /// `f0` is the 3dB corner frequency and
+ /// `fn` is the Nyquist frequency.
+ /// The corner frequency is warped in the usual way.
+ ///
+ /// For the second-order lowpass this is `[k**2/(1 << 32), -k/q]` with `q = 1/sqrt(2)`
+ /// for a Butterworth response.
+ ///
+ /// In addition to the poles at the corner frequency the filters have zeros at Nyquist.
+ ///
+ /// The first-order lowpass works fine and accurate for any positive gain
+ /// `1 <= k <= (1 << 31) - 1`.
+ /// The second-order lowpass works and is accurate for
+ /// `1 << 16 <= k <= q*(1 << 31)`.
+ type Config = [i32; N];
+ fn update(&mut self, x: i32, k: &Self::Config) -> i32 {
+ let mut d = x.saturating_sub((self.0[0] >> 32) as i32) as i64 * k[0] as i64;
+ let y;
+ if N >= 2 {
+ d += (self.0[1] >> 32) * k[1] as i64;
+ self.0[1] += d;
+ self.0[0] += self.0[1];
+ y = self.get();
+ // This creates the double Nyquist zero,
+ // compensates the gain lost in the signed i32 as (i32 as i64)*(i64 >> 32)
+ // multiplication while keeping the lowest bit significant, and
+ // copes better with wrap-around than Nyquist averaging.
+ self.0[0] += self.0[1];
+ self.0[1] += d;
+ } else {
+ self.0[0] += d;
+ y = self.get();
+ self.0[0] += d;
+ }
+ y
+ }
+ fn get(&self) -> i32 {
+ (self.0[0] >> 32) as i32
+ }
+ fn set(&mut self, x: i32) {
+ self.0[0] = (x as i64) << 32;
+ }
+}
+
+impl<const N: usize> Default for Lowpass<N> {
+ fn default() -> Self {
+ Self([0; N])
+ }
+}
+
+/// First order lowpass
+pub type Lowpass1 = Lowpass<1>;
+/// Second order lowpass
+pub type Lowpass2 = Lowpass<2>;
+
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 +
use serde::{Deserialize, Serialize};
+
+/// Type-II, sampled phase, discrete time PLL
+///
+/// This PLL tracks the frequency and phase of an input signal with respect to the sampling clock.
+/// The open loop transfer function is I^2,I from input phase to output phase and P,I from input
+/// phase to output frequency.
+///
+/// The transfer functions (for phase and frequency) contain an additional zero at Nyquist.
+///
+/// The PLL locks to any frequency (i.e. it locks to the alias in the first Nyquist zone) and is
+/// stable for any gain (1 <= shift <= 30). It has a single parameter that determines the loop
+/// bandwidth in octave steps. The gain can be changed freely between updates.
+///
+/// The frequency and phase settling time constants for a frequency/phase jump are `1 << shift`
+/// update cycles. The loop bandwidth is `1/(2*pi*(1 << shift))` in units of the sample rate.
+/// While the phase is being settled after settling the frequency, there is a typically very
+/// small frequency overshoot.
+///
+/// All math is naturally wrapping 32 bit integer. Phase and frequency are understood modulo that
+/// overflow in the first Nyquist zone. Expressing the IIR equations in other ways (e.g. single
+/// (T)-DF-{I,II} biquad/IIR) would break on overflow (i.e. every cycle).
+///
+/// There are no floating point rounding errors here. But there is integer quantization/truncation
+/// error of the `shift` lowest bits leading to a phase offset for very low gains. Truncation
+/// bias is applied. Rounding is "half up". The phase truncation error can be removed very
+/// efficiently by dithering.
+///
+/// This PLL does not unwrap phase slips accumulated during (frequency) lock acquisition.
+/// This can and should be implemented elsewhere by unwrapping and scaling the input phase
+/// and un-scaling and wrapping output phase and frequency. This then affects dynamic range,
+/// gain, and noise accordingly.
+///
+/// The extension to I^3,I^2,I behavior to track chirps phase-accurately or to i64 data to
+/// increase resolution for extremely narrowband applications is obvious.
+#[derive(Copy, Clone, Default, Deserialize, Serialize)]
+pub struct PLL {
+ // last input phase
+ x: i32,
+ // filtered frequency
+ f: i64,
+ // filtered output phase
+ y: i64,
+}
+
+impl PLL {
+ /// Update the PLL with a new phase sample. This needs to be called (sampled) periodically.
+ /// The signal's phase/frequency is reconstructed relative to the sampling period.
+ ///
+ /// Args:
+ /// * `x`: New input phase sample or None if a sample has been missed.
+ /// * `k`: Feedback gain.
+ ///
+ /// Returns:
+ /// A tuple of instantaneous phase and frequency estimates.
+ pub fn update(&mut self, x: Option<i32>, k: i32) -> (i32, i32) {
+ if let Some(x) = x {
+ let dx = x.wrapping_sub(self.x);
+ self.x = x;
+ let df = dx.wrapping_sub((self.f >> 32) as i32) as i64 * k as i64;
+ self.f = self.f.wrapping_add(df);
+ let f = (self.f >> 32) as i32;
+ self.y = self.y.wrapping_add(self.f);
+ self.f = self.f.wrapping_add(df);
+ let dy = x.wrapping_sub((self.y >> 32) as i32) as i64 * k as i64;
+ self.y = self.y.wrapping_add(dy);
+ let y = (self.y >> 32) as i32;
+ self.y = self.y.wrapping_add(dy);
+ (y, f)
+ } else {
+ self.y = self.y.wrapping_add(self.f);
+ self.x = self.x.wrapping_add((self.f >> 32) as i32);
+ ((self.y >> 32) as _, (self.f >> 32) as _)
+ }
+ }
+
+ /// Return the current phase estimate
+ pub fn phase(&self) -> i32 {
+ (self.y >> 32) as _
+ }
+
+ /// Return the current frequency estimate
+ pub fn frequency(&self) -> i32 {
+ (self.f >> 32) as _
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ #[test]
+ fn mini() {
+ let mut p = PLL::default();
+ let k = 1 << 24;
+ let (y, f) = p.update(Some(0x10000), k);
+ assert_eq!(y, 0x1ff);
+ assert_eq!(f, 0x100);
+ }
+
+ #[test]
+ fn converge() {
+ let mut p = PLL::default();
+ let k = 1 << 24;
+ let f0 = 0x71f63049_i32;
+ let n = 1 << 14;
+ let mut x = 0i32;
+ for i in 0..n {
+ x = x.wrapping_add(f0);
+ let (y, f) = p.update(Some(x), k);
+ if i > n / 4 {
+ assert_eq!(f.wrapping_sub(f0).abs() <= 1, true);
+ }
+ if i > n / 2 {
+ assert_eq!(y.wrapping_sub(x).abs() <= 1, true);
+ }
+ }
+ }
+}
+
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 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +
/// Reciprocal PLL.
+///
+/// Consumes noisy, quantized timestamps of a reference signal and reconstructs
+/// the phase and frequency of the update() invocations with respect to (and in units of
+/// 1 << 32 of) that reference.
+/// In other words, `update()` rate ralative to reference frequency,
+/// `u32::MAX` corresponding to both being equal.
+#[derive(Copy, Clone, Default)]
+pub struct RPLL {
+ dt2: u32, // 1 << dt2 is the counter rate to update() rate ratio
+ x: i32, // previous timestamp
+ ff: u32, // current frequency estimate from frequency loop
+ f: u32, // current frequency estimate from both frequency and phase loop
+ y: i32, // current phase estimate
+}
+
+impl RPLL {
+ /// Create a new RPLL instance.
+ ///
+ /// Args:
+ /// * dt2: inverse update() rate. 1 << dt2 is the counter rate to update() rate ratio.
+ ///
+ /// Returns:
+ /// Initialized RPLL instance.
+ pub fn new(dt2: u32) -> Self {
+ Self {
+ dt2,
+ ..Default::default()
+ }
+ }
+
+ /// Advance the RPLL and optionally supply a new timestamp.
+ ///
+ /// Args:
+ /// * input: Optional new timestamp (wrapping around at the i32 boundary).
+ /// There can be at most one timestamp per `update()` cycle (1 << dt2 counter cycles).
+ /// * shift_frequency: Frequency lock settling time. 1 << shift_frequency is
+ /// frequency lock settling time in counter periods. The settling time must be larger
+ /// than the signal period to lock to.
+ /// * shift_phase: Phase lock settling time. Usually one less than
+ /// `shift_frequency` (see there).
+ ///
+ /// Returns:
+ /// A tuple containing the current phase (wrapping at the i32 boundary, pi) and
+ /// frequency.
+ pub fn update(
+ &mut self,
+ input: Option<i32>,
+ shift_frequency: u32,
+ shift_phase: u32,
+ ) -> (i32, u32) {
+ debug_assert!(shift_frequency >= self.dt2);
+ debug_assert!(shift_phase >= self.dt2);
+ // Advance phase
+ self.y = self.y.wrapping_add(self.f as i32);
+ if let Some(x) = input {
+ // Reference period in counter cycles
+ let dx = x.wrapping_sub(self.x);
+ // Store timestamp for next time.
+ self.x = x;
+ // Phase using the current frequency estimate
+ let p_sig_64 = self.ff as u64 * dx as u64;
+ // Add half-up rounding bias and apply gain/attenuation
+ let p_sig =
+ ((p_sig_64 + (1u32 << (shift_frequency - 1)) as u64) >> shift_frequency) as u32;
+ // Reference phase (1 << dt2 full turns) with gain/attenuation applied
+ let p_ref = 1u32 << (32 + self.dt2 - shift_frequency);
+ // Update frequency lock
+ self.ff = self.ff.wrapping_add(p_ref.wrapping_sub(p_sig));
+ // Time in counter cycles between timestamp and "now"
+ let dt = (x.wrapping_neg() & ((1 << self.dt2) - 1)) as u32;
+ // Reference phase estimate "now"
+ let y_ref = (self.f >> self.dt2).wrapping_mul(dt) as i32;
+ // Phase error with gain
+ let dy = y_ref.wrapping_sub(self.y) >> (shift_phase - self.dt2);
+ // Current frequency estimate from frequency lock and phase error
+ self.f = self.ff.wrapping_add(dy as u32);
+ }
+ (self.y, self.f)
+ }
+
+ /// Return the current phase estimate
+ pub fn phase(&self) -> i32 {
+ self.y
+ }
+
+ /// Return the current frequency estimate
+ pub fn frequency(&self) -> u32 {
+ self.f
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::RPLL;
+ use ndarray::prelude::*;
+ use rand::{prelude::*, rngs::StdRng};
+ use std::vec::Vec;
+
+ #[test]
+ fn make() {
+ let _ = RPLL::new(8);
+ }
+
+ struct Harness {
+ rpll: RPLL,
+ shift_frequency: u32,
+ shift_phase: u32,
+ noise: i32,
+ period: i32,
+ next: i32,
+ next_noisy: i32,
+ time: i32,
+ rng: StdRng,
+ }
+
+ impl Harness {
+ fn default() -> Self {
+ Self {
+ rpll: RPLL::new(8),
+ shift_frequency: 9,
+ shift_phase: 8,
+ noise: 0,
+ period: 333,
+ next: 111,
+ next_noisy: 111,
+ time: 0,
+ rng: StdRng::seed_from_u64(42),
+ }
+ }
+
+ fn run(&mut self, n: usize) -> (Vec<f32>, Vec<f32>) {
+ assert!(self.period >= 1 << self.rpll.dt2);
+ assert!(self.period < 1 << self.shift_frequency);
+ assert!(self.period < 1 << self.shift_phase + 1);
+
+ let mut y = Vec::<f32>::new();
+ let mut f = Vec::<f32>::new();
+ for _ in 0..n {
+ let timestamp = if self.time - self.next_noisy >= 0 {
+ assert!(self.time - self.next_noisy < 1 << self.rpll.dt2);
+ self.next = self.next.wrapping_add(self.period);
+ let timestamp = self.next_noisy;
+ let p_noise = self.rng.gen_range(-self.noise..=self.noise);
+ self.next_noisy = self.next.wrapping_add(p_noise);
+ Some(timestamp)
+ } else {
+ None
+ };
+ let (yi, fi) = self
+ .rpll
+ .update(timestamp, self.shift_frequency, self.shift_phase);
+
+ let y_ref = (self.time.wrapping_sub(self.next) as i64 * (1i64 << 32)
+ / self.period as i64) as i32;
+ // phase error
+ y.push(yi.wrapping_sub(y_ref) as f32 / 2f32.powi(32));
+
+ let p_ref = 1 << 32 + self.rpll.dt2;
+ let p_sig = fi as u64 * self.period as u64;
+ // relative frequency error
+ f.push(
+ p_sig.wrapping_sub(p_ref) as i64 as f32 / 2f32.powi(32 + self.rpll.dt2 as i32),
+ );
+
+ // advance time
+ self.time = self.time.wrapping_add(1 << self.rpll.dt2);
+ }
+ (y, f)
+ }
+
+ fn measure(&mut self, n: usize, limits: [f32; 4]) {
+ let t_settle = (1 << self.shift_frequency - self.rpll.dt2 + 4)
+ + (1 << self.shift_phase - self.rpll.dt2 + 4);
+ self.run(t_settle);
+
+ let (y, f) = self.run(n);
+ let y = Array::from(y);
+ let f = Array::from(f);
+ // println!("{:?} {:?}", f, y);
+
+ let fm = f.mean().unwrap();
+ let fs = f.std_axis(Axis(0), 0.).into_scalar();
+ let ym = y.mean().unwrap();
+ let ys = y.std_axis(Axis(0), 0.).into_scalar();
+
+ println!("f: {:.2e}±{:.2e}; y: {:.2e}±{:.2e}", fm, fs, ym, ys);
+
+ let m = [fm, fs, ym, ys];
+
+ print!("relative: ");
+ for i in 0..m.len() {
+ let rel = m[i].abs() / limits[i].abs();
+ print!("{:.2e} ", rel);
+ assert!(
+ rel <= 1.,
+ "idx {}, have |{:.2e}| > limit {:.2e}",
+ i,
+ m[i],
+ limits[i]
+ );
+ }
+ println!();
+ }
+ }
+
+ #[test]
+ fn default() {
+ let mut h = Harness::default();
+
+ h.measure(1 << 16, [1e-11, 4e-8, 2e-8, 2e-8]);
+ }
+
+ #[test]
+ fn noisy() {
+ let mut h = Harness::default();
+ h.noise = 10;
+ h.shift_frequency = 23;
+ h.shift_phase = 22;
+
+ h.measure(1 << 16, [3e-9, 3e-6, 4e-4, 2e-4]);
+ }
+
+ #[test]
+ fn narrow_fast() {
+ let mut h = Harness::default();
+ h.period = 990;
+ h.next = 351;
+ h.next_noisy = h.next;
+ h.noise = 5;
+ h.shift_frequency = 23;
+ h.shift_phase = 22;
+
+ h.measure(1 << 16, [2e-9, 2e-6, 1e-3, 1e-4]);
+ }
+
+ #[test]
+ fn narrow_slow() {
+ let mut h = Harness::default();
+ h.period = 1818181;
+ h.next = 35281;
+ h.next_noisy = h.next;
+ h.noise = 1000;
+ h.shift_frequency = 23;
+ h.shift_phase = 22;
+
+ h.measure(1 << 16, [2e-5, 6e-4, 2e-4, 2e-4]);
+ }
+
+ #[test]
+ fn wide_fast() {
+ let mut h = Harness::default();
+ h.period = 990;
+ h.next = 351;
+ h.next_noisy = h.next;
+ h.noise = 5;
+ h.shift_frequency = 10;
+ h.shift_phase = 9;
+
+ h.measure(1 << 16, [5e-7, 3e-2, 2e-5, 2e-2]);
+ }
+
+ #[test]
+ fn wide_slow() {
+ let mut h = Harness::default();
+ h.period = 1818181;
+ h.next = 35281;
+ h.next_noisy = h.next;
+ h.noise = 1000;
+ h.shift_frequency = 21;
+ h.shift_phase = 20;
+
+ h.measure(1 << 16, [2e-4, 6e-3, 2e-4, 2e-3]);
+ }
+
+ #[test]
+ fn batch_fast_narrow() {
+ let mut h = Harness::default();
+ h.rpll.dt2 = 8 + 3;
+ h.period = 2431;
+ h.next = 35281;
+ h.next_noisy = h.next;
+ h.noise = 100;
+ h.shift_frequency = 23;
+ h.shift_phase = 23;
+
+ h.measure(1 << 16, [1e-8, 2e-5, 6e-4, 6e-4]);
+ }
+}
+
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 +
use core::ops::{Add, Mul, Neg};
+
+pub fn abs<T>(x: T) -> T
+where
+ T: PartialOrd + Default + Neg<Output = T>,
+{
+ if x >= T::default() {
+ x
+ } else {
+ -x
+ }
+}
+
+// These are implemented here because core::f32 doesn't have them (yet).
+// They are naive and don't handle inf/nan.
+// `compiler-intrinsics`/llvm should have better (robust, universal, and
+// faster) implementations.
+
+pub fn copysign<T>(x: T, y: T) -> T
+where
+ T: PartialOrd + Default + Neg<Output = T>,
+{
+ if (x >= T::default() && y >= T::default()) || (x <= T::default() && y <= T::default()) {
+ x
+ } else {
+ -x
+ }
+}
+
+// Multiply-accumulate vectors `x` and `a`.
+//
+// A.k.a. dot product.
+// Rust/LLVM optimize this nicely.
+pub fn macc<T>(y0: T, x: &[T], a: &[T]) -> T
+where
+ T: Add<Output = T> + Mul<Output = T> + Copy,
+{
+ x.iter()
+ .zip(a)
+ .map(|(x, a)| *x * *a)
+ .fold(y0, |y, xa| y + xa)
+}
+
+pub fn macc_i32(y0: i32, x: &[i32], a: &[i32], shift: u32) -> i32 {
+ // Rounding bias, half up
+ let y0 = ((y0 as i64) << shift) + (1 << (shift - 1));
+ let y = x
+ .iter()
+ .zip(a)
+ .map(|(x, a)| *x as i64 * *a as i64)
+ .fold(y0, |y, xa| y + xa);
+ (y >> shift) as i32
+}
+
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 +159 +160 +
use core::cmp::PartialOrd;
+use num_traits::{identities::Zero, ops::wrapping::WrappingSub};
+use serde::{Deserialize, Serialize};
+
+/// Subtract `y - x` with signed overflow.
+///
+/// This is very similar to `i32::overflowing_sub(y, x)` except that the
+/// overflow indicator is not a boolean but the signum of the overflow.
+/// Additionally it's typically faster.
+///
+/// Returns:
+/// A tuple containg the (wrapped) difference `y - x` and the signum of the
+/// overflow.
+#[inline(always)]
+pub fn overflowing_sub<T>(y: T, x: T) -> (T, i32)
+where
+ T: WrappingSub + Zero + PartialOrd,
+{
+ let delta = y.wrapping_sub(&x);
+ let wrap = (delta >= T::zero()) as i32 - (y >= x) as i32;
+ (delta, wrap)
+}
+
+/// Combine high and low i32 into a single downscaled i32, saturating monotonically.
+///
+/// Args:
+/// `lo`: LSB i32 to scale down by `shift` and range-extend with `hi`
+/// `hi`: MSB i32 to scale up and extend `lo` with. Output will be clipped if
+/// `hi` exceeds the output i32 range.
+/// `shift`: Downscale `lo` by that many bits. Values from 1 to 32 inclusive
+/// are valid.
+pub fn saturating_scale(lo: i32, hi: i32, shift: u32) -> i32 {
+ debug_assert!(shift > 0);
+ debug_assert!(shift <= 32);
+ let hi_range = -1 << (shift - 1);
+ if hi <= hi_range {
+ i32::MIN - hi_range
+ } else if -hi <= hi_range {
+ hi_range - i32::MIN
+ } else {
+ (lo >> shift) + (hi << (32 - shift))
+ }
+}
+
+/// Overflow unwrapper.
+///
+/// This is unwrapping as in the phase and overflow unwrapping context, not
+/// unwrapping as in the `Result`/`Option` context.
+#[derive(Copy, Clone, Default, Deserialize, Serialize)]
+pub struct Unwrapper<T> {
+ // last input
+ x: T,
+ // last wraps
+ w: i32,
+}
+
+impl<T> Unwrapper<T>
+where
+ T: WrappingSub + Zero + PartialOrd + Copy,
+{
+ /// Unwrap a new sample from a sequence and update the unwrapper state.
+ ///
+ /// Args:
+ /// * `x`: New sample
+ ///
+ /// Returns:
+ /// A tuple containing the (wrapped) difference `x - x_old` and the
+ /// signed number of wraps accumulated by the new sample.
+ pub fn update(&mut self, x: T) -> (T, i32) {
+ let (dx, dw) = overflowing_sub(x, self.x);
+ self.x = x;
+ self.w = self.w.wrapping_add(dw);
+ (dx, self.w)
+ }
+
+ /// Return the current number of wraps
+ pub fn wraps(&self) -> i32 {
+ self.w
+ }
+
+ /// Return the last known phase
+ pub fn phase(&self) -> T {
+ self.x
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ #[test]
+ fn overflowing_sub_correctness() {
+ for (x0, x1, v) in [
+ (0i32, 0i32, 0i32),
+ (0, 1, 0),
+ (0, -1, 0),
+ (1, 0, 0),
+ (-1, 0, 0),
+ (0, 0x7fff_ffff, 0),
+ (-1, 0x7fff_ffff, -1),
+ (-2, 0x7fff_ffff, -1),
+ (-1, -0x8000_0000, 0),
+ (0, -0x8000_0000, 0),
+ (1, -0x8000_0000, 1),
+ (-0x6000_0000, 0x6000_0000, -1),
+ (0x6000_0000, -0x6000_0000, 1),
+ (-0x4000_0000, 0x3fff_ffff, 0),
+ (-0x4000_0000, 0x4000_0000, -1),
+ (-0x4000_0000, 0x4000_0001, -1),
+ (0x4000_0000, -0x3fff_ffff, 0),
+ (0x4000_0000, -0x4000_0000, 0),
+ (0x4000_0000, -0x4000_0001, 1),
+ ]
+ .iter()
+ {
+ let (dx, w) = overflowing_sub(*x1, *x0);
+ assert_eq!(*v, w, " = overflowing_sub({:#x}, {:#x})", *x0, *x1);
+ let (dx0, w0) = x1.overflowing_sub(*x0);
+ assert_eq!(w0, w != 0);
+ assert_eq!(dx, dx0);
+ }
+ }
+
+ #[test]
+ fn saturating_scale_correctness() {
+ let shift = 8;
+ for (lo, hi, res) in [
+ (0i32, 0i32, 0i32),
+ (0, 1, 0x0100_0000),
+ (0, -1, -0x0100_0000),
+ (0x100, 0, 1),
+ (-1 << 31, 0, -1 << 23),
+ (0x7fffffff, 0, 0x007f_ffff),
+ (0x7fffffff, 1, 0x0017f_ffff),
+ (-0x7fffffff, -1, -0x0180_0000),
+ (0x1234_5600, 0x7f, 0x7f12_3456),
+ (0x1234_5600, -0x7f, -0x7f00_0000 + 0x12_3456),
+ (0, 0x7f, 0x7f00_0000),
+ (0, 0x80, 0x7fff_ff80),
+ (0, -0x7f, -0x7f00_0000),
+ (0, -0x80, -0x7fff_ff80),
+ (0x7fff_ffff, 0x7f, 0x7f7f_ffff),
+ (-0x8000_0000, 0x7f, 0x7e80_0000),
+ (-0x8000_0000, -0x7f, -0x7f80_0000),
+ (0x7fff_ffff, -0x7f, -0x7e80_0001),
+ (0x100, 0x7f, 0x7f00_0001),
+ (0, -0x80, -0x7fff_ff80),
+ (-1 << 31, 0x80, 0x7fff_ff80),
+ (-1 << 31, -0x80, -0x7fff_ff80),
+ ]
+ .iter()
+ {
+ let s = saturating_scale(*lo, *hi, shift);
+ assert_eq!(
+ *res, s,
+ "{:#x} != {:#x} = saturating_scale({:#x}, {:#x}, {:#x})",
+ *res, s, *lo, *hi, shift
+ );
+ }
+ }
+}
+
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 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499 +500 +501 +502 +503 +504 +505 +506 +507 +508 +509 +510 +511 +512 +513 +514 +515 +516 +517 +518 +519 +520 +521 +522 +523 +524 +525 +526 +527 +528 +529 +530 +531 +532 +533 +534 +535 +536 +537 +
//! # Lockin
+//!
+//! The `lockin` application implements a lock-in amplifier using either an external or internally
+//! generated reference.
+//!
+//! ## Features
+//! * Up to 800 kHz sampling
+//! * Up to 400 kHz modulation frequency
+//! * Supports internal and external reference sources:
+//! 1. Internal: Generate reference internally and output on one of the channel outputs
+//! 2. External: Reciprocal PLL, reference input applied to DI0.
+//! * Adjustable PLL and locking time constants
+//! * Adjustable phase offset and harmonic index
+//! * Run-time configurable output modes (in-phase, quadrature, magnitude, log2 power, phase, frequency)
+//! * Input/output data streamng via UDP
+//!
+//! ## Settings
+//! Refer to the [Settings] structure for documentation of run-time configurable settings for this
+//! application.
+//!
+//! ## Telemetry
+//! Refer to [Telemetry] for information about telemetry reported by this application.
+//!
+//! ## Livestreaming
+//! This application streams raw ADC and DAC data over UDP. Refer to
+//! [stabilizer::net::data_stream](../stabilizer/net/data_stream/index.html) for more information.
+#![deny(warnings)]
+#![no_std]
+#![no_main]
+
+use core::{
+ convert::TryFrom,
+ mem::MaybeUninit,
+ sync::atomic::{fence, Ordering},
+};
+
+use fugit::ExtU64;
+use mutex_trait::prelude::*;
+
+use idsp::{Accu, Chain, Complex, ComplexExt, Filter, Lockin, Lowpass, RPLL};
+
+use stabilizer::{
+ hardware::{
+ self,
+ adc::{Adc0Input, Adc1Input, AdcCode},
+ afe::Gain,
+ dac::{Dac0Output, Dac1Output, DacCode},
+ hal,
+ input_stamper::InputStamper,
+ serial_terminal::SerialTerminal,
+ signal_generator,
+ timers::SamplingTimer,
+ DigitalInput0, DigitalInput1, SystemTimer, Systick, AFE0, AFE1,
+ },
+ net::{
+ data_stream::{FrameGenerator, StreamFormat, StreamTarget},
+ miniconf::Tree,
+ serde::{Deserialize, Serialize},
+ telemetry::{Telemetry, TelemetryBuffer},
+ NetworkState, NetworkUsers,
+ },
+};
+
+// The logarithm of the number of samples in each batch process. This corresponds with 2^3 samples
+// per batch = 8 samples
+const BATCH_SIZE_LOG2: u32 = 3;
+const BATCH_SIZE: usize = 1 << BATCH_SIZE_LOG2;
+
+// The logarithm of the number of 100MHz timer ticks between each sample. This corresponds with a
+// sampling period of 2^7 = 128 ticks. At 100MHz, 10ns per tick, this corresponds to a sampling
+// period of 1.28 uS or 781.25 KHz.
+const SAMPLE_TICKS_LOG2: u32 = 7;
+const SAMPLE_TICKS: u32 = 1 << SAMPLE_TICKS_LOG2;
+
+#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
+enum Conf {
+ /// Output the lockin magnitude.
+ Magnitude,
+ /// Output the phase of the lockin
+ Phase,
+ /// Output the lockin reference frequency as a sinusoid
+ ReferenceFrequency,
+ /// Output the logarithmic power of the lockin
+ LogPower,
+ /// Output the in-phase component of the lockin signal.
+ InPhase,
+ /// Output the quadrature component of the lockin signal.
+ Quadrature,
+ /// Output the lockin internal modulation frequency as a sinusoid
+ Modulation,
+}
+
+#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
+enum LockinMode {
+ /// Utilize an internally generated reference for demodulation
+ Internal,
+ /// Utilize an external modulation signal supplied to DI0
+ External,
+}
+
+#[derive(Copy, Clone, Debug, Tree)]
+pub struct Settings {
+ /// Configure the Analog Front End (AFE) gain.
+ ///
+ /// # Path
+ /// `afe/<n>`
+ ///
+ /// * `<n>` specifies which channel to configure. `<n>` := [0, 1]
+ ///
+ /// # Value
+ /// Any of the variants of [Gain] enclosed in double quotes.
+ #[tree]
+ afe: [Gain; 2],
+
+ /// Specifies the operational mode of the lockin.
+ ///
+ /// # Path
+ /// `lockin_mode`
+ ///
+ /// # Value
+ /// One of the variants of [LockinMode] enclosed in double quotes.
+ lockin_mode: LockinMode,
+
+ /// Specifis the PLL time constant.
+ ///
+ /// # Path
+ /// `pll_tc/<n>`
+ ///
+ /// * `<n>` specifies which channel to configure. `<n>` := [0, 1]
+ ///
+ /// # Value
+ /// The PLL time constant exponent (1-31).
+ pll_tc: [u32; 2],
+
+ /// Specifies the lockin lowpass gains.
+ ///
+ /// # Path
+ /// `lockin_k`
+ ///
+ /// # Value
+ /// The lockin low-pass coefficients. See [`idsp::Lowpass`] for determining them.
+ lockin_k: <Lowpass<2> as Filter>::Config,
+
+ /// Specifies which harmonic to use for the lockin.
+ ///
+ /// # Path
+ /// `lockin_harmonic`
+ ///
+ /// # Value
+ /// Harmonic index of the LO. -1 to _de_modulate the fundamental (complex conjugate)
+ lockin_harmonic: i32,
+
+ /// Specifies the LO phase offset.
+ ///
+ /// # Path
+ /// `lockin_phase`
+ ///
+ /// # Value
+ /// Demodulation LO phase offset. Units are in terms of i32, where [i32::MIN] is equivalent to
+ /// -pi and [i32::MAX] is equivalent to +pi.
+ lockin_phase: i32,
+
+ /// Specifies DAC output mode.
+ ///
+ /// # Path
+ /// `output_conf/<n>`
+ ///
+ /// * `<n>` specifies which channel to configure. `<n>` := [0, 1]
+ ///
+ /// # Value
+ /// One of the variants of [Conf] enclosed in double quotes.
+ #[tree]
+ output_conf: [Conf; 2],
+
+ /// Specifies the telemetry output period in seconds.
+ ///
+ /// # Path
+ /// `telemetry_period`
+ ///
+ /// # Value
+ /// Any non-zero value less than 65536.
+ telemetry_period: u16,
+
+ /// Specifies the target for data livestreaming.
+ ///
+ /// # Path
+ /// `stream_target`
+ ///
+ /// # Value
+ /// See [StreamTarget#miniconf]
+ stream_target: StreamTarget,
+}
+
+impl Default for Settings {
+ fn default() -> Self {
+ Self {
+ afe: [Gain::G1; 2],
+
+ lockin_mode: LockinMode::External,
+
+ pll_tc: [21, 21], // frequency and phase settling time (log2 counter cycles)
+
+ lockin_k: [0x8_0000, -0x400_0000], // lockin lowpass gains
+ lockin_harmonic: -1, // Harmonic index of the LO: -1 to _de_modulate the fundamental (complex conjugate)
+ lockin_phase: 0, // Demodulation LO phase offset
+
+ output_conf: [Conf::InPhase, Conf::Quadrature],
+ // The default telemetry period in seconds.
+ telemetry_period: 10,
+
+ stream_target: StreamTarget::default(),
+ }
+ }
+}
+
+#[rtic::app(device = stabilizer::hardware::hal::stm32, peripherals = true, dispatchers=[DCMI, JPEG, SDMMC])]
+mod app {
+ use super::*;
+
+ #[monotonic(binds = SysTick, default = true, priority = 2)]
+ type Monotonic = Systick;
+
+ #[shared]
+ struct Shared {
+ usb_terminal: SerialTerminal,
+ network: NetworkUsers<Settings, Telemetry, 2>,
+ settings: Settings,
+ telemetry: TelemetryBuffer,
+ }
+
+ #[local]
+ struct Local {
+ sampling_timer: SamplingTimer,
+ digital_inputs: (DigitalInput0, DigitalInput1),
+ timestamper: InputStamper,
+ afes: (AFE0, AFE1),
+ adcs: (Adc0Input, Adc1Input),
+ dacs: (Dac0Output, Dac1Output),
+ pll: RPLL,
+ lockin: Lockin<Chain<2, Lowpass<2>>>,
+ signal_generator: signal_generator::SignalGenerator,
+ generator: FrameGenerator,
+ cpu_temp_sensor: stabilizer::hardware::cpu_temp_sensor::CpuTempSensor,
+ }
+
+ #[init]
+ fn init(c: init::Context) -> (Shared, Local, init::Monotonics) {
+ let clock = SystemTimer::new(|| monotonics::now().ticks() as u32);
+
+ // Configure the microcontroller
+ let (mut stabilizer, _pounder) = hardware::setup::setup(
+ c.core,
+ c.device,
+ clock,
+ BATCH_SIZE,
+ SAMPLE_TICKS,
+ );
+
+ let mut network = NetworkUsers::new(
+ stabilizer.net.stack,
+ stabilizer.net.phy,
+ clock,
+ env!("CARGO_BIN_NAME"),
+ stabilizer.net.mac_address,
+ option_env!("BROKER").unwrap_or("mqtt"),
+ );
+
+ let generator = network.configure_streaming(StreamFormat::AdcDacData);
+
+ let shared = Shared {
+ network,
+ usb_terminal: stabilizer.usb_serial,
+ telemetry: TelemetryBuffer::default(),
+ settings: Settings::default(),
+ };
+
+ 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 mut local = Local {
+ sampling_timer: stabilizer.adc_dac_timer,
+ digital_inputs: stabilizer.digital_inputs,
+ afes: stabilizer.afes,
+ adcs: stabilizer.adcs,
+ dacs: stabilizer.dacs,
+ timestamper: stabilizer.timestamper,
+
+ pll: RPLL::new(SAMPLE_TICKS_LOG2 + BATCH_SIZE_LOG2),
+ lockin: Lockin::default(),
+ signal_generator: signal_generator::SignalGenerator::new(
+ signal_config,
+ ),
+
+ generator,
+ cpu_temp_sensor: stabilizer.temperature_sensor,
+ };
+
+ // Enable ADC/DAC events
+ local.adcs.0.start();
+ local.adcs.1.start();
+ local.dacs.0.start();
+ local.dacs.1.start();
+
+ // Spawn a settings and telemetry update for default settings.
+ settings_update::spawn().unwrap();
+ telemetry::spawn().unwrap();
+ ethernet_link::spawn().unwrap();
+ start::spawn_after(100.millis()).unwrap();
+
+ // Start recording digital input timestamps.
+ stabilizer.timestamp_timer.start();
+
+ // Enable the timestamper.
+ local.timestamper.start();
+
+ (shared, local, init::Monotonics(stabilizer.systick))
+ }
+
+ #[task(priority = 1, local=[sampling_timer])]
+ fn start(c: start::Context) {
+ // Start sampling ADCs and DACs.
+ c.local.sampling_timer.start();
+ }
+
+ /// Main DSP processing routine.
+ ///
+ /// See `dual-iir` for general notes on processing time and timing.
+ ///
+ /// This is an implementation of a externally (DI0) referenced PLL lockin on the ADC0 signal.
+ /// It outputs either I/Q or power/phase on DAC0/DAC1. Data is normalized to full scale.
+ /// PLL bandwidth, filter bandwidth, slope, and x/y or power/phase post-filters are available.
+ #[task(binds=DMA1_STR4, shared=[settings, telemetry], local=[adcs, dacs, lockin, timestamper, pll, generator, signal_generator], priority=3)]
+ #[link_section = ".itcm.process"]
+ fn process(c: process::Context) {
+ let process::SharedResources {
+ settings,
+ telemetry,
+ } = c.shared;
+
+ let process::LocalResources {
+ timestamper,
+ adcs: (adc0, adc1),
+ dacs: (dac0, dac1),
+ pll,
+ lockin,
+ signal_generator,
+ generator,
+ } = c.local;
+
+ (settings, telemetry).lock(|settings, telemetry| {
+ let (reference_phase, reference_frequency) =
+ match settings.lockin_mode {
+ LockinMode::External => {
+ let timestamp =
+ timestamper.latest_timestamp().unwrap_or(None); // Ignore data from timer capture overflows.
+ let (pll_phase, pll_frequency) = pll.update(
+ timestamp.map(|t| t as i32),
+ settings.pll_tc[0],
+ settings.pll_tc[1],
+ );
+ (pll_phase, (pll_frequency >> BATCH_SIZE_LOG2) as i32)
+ }
+ LockinMode::Internal => {
+ // Reference phase and frequency are known.
+ (1i32 << 30, 1i32 << (32 - BATCH_SIZE_LOG2))
+ }
+ };
+
+ let sample_frequency =
+ reference_frequency.wrapping_mul(settings.lockin_harmonic);
+ let sample_phase = settings.lockin_phase.wrapping_add(
+ reference_phase.wrapping_mul(settings.lockin_harmonic),
+ );
+
+ (adc0, adc1, dac0, dac1).lock(|adc0, adc1, dac0, dac1| {
+ let adc_samples = [adc0, adc1];
+ let mut dac_samples = [dac0, dac1];
+
+ // Preserve instruction and data ordering w.r.t. DMA flag access.
+ fence(Ordering::SeqCst);
+
+ let output: Complex<i32> = adc_samples[0]
+ .iter()
+ // Zip in the LO phase.
+ .zip(Accu::new(sample_phase, sample_frequency))
+ // Convert to signed, MSB align the ADC sample, update the Lockin (demodulate, filter)
+ .map(|(&sample, phase)| {
+ let s = (sample as i16 as i32) << 16;
+ lockin.update(s, phase, &settings.lockin_k)
+ })
+ // Decimate
+ .last()
+ .unwrap()
+ * 2; // Full scale assuming the 2f component is gone.
+
+ // Convert to DAC data.
+ for (channel, samples) in dac_samples.iter_mut().enumerate() {
+ for sample in samples.iter_mut() {
+ let value = match settings.output_conf[channel] {
+ Conf::Magnitude => output.abs_sqr() as i32 >> 16,
+ Conf::Phase => output.arg() >> 16,
+ Conf::LogPower => output.log2() << 8,
+ Conf::ReferenceFrequency => {
+ reference_frequency >> 16
+ }
+ Conf::InPhase => output.re >> 16,
+ Conf::Quadrature => output.im >> 16,
+
+ Conf::Modulation => {
+ signal_generator.next().unwrap() as i32
+ }
+ };
+
+ *sample = DacCode::from(value as i16).0;
+ }
+ }
+
+ // Stream the data.
+ const N: usize = BATCH_SIZE * core::mem::size_of::<i16>()
+ / core::mem::size_of::<MaybeUninit<u8>>();
+ generator.add(|buf| {
+ for (data, buf) in adc_samples
+ .iter()
+ .chain(dac_samples.iter())
+ .zip(buf.chunks_exact_mut(N))
+ {
+ let data = unsafe {
+ core::slice::from_raw_parts(
+ data.as_ptr() as *const MaybeUninit<u8>,
+ N,
+ )
+ };
+ buf.copy_from_slice(data)
+ }
+ N * 4
+ });
+
+ // Update telemetry measurements.
+ telemetry.adcs =
+ [AdcCode(adc_samples[0][0]), AdcCode(adc_samples[1][0])];
+
+ telemetry.dacs =
+ [DacCode(dac_samples[0][0]), DacCode(dac_samples[1][0])];
+
+ // Preserve instruction and data ordering w.r.t. DMA flag access.
+ fence(Ordering::SeqCst);
+ });
+ });
+ }
+
+ #[idle(shared=[network, usb_terminal])]
+ fn idle(mut c: idle::Context) -> ! {
+ loop {
+ match c.shared.network.lock(|net| net.update()) {
+ NetworkState::SettingsChanged(_path) => {
+ settings_update::spawn().unwrap()
+ }
+ NetworkState::Updated => {}
+ NetworkState::NoChange => {
+ // We can't sleep if USB is not in suspend.
+ if c.shared
+ .usb_terminal
+ .lock(|terminal| terminal.usb_is_suspended())
+ {
+ cortex_m::asm::wfi();
+ }
+ }
+ }
+ }
+ }
+
+ #[task(priority = 1, local=[afes], shared=[network, settings])]
+ fn settings_update(mut c: settings_update::Context) {
+ let settings = c.shared.network.lock(|net| *net.miniconf.settings());
+ c.shared.settings.lock(|current| *current = settings);
+
+ c.local.afes.0.set_gain(settings.afe[0]);
+ c.local.afes.1.set_gain(settings.afe[1]);
+
+ let target = settings.stream_target.into();
+ c.shared.network.lock(|net| net.direct_stream(target));
+ }
+
+ #[task(priority = 1, local=[digital_inputs, cpu_temp_sensor], shared=[network, settings, telemetry])]
+ fn telemetry(mut c: telemetry::Context) {
+ let mut telemetry: TelemetryBuffer =
+ c.shared.telemetry.lock(|telemetry| *telemetry);
+
+ telemetry.digital_inputs = [
+ c.local.digital_inputs.0.is_high(),
+ c.local.digital_inputs.1.is_high(),
+ ];
+
+ let (gains, telemetry_period) = c
+ .shared
+ .settings
+ .lock(|settings| (settings.afe, settings.telemetry_period));
+
+ c.shared.network.lock(|net| {
+ net.telemetry.publish(&telemetry.finalize(
+ gains[0],
+ gains[1],
+ c.local.cpu_temp_sensor.get_temperature().unwrap(),
+ ))
+ });
+
+ // Schedule the telemetry task in the future.
+ telemetry::Monotonic::spawn_after((telemetry_period as u64).secs())
+ .unwrap();
+ }
+
+ #[task(priority = 1, shared=[usb_terminal])]
+ fn usb(mut c: usb::Context) {
+ // Handle the USB serial terminal.
+ c.shared.usb_terminal.lock(|usb| usb.process());
+
+ // Schedule to run this task every 10 milliseconds.
+ usb::spawn_after(10u64.millis()).unwrap();
+ }
+
+ #[task(priority = 1, shared=[network])]
+ fn ethernet_link(mut c: ethernet_link::Context) {
+ c.shared.network.lock(|net| net.processor.handle_link());
+ ethernet_link::Monotonic::spawn_after(1.secs()).unwrap();
+ }
+
+ #[task(binds = ETH, priority = 1)]
+ fn eth(_: eth::Context) {
+ unsafe { hal::ethernet::interrupt_handler() }
+ }
+}
+
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 +
use crate::{Error, Increment, Key, Metadata, TreeDeserialize, TreeKey, TreeSerialize};
+use serde::{de::Deserialize, Deserializer, Serialize, Serializer};
+
+/// Returns the number of digits required to format an integer less than `x`.
+const fn digits(x: usize) -> usize {
+ let mut max = 10;
+ let mut digits = 1;
+
+ while x > max {
+ max *= 10;
+ digits += 1;
+ }
+ digits
+}
+
+// Y >= 2
+macro_rules! depth {
+ ($($y:literal)+) => {$(
+ impl<T: TreeKey<{$y - 1}>, const N: usize> TreeKey<$y> for [T; N] {
+ fn name_to_index(value: &str) -> Option<usize> {
+ value.parse().ok()
+ }
+ fn traverse_by_key<K, F, E>(mut keys: K, mut func: F) -> Result<usize, Error<E>>
+ where
+ K: Iterator,
+ K::Item: Key,
+ F: FnMut(usize, &str) -> Result<(), E>,
+ {
+ let key = keys.next().ok_or(Error::TooShort(0))?;
+ let index = key.find::<$y, Self>().ok_or(Error::NotFound(1))?;
+ if index >= N {
+ return Err(Error::NotFound(1));
+ }
+ func(index, itoa::Buffer::new().format(index))?;
+ T::traverse_by_key(keys, func).increment()
+ }
+
+ fn metadata() -> Metadata {
+ let mut meta = T::metadata();
+
+ meta.max_length += digits(N);
+ meta.max_depth += 1;
+ meta.count *= N;
+
+ meta
+ }
+ }
+
+ impl<T: TreeSerialize<{$y - 1}>, const N: usize> TreeSerialize<$y> for [T; N] {
+ fn serialize_by_key<K, S>(&self, mut keys: K, ser: S) -> Result<usize, Error<S::Error>>
+ where
+ K: Iterator,
+ K::Item: Key,
+ S: Serializer,
+ {
+ let key = keys.next().ok_or(Error::TooShort(0))?;
+ let index = key.find::<$y, Self>().ok_or(Error::NotFound(1))?;
+ let item = self.get(index).ok_or(Error::NotFound(1))?;
+ item.serialize_by_key(keys, ser).increment()
+ }
+ }
+
+ impl<'de, T: TreeDeserialize<'de, {$y - 1}>, const N: usize> TreeDeserialize<'de, $y> for [T; N] {
+ fn deserialize_by_key<K, D>(&mut self, mut keys: K, de: D) -> Result<usize, Error<D::Error>>
+ where
+ K: Iterator,
+ K::Item: Key,
+ D: Deserializer<'de>,
+ {
+ let key = keys.next().ok_or(Error::TooShort(0))?;
+ let index = key.find::<$y, Self>().ok_or(Error::NotFound(1))?;
+ let item = self.get_mut(index).ok_or(Error::NotFound(1))?;
+ item.deserialize_by_key(keys, de).increment()
+ }
+ }
+ )+}
+}
+depth!(2 3 4 5 6 7 8);
+
+// Y == 1
+impl<T, const N: usize> TreeKey for [T; N] {
+ fn name_to_index(value: &str) -> Option<usize> {
+ value.parse().ok()
+ }
+
+ fn traverse_by_key<K, F, E>(mut keys: K, mut func: F) -> Result<usize, Error<E>>
+ where
+ K: Iterator,
+ K::Item: Key,
+ F: FnMut(usize, &str) -> Result<(), E>,
+ {
+ let key = keys.next().ok_or(Error::TooShort(0))?;
+ match key.find::<1, Self>() {
+ Some(index) if index < N => {
+ func(index, itoa::Buffer::new().format(index))?;
+ Ok(1)
+ }
+ _ => Err(Error::NotFound(1)),
+ }
+ }
+
+ fn metadata() -> Metadata {
+ Metadata {
+ max_length: digits(N),
+ max_depth: 1,
+ count: N,
+ }
+ }
+}
+
+impl<T: Serialize, const N: usize> TreeSerialize for [T; N] {
+ fn serialize_by_key<K, S>(&self, mut keys: K, ser: S) -> Result<usize, Error<S::Error>>
+ where
+ K: Iterator,
+ K::Item: Key,
+ S: Serializer,
+ {
+ let key = keys.next().ok_or(Error::TooShort(0))?;
+ let index = key.find::<1, Self>().ok_or(Error::NotFound(1))?;
+ let item = self.get(index).ok_or(Error::NotFound(1))?;
+ // Precedence
+ if keys.next().is_some() {
+ Err(Error::TooLong(1))
+ } else {
+ item.serialize(ser)?;
+ Ok(1)
+ }
+ }
+}
+
+impl<'de, T: Deserialize<'de>, const N: usize> TreeDeserialize<'de> for [T; N] {
+ fn deserialize_by_key<K, D>(&mut self, mut keys: K, de: D) -> Result<usize, Error<D::Error>>
+ where
+ K: Iterator,
+ K::Item: Key,
+ D: Deserializer<'de>,
+ {
+ let key = keys.next().ok_or(Error::TooShort(0))?;
+ let index = key.find::<1, Self>().ok_or(Error::NotFound(1))?;
+ let item = self.get_mut(index).ok_or(Error::NotFound(1))?;
+ // Precedence
+ if keys.next().is_some() {
+ Err(Error::TooLong(1))
+ } else {
+ *item = T::deserialize(de)?;
+ Ok(1)
+ }
+ }
+}
+
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 +
use crate::{Error, TreeKey};
+use core::{fmt::Write, marker::PhantomData};
+
+/// An iterator over the paths in a `TreeKey`.
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct PathIter<'a, M: ?Sized, const Y: usize, P> {
+ /// Zero-size markers to allow being generic over M/P (by constraining the type parameters).
+ m: PhantomData<M>,
+ p: PhantomData<P>,
+
+ /// The iteration state.
+ ///
+ /// It contains the current field/element index at each path hierarchy level
+ /// and needs to be at least as large as the maximum path depth.
+ state: [usize; Y],
+
+ /// The remaining length of the iterator.
+ ///
+ /// It is used to provide an exact and trusted [Iterator::size_hint] ([core::iter::TrustedLen]).
+ ///
+ /// It may be None to indicate unknown length.
+ count: Option<usize>,
+
+ /// The separator before each name.
+ separator: &'a str,
+}
+
+impl<'a, M, const Y: usize, P> PathIter<'a, M, Y, P>
+where
+ M: TreeKey<Y> + ?Sized,
+{
+ pub(crate) fn new(separator: &'a str) -> Self {
+ let meta = M::metadata();
+ assert!(Y >= meta.max_depth);
+ let mut s = Self::new_unchecked(separator);
+ s.count = Some(meta.count);
+ s
+ }
+
+ pub(crate) fn new_unchecked(separator: &'a str) -> Self {
+ Self {
+ count: None,
+ separator,
+ state: [0; Y],
+ m: PhantomData,
+ p: PhantomData,
+ }
+ }
+}
+
+impl<'a, M, const Y: usize, P> Iterator for PathIter<'a, M, Y, P>
+where
+ M: TreeKey<Y> + ?Sized,
+ P: Write + Default,
+{
+ type Item = Result<P, core::fmt::Error>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ let mut path = P::default();
+
+ loop {
+ return match M::path(self.state, &mut path, self.separator) {
+ // Out of valid indices at the root: iteration done
+ Err(Error::NotFound(1)) => {
+ debug_assert_eq!(self.count.unwrap_or_default(), 0);
+ None
+ }
+ // Node not found at depth: reset current index, increment parent index,
+ // then retry path()
+ Err(Error::NotFound(depth @ 2..)) => {
+ path = P::default();
+ self.state[depth - 1] = 0;
+ self.state[depth - 2] += 1;
+ continue;
+ }
+ // Found a leaf at the root: leaf Option/newtype
+ // Since there is no way to end iteration by hoping for `NotFound` on a leaf Option,
+ // we force the count to Some(0) and trigger on that.
+ Ok(0) => {
+ if self.count == Some(0) {
+ None
+ } else {
+ debug_assert_eq!(self.count.unwrap_or(1), 1);
+ self.count = Some(0);
+ Some(Ok(path))
+ }
+ }
+ // Non-root leaf: advance index at current depth
+ Ok(depth) => {
+ self.count = self.count.map(|c| c - 1);
+ self.state[depth - 1] += 1;
+ Some(Ok(path))
+ }
+ // `core::fmt::Write` error (e.g. heapless::String capacity limit).
+ Err(Error::Inner(e @ core::fmt::Error)) => Some(Err(e)),
+ // * NotFound(0) Not having consumed any name/index, the only possible case
+ // is a leaf (e.g. `Option` or newtype), those however can not return `NotFound`.
+ // * TooShort is excluded by construction.
+ // * No other errors are returned by traverse_by_key()/path()
+ _ => unreachable!(),
+ };
+ }
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ (self.count.unwrap_or_default(), self.count)
+ }
+}
+
+impl<'a, M, const Y: usize, P> core::iter::FusedIterator for PathIter<'a, M, Y, P>
+where
+ M: TreeKey<Y>,
+ P: Write + Default,
+{
+}
+
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 +
use crate::{Error, TreeDeserialize, TreeSerialize};
+use serde_json_core::{de, ser};
+
+/// Miniconf with "JSON and `/`".
+///
+/// Access items with `'/'` as path separator and JSON (from `serde-json-core`)
+/// as serialization/deserialization payload format.
+pub trait JsonCoreSlash<'de, const Y: usize = 1>:
+ TreeSerialize<Y> + TreeDeserialize<'de, Y>
+{
+ /// Update an element by path.
+ ///
+ /// # Args
+ /// * `path` - The path to the element. Everything before the first `'/'` is ignored.
+ /// * `data` - The serialized data making up the content.
+ ///
+ /// # Returns
+ /// The number of bytes consumed from `data` or an [Error].
+ fn set_json(&mut self, path: &str, data: &'de [u8]) -> Result<usize, Error<de::Error>>;
+
+ /// Retrieve a serialized value by path.
+ ///
+ /// # Args
+ /// * `path` - The path to the element.
+ /// * `data` - The buffer to serialize the data into.
+ ///
+ /// # Returns
+ /// The number of bytes used in the `data` buffer or an [Error].
+ fn get_json(&self, path: &str, data: &mut [u8]) -> Result<usize, Error<ser::Error>>;
+
+ /// Update an element by indices.
+ ///
+ /// # Args
+ /// * `indices` - The indices to the element. Everything before the first `'/'` is ignored.
+ /// * `data` - The serialized data making up the content.
+ ///
+ /// # Returns
+ /// The number of bytes consumed from `data` or an [Error].
+ fn set_json_by_index(
+ &mut self,
+ indices: &[usize],
+ data: &'de [u8],
+ ) -> Result<usize, Error<de::Error>>;
+
+ /// Retrieve a serialized value by indices.
+ ///
+ /// # Args
+ /// * `indices` - The indices to the element.
+ /// * `data` - The buffer to serialize the data into.
+ ///
+ /// # Returns
+ /// The number of bytes used in the `data` buffer or an [Error].
+ fn get_json_by_index(
+ &self,
+ indices: &[usize],
+ data: &mut [u8],
+ ) -> Result<usize, Error<ser::Error>>;
+}
+
+impl<'de, T: TreeSerialize<Y> + TreeDeserialize<'de, Y>, const Y: usize> JsonCoreSlash<'de, Y>
+ for T
+{
+ fn set_json(&mut self, path: &str, data: &'de [u8]) -> Result<usize, Error<de::Error>> {
+ let mut de = de::Deserializer::new(data);
+ self.deserialize_by_key(path.split('/').skip(1), &mut de)?;
+ de.end().map_err(Error::PostDeserialization)
+ }
+
+ fn get_json(&self, path: &str, data: &mut [u8]) -> Result<usize, Error<ser::Error>> {
+ let mut ser = ser::Serializer::new(data);
+ self.serialize_by_key(path.split('/').skip(1), &mut ser)?;
+ Ok(ser.end())
+ }
+
+ fn set_json_by_index(
+ &mut self,
+ indices: &[usize],
+ data: &'de [u8],
+ ) -> Result<usize, Error<de::Error>> {
+ let mut de = de::Deserializer::new(data);
+ self.deserialize_by_key(indices.iter().copied(), &mut de)?;
+ de.end().map_err(Error::PostDeserialization)
+ }
+
+ fn get_json_by_index(
+ &self,
+ indices: &[usize],
+ data: &mut [u8],
+ ) -> Result<usize, Error<ser::Error>> {
+ let mut ser = ser::Serializer::new(data);
+ self.serialize_by_key(indices.iter().copied(), &mut ser)?;
+ Ok(ser.end())
+ }
+}
+
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 +
#![cfg_attr(not(any(test, doctest, feature = "std")), no_std)]
+#![cfg_attr(feature = "json-core", doc = include_str!("../README.md"))]
+#![cfg_attr(not(feature = "json-core"), doc = "Miniconf")]
+#![deny(rust_2018_compatibility)]
+#![deny(rust_2018_idioms)]
+#![warn(missing_docs)]
+#![forbid(unsafe_code)]
+
+pub use miniconf_derive::*;
+mod tree;
+pub use tree::*;
+mod array;
+pub use array::*;
+mod iter;
+pub use iter::*;
+mod option;
+pub use option::*;
+
+#[cfg(feature = "json-core")]
+mod json_core;
+#[cfg(feature = "json-core")]
+pub use json_core::*;
+
+#[cfg(feature = "mqtt-client")]
+mod mqtt_client;
+#[cfg(feature = "mqtt-client")]
+pub use mqtt_client::*;
+
+#[cfg(feature = "mqtt-client")]
+pub use minimq;
+
+// re-export for proc-macro
+#[doc(hidden)]
+pub use serde::{de::DeserializeOwned, Deserialize, Deserializer, Serialize, Serializer};
+
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 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499 +500 +501 +502 +503 +504 +505 +506 +507 +508 +509 +510 +511 +512 +513 +514 +515 +516 +517 +518 +519 +520 +521 +522 +523 +524 +525 +526 +527 +528 +529 +530 +531 +532 +533 +534 +535 +536 +537 +538 +539 +540 +541 +542 +543 +544 +545 +546 +547 +548 +549 +550 +551 +552 +553 +554 +555 +556 +557 +558 +559 +560 +561 +562 +563 +564 +565 +566 +567 +568 +569 +570 +571 +572 +573 +574 +575 +576 +577 +578 +579 +580 +581 +582 +583 +584 +585 +586 +587 +588 +589 +590 +591 +592 +593 +594 +595 +596 +597 +598 +599 +600 +601 +602 +603 +604 +605 +606 +607 +608 +609 +610 +611 +612 +613 +614 +615 +616 +617 +618 +619 +620 +621 +622 +623 +624 +625 +626 +627 +628 +629 +630 +631 +632 +633 +634 +635 +636 +
use crate::{Error, JsonCoreSlash, TreeKey};
+use heapless::{String, Vec};
+use minimq::{
+ embedded_nal::TcpClientStack,
+ embedded_time,
+ types::{SubscriptionOptions, TopicFilter},
+ DeferredPublication, Publication, QoS,
+};
+
+use embedded_io::Write;
+
+// The maximum topic length of any settings path.
+const MAX_TOPIC_LENGTH: usize = 128;
+
+// The maximum amount of correlation data that will be cached for listing. This is set to function
+// with the Miniconf python client (i.e. 32 bytes can encode a UUID).
+const MAX_CD_LENGTH: usize = 32;
+
+// The delay after not receiving messages after initial connection that settings will be
+// republished.
+const REPUBLISH_TIMEOUT_SECONDS: u32 = 2;
+
+type Iter<M, const Y: usize> = crate::PathIter<'static, M, Y, String<MAX_TOPIC_LENGTH>>;
+
+mod sm {
+ use super::{Iter, TreeKey, REPUBLISH_TIMEOUT_SECONDS};
+ use minimq::embedded_time::{self, duration::Extensions, Instant};
+ use smlang::statemachine;
+
+ statemachine! {
+ transitions: {
+ *Initial + Connected = ConnectedToBroker,
+ ConnectedToBroker + IndicatedLife = PendingSubscribe,
+
+ // After initial subscriptions, we start a timeout to republish all settings.
+ PendingSubscribe + Subscribed / start_republish_timeout = PendingRepublish,
+
+ // Settings republish can be completed any time after subscription.
+ PendingRepublish + StartRepublish / start_republish = RepublishingSettings,
+ RepublishingSettings + StartRepublish / start_republish = RepublishingSettings,
+ Active + StartRepublish / start_republish = RepublishingSettings,
+
+ // After republishing settings, we are in an idle "active" state.
+ RepublishingSettings + Complete = Active,
+
+ // All states transition back to `initial` on reset.
+ _ + Reset = Initial,
+ }
+ }
+
+ pub struct Context<C: embedded_time::Clock, M: TreeKey<Y>, const Y: usize> {
+ clock: C,
+ timeout: Option<Instant<C>>,
+ pub republish_state: Iter<M, Y>,
+ }
+
+ impl<C: embedded_time::Clock, M: TreeKey<Y>, const Y: usize> Context<C, M, Y> {
+ pub fn new(clock: C) -> Self {
+ Self {
+ clock,
+ timeout: None,
+ // Skip redundant check (done comprehensively in `MqttClient::new()`)
+ republish_state: M::iter_paths_unchecked("/"),
+ }
+ }
+
+ pub fn republish_has_timed_out(&self) -> bool {
+ if let Some(timeout) = self.timeout {
+ self.clock.try_now().unwrap() > timeout
+ } else {
+ false
+ }
+ }
+ }
+
+ impl<C: embedded_time::Clock, M: TreeKey<Y>, const Y: usize> StateMachineContext
+ for Context<C, M, Y>
+ {
+ fn start_republish_timeout(&mut self) {
+ self.timeout
+ .replace(self.clock.try_now().unwrap() + REPUBLISH_TIMEOUT_SECONDS.seconds());
+ }
+
+ fn start_republish(&mut self) {
+ // Skip redundant check (done comprehensively in `MqttClient::new()`)
+ self.republish_state = M::iter_paths_unchecked("/");
+ }
+ }
+}
+
+enum Command<'a> {
+ List,
+ Get { path: &'a str },
+ Set { path: &'a str, value: &'a [u8] },
+}
+
+impl<'a> Command<'a> {
+ fn from_message(topic: &'a str, value: &'a [u8]) -> Result<Self, ()> {
+ let path = topic.strip_prefix('/').unwrap_or(topic);
+
+ if path == "list" {
+ Ok(Command::List)
+ } else {
+ match path.strip_prefix("settings") {
+ Some(path) => {
+ if value.is_empty() {
+ Ok(Command::Get { path })
+ } else {
+ Ok(Command::Set { path, value })
+ }
+ }
+ _ => Err(()),
+ }
+ }
+ }
+}
+
+struct ListCache {
+ topic: String<MAX_TOPIC_LENGTH>,
+ correlation_data: Option<Vec<u8, MAX_CD_LENGTH>>,
+}
+
+/// MQTT settings interface.
+///
+/// # Design
+/// The MQTT client places the [TreeKey] paths `<path>` at the MQTT `<prefix>/settings/<path>` topic,
+/// where `<prefix>` is provided in the client constructor.
+///
+/// It publishes its alive-ness as a `1` to `<prefix>/alive` and sets a will to publish `0` there when
+/// it is disconnected.
+///
+/// # Limitations
+/// The MQTT client logs failures to subscribe to the settings topic, but does not re-attempt to
+/// connect to it when errors occur.
+///
+/// The client only supports paths up to `MAX_TOPIC_LENGTH = 128` byte length.
+/// Re-publication timeout is fixed to `REPUBLISH_TIMEOUT_SECONDS = 2` seconds.
+///
+/// # Example
+/// ```
+/// use miniconf::{MqttClient, Tree};
+///
+/// #[derive(Tree, Clone, Default)]
+/// struct Settings {
+/// foo: bool,
+/// }
+///
+/// let mut buffer = [0u8; 1024];
+/// let localhost: minimq::embedded_nal::IpAddr = "127.0.0.1".parse().unwrap();
+/// let mut client: MqttClient<'_, _, _, _, minimq::broker::IpBroker, 1> = MqttClient::new(
+/// std_embedded_nal::Stack::default(),
+/// "quartiq/application/12345", // prefix
+/// std_embedded_time::StandardClock::default(),
+/// Settings::default(),
+/// minimq::ConfigBuilder::new(localhost.into(), &mut buffer).keepalive_interval(60),
+/// )
+/// .unwrap();
+///
+/// client
+/// .handled_update(|path, old_settings, new_settings| {
+/// if new_settings.foo {
+/// return Err("Foo!");
+/// }
+/// *old_settings = new_settings.clone();
+/// Ok(())
+/// })
+/// .unwrap();
+/// ```
+pub struct MqttClient<'buf, Settings, Stack, Clock, Broker, const Y: usize>
+where
+ Settings: TreeKey<Y> + Clone,
+ Stack: TcpClientStack,
+ Clock: embedded_time::Clock,
+ Broker: minimq::Broker,
+{
+ mqtt: minimq::Minimq<'buf, Stack, Clock, Broker>,
+ settings: Settings,
+ state: sm::StateMachine<sm::Context<Clock, Settings, Y>>,
+ prefix: String<MAX_TOPIC_LENGTH>,
+ listing_state: Option<(ListCache, Iter<Settings, Y>)>,
+}
+
+impl<'buf, Settings, Stack, Clock, Broker, const Y: usize>
+ MqttClient<'buf, Settings, Stack, Clock, Broker, Y>
+where
+ for<'de> Settings: JsonCoreSlash<'de, Y> + Clone,
+ Stack: TcpClientStack,
+ Clock: embedded_time::Clock + Clone,
+ Broker: minimq::Broker,
+{
+ /// Construct a new MQTT settings interface.
+ ///
+ /// # Args
+ /// * `stack` - The network stack to use for communication.
+ /// * `prefix` - The MQTT device prefix to use for this device.
+ /// * `clock` - The clock for managing the MQTT connection.
+ /// * `settings` - The initial settings values.
+ /// * `config` - The configuration of the MQTT client.
+ pub fn new(
+ stack: Stack,
+ prefix: &str,
+ clock: Clock,
+ settings: Settings,
+ config: minimq::ConfigBuilder<'buf, Broker>,
+ ) -> Result<Self, minimq::ProtocolError> {
+ // Configure a will so that we can indicate whether or not we are connected.
+ let prefix = String::from(prefix);
+ let mut connection_topic = prefix.clone();
+ connection_topic.push_str("/alive").unwrap();
+ let will = minimq::Will::new(&connection_topic, b"0", &[])?
+ .retained()
+ .qos(QoS::AtMostOnce);
+
+ let config = config.autodowngrade_qos().will(will)?;
+
+ let mqtt = minimq::Minimq::new(stack, clock.clone(), config);
+
+ let meta = Settings::metadata().separator("/");
+ assert!(prefix.len() + "/settings".len() + meta.max_length <= MAX_TOPIC_LENGTH);
+
+ Ok(Self {
+ mqtt,
+ state: sm::StateMachine::new(sm::Context::new(clock)),
+ settings,
+ prefix,
+ listing_state: None,
+ })
+ }
+
+ fn handle_listing(&mut self) {
+ let Some((cache, iter)) = &mut self.listing_state else {
+ return;
+ };
+
+ while self.mqtt.client().can_publish(QoS::AtLeastOnce) {
+ // Note(unwrap): Publishing should not fail because `can_publish()` was checked before
+ // attempting this publish.
+ let (code, path) = iter
+ .next()
+ .map(|path| (ResponseCode::Continue, path.unwrap()))
+ .unwrap_or((ResponseCode::Ok, String::new()));
+
+ let props = [code.as_user_property()];
+ let outgoing = Publication::new(path.as_bytes())
+ .topic(&cache.topic)
+ .properties(&props)
+ .qos(QoS::AtLeastOnce);
+
+ let outgoing = if let Some(cd) = &cache.correlation_data {
+ outgoing.correlate(cd)
+ } else {
+ outgoing
+ };
+
+ let publication = match outgoing.finish() {
+ Ok(response) => response,
+ Err(e) => {
+ // Something went wrong. Abort the listing.
+ log::error!("Listing failed to build response: {e:?}");
+ self.listing_state.take();
+ return;
+ }
+ };
+
+ // Note(unwrap) We already checked that we can publish earlier.
+ self.mqtt.client().publish(publication).unwrap();
+
+ // If we're done with listing, bail out of the loop.
+ if code != ResponseCode::Continue {
+ self.listing_state.take();
+ break;
+ }
+ }
+ }
+
+ fn handle_republish(&mut self) {
+ while self.mqtt.client().can_publish(QoS::AtMostOnce) {
+ let Some(topic) = self.state.context_mut().republish_state.next() else {
+ // If we got here, we completed iterating over the topics and published them all.
+ self.state.process_event(sm::Events::Complete).unwrap();
+ break;
+ };
+
+ let topic = topic.unwrap();
+
+ let mut prefixed_topic = self.prefix.clone();
+ prefixed_topic
+ .push_str("/settings")
+ .and_then(|_| prefixed_topic.push_str(&topic))
+ .unwrap();
+
+ // Note(unwrap): This should not fail because `can_publish()` was checked before
+ // attempting this publish.
+ match self.mqtt.client().publish(
+ // If the topic is not present, we'll fail to serialize the setting into the
+ // payload and will never publish. The iterator has already incremented, so this is
+ // acceptable.
+ DeferredPublication::new(|buf| self.settings.get_json(&topic, buf))
+ .topic(&prefixed_topic)
+ .finish()
+ .unwrap(),
+ ) {
+ Err(minimq::PubError::Serialization(Error::Absent(_))) => {}
+
+ // If the value is too large to serialize, print an error to the topic instead
+ Err(minimq::PubError::Error(minimq::Error::Minimq(
+ minimq::MinimqError::Protocol(minimq::ProtocolError::Serialization(
+ minimq::SerError::InsufficientMemory,
+ )),
+ ))) => {
+ self.mqtt
+ .client()
+ .publish(
+ Publication::new(b"<error: serialization too large>")
+ .topic(&prefixed_topic)
+ .properties(&[ResponseCode::Error.as_user_property()])
+ .finish()
+ .unwrap(),
+ )
+ .unwrap();
+ }
+ other => other.unwrap(),
+ }
+ }
+ }
+
+ fn handle_subscription(&mut self) {
+ log::info!("MQTT connected, subscribing to settings");
+
+ // Note(unwrap): We construct a string with two more characters than the prefix
+ // structure, so we are guaranteed to have space for storage.
+ let mut settings_topic = self.prefix.clone();
+ settings_topic.push_str("/settings/#").unwrap();
+ let mut list_topic = self.prefix.clone();
+ list_topic.push_str("/list").unwrap();
+
+ let opts = SubscriptionOptions::default().ignore_local_messages();
+ let topics = [
+ TopicFilter::new(&settings_topic).options(opts),
+ TopicFilter::new(&list_topic).options(opts),
+ ];
+
+ if self.mqtt.client().subscribe(&topics, &[]).is_ok() {
+ self.state.process_event(sm::Events::Subscribed).unwrap();
+ }
+ }
+
+ fn handle_indicating_alive(&mut self) {
+ // Publish a connection status message.
+ let mut connection_topic = self.prefix.clone();
+ connection_topic.push_str("/alive").unwrap();
+
+ if self
+ .mqtt
+ .client()
+ .publish(
+ Publication::new(b"1")
+ .topic(&connection_topic)
+ .retain()
+ .finish()
+ .unwrap(),
+ )
+ .is_ok()
+ {
+ self.state.process_event(sm::Events::IndicatedLife).unwrap();
+ }
+ }
+
+ /// Update the MQTT interface and service the network. Pass any settings changes to the handler
+ /// supplied.
+ ///
+ /// # Args
+ /// * `handler` - A closure called with updated settings that can be used to apply current
+ /// settings or validate the configuration. Arguments are (path, old_settings, new_settings).
+ ///
+ /// # Returns
+ /// True if the settings changed. False otherwise.
+ pub fn handled_update<F, E>(&mut self, handler: F) -> Result<bool, minimq::Error<Stack::Error>>
+ where
+ F: FnMut(&str, &mut Settings, &Settings) -> Result<(), E>,
+ E: core::fmt::Display,
+ {
+ if !self.mqtt.client().is_connected() {
+ // Note(unwrap): It's always safe to reset.
+ self.state.process_event(sm::Events::Reset).unwrap();
+ }
+
+ match *self.state.state() {
+ sm::States::Initial => {
+ if self.mqtt.client().is_connected() {
+ self.state.process_event(sm::Events::Connected).unwrap();
+ }
+ }
+ sm::States::ConnectedToBroker => self.handle_indicating_alive(),
+ sm::States::PendingSubscribe => self.handle_subscription(),
+ sm::States::PendingRepublish => {
+ if self.state.context().republish_has_timed_out() {
+ self.state
+ .process_event(sm::Events::StartRepublish)
+ .unwrap();
+ }
+ }
+ sm::States::RepublishingSettings => self.handle_republish(),
+
+ // Nothing to do in the active state.
+ sm::States::Active => {}
+ }
+
+ self.handle_listing();
+
+ // All states must handle MQTT traffic.
+ self.handle_mqtt_traffic(handler)
+ }
+
+ fn handle_mqtt_traffic<F, E>(
+ &mut self,
+ mut handler: F,
+ ) -> Result<bool, minimq::Error<Stack::Error>>
+ where
+ F: FnMut(&str, &mut Settings, &Settings) -> Result<(), E>,
+ E: core::fmt::Display,
+ {
+ let mut updated = false;
+ match self.mqtt.poll(|client, topic, message, properties| {
+ let Some(path) = topic.strip_prefix(self.prefix.as_str()) else {
+ log::info!("Unexpected topic prefix: {topic}");
+ return;
+ };
+
+ let Ok(command) = Command::from_message(path, message) else {
+ log::info!("Unknown Miniconf command: {path}");
+ return;
+ };
+
+ match command {
+ Command::List => {
+ if !properties
+ .into_iter()
+ .any(|prop| matches!(prop, Ok(minimq::Property::ResponseTopic(_))))
+ {
+ log::info!("Discarding `List` without `ResponseTopic`");
+ return;
+ }
+
+ let response = match self.listing_state {
+ Some(_) => "`List` already in progress",
+ None => {
+ match handle_listing_request(properties) {
+ Err(msg) => msg,
+ Ok(cache) => {
+ self.listing_state
+ .replace((cache, Settings::iter_paths_unchecked("/")));
+
+ // There is no positive response sent during list commands,
+ // instead, the response is sent as a property of the listed
+ // elements. As such, we are now finished processing a list
+ // command.
+ return;
+ }
+ }
+ }
+ };
+
+ let props = [ResponseCode::Error.as_user_property()];
+ if let Ok(response) = minimq::Publication::new(response.as_bytes())
+ .reply(properties)
+ .properties(&props)
+ .qos(QoS::AtLeastOnce)
+ .finish()
+ {
+ client.publish(response).ok();
+ }
+ }
+
+ Command::Get { path } => {
+ let props = [ResponseCode::Ok.as_user_property()];
+ let Ok(message) =
+ DeferredPublication::new(|buf| self.settings.get_json(path, buf))
+ .properties(&props)
+ .reply(properties)
+ // Override the response topic with the path.
+ .qos(QoS::AtLeastOnce)
+ .finish()
+ else {
+ // If we can't create the publication, it's because there's no way to reply
+ // to the message. Since we don't know where to send things, abort now and
+ // complete handling of the `Get` request.
+ return;
+ };
+
+ if let Err(minimq::PubError::Serialization(err)) = client.publish(message) {
+ if let Ok(message) = DeferredPublication::new(|mut buf| {
+ let start = buf.len();
+ write!(buf, "{}", err).and_then(|_| Ok(start - buf.len()))
+ })
+ .properties(&[ResponseCode::Error.as_user_property()])
+ .reply(properties)
+ .qos(QoS::AtLeastOnce)
+ .finish()
+ {
+ // Try to send the error as a best-effort. If we don't have enough
+ // buffer space to encode the error, there's nothing more we can do.
+ client.publish(message).ok();
+ };
+ }
+ }
+
+ Command::Set { path, value } => {
+ let mut new_settings = self.settings.clone();
+ if let Err(err) = new_settings.set_json(path, value) {
+ if let Ok(response) = DeferredPublication::new(|mut buf| {
+ let start = buf.len();
+ write!(buf, "{}", err).and_then(|_| Ok(start - buf.len()))
+ })
+ .properties(&[ResponseCode::Error.as_user_property()])
+ .reply(properties)
+ .qos(QoS::AtLeastOnce)
+ .finish()
+ {
+ client.publish(response).ok();
+ }
+ return;
+ };
+
+ updated = true;
+
+ match handler(path, &mut self.settings, &new_settings) {
+ Ok(_) => {
+ if let Ok(response) = Publication::new("OK".as_bytes())
+ .properties(&[ResponseCode::Ok.as_user_property()])
+ .reply(properties)
+ .qos(QoS::AtLeastOnce)
+ .finish()
+ {
+ client.publish(response).ok();
+ }
+ }
+ Err(err) => {
+ if let Ok(response) = DeferredPublication::new(|mut buf| {
+ let start = buf.len();
+ write!(buf, "{}", err).and_then(|_| Ok(start - buf.len()))
+ })
+ .properties(&[ResponseCode::Error.as_user_property()])
+ .reply(properties)
+ .qos(QoS::AtLeastOnce)
+ .finish()
+ {
+ client.publish(response).ok();
+ }
+ }
+ }
+ }
+ }
+ }) {
+ Ok(_) => Ok(updated),
+ Err(minimq::Error::SessionReset) => {
+ log::warn!("Session reset");
+ self.state.process_event(sm::Events::Reset).unwrap();
+ Ok(false)
+ }
+ Err(other) => Err(other),
+ }
+ }
+
+ /// Update the settings from the network stack without any specific handling.
+ ///
+ /// # Returns
+ /// True if the settings changed. False otherwise
+ pub fn update(&mut self) -> Result<bool, minimq::Error<Stack::Error>> {
+ self.handled_update(|_, old, new| {
+ *old = new.clone();
+ Result::<_, &'static str>::Ok(())
+ })
+ }
+
+ /// Get the current settings from miniconf.
+ pub fn settings(&self) -> &Settings {
+ &self.settings
+ }
+
+ /// Force republication of the current settings.
+ ///
+ /// # Note
+ /// This is intended to be used if modification of a setting had side effects that affected
+ /// another setting.
+ pub fn force_republish(&mut self) {
+ self.state.process_event(sm::Events::StartRepublish).ok();
+ }
+}
+
+#[derive(Debug, Copy, Clone, PartialEq)]
+enum ResponseCode {
+ Ok,
+ Continue,
+ Error,
+}
+
+impl ResponseCode {
+ fn as_user_property(self) -> minimq::Property<'static> {
+ let string = match self {
+ ResponseCode::Ok => "Ok",
+ ResponseCode::Continue => "Continue",
+ ResponseCode::Error => "Error",
+ };
+
+ minimq::Property::UserProperty(
+ minimq::types::Utf8String("code"),
+ minimq::types::Utf8String(string),
+ )
+ }
+}
+
+fn handle_listing_request(
+ properties: &minimq::types::Properties<'_>,
+) -> Result<ListCache, &'static str> {
+ // If the response topic is too long, send an error
+ let response_topic = properties.into_iter().response_topic().unwrap();
+
+ // If there is a CD and it's too long, send an error response.
+ let correlation_data = if let Some(cd) = properties.into_iter().find_map(|prop| {
+ if let Ok(minimq::Property::CorrelationData(cd)) = prop {
+ Some(cd.0)
+ } else {
+ None
+ }
+ }) {
+ Some(Vec::try_from(cd).map_err(|_| "Correlation data too long")?)
+ } else {
+ None
+ };
+
+ Ok(ListCache {
+ topic: String::try_from(response_topic).map_err(|_| "Response topic too long")?,
+ correlation_data,
+ })
+}
+
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 +
use crate::{Error, Key, Metadata, TreeDeserialize, TreeKey, TreeSerialize};
+use serde::{de::Deserialize, Deserializer, Serialize, Serializer};
+
+// `Option` does not add to the path hierarchy (does not consume from `keys` or call `func`).
+// But it does add one Tree API layer between its `Tree<Y>` level
+// and its inner type `Tree<Y'>` level: `Y' = Y - 1`.
+
+// the Y >= 2 cases:
+macro_rules! depth {
+ ($($y:literal)+) => {$(
+ impl<T: TreeKey<{$y - 1}>> TreeKey<$y> for Option<T> {
+ fn name_to_index(_value: &str) -> Option<usize> {
+ None
+ }
+
+ fn traverse_by_key<K, F, E>(keys: K, func: F) -> Result<usize, Error<E>>
+ where
+ K: Iterator,
+ K::Item: Key,
+ F: FnMut(usize, &str) -> Result<(), E>,
+ {
+ T::traverse_by_key(keys, func)
+ }
+
+ fn metadata() -> Metadata {
+ T::metadata()
+ }
+ }
+
+ impl<T: TreeSerialize<{$y - 1}>> TreeSerialize<$y> for Option<T> {
+ fn serialize_by_key<K, S>(&self, keys: K, ser: S) -> Result<usize, Error<S::Error>>
+ where
+ K: Iterator,
+ K::Item: Key,
+ S: Serializer,
+ {
+ if let Some(inner) = self {
+ inner.serialize_by_key(keys, ser)
+ } else {
+ Err(Error::Absent(0))
+ }
+ }
+ }
+
+ impl<'de, T: TreeDeserialize<'de, {$y - 1}>> TreeDeserialize<'de, $y> for Option<T> {
+ fn deserialize_by_key<K, D>(&mut self, keys: K, de: D) -> Result<usize, Error<D::Error>>
+ where
+ K: Iterator,
+ K::Item: Key,
+ D: Deserializer<'de>,
+ {
+ if let Some(inner) = self {
+ inner.deserialize_by_key(keys, de)
+ } else {
+ Err(Error::Absent(0))
+ }
+ }
+ }
+ )+}
+}
+depth!(2 3 4 5 6 7 8);
+
+// Y == 1
+impl<T> TreeKey for Option<T> {
+ fn name_to_index(_value: &str) -> Option<usize> {
+ None
+ }
+
+ fn traverse_by_key<K, F, E>(_keys: K, _func: F) -> Result<usize, Error<E>>
+ where
+ F: FnMut(usize, &str) -> Result<(), E>,
+ {
+ Ok(0)
+ }
+
+ fn metadata() -> Metadata {
+ Metadata {
+ count: 1,
+ ..Default::default()
+ }
+ }
+}
+
+impl<T: Serialize> TreeSerialize for Option<T> {
+ fn serialize_by_key<K, S>(&self, mut keys: K, ser: S) -> Result<usize, Error<S::Error>>
+ where
+ K: Iterator,
+ S: Serializer,
+ {
+ if keys.next().is_some() {
+ Err(Error::TooLong(0))
+ } else if let Some(inner) = self {
+ inner.serialize(ser)?;
+ Ok(0)
+ } else {
+ Err(Error::Absent(0))
+ }
+ }
+}
+
+impl<'de, T: Deserialize<'de>> TreeDeserialize<'de> for Option<T> {
+ fn deserialize_by_key<K, D>(&mut self, mut keys: K, de: D) -> Result<usize, Error<D::Error>>
+ where
+ K: Iterator,
+ D: Deserializer<'de>,
+ {
+ if keys.next().is_some() {
+ Err(Error::TooLong(0))
+ } else if let Some(inner) = self {
+ *inner = T::deserialize(de)?;
+ Ok(0)
+ } else {
+ Err(Error::Absent(0))
+ }
+ }
+}
+
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 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499 +500 +501 +502 +503 +504 +505 +506 +507 +508 +509 +510 +511 +512 +513 +514 +515 +516 +517 +518 +519 +520 +521 +522 +523 +524 +525 +526 +527 +528 +529 +530 +531 +532 +533 +534 +535 +536 +537 +538 +539 +540 +541 +542 +543 +544 +545 +546 +547 +548 +549 +550 +551 +552 +553 +554 +555 +556 +557 +558 +559 +560 +561 +562 +563 +564 +565 +566 +567 +568 +569 +570 +571 +572 +573 +574 +575 +576 +577 +578 +579 +580 +581 +582 +583 +584 +585 +586 +587 +588 +589 +590 +591 +592 +593 +594 +595 +596 +597 +598 +599 +600 +
use crate::PathIter;
+use core::fmt::{Display, Formatter, Write};
+use serde::{Deserializer, Serializer};
+
+/// Errors that can occur when using the Tree traits.
+///
+/// A `usize` member indicates the key depth where the error occurred.
+/// The depth here is the number of names or indices consumed.
+/// It is also the number of separators in a path or the length
+/// of an indices slice.
+///
+/// If multiple errors are applicable simultaneously the precedence
+/// is from high to low:
+///
+/// `Absent > TooShort > NotFound > TooLong > Inner > PostDeserialization`
+/// before any `Ok`.
+#[non_exhaustive]
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum Error<E> {
+ /// The key is valid, but does not exist at runtime.
+ ///
+ /// This is the case if an [`Option`] using the `Tree*` traits
+ /// is `None` at runtime. See also [`TreeKey#option`].
+ Absent(usize),
+
+ /// The key ends early and does not reach a leaf node.
+ TooShort(usize),
+
+ /// The key was not found (index unparsable or too large, name not found or invalid).
+ NotFound(usize),
+
+ /// The key is too long and goes beyond a leaf node.
+ TooLong(usize),
+
+ /// The value provided could not be serialized or deserialized
+ /// or the traversal function returned an error.
+ Inner(E),
+
+ /// There was an error after deserializing a value.
+ ///
+ /// The `Deserializer` has encountered an error only after successfully
+ /// deserializing a value. This is the case if there is additional unexpected data.
+ /// The [`TreeDeserialize::deserialize_by_key()`] update takes place but this
+ /// error will be returned.
+ PostDeserialization(E),
+}
+
+impl<E: core::fmt::Display> Display for Error<E> {
+ fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
+ match self {
+ Error::Absent(index) => {
+ write!(f, "Path is not currently available (Key level: {})", index)
+ }
+ Error::TooShort(index) => {
+ write!(f, "Provided path was too short (Key level: {})", index)
+ }
+ Error::NotFound(index) => {
+ write!(f, "The provided path was not found (Key level: {})", index)
+ }
+ Error::TooLong(index) => {
+ write!(f, "The provided path was too long (Key level: {})", index)
+ }
+ Error::Inner(error) => {
+ write!(f, "Value could not be (de)serialized: ")?;
+ error.fmt(f)
+ }
+ Error::PostDeserialization(error) => {
+ write!(f, "Error after deserialization: ")?;
+ error.fmt(f)
+ }
+ }
+ }
+}
+
+impl<T> From<T> for Error<T> {
+ fn from(value: T) -> Self {
+ Error::Inner(value)
+ }
+}
+
+/// Pass a [`Result`] up one hierarchy level, incrementing its usize member.
+pub trait Increment {
+ /// Increment the `depth` member by one.
+ fn increment(self) -> Self;
+}
+
+impl<E> Increment for Result<usize, Error<E>> {
+ fn increment(self) -> Self {
+ match self {
+ Ok(i) => Ok(i + 1),
+ Err(Error::NotFound(i)) => Err(Error::NotFound(i + 1)),
+ Err(Error::TooShort(i)) => Err(Error::TooShort(i + 1)),
+ Err(Error::TooLong(i)) => Err(Error::TooLong(i + 1)),
+ Err(Error::Absent(i)) => Err(Error::Absent(i + 1)),
+ e => e,
+ }
+ }
+}
+
+/// Unit struct to indicate a short indices iterator in [`TreeKey::indices()`].
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub struct SliceShort;
+
+/// Metadata about a [TreeKey] namespace.
+#[non_exhaustive]
+#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
+pub struct Metadata {
+ /// The maximum length of a path in bytes.
+ ///
+ /// This is the concatenation of all names in a path
+ /// and does not include separators.
+ /// It includes paths that may be [`Error::Absent`] at runtime.
+ pub max_length: usize,
+
+ /// The maximum path depth.
+ ///
+ /// This is equal to the maximum number of path hierarchy separators.
+ /// It may be smaller than the [Tree recursion depth const generic paramerter `Y`](TreeKey#recursion).
+ /// It includes paths that may be [`Error::Absent`] at runtime.
+ pub max_depth: usize,
+
+ /// The total number of paths.
+ ///
+ /// This includes paths that may be [`Error::Absent`] at runtime.
+ pub count: usize,
+}
+
+impl Metadata {
+ /// Add separator length to the maximum path length.
+ ///
+ /// To obtain an upper bound on the maximum length of all paths
+ /// including separators, this adds `max_depth*separator_length`.
+ pub fn separator(self, separator: &str) -> Self {
+ Self {
+ max_length: self.max_length + self.max_depth * separator.len(),
+ ..self
+ }
+ }
+}
+
+/// Capability to convert a key into a node index for a given `M: TreeKey`.
+pub trait Key {
+ /// Convert the key `self` to a `usize` index.
+ fn find<const Y: usize, M: TreeKey<Y>>(&self) -> Option<usize>;
+}
+
+// `usize` index as Key
+impl Key for usize {
+ #[inline]
+ fn find<const Y: usize, M>(&self) -> Option<usize> {
+ Some(*self)
+ }
+}
+
+// &str name as Key
+impl Key for &str {
+ #[inline]
+ fn find<const Y: usize, M: TreeKey<Y>>(&self) -> Option<usize> {
+ M::name_to_index(self)
+ }
+}
+
+/// Traversal, iteration, and serialization/deserialization of nodes in a tree.
+///
+/// The following documentation sections on `TreeKey<Y>` apply analogously to `TreeSerialize<Y>`
+/// and `TreeDeserialize<Y>`.
+///
+/// # Recursion
+///
+/// The `TreeKey` trait (and the `TreeSerialize`/`TreeDeserialize` traits as well)
+/// are meant to be implemented
+/// recursively on nested data structures. Recursion here means that a container
+/// that implements `TreeKey`, may call on the `TreeKey` implementations of
+/// inner types.
+///
+/// The const parameter `Y` in the traits here is the recursion depth and determines the
+/// maximum nesting of `TreeKey` layers. It's at least `1` and defaults to `1`.
+///
+/// The recursion depth `Y` doubles as an upper bound to the key length
+/// (the depth/height of the tree):
+/// An implementor of `TreeKey<Y>` may consume at most `Y` items from the
+/// `keys` iterator argument in the recursive methods ([`TreeSerialize::serialize_by_key()`],
+/// [`TreeDeserialize::deserialize_by_key()`], [`TreeKey::traverse_by_key()`]). This includes
+/// both the items consumed directly before recursing and those consumed indirectly
+/// by recursing into inner types. In the same way it may call `func` in
+/// [`TreeKey::traverse_by_key()`] at most `Y` times, again including those calls due
+/// to recursion into inner `Miniconf` types.
+///
+/// This implies that if an implementor `T` of `TreeKey<Y>` (with `Y >= 1`) contains and recurses into
+/// an inner type using that type's `TreeKey<Z>` implementation, then `1 <= Z <= Y` must
+/// hold and `T` may consume at most `Y - Z` items from the `keys` iterators and call
+/// `func` at most `Y - Z` times.
+///
+/// # Keys
+///
+/// The keys used to locate nodes can be either iterators over `usize` or iterators
+/// over `&str` names.
+///
+/// `usize` may appear like ASN.1 Object Identifiers.
+/// `&str` keys are sequences of names, like path names. When concatenated, they are separated by
+/// path hierarchy separators, e.g. `'/'`.
+///
+/// # Derive macros
+///
+/// Derive macros to automatically implement the correct `TreeKey<Y>` traits on a struct `S` are available through
+/// [`macro@crate::TreeKey`], [`macro@crate::TreeSerialize`], and [`macro@crate::TreeDeserialize`].
+/// A shorthand derive macro that derives all three trait implementations is also available at [`macro@crate::Tree`].
+///
+/// To derive `TreeSerialize`/`TreeDeserialize`, each field in the struct must either implement
+/// [`serde::Serialize`]/[`serde::de::DeserializeOwned`]
+/// (and ultimately also be supported by the intended [`serde::Serializer`]/[`serde::Deserializer`] backend)
+/// or implement the respective `TreeSerialize`/`TreeDeserialize` trait themselves for the required remaining
+/// recursion depth.
+///
+/// For each field, the remaining recursion depth is configured through the `#[tree(depth(Y))]`
+/// attribute, with `Y = 1` being the implied default when using `#[tree()]` and `Y = 0` invalid.
+/// If the attribute is not present, the field is a leaf and accessed only through its
+/// [`serde::Serialize`]/[`serde::Deserialize`] implementation.
+/// With the attribute present the field is accessed through its `TreeKey<Y>` implementation with the given
+/// remaining recursion depth.
+///
+/// # Array
+///
+/// Blanket implementations of the `TreeKey` traits are provided for homogeneous arrays [`[T; N]`](core::array)
+/// up to recursion depth `Y = 8`.
+///
+/// When a [`[T; N]`](core::array) is used as `TreeKey<Y>` (i.e. marked as `#[tree(depth(Y))]` in a struct)
+/// and `Y > 1` each item of the array is accessed as a `TreeKey` tree.
+/// For a depth `Y = 0` (attribute absent), the entire array is accessed as one atomic
+/// value. For `Y = 1` each index of the array is is instead accessed as
+/// one atomic value.
+///
+/// The type to use depends on the desired semantics of the data contained in the array. If the array
+/// contains `TreeKey` items, one can (and often wants to) use `Y >= 2`.
+/// However, if each element in the array should be individually configurable as a single value (e.g. a list
+/// of `u32`), then `Y = 1` can be used. With `Y = 0` all items are to be accessed simultaneously and atomically.
+/// For e.g. `[[T; 2]; 3] where T: TreeKey<3>` the recursion depth is `Y = 5`. It automatically implements
+/// `TreeKey<5>`.
+/// For `[[T; 2]; 3] where T: Serialize + DeserializeOwned`, any `Y <= 2` is available.
+///
+/// # Option
+///
+/// Blanket implementations of the `TreeKey` traits are provided for [`Option<T>`]
+/// up to recursion depth `Y = 8`.
+///
+/// These implementation do not alter the path hierarchy and do not consume any items from the `keys`
+/// iterators. The `TreeKey` behavior of an [`Option`] is such that the `None` variant makes the corresponding part
+/// of the tree inaccessible at run-time. It will still be iterated over by [`TreeKey::iter_paths()`] but attempts
+/// to [`TreeSerialize::serialize_by_key()`] or [`TreeDeserialize::deserialize_by_key()`] them
+/// return [`Error::Absent`].
+/// This is intended as a mechanism to provide run-time construction of the namespace. In some
+/// cases, run-time detection may indicate that some component is not present. In this case,
+/// namespaces will not be exposed for it.
+///
+/// If the depth specified by the `#[tree(depth(Y))]` attribute exceeds 1,
+/// the `Option` can be used to access within the inner type using its `TreeKey` trait.
+/// If there is no `tree` attribute on an `Option` field in a `struct or in an array,
+/// JSON `null` corresponds to `None` as usual and the `TreeKey` trait is not used.
+///
+/// The following example shows potential usage of arrays and `Option`:
+///
+/// ```
+/// # use miniconf::TreeKey;
+/// #[derive(TreeKey)]
+/// struct S {
+/// // "/b/1/2" = 5
+/// #[tree(depth(2))]
+/// b: [[u32; 3]; 3],
+/// // "/c/0" = [3,4], optionally absent at runtime
+/// #[tree(depth(2))]
+/// c: [Option<[u32; 2]>; 2],
+/// }
+/// ```
+///
+/// ## Generics
+///
+/// The macros add bounds to generic types of the struct they are acting on.
+/// If a generic type parameter `T` of the struct `S<T>`is used as a type parameter to a
+/// field type `a: F1<F2<T>>` the type `T` will be considered to reside at type depth `X = 2` (as it is
+/// within `F2` which is within `F1`) and the following bounds will be applied:
+///
+/// * With the `#[tree()]` attribute not present on `a`, `T` will receive bounds `Serialize`/`DeserializeOwned` when
+/// `TreeSerialize`/`TreeDeserialize` is derived.
+/// * With `#[tree(depth(Y))]`, and `Y - X < 1` it will also receive bounds `Serialize + DeserializeOwned`.
+/// * For `Y - X >= 1` it will receive the bound `T: TreeKey<Y - X>`.
+///
+/// E.g. In the following `T` resides at depth `2` and `T: TreeKey<1>` will be inferred:
+///
+/// ```
+/// # use miniconf::TreeKey;
+/// #[derive(TreeKey)]
+/// struct S<T> {
+/// #[tree(depth(3))]
+/// a: [Option<T>; 2],
+/// };
+/// // This works as [u32; N] implements TreeKey<1>:
+/// S::<[u32; 5]>::metadata();
+/// // This does not compile as u32 does not implement TreeKey<1>:
+/// // S::<u32>::metadata();
+/// ```
+///
+/// This behavior is upheld by and compatible with all implementations in this crate. It is only violated
+/// when deriving `TreeKey` for a struct that (a) forwards its own type parameters as type
+/// parameters to its field types, (b) uses `TreeKey` on those fields, and (c) those field
+/// types use their type parameters at other levels than `TreeKey<Y - 1>`. See the
+/// `test_derive_macro_bound_failure` test in `tests/generics.rs`.
+///
+/// # Example
+///
+/// See the [`crate`] documentation for an example showing how the traits and the derive macros work.
+pub trait TreeKey<const Y: usize = 1> {
+ /// Convert a node name to a node index.
+ ///
+ /// The details of the mapping and the `usize` index values
+ /// are an implementation detail and only need to be stable for at runtime.
+ ///
+ /// ```
+ /// # use miniconf::TreeKey;
+ /// #[derive(TreeKey)]
+ /// struct S {
+ /// foo: u32,
+ /// bar: u16,
+ /// }
+ /// assert_eq!(S::name_to_index("bar"), Some(1));
+ /// ```
+ fn name_to_index(name: &str) -> Option<usize>;
+
+ /// Call a function for each node on the path described by keys.
+ ///
+ /// Traversal is aborted once `func` returns an `Err(E)`.
+ ///
+ /// May not exhaust `keys` if a leaf is found early. i.e. `keys`
+ /// may be longer than required.
+ /// If `Self` is a leaf, nothing will be consumed from `keys`
+ /// and `Ok(0)` will be returned.
+ /// If `Self` is non-leaf (internal node) and the iterator is
+ /// exhausted (empty),
+ /// `Err(Error::TooShort(0))` will be returned.
+ ///
+ /// ```
+ /// # use miniconf::TreeKey;
+ /// #[derive(TreeKey)]
+ /// struct S {
+ /// foo: u32,
+ /// bar: u16,
+ /// };
+ /// assert_eq!(
+ /// S::traverse_by_key(["bar"].into_iter(), |index, name| {
+ /// assert_eq!((1, "bar"), (index, name));
+ /// Ok::<_, ()>(())
+ /// }),
+ /// Ok(1)
+ /// );
+ /// ```
+ ///
+ /// # Args
+ /// * `keys`: An `Iterator` of `Key`s identifying the node.
+ /// * `func`: A `FnMut` to be called for each node on the path. Its arguments are
+ /// the index and the name of the node at the given depth. Returning `Err()` aborts
+ /// the traversal.
+ ///
+ /// # Returns
+ /// Final node depth on success
+ fn traverse_by_key<K, F, E>(keys: K, func: F) -> Result<usize, Error<E>>
+ where
+ K: Iterator,
+ K::Item: Key,
+ // Writing this to return an iterator instead of using a callback
+ // would have worse performance (O(n^2) instead of O(n) for matching)
+ F: FnMut(usize, &str) -> Result<(), E>;
+
+ /// Get metadata about the paths in the namespace.
+ ///
+ /// ```
+ /// # use miniconf::TreeKey;
+ /// #[derive(TreeKey)]
+ /// struct S {
+ /// foo: u32,
+ /// bar: u16,
+ /// };
+ /// let m = S::metadata();
+ /// assert_eq!((m.max_depth, m.max_length, m.count), (1, 3, 2));
+ /// ```
+ fn metadata() -> Metadata;
+
+ /// Convert keys to path.
+ ///
+ /// This is typically called through a [PathIter] returned by [TreeKey::iter_paths].
+ ///
+ /// `keys` may be longer than required. Extra items are ignored.
+ ///
+ /// ```
+ /// # #[cfg(feature = "std")] {
+ /// # use miniconf::TreeKey;
+ /// #[derive(TreeKey)]
+ /// struct S {
+ /// foo: u32,
+ /// bar: u16,
+ /// };
+ /// let mut s = String::new();
+ /// S::path([1], &mut s, "/").unwrap();
+ /// assert_eq!(s, "/bar");
+ /// # }
+ /// ```
+ ///
+ /// # Args
+ /// * `keys`: An `Iterator` of `Key`s identifying the node.
+ /// * `path`: A string to write the separators and node names into.
+ /// See also [TreeKey::metadata()] for upper bounds on path length.
+ /// * `sep`: The path hierarchy separator to be inserted before each name.
+ ///
+ /// # Returns
+ /// Final node depth on success
+ fn path<K, P>(keys: K, mut path: P, sep: &str) -> Result<usize, Error<core::fmt::Error>>
+ where
+ K: IntoIterator,
+ K::Item: Key,
+ P: Write,
+ {
+ Self::traverse_by_key(keys.into_iter(), |_index, name| {
+ path.write_str(sep).and_then(|_| path.write_str(name))
+ })
+ }
+
+ /// Convert keys to `indices`.
+ ///
+ /// This determines the `indices` of the item specified by `keys`.
+ ///
+ /// See also [`TreeKey::path()`] for the analogous function.
+ ///
+ /// Entries in `indices` at and beyond the `depth` returned are unaffected.
+ ///
+ /// ```
+ /// # use miniconf::TreeKey;
+ /// #[derive(TreeKey)]
+ /// struct S {
+ /// foo: u32,
+ /// bar: u16,
+ /// };
+ /// let mut i = [0; 2];
+ /// let depth = S::indices(["bar"], &mut i).unwrap();
+ /// assert_eq!(&i[..depth], &[1]);
+ /// ```
+ ///
+ /// # Args
+ /// * `keys`: An `Iterator` of `Key`s identifying the node.
+ /// * `indices`: An iterator of `&mut usize` to write the node indices into.
+ /// If `indices` is shorter than the node depth, [`Error<SliceShort>`] is returned
+ /// See also [TreeKey::metadata()] for upper bounds on depth.
+ ///
+ /// # Returns
+ /// Final node depth on success
+ fn indices<'a, K, I>(keys: K, indices: I) -> Result<usize, Error<SliceShort>>
+ where
+ K: IntoIterator,
+ K::Item: Key,
+ I: IntoIterator<Item = &'a mut usize>,
+ {
+ let mut indices = indices.into_iter();
+ Self::traverse_by_key(keys.into_iter(), |index, _name| {
+ let idx = indices.next().ok_or(SliceShort)?;
+ *idx = index;
+ Ok(())
+ })
+ }
+
+ /// Create an iterator of all possible paths.
+ ///
+ /// This is a depth-first walk.
+ /// The iterator will walk all paths, including those that may be absent at
+ /// run-time (see [Option]).
+ /// The iterator has an exact and trusted [Iterator::size_hint].
+ ///
+ /// ```
+ /// # #[cfg(feature = "std")] {
+ /// # use miniconf::TreeKey;
+ /// #[derive(TreeKey)]
+ /// struct S {
+ /// foo: u32,
+ /// bar: u16,
+ /// };
+ /// let paths: Vec<String> = S::iter_paths("/").map(|p| p.unwrap()).collect();
+ /// assert_eq!(paths, ["/foo", "/bar"]);
+ /// # }
+ /// ```
+ ///
+ /// # Generics
+ /// * `P` - The type to hold the path. Needs to be `core::fmt::Write + Default`
+ ///
+ /// # Args
+ /// * `sep` - The path hierarchy separator
+ ///
+ /// # Returns
+ /// An iterator of paths with a trusted and exact [`Iterator::size_hint()`].
+ #[inline]
+ fn iter_paths<P: Write>(sep: &str) -> PathIter<'_, Self, Y, P> {
+ PathIter::new(sep)
+ }
+
+ /// Create an unchecked iterator of all possible paths.
+ ///
+ /// See also [TreeKey::iter_paths].
+ ///
+ /// ```
+ /// # #[cfg(feature = "std")] {
+ /// # use miniconf::TreeKey;
+ /// #[derive(TreeKey)]
+ /// struct S {
+ /// foo: u32,
+ /// bar: u16,
+ /// };
+ /// let paths: Vec<String> = S::iter_paths_unchecked("/").map(|p| p.unwrap()).collect();
+ /// assert_eq!(paths, ["/foo", "/bar"]);
+ /// # }
+ /// ```
+ ///
+ /// # Generics
+ /// * `P` - The type to hold the path. Needs to be `core::fmt::Write + Default`.
+ ///
+ /// # Args
+ /// * `sep` - The path hierarchy separator
+ ///
+ /// # Returns
+ /// A iterator of paths.
+ #[inline]
+ fn iter_paths_unchecked<P: Write>(sep: &str) -> PathIter<'_, Self, Y, P> {
+ PathIter::new_unchecked(sep)
+ }
+}
+
+/// Serialize a leaf node by its keys.
+///
+/// See also [`crate::JsonCoreSlash`] for a convenient blanket implementation using this trait.
+pub trait TreeSerialize<const Y: usize = 1>: TreeKey<Y> {
+ /// Serialize a node by keys.
+ ///
+ /// ```
+ /// # #[cfg(feature = "json-core")] {
+ /// # use miniconf::{TreeSerialize, TreeKey};
+ /// #[derive(TreeKey, TreeSerialize)]
+ /// struct S {
+ /// foo: u32,
+ /// bar: u16,
+ /// };
+ /// let s = S { foo: 9, bar: 11 };
+ /// let mut buf = [0u8; 10];
+ /// let mut ser = serde_json_core::ser::Serializer::new(&mut buf);
+ /// s.serialize_by_key(["bar"].into_iter(), &mut ser).unwrap();
+ /// let length = ser.end();
+ /// assert_eq!(&buf[..length], b"11");
+ /// # }
+ /// ```
+ ///
+ /// # Args
+ /// * `keys`: An `Iterator` of `Key`s identifying the node.
+ /// * `ser`: A `Serializer` to to serialize the value.
+ ///
+ /// # Returns
+ /// Node depth on success.
+ fn serialize_by_key<K, S>(&self, keys: K, ser: S) -> Result<usize, Error<S::Error>>
+ where
+ K: Iterator,
+ K::Item: Key,
+ S: Serializer;
+}
+
+/// Deserialize a leaf node by its keys.
+///
+/// See also [`crate::JsonCoreSlash`] for a convenient blanket implementation using this trait.
+pub trait TreeDeserialize<'de, const Y: usize = 1>: TreeKey<Y> {
+ /// Deserialize an node by keys.
+ ///
+ /// ```
+ /// # #[cfg(feature = "json-core")] {
+ /// # use miniconf::{TreeDeserialize, TreeKey};
+ /// #[derive(TreeKey, TreeDeserialize)]
+ /// struct S {
+ /// foo: u32,
+ /// bar: u16,
+ /// };
+ /// let mut s = S { foo: 9, bar: 11 };
+ /// let mut de = serde_json_core::de::Deserializer::new(b"7");
+ /// s.deserialize_by_key(["bar"].into_iter(), &mut de).unwrap();
+ /// de.end().unwrap();
+ /// assert_eq!(s.bar, 7);
+ /// # }
+ /// ```
+ ///
+ /// # Args
+ /// * `keys`: An `Iterator` of `Key`s identifying the node.
+ /// * `de`: A `Deserializer` to deserialize the value.
+ ///
+ /// # Returns
+ /// Node depth on success
+ fn deserialize_by_key<K, D>(&mut self, keys: K, de: D) -> Result<usize, Error<D::Error>>
+ where
+ K: Iterator,
+ K::Item: Key,
+ D: Deserializer<'de>;
+}
+
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 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +
//! Stabilizer ADC management interface
+//!
+//! # Design
+//!
+//! Stabilizer ADCs are connected to the MCU via a simplex, SPI-compatible interface. The ADCs
+//! require a setup conversion time after asserting the CSn (convert) signal to generate the ADC
+//! code from the sampled level. Once the setup time has elapsed, the ADC data is clocked out of
+//! MISO. The internal setup time is managed by the SPI peripheral via a CSn setup time parameter
+//! during SPI configuration, which allows offloading the management of the setup time to hardware.
+//!
+//! Because of the SPI-compatibility of the ADCs, a single SPI peripheral + DMA is used to automate
+//! the collection of multiple ADC samples without requiring processing by the CPU, which reduces
+//! overhead and provides the CPU with more time for processing-intensive tasks, like DSP.
+//!
+//! The automation of sample collection utilizes three DMA streams, the SPI peripheral, and two
+//! timer compare channel for each ADC. One timer comparison channel is configured to generate a
+//! comparison event every time the timer is equal to a specific value. Each comparison then
+//! generates a DMA transfer event to write into the SPI CR1 register to initiate the transfer.
+//! This allows the SPI interface to periodically read a single sample. The other timer comparison
+//! channel is configured to generate a comparison event slightly before the first (~10 timer
+//! cycles). This channel triggers a separate DMA stream to clear the EOT flag within the SPI
+//! peripheral. The EOT flag must be cleared after each transfer or the SPI peripheral will not
+//! properly complete the single conversion. Thus, by using two DMA streams and timer comparison
+//! channels, the SPI can regularly acquire ADC samples.
+//!
+//! In order to collect the acquired ADC samples into a RAM buffer, a final DMA transfer is
+//! configured to read from the SPI RX FIFO into RAM. The request for this transfer is connected to
+//! the SPI RX data signal, so the SPI peripheral will request to move data into RAM whenever it is
+//! available. When enough samples have been collected, a transfer-complete interrupt is generated
+//! and the ADC samples are available for processing.
+//!
+//! After a complete transfer of a batch of samples, the inactive buffer is available to the
+//! user for processing. The processing must complete before the DMA transfer of the next batch
+//! completes.
+//!
+//! ## Starting Data Collection
+//!
+//! Because the DMA data collection is automated via timer count comparisons and DMA transfers, the
+//! ADCs can be initialized and configured, but will not begin sampling the external ADCs until the
+//! sampling timer is enabled. As such, the sampling timer should be enabled after all
+//! initialization has completed and immediately before the embedded processing loop begins.
+//!
+//!
+//! ## Batch Sizing
+//!
+//! The ADCs collect a group of N samples, which is referred to as a batch. The size of the batch
+//! is configured by the user at compile-time to allow for a custom-tailored implementation. Larger
+//! batch sizes generally provide for lower overhead and more processing time per sample, but come
+//! at the expense of increased input -> output latency.
+//!
+//!
+//! # Note
+//!
+//! While there are two ADCs, only a single ADC is configured to generate transfer-complete
+//! interrupts. This is done because it is assumed that the ADCs will always be sampled
+//! simultaneously. If only a single ADC is used, it must always be ADC0, as ADC1 will not generate
+//! transfer-complete interrupts.
+//!
+//! There is a very small amount of latency between sampling of ADCs due to bus matrix priority. As
+//! such, one of the ADCs will be sampled marginally earlier before the other because the DMA
+//! requests are generated simultaneously. This can be avoided by providing a known offset to the
+//! sample DMA requests, which can be completed by setting e.g. ADC0's comparison to a counter
+//! value of 0 and ADC1's comparison to a counter value of 1.
+//!
+//! In this implementation, double buffer mode DMA transfers are used because the SPI RX FIFOs
+//! have finite depth, FIFO access is slower than AXISRAM access, and because the single
+//! buffer mode DMA disable/enable and buffer update sequence is slow.
+use stm32h7xx_hal as hal;
+
+use mutex_trait::Mutex;
+
+use super::design_parameters::{SampleBuffer, MAX_SAMPLE_BUFFER_SIZE};
+use super::timers;
+
+use hal::{
+ dma::{
+ config::Priority,
+ dma::{DMAReq, DmaConfig},
+ traits::TargetAddress,
+ DMAError, MemoryToPeripheral, PeripheralToMemory, Transfer,
+ },
+ spi::{HalDisabledSpi, HalEnabledSpi, HalSpi},
+};
+
+/// A type representing an ADC sample.
+#[derive(Copy, Clone)]
+pub struct AdcCode(pub u16);
+
+impl AdcCode {
+ // The ADC has a differential input with a range of +/- 4.096 V and 16-bit resolution.
+ // The gain into the two inputs is 1/5.
+ const FULL_SCALE: f32 = 5.0 / 2.0 * 4.096;
+ const VOLT_PER_LSB: f32 = -Self::FULL_SCALE / i16::MIN as f32;
+ const LSB_PER_VOLT: f32 = 1. / Self::VOLT_PER_LSB;
+}
+
+impl From<u16> for AdcCode {
+ /// Construct an ADC code from a provided binary (ADC-formatted) code.
+ fn from(value: u16) -> Self {
+ Self(value)
+ }
+}
+
+impl From<i16> for AdcCode {
+ /// Construct an ADC code from the stabilizer-defined code (i16 full range).
+ fn from(value: i16) -> Self {
+ Self(value as u16)
+ }
+}
+
+impl From<AdcCode> for i16 {
+ /// Get a stabilizer-defined code from the ADC code.
+ fn from(code: AdcCode) -> i16 {
+ code.0 as i16
+ }
+}
+
+impl From<AdcCode> for u16 {
+ /// Get an ADC-frmatted binary value from the code.
+ fn from(code: AdcCode) -> u16 {
+ code.0
+ }
+}
+
+impl From<AdcCode> for f32 {
+ /// Convert raw ADC codes to/from voltage levels.
+ ///
+ /// # Note
+ /// This does not account for the programmable gain amplifier at the signal input.
+ fn from(code: AdcCode) -> f32 {
+ i16::from(code) as f32 * AdcCode::VOLT_PER_LSB
+ }
+}
+
+impl TryFrom<f32> for AdcCode {
+ type Error = ();
+
+ fn try_from(voltage: f32) -> Result<AdcCode, ()> {
+ let code = voltage * Self::LSB_PER_VOLT;
+ if !(i16::MIN as f32..=i16::MAX as f32).contains(&code) {
+ Err(())
+ } else {
+ Ok(AdcCode::from(code as i16))
+ }
+ }
+}
+
+// The following data is written by the timer ADC sample trigger into the SPI CR1 to start the
+// transfer. Data in AXI SRAM is not initialized on boot, so the contents are random. This value is
+// initialized during setup.
+#[link_section = ".axisram.buffers"]
+static mut SPI_START: [u32; 1] = [0x00; 1];
+
+// The following data is written by the timer flag clear trigger into the SPI IFCR register to clear
+// the EOT flag. Data in AXI SRAM is not initialized on boot, so the contents are random. This
+// value is initialized during setup.
+#[link_section = ".axisram.buffers"]
+static mut SPI_EOT_CLEAR: [u32; 1] = [0x00];
+
+// The following global buffers are used for the ADC sample DMA transfers. Two buffers are used for
+// each transfer in a ping-pong buffer configuration (one is being acquired while the other is being
+// processed). Note that the contents of AXI SRAM is uninitialized, so the buffer contents on
+// startup are undefined. The dimensions are `ADC_BUF[adc_index][ping_pong_index][sample_index]`.
+#[link_section = ".axisram.buffers"]
+static mut ADC_BUF: [[SampleBuffer; 2]; 2] =
+ [[[0; MAX_SAMPLE_BUFFER_SIZE]; 2]; 2];
+
+macro_rules! adc_input {
+ ($name:ident, $index:literal, $trigger_stream:ident, $data_stream:ident, $clear_stream:ident,
+ $spi:ident, $trigger_channel:ident, $dma_req:ident, $clear_channel:ident, $dma_clear_req:ident) => {
+
+ paste::paste! {
+
+ /// $spi-CR is used as a type for indicating a DMA transfer into the SPI control
+ /// register whenever the tim2 update dma request occurs.
+ struct [< $spi CR >] {
+ _channel: timers::tim2::$trigger_channel,
+ }
+ impl [< $spi CR >] {
+ pub fn new(_channel: timers::tim2::$trigger_channel) -> Self {
+ Self { _channel }
+ }
+ }
+
+ // Note(unsafe): This structure is only safe to instantiate once. The DMA request is
+ // hard-coded and may only be used if ownership of the timer2 $trigger_channel compare
+ // channel is assured, which is ensured by maintaining ownership of the channel.
+ unsafe impl TargetAddress<MemoryToPeripheral> for [< $spi CR >] {
+
+ type MemSize = u32;
+
+ /// SPI DMA requests are generated whenever TIM2 CHx ($dma_req) comparison occurs.
+ const REQUEST_LINE: Option<u8> = Some(DMAReq::$dma_req as u8);
+
+ /// Whenever the DMA request occurs, it should write into SPI's CR1 to start the
+ /// transfer.
+ fn address(&self) -> usize {
+ // Note(unsafe): It is assumed that SPI is owned by another DMA transfer. This
+ // is only safe because we are writing to a configuration register.
+ let regs = unsafe { &*hal::stm32::$spi::ptr() };
+ ®s.cr1 as *const _ as usize
+ }
+ }
+
+ /// $spi-IFCR is used as a type for indicating a DMA transfer into the SPI flag clear
+ /// register whenever the tim3 compare dma request occurs. The flag must be cleared
+ /// before the transfer starts.
+ struct [< $spi IFCR >] {
+ _channel: timers::tim3::$clear_channel,
+ }
+
+ impl [< $spi IFCR >] {
+ pub fn new(_channel: timers::tim3::$clear_channel) -> Self {
+ Self { _channel }
+ }
+ }
+
+ // Note(unsafe): This structure is only safe to instantiate once. The DMA request is
+ // hard-coded and may only be used if ownership of the timer3 $clear_channel compare
+ // channel is assured, which is ensured by maintaining ownership of the channel.
+ unsafe impl TargetAddress<MemoryToPeripheral> for [< $spi IFCR >] {
+ type MemSize = u32;
+
+ /// SPI DMA requests are generated whenever TIM3 CHx ($dma_clear_req) comparison
+ /// occurs.
+ const REQUEST_LINE: Option<u8> = Some(DMAReq::$dma_clear_req as u8);
+
+ /// Whenever the DMA request occurs, it should write into SPI's IFCR to clear the
+ /// EOT flag to allow the next transmission.
+ fn address(&self) -> usize {
+ // Note(unsafe): It is assumed that SPI is owned by another DMA transfer and
+ // this DMA is only used for writing to the configuration registers.
+ let regs = unsafe { &*hal::stm32::$spi::ptr() };
+ ®s.ifcr as *const _ as usize
+ }
+ }
+
+ /// Represents data associated with ADC.
+ pub struct $name {
+ transfer: Transfer<
+ hal::dma::dma::$data_stream<hal::stm32::DMA1>,
+ hal::spi::Spi<hal::stm32::$spi, hal::spi::Disabled, u16>,
+ PeripheralToMemory,
+ &'static mut [u16],
+ hal::dma::DBTransfer,
+ >,
+ trigger_transfer: Transfer<
+ hal::dma::dma::$trigger_stream<hal::stm32::DMA1>,
+ [< $spi CR >],
+ MemoryToPeripheral,
+ &'static mut [u32; 1],
+ hal::dma::DBTransfer,
+ >,
+ clear_transfer: Transfer<
+ hal::dma::dma::$clear_stream<hal::stm32::DMA1>,
+ [< $spi IFCR >],
+ MemoryToPeripheral,
+ &'static mut [u32; 1],
+ hal::dma::DBTransfer,
+ >,
+ }
+
+ impl $name {
+ /// Construct the ADC input channel.
+ ///
+ /// # Args
+ /// * `spi` - The SPI interface used to communicate with the ADC.
+ /// * `trigger_stream` - The DMA stream used to trigger each ADC transfer by
+ /// writing a word into the SPI TX FIFO.
+ /// * `data_stream` - The DMA stream used to read samples received over SPI into a data buffer.
+ /// * `clear_stream` - The DMA stream used to clear the EOT flag in the SPI peripheral.
+ /// * `trigger_channel` - The ADC sampling timer output compare channel for read triggers.
+ /// * `clear_channel` - The shadow sampling timer output compare channel used for
+ /// clearing the SPI EOT flag.
+ pub fn new(
+ spi: hal::spi::Spi<hal::stm32::$spi, hal::spi::Enabled, u16>,
+ trigger_stream: hal::dma::dma::$trigger_stream<
+ hal::stm32::DMA1,
+ >,
+ data_stream: hal::dma::dma::$data_stream<hal::stm32::DMA1>,
+ clear_stream: hal::dma::dma::$clear_stream<hal::stm32::DMA1>,
+ trigger_channel: timers::tim2::$trigger_channel,
+ clear_channel: timers::tim3::$clear_channel,
+ batch_size: usize,
+ ) -> Self {
+ // The flag clear DMA transfer always clears the EOT flag in the SPI
+ // peripheral. It has the highest priority to ensure it is completed before the
+ // transfer trigger.
+ let clear_config = DmaConfig::default()
+ .priority(Priority::VeryHigh)
+ .circular_buffer(true);
+
+ unsafe {
+ SPI_EOT_CLEAR[0] = 1 << 3;
+ }
+
+ // Generate DMA events when the timer hits zero (roll-over). This must be before
+ // the trigger channel DMA occurs, as if the trigger occurs first, the
+ // transmission will not occur.
+ clear_channel.listen_dma();
+ clear_channel.to_output_compare(0);
+
+ let clear_transfer: Transfer<
+ _,
+ _,
+ MemoryToPeripheral,
+ _,
+ _,
+ > = Transfer::init(
+ clear_stream,
+ [< $spi IFCR >]::new(clear_channel),
+ // Note(unsafe): Because this is a Memory->Peripheral transfer, this data is
+ // never actually modified. It technically only needs to be immutably
+ // borrowed, but the current HAL API only supports mutable borrows.
+ unsafe { &mut SPI_EOT_CLEAR },
+ None,
+ clear_config,
+ );
+
+ // Generate DMA events when an output compare of the timer hits the specified
+ // value.
+ trigger_channel.listen_dma();
+ trigger_channel.to_output_compare(2 + $index);
+
+ // The trigger stream constantly writes to the SPI CR1 using a static word
+ // (which is a static value to enable the SPI transfer). Thus, neither the
+ // memory or peripheral address ever change. This is run in circular mode to be
+ // completed at every DMA request.
+ let trigger_config = DmaConfig::default()
+ .priority(Priority::High)
+ .circular_buffer(true);
+
+ // Note(unsafe): This word is initialized once per ADC initialization to verify
+ // it is initialized properly.
+ unsafe {
+ // Write a binary code into the SPI control register to initiate a transfer.
+ SPI_START[0] = 0x201;
+ };
+
+ // Construct the trigger stream to write from memory to the peripheral.
+ let trigger_transfer: Transfer<
+ _,
+ _,
+ MemoryToPeripheral,
+ _,
+ _,
+ > = Transfer::init(
+ trigger_stream,
+ [< $spi CR >]::new(trigger_channel),
+ // Note(unsafe): Because this is a Memory->Peripheral transfer, this data is never
+ // actually modified. It technically only needs to be immutably borrowed, but the
+ // current HAL API only supports mutable borrows.
+ unsafe { &mut SPI_START },
+ None,
+ trigger_config,
+ );
+
+ // The data stream constantly reads from the SPI RX FIFO into a RAM buffer. The peripheral
+ // stalls reads of the SPI RX FIFO until data is available, so the DMA transfer completes
+ // after the requested number of samples have been collected. Note that only ADC1's (sic!)
+ // data stream is used to trigger a transfer completion interrupt.
+ let data_config = DmaConfig::default()
+ .memory_increment(true)
+ .double_buffer(true)
+ .transfer_complete_interrupt($index == 1)
+ .priority(Priority::VeryHigh);
+
+ // A SPI peripheral error interrupt is used to determine if the RX FIFO
+ // overflows. This indicates that samples were dropped due to excessive
+ // processing time in the main application (e.g. a second DMA transfer completes
+ // before the first was done with processing). This is used as a flow control
+ // indicator to guarantee that no ADC samples are lost.
+ let mut spi = spi.disable();
+ spi.listen(hal::spi::Event::Error);
+
+ // The data transfer is always a transfer of data from the peripheral to a RAM
+ // buffer.
+ let data_transfer: Transfer<_, _, PeripheralToMemory, _, _> =
+ Transfer::init(
+ data_stream,
+ spi,
+ // Note(unsafe): The ADC_BUF[$index] is "owned" by this peripheral.
+ // It shall not be used anywhere else in the module.
+ unsafe { &mut ADC_BUF[$index][0][..batch_size] },
+ unsafe { Some(&mut ADC_BUF[$index][1][..batch_size]) },
+ data_config,
+ );
+
+ Self {
+ transfer: data_transfer,
+ trigger_transfer,
+ clear_transfer,
+ }
+ }
+
+ /// Enable the ADC DMA transfer sequence.
+ pub fn start(&mut self) {
+ self.transfer.start(|spi| {
+ spi.enable_dma_rx();
+
+ spi.inner().cr2.modify(|_, w| w.tsize().bits(1));
+ spi.inner().cr1.modify(|_, w| w.spe().set_bit());
+ });
+
+ self.clear_transfer.start(|_| {});
+ self.trigger_transfer.start(|_| {});
+
+ }
+
+ /// Wait for the transfer of the currently active buffer to complete,
+ /// then call a function on the now inactive buffer and acknowledge the
+ /// transfer complete flag.
+ ///
+ /// NOTE(unsafe): Memory safety and access ordering is not guaranteed
+ /// (see the HAL DMA docs).
+ pub fn with_buffer<F, R>(&mut self, f: F) -> Result<R, DMAError>
+ where
+ F: FnOnce(&mut &'static mut [u16]) -> R,
+ {
+ unsafe { self.transfer.next_dbm_transfer_with(|buf, _current| f(buf)) }
+ }
+ }
+
+ // This is not actually a Mutex. It only re-uses the semantics and macros of mutex-trait
+ // to reduce rightward drift when jointly calling `with_buffer(f)` on multiple DAC/ADCs.
+ impl Mutex for $name {
+ type Data = &'static mut [u16];
+ fn lock<R>(&mut self, f: impl FnOnce(&mut Self::Data) -> R) -> R {
+ self.with_buffer(f).unwrap()
+ }
+ }
+ }
+ };
+}
+
+adc_input!(
+ Adc0Input, 0, Stream0, Stream1, Stream2, SPI2, Channel1, Tim2Ch1, Channel1,
+ Tim3Ch1
+);
+adc_input!(
+ Adc1Input, 1, Stream3, Stream4, Stream5, SPI3, Channel2, Tim2Ch2, Channel2,
+ Tim3Ch2
+);
+
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 +
use serde::{Deserialize, Serialize};
+
+use core::convert::TryFrom;
+use num_enum::TryFromPrimitive;
+
+#[derive(Copy, Clone, Debug, Serialize, Deserialize, TryFromPrimitive)]
+#[repr(u8)]
+pub enum Gain {
+ G1 = 0b00,
+ G2 = 0b01,
+ G5 = 0b10,
+ G10 = 0b11,
+}
+
+/// A programmable gain amplifier that allows for setting the gain via GPIO.
+pub struct ProgrammableGainAmplifier<A0, A1> {
+ a0: A0,
+ a1: A1,
+}
+
+impl Gain {
+ /// Get the AFE gain as a numerical value.
+ pub fn as_multiplier(self) -> f32 {
+ match self {
+ Gain::G1 => 1.0,
+ Gain::G2 => 2.0,
+ Gain::G5 => 5.0,
+ Gain::G10 => 10.0,
+ }
+ }
+}
+
+impl<A0, A1> ProgrammableGainAmplifier<A0, A1>
+where
+ A0: embedded_hal::digital::v2::StatefulOutputPin,
+ A0::Error: core::fmt::Debug,
+ A1: embedded_hal::digital::v2::StatefulOutputPin,
+ A1::Error: core::fmt::Debug,
+{
+ /// Construct a new programmable gain driver.
+ ///
+ /// Args:
+ /// * `a0` - An output connected to the A0 input of the amplifier.
+ /// * `a1` - An output connected to the A1 input of the amplifier.
+ pub fn new(a0: A0, a1: A1) -> Self {
+ let mut afe = Self { a0, a1 };
+
+ afe.set_gain(Gain::G1);
+
+ afe
+ }
+
+ /// Set the gain of the front-end.
+ pub fn set_gain(&mut self, gain: Gain) {
+ if (gain as u8 & 0b01) != 0 {
+ self.a0.set_high().unwrap();
+ } else {
+ self.a0.set_low().unwrap();
+ }
+
+ if (gain as u8 & 0b10) != 0 {
+ self.a1.set_high().unwrap()
+ } else {
+ self.a1.set_low().unwrap();
+ }
+ }
+
+ /// Get the programmed gain of the analog front-end.
+ pub fn get_gain(&self) -> Gain {
+ let mut code: u8 = 0;
+ if self.a0.is_set_high().unwrap() {
+ code |= 0b1;
+ }
+ if self.a1.is_set_high().unwrap() {
+ code |= 0b10;
+ }
+
+ // NOTE(unwrap): All possibilities covered.
+ Gain::try_from(code).unwrap()
+ }
+}
+
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 +
//! STM32 Temperature Sensor Driver
+//!
+//! # Description
+//! This file provides an API for measuring the internal STM32 temperature sensor. This temperature
+//! sensor measures the silicon junction temperature (Tj) and is connected via an internal ADC.
+use stm32h7xx_hal::{
+ self as hal,
+ signature::{TS_CAL_110, TS_CAL_30},
+};
+
+use super::shared_adc::{AdcChannel, AdcError};
+
+/// Helper utility to convert raw codes into temperature measurements.
+struct Calibration {
+ slope: f32,
+ offset: f32,
+}
+
+impl Calibration {
+ /// Construct the calibration utility.
+ pub fn new() -> Self {
+ let ts_cal2 = TS_CAL_110::read();
+ let ts_cal1 = TS_CAL_30::read();
+ let slope = (110. - 30.) / (ts_cal2 as f32 - ts_cal1 as f32);
+ let offset = 30. - slope * ts_cal1 as f32;
+ Self { slope, offset }
+ }
+
+ /// Convert a raw ADC sample to a temperature in degrees Celsius.
+ pub fn sample_to_temperature(&self, sample: u32) -> f32 {
+ // We use a 2.048V reference, but calibration data was taken at 3.3V.
+ let sample_3v3 = sample as f32 * 2.048 / 3.3;
+
+ self.slope * sample_3v3 + self.offset
+ }
+}
+
+/// A driver to access the CPU temeprature sensor.
+pub struct CpuTempSensor {
+ sensor: AdcChannel<'static, hal::stm32::ADC3, hal::adc::Temperature>,
+ calibration: Calibration,
+}
+
+impl CpuTempSensor {
+ /// Construct the temperature sensor.
+ ///
+ /// # Args
+ /// * `sensor` - The ADC channel of the integrated temperature sensor.
+ pub fn new(
+ sensor: AdcChannel<'static, hal::stm32::ADC3, hal::adc::Temperature>,
+ ) -> Self {
+ Self {
+ sensor,
+ calibration: Calibration::new(),
+ }
+ }
+
+ /// Get the temperature of the CPU in degrees Celsius.
+ pub fn get_temperature(&mut self) -> Result<f32, AdcError> {
+ self.sensor
+ .read_raw()
+ .map(|raw| self.calibration.sample_to_temperature(raw))
+ }
+}
+
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 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +
//! Stabilizer DAC management interface
+//!
+//! # Design
+//!
+//! Stabilizer DACs are connected to the MCU via a simplex, SPI-compatible interface. Each DAC
+//! accepts a 16-bit output code.
+//!
+//! In order to maximize CPU processing time, the DAC code updates are offloaded to hardware using
+//! a timer compare channel, DMA stream, and the DAC SPI interface.
+//!
+//! The timer comparison channel is configured to generate a DMA request whenever the comparison
+//! occurs. Thus, whenever a comparison happens, a single DAC code can be written to the output. By
+//! configuring a DMA stream for a number of successive DAC codes, hardware can regularly update
+//! the DAC without requiring the CPU.
+//!
+//! In order to ensure alignment between the ADC sample batches and DAC output code batches, a DAC
+//! output batch is always exactly 3 batches after the ADC batch that generated it.
+//!
+//! The DMA transfer for the DAC output codes utilizes a double-buffer mode to avoid losing any
+//! transfer events generated by the timer (for example, when 2 update cycles occur before the DMA
+//! transfer completion is handled). In this mode, by the time DMA swaps buffers, there is always a valid buffer in the
+//! "next-transfer" double-buffer location for the DMA transfer. Once a transfer completes,
+//! software then has exactly one batch duration to fill the next buffer before its
+//! transfer begins. If software does not meet this deadline, old data will be repeatedly generated
+//! on the output and output will be shifted by one batch.
+//!
+//! ## Multiple Samples to Single DAC Codes
+//!
+//! For some applications, it may be desirable to generate a single DAC code from multiple ADC
+//! samples. In order to maintain timing characteristics between ADC samples and DAC code outputs,
+//! applications are required to generate one DAC code for each ADC sample. To accomodate mapping
+//! multiple inputs to a single output, the output code can be repeated a number of times in the
+//! output buffer corresponding with the number of input samples that were used to generate it.
+//!
+//!
+//! # Note
+//!
+//! There is a very small amount of latency between updating the two DACs due to bus matrix
+//! priority. As such, one of the DACs will be updated marginally earlier before the other because
+//! the DMA requests are generated simultaneously. This can be avoided by providing a known offset
+//! to other DMA requests, which can be completed by setting e.g. DAC0's comparison to a
+//! counter value of 2 and DAC1's comparison to a counter value of 3. This will have the effect of
+//! generating the DAC updates with a known latency of 1 timer tick to each other and prevent the
+//! DMAs from racing for the bus. As implemented, the DMA channels utilize natural priority of the
+//! DMA channels to arbitrate which transfer occurs first.
+//!
+//!
+//! # Limitations
+//!
+//! While double-buffered mode is used for DMA to avoid lost DAC-update events, there is no check
+//! for re-use of a previously provided DAC output buffer. It is assumed that the DMA request is
+//! served promptly after the transfer completes.
+use stm32h7xx_hal as hal;
+
+use mutex_trait::Mutex;
+
+use super::design_parameters::{SampleBuffer, MAX_SAMPLE_BUFFER_SIZE};
+use super::timers;
+
+use core::convert::TryFrom;
+
+use hal::{
+ dma::{
+ dma::{DMAReq, DmaConfig},
+ traits::TargetAddress,
+ DMAError, MemoryToPeripheral, Transfer,
+ },
+ spi::{HalDisabledSpi, HalEnabledSpi, HalSpi},
+};
+
+// The following global buffers are used for the DAC code DMA transfers. Two buffers are used for
+// each transfer in a ping-pong buffer configuration (one is being prepared while the other is being
+// processed). Note that the contents of AXI SRAM is uninitialized, so the buffer contents on
+// startup are undefined. The dimensions are `ADC_BUF[adc_index][ping_pong_index][sample_index]`.
+#[link_section = ".axisram.buffers"]
+static mut DAC_BUF: [[SampleBuffer; 2]; 2] =
+ [[[0; MAX_SAMPLE_BUFFER_SIZE]; 2]; 2];
+
+/// Custom type for referencing DAC output codes.
+/// The internal integer is the raw code written to the DAC output register.
+#[derive(Copy, Clone)]
+pub struct DacCode(pub u16);
+impl DacCode {
+ // The DAC output range in bipolar mode (including the external output op-amp) is +/- 4.096
+ // V with 16-bit resolution. The anti-aliasing filter has an additional gain of 2.5.
+ pub const FULL_SCALE: f32 = 4.096 * 2.5;
+ pub const VOLT_PER_LSB: f32 = -Self::FULL_SCALE / i16::MIN as f32;
+ pub const LSB_PER_VOLT: f32 = 1. / Self::VOLT_PER_LSB;
+}
+
+impl TryFrom<f32> for DacCode {
+ type Error = ();
+
+ fn try_from(voltage: f32) -> Result<DacCode, ()> {
+ let code = voltage * Self::LSB_PER_VOLT;
+ if !(i16::MIN as f32..=i16::MAX as f32).contains(&code) {
+ Err(())
+ } else {
+ Ok(DacCode::from(code as i16))
+ }
+ }
+}
+
+impl From<DacCode> for f32 {
+ fn from(code: DacCode) -> f32 {
+ i16::from(code) as f32 * DacCode::VOLT_PER_LSB
+ }
+}
+
+impl From<DacCode> for i16 {
+ fn from(code: DacCode) -> i16 {
+ (code.0 as i16).wrapping_sub(i16::MIN)
+ }
+}
+
+impl From<i16> for DacCode {
+ /// Encode signed 16-bit values into DAC offset binary for a bipolar output configuration.
+ fn from(value: i16) -> Self {
+ Self(value.wrapping_add(i16::MIN) as u16)
+ }
+}
+
+impl From<u16> for DacCode {
+ /// Create a dac code from the provided DAC output code.
+ fn from(value: u16) -> Self {
+ Self(value)
+ }
+}
+
+macro_rules! dac_output {
+ ($name:ident, $index:literal, $data_stream:ident,
+ $spi:ident, $trigger_channel:ident, $dma_req:ident) => {
+ /// $spi is used as a type for indicating a DMA transfer into the SPI TX FIFO
+ struct $spi {
+ spi: hal::spi::Spi<hal::stm32::$spi, hal::spi::Disabled, u16>,
+ _channel: timers::tim2::$trigger_channel,
+ }
+
+ impl $spi {
+ pub fn new(
+ _channel: timers::tim2::$trigger_channel,
+ spi: hal::spi::Spi<hal::stm32::$spi, hal::spi::Disabled, u16>,
+ ) -> Self {
+ Self { spi, _channel }
+ }
+
+ /// Start the SPI and begin operating in a DMA-driven transfer mode.
+ pub fn start_dma(&mut self) {
+ // Allow the SPI FIFOs to operate using only DMA data channels.
+ self.spi.enable_dma_tx();
+
+ // Enable SPI and start it in infinite transaction mode.
+ self.spi.inner().cr1.modify(|_, w| w.spe().set_bit());
+ self.spi.inner().cr1.modify(|_, w| w.cstart().started());
+ }
+ }
+
+ // Note(unsafe): This is safe because the DMA request line is logically owned by this module.
+ // Additionally, the SPI is owned by this structure and is known to be configured for u16 word
+ // sizes.
+ unsafe impl TargetAddress<MemoryToPeripheral> for $spi {
+ /// SPI is configured to operate using 16-bit transfer words.
+ type MemSize = u16;
+
+ /// SPI DMA requests are generated whenever TIM2 CHx ($dma_req) comparison occurs.
+ const REQUEST_LINE: Option<u8> = Some(DMAReq::$dma_req as u8);
+
+ /// Whenever the DMA request occurs, it should write into SPI's TX FIFO.
+ fn address(&self) -> usize {
+ &self.spi.inner().txdr as *const _ as usize
+ }
+ }
+
+ /// Represents data associated with DAC.
+ pub struct $name {
+ // Note: SPI TX functionality may not be used from this structure to ensure safety with DMA.
+ transfer: Transfer<
+ hal::dma::dma::$data_stream<hal::stm32::DMA1>,
+ $spi,
+ MemoryToPeripheral,
+ &'static mut [u16],
+ hal::dma::DBTransfer,
+ >,
+ }
+
+ impl $name {
+ /// Construct the DAC output channel.
+ ///
+ /// # Args
+ /// * `spi` - The SPI interface used to communicate with the ADC.
+ /// * `stream` - The DMA stream used to write DAC codes over SPI.
+ /// * `trigger_channel` - The sampling timer output compare channel for update triggers.
+ pub fn new(
+ spi: hal::spi::Spi<hal::stm32::$spi, hal::spi::Enabled, u16>,
+ stream: hal::dma::dma::$data_stream<hal::stm32::DMA1>,
+ trigger_channel: timers::tim2::$trigger_channel,
+ batch_size: usize,
+ ) -> Self {
+ // Generate DMA events when an output compare of the timer hitting zero (timer roll over)
+ // occurs.
+ trigger_channel.listen_dma();
+ trigger_channel.to_output_compare(4 + $index);
+
+ // The stream constantly writes to the TX FIFO to write new update codes.
+ let trigger_config = DmaConfig::default()
+ .memory_increment(true)
+ .double_buffer(true)
+ .peripheral_increment(false);
+
+ // Listen for any potential SPI error signals, which may indicate that we are not generating
+ // update codes.
+ let mut spi = spi.disable();
+ spi.listen(hal::spi::Event::Error);
+
+ // AXISRAM is uninitialized. As such, we manually initialize it for a 0V DAC output
+ // here before starting the transfer .
+ // Note(unsafe): We currently own all DAC_BUF[index] buffers and are not using them
+ // elsewhere, so it is safe to access them here.
+ for buf in unsafe { DAC_BUF[$index].iter_mut() } {
+ for byte in buf.iter_mut() {
+ *byte = DacCode::try_from(0.0f32).unwrap().0;
+ }
+ }
+
+ // Construct the trigger stream to write from memory to the peripheral.
+ let transfer: Transfer<_, _, MemoryToPeripheral, _, _> =
+ Transfer::init(
+ stream,
+ $spi::new(trigger_channel, spi),
+ // Note(unsafe): This buffer is only used once and provided for the DMA transfer.
+ unsafe { &mut DAC_BUF[$index][0][..batch_size] },
+ // Note(unsafe): This buffer is only used once and provided for the DMA transfer.
+ unsafe { Some(&mut DAC_BUF[$index][1][..batch_size]) },
+ trigger_config,
+ );
+
+ Self { transfer }
+ }
+
+ pub fn start(&mut self) {
+ self.transfer.start(|spi| spi.start_dma());
+ }
+
+ /// Wait for the transfer of the currently active buffer to complete,
+ /// then call a function on the now inactive buffer and acknowledge the
+ /// transfer complete flag.
+ ///
+ /// NOTE(unsafe): Memory safety and access ordering is not guaranteed
+ /// (see the HAL DMA docs).
+ pub fn with_buffer<F, R>(&mut self, f: F) -> Result<R, DMAError>
+ where
+ F: FnOnce(&mut &'static mut [u16]) -> R,
+ {
+ unsafe {
+ self.transfer.next_dbm_transfer_with(|buf, _current| f(buf))
+ }
+ }
+ }
+
+ // This is not actually a Mutex. It only re-uses the semantics and macros of mutex-trait
+ // to reduce rightward drift when jointly calling `with_buffer(f)` on multiple DAC/ADCs.
+ impl Mutex for $name {
+ type Data = &'static mut [u16];
+ fn lock<R>(&mut self, f: impl FnOnce(&mut Self::Data) -> R) -> R {
+ self.with_buffer(f).unwrap()
+ }
+ }
+ };
+}
+
+dac_output!(Dac0Output, 0, Stream6, SPI4, Channel3, Tim2Ch3);
+dac_output!(Dac1Output, 1, Stream7, SPI5, Channel4, Tim2Ch4);
+
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 +
//! Basic blocking delay
+//!
+//! This module provides a basic asm-based blocking delay.
+//!
+//! # Note
+//! This implementation takes into account the Cortex-M7 CPU pipeline architecture to ensure delays
+//! are at least as long as specified.
+use embedded_hal::blocking::delay::{DelayMs, DelayUs};
+
+/// A basic delay implementation.
+pub struct AsmDelay {
+ frequency_us: u32,
+ frequency_ms: u32,
+}
+
+impl AsmDelay {
+ /// Create a new delay.
+ ///
+ /// # Args
+ /// * `freq` - The CPU core frequency.
+ pub fn new(freq: u32) -> AsmDelay {
+ // Note: Frequencies are scaled by 2 to account for the M7 dual instruction pipeline.
+ AsmDelay {
+ frequency_us: (freq / 1_000_000) * 2,
+ frequency_ms: (freq / 1_000) * 2,
+ }
+ }
+}
+
+impl<U> DelayUs<U> for AsmDelay
+where
+ U: Into<u32>,
+{
+ fn delay_us(&mut self, us: U) {
+ cortex_m::asm::delay(self.frequency_us * us.into())
+ }
+}
+
+impl<U> DelayMs<U> for AsmDelay
+where
+ U: Into<u32>,
+{
+ fn delay_ms(&mut self, ms: U) {
+ cortex_m::asm::delay(self.frequency_ms * ms.into())
+ }
+}
+
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 +
use stm32h7xx_hal::time::MegaHertz;
+
+/// The system clock, used in various timer calculations
+pub const SYSCLK: MegaHertz = MegaHertz::MHz(400);
+
+/// The ADC setup time is the number of seconds after the CSn line goes low before the serial clock
+/// may begin. This is used for performing the internal ADC conversion.
+pub const ADC_SETUP_TIME: f32 = 220e-9;
+
+/// The maximum DAC/ADC serial clock line frequency. This is a hardware limit.
+pub const ADC_DAC_SCK_MAX: MegaHertz = MegaHertz::MHz(50);
+
+/// The optimal counting frequency of the hardware timers used for timestamping and sampling.
+pub const TIMER_FREQUENCY: MegaHertz = MegaHertz::MHz(100);
+pub const TIMER_PERIOD: f32 = 1. / (TIMER_FREQUENCY.to_Hz() as f32);
+
+/// The QSPI frequency for communicating with the pounder DDS.
+pub const POUNDER_QSPI_FREQUENCY: MegaHertz = MegaHertz::MHz(50);
+
+/// The delay after initiating a QSPI transfer before asserting the IO_Update for the pounder DDS.
+// Pending Pounder Profile writes are up to 32 bytes (QSPI FIFO depth),
+// with 2 cycles required per byte, coming out to a total of 64 QSPI clock cycles.
+// The QSPI is configured for 50MHz, so this comes out to an offset
+// of 1280 ns. We use 1300 ns to be safe.
+pub const POUNDER_IO_UPDATE_DELAY: f32 = 1_300e-9;
+
+/// The duration to assert IO_Update for the pounder DDS.
+// IO_Update should be latched for 4 SYNC_CLK cycles after the QSPI profile write. With pounder
+// SYNC_CLK running at 100MHz (1/4 of the pounder reference clock of 500MHz), this corresponds to
+// 32ns. To accomodate rounding errors, we use 50ns instead.
+pub const POUNDER_IO_UPDATE_DURATION: f32 = 50e-9;
+
+/// The DDS reference clock frequency in MHz.
+pub const DDS_REF_CLK: MegaHertz = MegaHertz::MHz(100);
+
+/// The multiplier used for the DDS reference clock PLL.
+pub const DDS_MULTIPLIER: u8 = 5;
+
+/// The DDS system clock frequency after the internal PLL multiplication.
+#[allow(dead_code)]
+pub const DDS_SYSTEM_CLK: MegaHertz =
+ MegaHertz::MHz(DDS_REF_CLK.to_MHz() * DDS_MULTIPLIER as u32);
+
+/// The divider from the DDS system clock to the SYNC_CLK output (sync-clk is always 1/4 of sysclk).
+#[allow(dead_code)]
+pub const DDS_SYNC_CLK_DIV: u8 = 4;
+
+/// The maximum ADC/DAC sample processing buffer size.
+pub const MAX_SAMPLE_BUFFER_SIZE: usize = 32;
+
+pub type SampleBuffer = [u16; MAX_SAMPLE_BUFFER_SIZE];
+
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 +
use embedded_hal::blocking::{delay::DelayMs, i2c::WriteRead};
+
+// The EEPROM is a variant without address bits, so the 3 LSB of this word are "dont-cares".
+const I2C_ADDR: u8 = 0x50;
+
+// The MAC address is stored in the last 6 bytes of the 256 byte address space.
+const MAC_POINTER: u8 = 0xFA;
+
+pub fn read_eui48<T>(i2c: &mut T, delay: &mut impl DelayMs<u8>) -> [u8; 6]
+where
+ T: WriteRead,
+{
+ let mut previous_read: Option<[u8; 6]> = None;
+ // On Stabilizer v1.1 and earlier hardware, there is a fault where the I2C bus is not connected
+ // to the CPU until the P12V0A rail enables, which can take many seconds, or may never come up
+ // at all. During these transient turn-on conditions, we may fail the I2C read operation. To
+ // accomodate this, we repeat the I2C read for a set number of attempts with a fixed delay
+ // between them. Then, we wait for the bus to stabilize by waiting until the MAC address
+ // read-out is identical for two consecutive reads.
+ for _ in 0..40 {
+ let mut buffer = [0u8; 6];
+ if i2c
+ .write_read(I2C_ADDR, &[MAC_POINTER], &mut buffer)
+ .is_ok()
+ {
+ if let Some(old_read) = previous_read {
+ if old_read == buffer {
+ return buffer;
+ }
+ }
+
+ previous_read.replace(buffer);
+ } else {
+ // Remove any pending previous read if we failed the last attempt.
+ previous_read.take();
+ }
+
+ delay.delay_ms(100);
+ }
+
+ panic!("Failed to read MAC address");
+}
+
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 +
//! Digital Input 0 (DI0) reference clock timestamper
+//!
+//! This module provides a means of timestamping the rising edges of an external reference clock on
+//! the DI0 with a timer value from TIM5.
+//!
+//! # Design
+//! An input capture channel is configured on DI0 and fed into TIM5's capture channel 4. TIM5 is
+//! then run in a free-running mode with a configured tick rate (PSC) and maximum count value
+//! (ARR). Whenever an edge on DI0 triggers, the current TIM5 counter value is captured and
+//! recorded as a timestamp. This timestamp can be either directly read from the timer channel or
+//! can be collected asynchronously via DMA collection.
+//!
+//! To prevent silently discarding timestamps, the TIM5 input capture over-capture flag is
+//! continually checked. Any over-capture event (which indicates an overwritten timestamp) then
+//! triggers a panic to indicate the dropped timestamp so that design parameters can be adjusted.
+//!
+//! # Tradeoffs
+//! It appears that DMA transfers can take a significant amount of time to disable (400ns) if they
+//! are being prematurely stopped (such is the case here). As such, for a sample batch size of 1,
+//! this can take up a significant amount of the total available processing time for the samples.
+//! This module checks for any captured timestamps from the timer capture channel manually. In
+//! this mode, the maximum input clock frequency supported is dependant on the sampling rate and
+//! batch size.
+//!
+//! This module only supports DI0 for timestamping due to trigger constraints on the DIx pins. If
+//! timestamping is desired in DI1, a separate timer + capture channel will be necessary.
+use super::{hal, timers};
+
+/// The timestamper for DI0 reference clock inputs.
+pub struct InputStamper {
+ _di0_trigger: hal::gpio::gpioa::PA3<hal::gpio::Alternate<2>>,
+ capture_channel: timers::tim5::Channel4InputCapture,
+}
+
+impl InputStamper {
+ /// Construct the DI0 input timestamper.
+ ///
+ /// # Args
+ /// * `trigger` - The capture trigger input pin.
+ /// * `timer_channel - The timer channel used for capturing timestamps.
+ pub fn new(
+ trigger: hal::gpio::gpioa::PA3<hal::gpio::Alternate<2>>,
+ timer_channel: timers::tim5::Channel4,
+ ) -> Self {
+ // Utilize the TIM5 CH4 as an input capture channel - use TI4 (the DI0 input trigger) as the
+ // capture source.
+ let mut input_capture =
+ timer_channel.into_input_capture(timers::tim5::CaptureSource4::Ti4);
+
+ // Do not prescale the input capture signal - require 8 consecutive samples to record an
+ // incoming event - this prevents spurious glitches from triggering captures.
+ input_capture.configure_filter(timers::InputFilter::Div1N8);
+
+ Self {
+ capture_channel: input_capture,
+ _di0_trigger: trigger,
+ }
+ }
+
+ /// Start to capture timestamps on DI0.
+ #[allow(dead_code)]
+ pub fn start(&mut self) {
+ self.capture_channel.enable();
+ }
+
+ /// Get the latest timestamp that has occurred.
+ ///
+ /// # Note
+ /// This function must be called at least as often as timestamps arrive.
+ /// If an over-capture event occurs, this function will clear the overflow,
+ /// and return a new timestamp of unknown recency an `Err()`.
+ /// Note that this indicates at least one timestamp was inadvertently dropped.
+ #[allow(dead_code)]
+ pub fn latest_timestamp(&mut self) -> Result<Option<u32>, Option<u32>> {
+ self.capture_channel.latest_capture()
+ }
+}
+
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 +
//! Module for all hardware-specific setup of Stabilizer
+
+pub use embedded_hal;
+pub use stm32h7xx_hal as hal;
+
+pub mod adc;
+pub mod afe;
+pub mod cpu_temp_sensor;
+pub mod dac;
+pub mod delay;
+pub mod design_parameters;
+pub mod input_stamper;
+pub mod pounder;
+pub mod serial_terminal;
+pub mod setup;
+pub mod shared_adc;
+pub mod signal_generator;
+pub mod timers;
+
+mod eeprom;
+
+// Type alias for the analog front-end (AFE) for ADC0.
+pub type AFE0 = afe::ProgrammableGainAmplifier<
+ hal::gpio::gpiof::PF2<hal::gpio::Output<hal::gpio::PushPull>>,
+ hal::gpio::gpiof::PF5<hal::gpio::Output<hal::gpio::PushPull>>,
+>;
+
+// Type alias for the analog front-end (AFE) for ADC1.
+pub type AFE1 = afe::ProgrammableGainAmplifier<
+ hal::gpio::gpiod::PD14<hal::gpio::Output<hal::gpio::PushPull>>,
+ hal::gpio::gpiod::PD15<hal::gpio::Output<hal::gpio::PushPull>>,
+>;
+
+pub type UsbBus = stm32h7xx_hal::usb_hs::UsbBus<stm32h7xx_hal::usb_hs::USB2>;
+
+// Type alias for digital input 0 (DI0).
+pub type DigitalInput0 = hal::gpio::gpiog::PG9<hal::gpio::Input>;
+
+// Type alias for digital input 1 (DI1).
+pub type DigitalInput1 = hal::gpio::gpioc::PC15<hal::gpio::Input>;
+
+// Type alias for LVDS4 (digital input).
+pub type EemDigitalInput0 = hal::gpio::gpiod::PD1<hal::gpio::Input>;
+
+// Type alias for LVDS5 (digital input).
+pub type EemDigitalInput1 = hal::gpio::gpiod::PD2<hal::gpio::Input>;
+
+// Type alias for LVDS6 (digital output).
+pub type EemDigitalOutput0 = hal::gpio::gpiod::PD3<hal::gpio::Output>;
+
+// Type alias for LVDS7 (digital output).
+pub type EemDigitalOutput1 = hal::gpio::gpiod::PD4<hal::gpio::Output>;
+
+// Number of TX descriptors in the ethernet descriptor ring.
+const TX_DESRING_CNT: usize = 4;
+
+// Number of RX descriptors in the ethernet descriptor ring.
+const RX_DESRING_CNT: usize = 4;
+
+pub type NetworkStack = smoltcp_nal::NetworkStack<
+ 'static,
+ hal::ethernet::EthernetDMA<TX_DESRING_CNT, RX_DESRING_CNT>,
+ SystemTimer,
+>;
+
+pub type NetworkManager = smoltcp_nal::shared::NetworkManager<
+ 'static,
+ hal::ethernet::EthernetDMA<TX_DESRING_CNT, RX_DESRING_CNT>,
+ SystemTimer,
+>;
+
+pub type EthernetPhy = hal::ethernet::phy::LAN8742A<hal::ethernet::EthernetMAC>;
+
+/// System timer (RTIC Monotonic) tick frequency
+pub const MONOTONIC_FREQUENCY: u32 = 1_000;
+pub type Systick = systick_monotonic::Systick<MONOTONIC_FREQUENCY>;
+pub type SystemTimer = mono_clock::MonoClock<u32, MONOTONIC_FREQUENCY>;
+
+pub type I2c1 = hal::i2c::I2c<hal::stm32::I2C1>;
+pub type I2c1Proxy =
+ shared_bus::I2cProxy<'static, shared_bus::AtomicCheckMutex<I2c1>>;
+
+#[inline(never)]
+#[panic_handler]
+fn panic(info: &core::panic::PanicInfo) -> ! {
+ use core::{
+ fmt::Write,
+ sync::atomic::{AtomicBool, Ordering},
+ };
+ use cortex_m::asm;
+ use rtt_target::{ChannelMode, UpChannel};
+
+ cortex_m::interrupt::disable();
+
+ // Recursion protection
+ static PANICKED: AtomicBool = AtomicBool::new(false);
+ while PANICKED.load(Ordering::Relaxed) {
+ asm::bkpt();
+ }
+ PANICKED.store(true, Ordering::Relaxed);
+
+ // Turn on both red LEDs, FP_LED_1, FP_LED_3
+ let gpiod = unsafe { &*hal::stm32::GPIOD::ptr() };
+ gpiod.odr.modify(|_, w| w.odr6().high().odr12().high());
+
+ // Analogous to panic-rtt-target
+ if let Some(mut channel) = unsafe { UpChannel::conjure(0) } {
+ channel.set_mode(ChannelMode::BlockIfFull);
+ writeln!(channel, "{info}").ok();
+ }
+
+ // Abort
+ asm::udf();
+ // Halt
+ // loop { core::sync::atomic::compiler_fence(Ordering::SeqCst); }
+}
+
+#[cortex_m_rt::exception]
+unsafe fn HardFault(ef: &cortex_m_rt::ExceptionFrame) -> ! {
+ panic!("HardFault at {:#?}", ef);
+}
+
+#[cortex_m_rt::exception]
+unsafe fn DefaultHandler(irqn: i16) {
+ panic!("Unhandled exception (IRQn = {})", irqn);
+}
+
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 +
use super::{Channel, Error};
+
+/// Provide an interface for managing digital attenuators on Pounder hardware.
+///
+/// Note: The digital attenuators do not allow read-back of attenuation. To circumvent this, this
+/// driver maintains the attenuation code in both the shift register as well as the latched output
+/// register of the attenuators. This allows the "active" attenuation code to be read back by
+/// reading the shfit register. The downside of this approach is that any read is destructive, so a
+/// read-writeback approach is employed.
+pub trait AttenuatorInterface {
+ /// Set the attenuation of a single channel.
+ ///
+ /// Args:
+ /// * `channel` - The pounder channel to configure the attenuation of.
+ /// * `attenuation` - The desired attenuation of the channel in dB. This has a resolution of
+ /// 0.5dB.
+ fn set_attenuation(
+ &mut self,
+ channel: Channel,
+ attenuation: f32,
+ ) -> Result<f32, Error> {
+ if !(0.0..=31.5).contains(&attenuation) {
+ return Err(Error::Bounds);
+ }
+
+ // Calculate the attenuation code to program into the attenuator. The attenuator uses a
+ // code where the LSB is 0.5 dB.
+ let attenuation_code = (attenuation * 2.0) as u8;
+
+ // Read all the channels, modify the channel of interest, and write all the channels back.
+ // This ensures the staging register and the output register are always in sync.
+ let mut channels = [0_u8; 4];
+ self.transfer_attenuators(&mut channels)?;
+
+ // The lowest 2 bits of the 8-bit shift register on the attenuator are ignored. Shift the
+ // attenuator code into the upper 6 bits of the register value. Note that the attenuator
+ // treats inputs as active-low, so the code is inverted before writing.
+ channels[channel as usize] = !(attenuation_code << 2);
+ self.transfer_attenuators(&mut channels)?;
+
+ // Finally, latch the output of the updated channel to force it into an active state.
+ self.latch_attenuator(channel)?;
+
+ Ok(attenuation_code as f32 / 2.0)
+ }
+
+ /// Get the attenuation of a channel.
+ ///
+ /// Args:
+ /// * `channel` - The channel to get the attenuation of.
+ ///
+ /// Returns:
+ /// The programmed attenuation of the channel in dB.
+ fn get_attenuation(&mut self, channel: Channel) -> Result<f32, Error> {
+ let mut channels = [0_u8; 4];
+
+ // Reading the data always shifts data out of the staging registers, so we perform a
+ // duplicate write-back to ensure the staging register is always equal to the output
+ // register.
+ self.transfer_attenuators(&mut channels)?;
+ self.transfer_attenuators(&mut channels)?;
+
+ // The attenuation code is stored in the upper 6 bits of the register, where each LSB
+ // represents 0.5 dB. The attenuator stores the code as active-low, so inverting the result
+ // (before the shift) has the affect of transforming the bits of interest (and the
+ // dont-care bits) into an active-high state and then masking off the don't care bits. If
+ // the shift occurs before the inversion, the upper 2 bits (which would then be don't
+ // care) would contain erroneous data.
+ let attenuation_code = (!channels[channel as usize]) >> 2;
+
+ // Convert the desired channel code into dB of attenuation.
+ Ok(attenuation_code as f32 / 2.0)
+ }
+
+ fn reset_attenuators(&mut self) -> Result<(), Error>;
+
+ fn latch_attenuator(&mut self, channel: Channel) -> Result<(), Error>;
+
+ fn transfer_attenuators(
+ &mut self,
+ channels: &mut [u8; 4],
+ ) -> Result<(), Error>;
+}
+
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 +159 +160 +161 +162 +163 +164 +165 +166 +
//! The DdsOutput is used as an output stream to the pounder DDS.
+//!
+//! # Design
+//!
+//! The DDS stream interface is a means of quickly updating pounder DDS (direct digital synthesis)
+//! outputs of the AD9959 DDS chip. The DDS communicates via a quad-SPI interface and a single
+//! IO-update output pin.
+//!
+//! In order to update the DDS interface, the frequency tuning word, amplitude control word, and
+//! the phase offset word for a channel can be modified to change the frequency, amplitude, or
+//! phase on any of the 4 available output channels. Changes do not propagate to DDS outputs until
+//! the IO-update pin is toggled high to activate the new configurations. This allows multiple
+//! channels or parameters to be updated and then effects can take place simultaneously.
+//!
+//! In this implementation, the phase, frequency, or amplitude can be updated for any single
+//! collection of outputs simultaneously. This is done by serializing the register writes to the
+//! DDS into a single buffer of data and then writing the data over QSPI to the DDS.
+//!
+//! In order to minimize software overhead, data is written directly into the QSPI output FIFO. In
+//! order to accomplish this most efficiently, serialized data is written as 32-bit words to
+//! minimize the number of bus cycles necessary to write to the peripheral FIFO. A consequence of
+//! this is that additional unneeded register writes may be appended to align a transfer to 32-bit
+//! word sizes.
+//!
+//! In order to pulse the IO-update signal, the high-resolution timer output is used. The timer is
+//! configured to assert the IO-update signal after a predefined delay and then de-assert the
+//! signal after a predefined assertion duration. This allows for the actual QSPI transfer and
+//! IO-update toggle to be completed asynchronously to the rest of software processing - that is,
+//! software can schedule the DDS updates and then continue data processing. DDS updates then take
+//! place in the future when the IO-update is toggled by hardware.
+//!
+//!
+//! # Limitations
+//!
+//! The QSPI output FIFO is used as an intermediate buffer for holding pending QSPI writes. Because
+//! of this, the implementation only supports up to 16 serialized bytes (the QSPI FIFO is 8 32-bit
+//! words, or 32 bytes, wide) in a single update.
+//!
+//! There is currently no synchronization between completion of the QSPI data write and the
+//! IO-update signal. It is currently assumed that the QSPI transfer will always complete within a
+//! predefined delay (the pre-programmed IO-update timer delay).
+//!
+//!
+//! # Future Improvement
+//!
+//! In the future, it would be possible to utilize a DMA transfer to complete the QSPI transfer.
+//! Once the QSPI transfer completed, this could trigger the IO-update timer to start to
+//! asynchronously complete IO-update automatically. This would allow for arbitrary profile sizes
+//! and ensure that IO-update was in-sync with the QSPI transfer.
+//!
+//! Currently, serialization is performed on each processing cycle. If there is a
+//! compile-time-known register update sequence needed for the application, the serialization
+//! process can be done once and then register values can be written into a pre-computed serialized
+//! buffer to avoid the software overhead of much of the serialization process.
+use log::warn;
+use stm32h7xx_hal as hal;
+
+use super::{hrtimer::HighResTimerE, QspiInterface};
+use ad9959::{Channel, Mode, ProfileSerializer};
+
+/// The DDS profile update stream.
+pub struct DdsOutput {
+ _qspi: QspiInterface,
+ io_update_trigger: HighResTimerE,
+ mode: Mode,
+}
+
+impl DdsOutput {
+ /// Construct a new DDS output stream.
+ ///
+ /// # Note
+ /// It is assumed that the QSPI stream and the IO_Update trigger timer have been configured in a
+ /// way such that the profile has sufficient time to be written before the IO_Update signal is
+ /// generated.
+ ///
+ /// # Args
+ /// * `qspi` - The QSPI interface to the run the stream on.
+ /// * `io_update_trigger` - The HighResTimerE used to generate IO_Update pulses.
+ /// * `config` - The frozen DDS configuration.
+ pub fn new(
+ mut qspi: QspiInterface,
+ io_update_trigger: HighResTimerE,
+ mode: Mode,
+ ) -> Self {
+ qspi.start_stream().unwrap();
+ Self {
+ mode,
+ _qspi: qspi,
+ io_update_trigger,
+ }
+ }
+
+ /// Get a builder for serializing a Pounder DDS profile.
+ #[allow(dead_code)]
+ pub fn builder(&mut self) -> ProfileBuilder {
+ let mode = self.mode;
+ ProfileBuilder {
+ dds_output: self,
+ serializer: ProfileSerializer::new(mode),
+ }
+ }
+
+ /// Write a profile to the stream.
+ ///
+ /// # Note:
+ /// If a profile of more than 8 words is provided, the QSPI interface will likely
+ /// stall execution. If there are still bytes pending in the FIFO, the write will certainly
+ /// stall.
+ ///
+ /// # Args
+ /// * `profile` - The serialized DDS profile to write.
+ pub fn write(&mut self, profile: &[u32]) {
+ // Note(unsafe): We own the QSPI interface, so it is safe to access the registers in a raw
+ // fashion.
+ let regs = unsafe { &*hal::stm32::QUADSPI::ptr() };
+
+ // Warn if the fifo is still at least half full.
+ if regs.sr.read().flevel().bits() >= 16 {
+ warn!("QSPI stalling")
+ }
+
+ for word in profile.iter() {
+ // Note(unsafe): any bit pattern is valid for a TX FIFO write.
+ regs.dr.write(|w| unsafe { w.bits(*word) });
+ }
+
+ // Trigger the IO_update signal generating timer to asynchronous create the IO_Update pulse.
+ self.io_update_trigger.trigger();
+ }
+}
+
+/// A temporary builder for serializing and writing profiles.
+pub struct ProfileBuilder<'a> {
+ dds_output: &'a mut DdsOutput,
+ serializer: ProfileSerializer,
+}
+
+impl<'a> ProfileBuilder<'a> {
+ /// Update a number of channels with the provided configuration
+ ///
+ /// # Args
+ /// * `channels` - A list of channels to apply the configuration to.
+ /// * `ftw` - If provided, indicates a frequency tuning word for the channels.
+ /// * `pow` - If provided, indicates a phase offset word for the channels.
+ /// * `acr` - If provided, indicates the amplitude control register for the channels. The
+ /// 24-bits of the ACR should be stored in the last 3 LSB.
+ #[allow(dead_code)]
+ #[inline]
+ pub fn update_channels(
+ &mut self,
+ channels: Channel,
+ ftw: Option<u32>,
+ pow: Option<u16>,
+ acr: Option<u32>,
+ ) -> &mut Self {
+ self.serializer.update_channels(channels, ftw, pow, acr);
+ self
+ }
+
+ /// Write the profile to the DDS asynchronously.
+ #[allow(dead_code)]
+ #[inline]
+ pub fn write(&mut self) {
+ self.dds_output.write(self.serializer.finalize());
+ }
+}
+
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 +
//! The HRTimer (High Resolution Timer) is used to generate IO_Update pulses to the Pounder DDS.
+use stm32h7xx_hal::{
+ self as hal,
+ rcc::{rec, CoreClocks, ResetEnable},
+};
+
+/// A HRTimer output channel.
+#[allow(dead_code)]
+pub enum Channel {
+ One,
+ Two,
+}
+
+/// The high resolution timer. Currently, only Timer E is supported.
+pub struct HighResTimerE {
+ master: hal::stm32::HRTIM_MASTER,
+ timer: hal::stm32::HRTIM_TIME,
+ common: hal::stm32::HRTIM_COMMON,
+
+ clocks: CoreClocks,
+}
+
+impl HighResTimerE {
+ /// Construct a new high resolution timer for generating IO_update signals.
+ pub fn new(
+ timer_regs: hal::stm32::HRTIM_TIME,
+ master_regs: hal::stm32::HRTIM_MASTER,
+ common_regs: hal::stm32::HRTIM_COMMON,
+ clocks: CoreClocks,
+ prec: rec::Hrtim,
+ ) -> Self {
+ prec.reset().enable();
+
+ Self {
+ master: master_regs,
+ timer: timer_regs,
+ common: common_regs,
+ clocks,
+ }
+ }
+
+ /// Configure the timer to operate in single-shot mode.
+ ///
+ /// # Note
+ /// This will configure the timer to generate a single pulse on an output channel. The timer
+ /// will only count up once and must be `trigger()`'d after / configured.
+ ///
+ /// The output will be asserted from `set_offset` to `set_offset` + `set_duration` in the count.
+ ///
+ /// # Args
+ /// * `channel` - The timer output channel to configure.
+ /// * `set_duration` - The duration that the output should be asserted for.
+ /// * `set_offset` - The first time at which the output should be asserted.
+ pub fn configure_single_shot(
+ &mut self,
+ channel: Channel,
+ delay: f32,
+ duration: f32,
+ ) {
+ // Disable the timer before configuration.
+ self.master.mcr.modify(|_, w| w.tecen().clear_bit());
+
+ // Configure the desired timer for single shot mode with set and reset of the specified
+ // channel at the desired durations. The HRTIM is on APB2 (D2 domain), and the kernel clock
+ // is the APB bus clock.
+ let clk = self.clocks.timy_ker_ck().to_Hz() as f32;
+ let end = ((delay + duration) * clk) as u32 + 1;
+
+ // Determine the clock divider, which may be 1, 2, or 4. We will choose a clock divider that
+ // allows us the highest resolution per tick, so lower dividers are favored.
+ let div: u8 = if end < 0xFFDF {
+ 1
+ } else if (end / 2) < 0xFFDF {
+ 2
+ } else if (end / 4) < 0xFFDF {
+ 3
+ } else {
+ panic!("Unattainable timing parameters!");
+ };
+
+ // The period register must be greater than or equal to 3 cycles.
+ let period = (end / (1 << (div - 1)) as u32) as u16;
+ assert!(period > 2);
+
+ // We now have the prescaler and the period registers. Configure the timer.
+ // Note(unsafe): The prescaler is guaranteed to be greater than or equal to 4 (minimum
+ // allowed value) due to the addition. The setting is always 1, 2, or 3, which represents
+ // all valid values.
+ self.timer
+ .timecr
+ .modify(|_, w| unsafe { w.ck_pscx().bits(div + 4) });
+
+ // Note(unsafe): The period register is guaranteed to be a 16-bit value, which will fit in
+ // this register.
+ self.timer.perer.write(|w| unsafe { w.perx().bits(period) });
+
+ // Configure the comparator 1 level.
+ let delay = (delay * clk) as u16;
+ // Note(unsafe): The offset is always a 16-bit value, so is always valid for values >= 3, as
+ // specified by the datasheet.
+ assert!(delay >= 3);
+ self.timer
+ .cmp1er
+ .write(|w| unsafe { w.cmp1x().bits(delay) });
+
+ // Configure the set/reset signals.
+ // Set on compare with CMP1, reset upon reaching PER
+ match channel {
+ Channel::One => {
+ self.timer.sete1r.write(|w| w.cmp1().set_bit());
+ self.timer.rste1r.write(|w| w.per().set_bit());
+ self.common.oenr.write(|w| w.te1oen().set_bit());
+ }
+ Channel::Two => {
+ self.timer.sete2r.write(|w| w.cmp1().set_bit());
+ self.timer.rste2r.write(|w| w.per().set_bit());
+ self.common.oenr.write(|w| w.te2oen().set_bit());
+ }
+ }
+
+ // Enable the timer now that it is configured.
+ self.master.mcr.modify(|_, w| w.tecen().set_bit());
+ }
+
+ /// Generate a single trigger of the timer to start the output pulse generation.
+ pub fn trigger(&mut self) {
+ // Generate a reset event to force the timer to start counting.
+ self.common.cr2.write(|w| w.terst().set_bit());
+ }
+}
+
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 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +
use self::attenuators::AttenuatorInterface;
+
+use super::hal;
+use crate::hardware::{shared_adc::AdcChannel, I2c1Proxy};
+use embedded_hal::blocking::spi::Transfer;
+use enum_iterator::Sequence;
+use serde::{Deserialize, Serialize};
+
+pub mod attenuators;
+pub mod dds_output;
+pub mod hrtimer;
+pub mod rf_power;
+
+#[cfg(not(feature = "pounder_v1_0"))]
+pub mod timestamp;
+
+#[derive(Debug, Copy, Clone, Sequence)]
+pub enum GpioPin {
+ Led4Green,
+ Led5Red,
+ Led6Green,
+ Led7Red,
+ Led8Green,
+ Led9Red,
+ AttLe0,
+ AttLe1,
+ AttLe2,
+ AttLe3,
+ AttRstN,
+ OscEnN,
+ ExtClkSel,
+}
+
+impl From<GpioPin> for mcp230xx::Mcp23017 {
+ fn from(x: GpioPin) -> Self {
+ match x {
+ GpioPin::Led4Green => Self::A0,
+ GpioPin::Led5Red => Self::A1,
+ GpioPin::Led6Green => Self::A2,
+ GpioPin::Led7Red => Self::A3,
+ GpioPin::Led8Green => Self::A4,
+ GpioPin::Led9Red => Self::A5,
+ GpioPin::AttLe0 => Self::B0,
+ GpioPin::AttLe1 => Self::B1,
+ GpioPin::AttLe2 => Self::B2,
+ GpioPin::AttLe3 => Self::B3,
+ GpioPin::AttRstN => Self::B5,
+ GpioPin::OscEnN => Self::B6,
+ GpioPin::ExtClkSel => Self::B7,
+ }
+ }
+}
+
+#[derive(Debug, Copy, Clone)]
+pub enum Error {
+ Spi,
+ I2c,
+ Qspi(hal::xspi::QspiError),
+ Bounds,
+ InvalidAddress,
+ InvalidChannel,
+ Adc,
+ InvalidState,
+}
+
+impl From<hal::xspi::QspiError> for Error {
+ fn from(e: hal::xspi::QspiError) -> Error {
+ Error::Qspi(e)
+ }
+}
+
+/// The numerical value (discriminant) of the Channel enum is the index in the attenuator shift
+/// register as well as the attenuator latch enable signal index on the GPIO extender.
+#[derive(Debug, Copy, Clone)]
+#[allow(dead_code)]
+pub enum Channel {
+ In0 = 0,
+ Out0 = 1,
+ In1 = 2,
+ Out1 = 3,
+}
+
+impl From<Channel> for GpioPin {
+ fn from(x: Channel) -> Self {
+ match x {
+ Channel::In0 => GpioPin::AttLe0,
+ Channel::Out0 => GpioPin::AttLe1,
+ Channel::In1 => GpioPin::AttLe2,
+ Channel::Out1 => GpioPin::AttLe3,
+ }
+ }
+}
+
+#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
+pub struct DdsChannelState {
+ pub phase_offset: f32,
+ pub frequency: f32,
+ pub amplitude: f32,
+ pub enabled: bool,
+}
+
+#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
+pub struct ChannelState {
+ pub parameters: DdsChannelState,
+ pub attenuation: f32,
+}
+
+#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
+pub struct InputChannelState {
+ pub attenuation: f32,
+ pub power: f32,
+ pub mixer: DdsChannelState,
+}
+
+#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
+pub struct OutputChannelState {
+ pub attenuation: f32,
+ pub channel: DdsChannelState,
+}
+
+#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
+pub struct DdsClockConfig {
+ pub multiplier: u8,
+ pub reference_clock: f32,
+ pub external_clock: bool,
+}
+
+impl From<Channel> for ad9959::Channel {
+ /// Translate pounder channels to DDS output channels.
+ fn from(other: Channel) -> Self {
+ match other {
+ Channel::In0 => Self::TWO,
+ Channel::In1 => Self::FOUR,
+ Channel::Out0 => Self::ONE,
+ Channel::Out1 => Self::THREE,
+ }
+ }
+}
+
+/// A structure for the QSPI interface for the DDS.
+pub struct QspiInterface {
+ pub qspi: hal::xspi::Qspi<hal::stm32::QUADSPI>,
+ mode: ad9959::Mode,
+ streaming: bool,
+}
+
+impl QspiInterface {
+ /// Initialize the QSPI interface.
+ ///
+ /// Args:
+ /// * `qspi` - The QSPI peripheral driver.
+ pub fn new(
+ mut qspi: hal::xspi::Qspi<hal::stm32::QUADSPI>,
+ ) -> Result<Self, Error> {
+ // This driver only supports operation in 4-bit mode due to bus inconsistencies between the
+ // QSPI peripheral and the DDS. Instead, we will bit-bang communications in
+ // single-bit-two-wire to the DDS to configure it to 4-bit operation.
+ qspi.configure_mode(hal::xspi::QspiMode::FourBit)?;
+ Ok(Self {
+ qspi,
+ mode: ad9959::Mode::SingleBitTwoWire,
+ streaming: false,
+ })
+ }
+
+ pub fn start_stream(&mut self) -> Result<(), Error> {
+ self.qspi.is_busy()?;
+
+ // Configure QSPI for infinite transaction mode using only a data phase (no instruction or
+ // address).
+ let qspi_regs = unsafe { &*hal::stm32::QUADSPI::ptr() };
+ qspi_regs.fcr.modify(|_, w| w.ctcf().set_bit());
+
+ unsafe {
+ qspi_regs.dlr.write(|w| w.dl().bits(0xFFFF_FFFF));
+ qspi_regs.ccr.modify(|_, w| {
+ w.imode().bits(0).fmode().bits(0).admode().bits(0)
+ });
+ }
+
+ self.streaming = true;
+
+ Ok(())
+ }
+}
+
+impl ad9959::Interface for QspiInterface {
+ type Error = Error;
+
+ /// Configure the operations mode of the interface.
+ ///
+ /// Args:
+ /// * `mode` - The newly desired operational mode.
+ fn configure_mode(&mut self, mode: ad9959::Mode) -> Result<(), Error> {
+ self.mode = mode;
+
+ Ok(())
+ }
+
+ /// Write data over QSPI to the DDS.
+ ///
+ /// Args:
+ /// * `addr` - The address to write over QSPI to the DDS.
+ /// * `data` - The data to write.
+ fn write(&mut self, addr: u8, data: &[u8]) -> Result<(), Error> {
+ if (addr & 0x80) != 0 {
+ return Err(Error::InvalidAddress);
+ }
+
+ // The QSPI interface implementation always operates in 4-bit mode because the AD9959 uses
+ // IO3 as SYNC_IO in some output modes. In order for writes to be successful, SYNC_IO must
+ // be driven low. However, the QSPI peripheral forces IO3 high when operating in 1 or 2 bit
+ // modes. As a result, any writes while in single- or dual-bit modes has to instead write
+ // the data encoded into 4-bit QSPI data so that IO3 can be driven low.
+ match self.mode {
+ ad9959::Mode::SingleBitTwoWire => {
+ // Encode the data into a 4-bit QSPI pattern.
+
+ // In 4-bit mode, we can send 2 bits of address and data per byte transfer. As
+ // such, we need at least 4x more bytes than the length of data. To avoid dynamic
+ // allocation, we assume the maximum transaction length for single-bit-two-wire is
+ // 2 bytes.
+ let mut encoded_data: [u8; 12] = [0; 12];
+
+ if (data.len() * 4) > (encoded_data.len() - 4) {
+ return Err(Error::Bounds);
+ }
+
+ // Encode the address into the first 4 bytes.
+ for address_bit in 0..8 {
+ let offset: u8 = {
+ if address_bit % 2 != 0 {
+ 4
+ } else {
+ 0
+ }
+ };
+
+ // Encode MSB first. Least significant bits are placed at the most significant
+ // byte.
+ let byte_position = 3 - (address_bit >> 1) as usize;
+
+ if addr & (1 << address_bit) != 0 {
+ encoded_data[byte_position] |= 1 << offset;
+ }
+ }
+
+ // Encode the data into the remaining bytes.
+ for byte_index in 0..data.len() {
+ let byte = data[byte_index];
+ for bit in 0..8 {
+ let offset: u8 = {
+ if bit % 2 != 0 {
+ 4
+ } else {
+ 0
+ }
+ };
+
+ // Encode MSB first. Least significant bits are placed at the most
+ // significant byte.
+ let byte_position = 3 - (bit >> 1) as usize;
+
+ if byte & (1 << bit) != 0 {
+ encoded_data
+ [(byte_index + 1) * 4 + byte_position] |=
+ 1 << offset;
+ }
+ }
+ }
+
+ let (encoded_address, encoded_payload) = {
+ let end_index = (1 + data.len()) * 4;
+ (encoded_data[0], &encoded_data[1..end_index])
+ };
+
+ self.qspi.write(encoded_address, encoded_payload)?;
+
+ Ok(())
+ }
+ ad9959::Mode::FourBitSerial => {
+ if self.streaming {
+ Err(Error::InvalidState)
+ } else {
+ self.qspi.write(addr, data)?;
+ Ok(())
+ }
+ }
+ _ => Err(Error::InvalidState),
+ }
+ }
+
+ fn read(&mut self, addr: u8, dest: &mut [u8]) -> Result<(), Error> {
+ if (addr & 0x80) != 0 {
+ return Err(Error::InvalidAddress);
+ }
+
+ // This implementation only supports operation (read) in four-bit-serial mode.
+ if self.mode != ad9959::Mode::FourBitSerial {
+ return Err(Error::InvalidState);
+ }
+
+ self.qspi.read(0x80 | addr, dest)?;
+
+ Ok(())
+ }
+}
+
+/// A structure containing implementation for Pounder hardware.
+pub struct PounderDevices {
+ mcp23017: mcp230xx::Mcp230xx<I2c1Proxy, mcp230xx::Mcp23017>,
+ pub lm75: lm75::Lm75<I2c1Proxy, lm75::ic::Lm75>,
+ attenuator_spi: hal::spi::Spi<hal::stm32::SPI1, hal::spi::Enabled, u8>,
+ pwr0: AdcChannel<
+ 'static,
+ hal::stm32::ADC1,
+ hal::gpio::gpiof::PF11<hal::gpio::Analog>,
+ >,
+ pwr1: AdcChannel<
+ 'static,
+ hal::stm32::ADC2,
+ hal::gpio::gpiof::PF14<hal::gpio::Analog>,
+ >,
+ aux_adc0: AdcChannel<
+ 'static,
+ hal::stm32::ADC3,
+ hal::gpio::gpiof::PF3<hal::gpio::Analog>,
+ >,
+ aux_adc1: AdcChannel<
+ 'static,
+ hal::stm32::ADC3,
+ hal::gpio::gpiof::PF4<hal::gpio::Analog>,
+ >,
+}
+
+impl PounderDevices {
+ /// Construct and initialize pounder-specific hardware.
+ ///
+ /// Args:
+ /// * `lm75` - The temperature sensor on Pounder.
+ /// * `mcp23017` - The GPIO expander on Pounder.
+ /// * `attenuator_spi` - A SPI interface to control digital attenuators.
+ /// * `pwr0` - The ADC channel to measure the IN0 input power.
+ /// * `pwr1` - The ADC channel to measure the IN1 input power.
+ /// * `aux_adc0` - The ADC channel to measure the ADC0 auxiliary input.
+ /// * `aux_adc1` - The ADC channel to measure the ADC1 auxiliary input.
+ pub fn new(
+ lm75: lm75::Lm75<I2c1Proxy, lm75::ic::Lm75>,
+ mcp23017: mcp230xx::Mcp230xx<I2c1Proxy, mcp230xx::Mcp23017>,
+ attenuator_spi: hal::spi::Spi<hal::stm32::SPI1, hal::spi::Enabled, u8>,
+ pwr0: AdcChannel<
+ 'static,
+ hal::stm32::ADC1,
+ hal::gpio::gpiof::PF11<hal::gpio::Analog>,
+ >,
+ pwr1: AdcChannel<
+ 'static,
+ hal::stm32::ADC2,
+ hal::gpio::gpiof::PF14<hal::gpio::Analog>,
+ >,
+ aux_adc0: AdcChannel<
+ 'static,
+ hal::stm32::ADC3,
+ hal::gpio::gpiof::PF3<hal::gpio::Analog>,
+ >,
+ aux_adc1: AdcChannel<
+ 'static,
+ hal::stm32::ADC3,
+ hal::gpio::gpiof::PF4<hal::gpio::Analog>,
+ >,
+ ) -> Result<Self, Error> {
+ let mut devices = Self {
+ lm75,
+ mcp23017,
+ attenuator_spi,
+ pwr0,
+ pwr1,
+ aux_adc0,
+ aux_adc1,
+ };
+
+ // Configure power-on-default state for pounder. All LEDs are off, on-board oscillator
+ // selected and enabled, attenuators out of reset. Note that testing indicates the
+ // output state needs to be set first to properly update the output registers.
+ for pin in enum_iterator::all::<GpioPin>() {
+ devices
+ .mcp23017
+ .set_gpio(pin.into(), mcp230xx::Level::Low)
+ .map_err(|_| Error::I2c)?;
+ devices
+ .mcp23017
+ .set_direction(pin.into(), mcp230xx::Direction::Output)
+ .map_err(|_| Error::I2c)?;
+ }
+ devices.reset_attenuators().unwrap();
+ Ok(devices)
+ }
+
+ /// Sample one of the two auxiliary ADC channels associated with the respective RF input channel.
+ pub fn sample_aux_adc(&mut self, channel: Channel) -> Result<f32, Error> {
+ let adc_scale = match channel {
+ Channel::In0 => self.aux_adc0.read_normalized().unwrap(),
+ Channel::In1 => self.aux_adc1.read_normalized().unwrap(),
+ _ => return Err(Error::InvalidChannel),
+ };
+
+ // Convert analog percentage to voltage. Note that the ADC uses an external 2.048V analog
+ // reference.
+ Ok(adc_scale * 2.048)
+ }
+
+ /// Set the state (its electrical level) of the given GPIO pin on Pounder.
+ pub fn set_gpio_pin(
+ &mut self,
+ pin: GpioPin,
+ level: mcp230xx::Level,
+ ) -> Result<(), Error> {
+ self.mcp23017
+ .set_gpio(pin.into(), level)
+ .map_err(|_| Error::I2c)
+ }
+
+ /// Select external reference clock input.
+ pub fn set_ext_clk(&mut self, enabled: bool) -> Result<(), Error> {
+ let level = if enabled {
+ mcp230xx::Level::High
+ } else {
+ mcp230xx::Level::Low
+ };
+ // Active low
+ self.set_gpio_pin(GpioPin::OscEnN, level)?;
+ self.set_gpio_pin(GpioPin::ExtClkSel, level)
+ }
+}
+
+impl attenuators::AttenuatorInterface for PounderDevices {
+ /// Reset all of the attenuators to a power-on default state.
+ fn reset_attenuators(&mut self) -> Result<(), Error> {
+ // Active low
+ self.set_gpio_pin(GpioPin::AttRstN, mcp230xx::Level::Low)?;
+ self.set_gpio_pin(GpioPin::AttRstN, mcp230xx::Level::High)
+ }
+
+ /// Latch a configuration into a digital attenuator.
+ ///
+ /// Args:
+ /// * `channel` - The attenuator channel to latch.
+ fn latch_attenuator(&mut self, channel: Channel) -> Result<(), Error> {
+ // Rising edge sensitive
+ // Be robust against initial state: drive low, then high (contrary to the datasheet figure).
+ self.set_gpio_pin(channel.into(), mcp230xx::Level::Low)?;
+ self.set_gpio_pin(channel.into(), mcp230xx::Level::High)
+ }
+
+ /// Read the raw attenuation codes stored in the attenuator shift registers.
+ ///
+ /// Args:
+ /// * `channels` - A 4 byte slice to be shifted into the
+ /// attenuators and to contain the data shifted out.
+ fn transfer_attenuators(
+ &mut self,
+ channels: &mut [u8; 4],
+ ) -> Result<(), Error> {
+ self.attenuator_spi
+ .transfer(channels)
+ .map_err(|_| Error::Spi)?;
+
+ Ok(())
+ }
+}
+
+impl rf_power::PowerMeasurementInterface for PounderDevices {
+ /// Sample an ADC channel.
+ ///
+ /// Args:
+ /// * `channel` - The channel to sample.
+ ///
+ /// Returns:
+ /// The sampled voltage of the specified channel.
+ fn sample_converter(&mut self, channel: Channel) -> Result<f32, Error> {
+ let adc_scale = match channel {
+ Channel::In0 => self.pwr0.read_normalized().unwrap(),
+ Channel::In1 => self.pwr1.read_normalized().unwrap(),
+ _ => return Err(Error::InvalidChannel),
+ };
+
+ // Convert analog percentage to voltage. Note that the ADC uses an external 2.048V analog
+ // reference.
+ Ok(adc_scale * 2.048)
+ }
+}
+
use super::{Channel, Error};
+
+/// Provide an interface to measure RF input power in dBm.
+pub trait PowerMeasurementInterface {
+ fn sample_converter(&mut self, channel: Channel) -> Result<f32, Error>;
+
+ /// Measure the power of an input channel in dBm.
+ ///
+ /// Args:
+ /// * `channel` - The pounder input channel to measure the power of.
+ ///
+ /// Returns:
+ /// Power in dBm after the digitally controlled attenuator before the amplifier.
+ fn measure_power(&mut self, channel: Channel) -> Result<f32, Error> {
+ let analog_measurement = self.sample_converter(channel)?;
+
+ // The AD8363 with VSET connected to VOUT provides an output voltage of 51.7 mV/dB at
+ // 100MHz with an intercept of -58 dBm.
+ // It is placed behind a 20 dB tap.
+ Ok(analog_measurement * (1. / 0.0517) + (-58. + 20.))
+ }
+}
+
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 +
//! ADC sample timestamper using external Pounder reference clock.
+//!
+//! # Design
+//!
+//! The pounder timestamper utilizes the pounder SYNC_CLK output as a fast external reference clock
+//! for recording a timestamp for each of the ADC samples.
+//!
+//! To accomplish this, a timer peripheral is configured to be driven by an external clock input.
+//! Due to the limitations of clock frequencies allowed by the timer peripheral, the SYNC_CLK input
+//! is divided by 4. This clock then clocks the timer peripheral in a free-running mode with an ARR
+//! (max count register value) configured to overflow once per ADC sample batch.
+//!
+//! Once the timer is configured, an input capture is configured to record the timer count
+//! register. The input capture is configured to utilize an internal trigger for the input capture.
+//! The internal trigger is selected such that when a sample is generated on ADC0, the input
+//! capture is simultaneously triggered. That trigger is prescaled (its rate is divided) by the
+//! batch size. This results in the input capture triggering identically to when the ADC samples
+//! the last sample of the batch. That sample is then available for processing by the user.
+use crate::hardware::timers;
+use stm32h7xx_hal as hal;
+
+/// Software unit to timestamp stabilizer ADC samples using an external pounder reference clock.
+pub struct Timestamper {
+ timer: timers::PounderTimestampTimer,
+ capture_channel: timers::tim8::Channel1InputCapture,
+}
+
+impl Timestamper {
+ /// Construct the pounder sample timestamper.
+ ///
+ /// # Args
+ /// * `timestamp_timer` - The timer peripheral used for capturing timestamps from.
+ /// * `capture_channel` - The input capture channel for collecting timestamps.
+ /// * `sampling_timer` - The stabilizer ADC sampling timer.
+ /// * `_clock_input` - The input pin for the external clock from Pounder.
+ /// * `batch_size` - The number of samples in each batch.
+ ///
+ /// # Returns
+ /// The new pounder timestamper in an operational state.
+ pub fn new(
+ mut timestamp_timer: timers::PounderTimestampTimer,
+ capture_channel: timers::tim8::Channel1,
+ sampling_timer: &mut timers::SamplingTimer,
+ _clock_input: hal::gpio::gpioa::PA0<hal::gpio::Alternate<3>>,
+ batch_size: usize,
+ ) -> Self {
+ // The sampling timer should generate a trigger output when CH1 comparison occurs.
+ sampling_timer.generate_trigger(timers::TriggerGenerator::ComparePulse);
+
+ // The timestamp timer trigger input should use TIM2 (SamplingTimer)'s trigger, which is
+ // mapped to ITR1.
+ timestamp_timer.set_trigger_source(timers::TriggerSource::Trigger1);
+
+ // The capture channel should capture whenever the trigger input occurs.
+ let mut input_capture = capture_channel
+ .into_input_capture(timers::tim8::CaptureSource1::Trc);
+
+ let prescaler = match batch_size {
+ 1 => timers::Prescaler::Div1,
+ 2 => timers::Prescaler::Div2,
+ 4 => timers::Prescaler::Div4,
+ 8 => timers::Prescaler::Div8,
+ _ => panic!("Batch size does not support DDS timestamping"),
+ };
+
+ // Capture at the batch period.
+ input_capture.configure_prescaler(prescaler);
+
+ Self {
+ timer: timestamp_timer,
+ capture_channel: input_capture,
+ }
+ }
+
+ /// Start collecting timestamps.
+ pub fn start(&mut self) {
+ self.capture_channel.enable();
+ }
+
+ /// Update the period of the underlying timestamp timer.
+ pub fn update_period(&mut self, period: u16) {
+ self.timer.set_period_ticks(period);
+ }
+
+ /// Obtain a timestamp.
+ ///
+ /// # Returns
+ /// A `Result` potentially indicating capture overflow and containing a `Option` of a captured
+ /// timestamp.
+ pub fn latest_timestamp(&mut self) -> Result<Option<u16>, Option<u16>> {
+ self.capture_channel.latest_capture()
+ }
+}
+
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 +
use super::UsbBus;
+use core::fmt::Write;
+
+static OUTPUT_BUFFER: bbqueue::BBBuffer<512> = bbqueue::BBBuffer::new();
+
+pub struct OutputBuffer {
+ producer: bbqueue::Producer<'static, 512>,
+}
+
+impl Write for OutputBuffer {
+ fn write_str(&mut self, s: &str) -> core::fmt::Result {
+ let data = s.as_bytes();
+
+ // Write as much data as possible to the output buffer.
+ let Ok(mut grant) = self.producer.grant_max_remaining(data.len())
+ else {
+ // Output buffer is full, silently drop the data.
+ return Ok(());
+ };
+
+ let len = grant.buf().len();
+ grant.buf().copy_from_slice(&data[..len]);
+ grant.commit(len);
+ Ok(())
+ }
+}
+
+pub struct SerialTerminal {
+ usb_device: usb_device::device::UsbDevice<'static, UsbBus>,
+ usb_serial: usbd_serial::SerialPort<'static, UsbBus>,
+ output: bbqueue::Consumer<'static, 512>,
+ buffer: OutputBuffer,
+}
+
+impl SerialTerminal {
+ pub fn new(
+ usb_device: usb_device::device::UsbDevice<'static, UsbBus>,
+ usb_serial: usbd_serial::SerialPort<'static, UsbBus>,
+ ) -> Self {
+ let (producer, consumer) = OUTPUT_BUFFER.try_split().unwrap();
+
+ Self {
+ buffer: OutputBuffer { producer },
+ usb_device,
+ usb_serial,
+ output: consumer,
+ }
+ }
+
+ fn flush(&mut self) {
+ let read = match self.output.read() {
+ Ok(grant) => grant,
+ Err(bbqueue::Error::InsufficientSize) => return,
+ err => err.unwrap(),
+ };
+
+ match self.usb_serial.write(read.buf()) {
+ Ok(count) => read.release(count),
+ Err(usbd_serial::UsbError::WouldBlock) => read.release(0),
+ Err(_) => {
+ let len = read.buf().len();
+ read.release(len);
+ }
+ }
+ }
+
+ pub fn usb_is_suspended(&self) -> bool {
+ self.usb_device.state() == usb_device::device::UsbDeviceState::Suspend
+ }
+
+ pub fn process(&mut self) {
+ self.flush();
+
+ if !self.usb_device.poll(&mut [&mut self.usb_serial]) {
+ return;
+ }
+
+ let mut buffer = [0u8; 64];
+ match self.usb_serial.read(&mut buffer) {
+ Ok(count) => {
+ for &value in &buffer[..count] {
+ writeln!(self.buffer, "echo: {}", value as char).unwrap();
+ }
+ }
+
+ Err(usbd_serial::UsbError::WouldBlock) => {}
+ Err(_) => {
+ // Clear the output buffer if USB is not connected.
+ while let Ok(grant) = self.output.read() {
+ let len = grant.buf().len();
+ grant.release(len);
+ }
+ }
+ }
+ }
+}
+
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 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499 +500 +501 +502 +503 +504 +505 +506 +507 +508 +509 +510 +511 +512 +513 +514 +515 +516 +517 +518 +519 +520 +521 +522 +523 +524 +525 +526 +527 +528 +529 +530 +531 +532 +533 +534 +535 +536 +537 +538 +539 +540 +541 +542 +543 +544 +545 +546 +547 +548 +549 +550 +551 +552 +553 +554 +555 +556 +557 +558 +559 +560 +561 +562 +563 +564 +565 +566 +567 +568 +569 +570 +571 +572 +573 +574 +575 +576 +577 +578 +579 +580 +581 +582 +583 +584 +585 +586 +587 +588 +589 +590 +591 +592 +593 +594 +595 +596 +597 +598 +599 +600 +601 +602 +603 +604 +605 +606 +607 +608 +609 +610 +611 +612 +613 +614 +615 +616 +617 +618 +619 +620 +621 +622 +623 +624 +625 +626 +627 +628 +629 +630 +631 +632 +633 +634 +635 +636 +637 +638 +639 +640 +641 +642 +643 +644 +645 +646 +647 +648 +649 +650 +651 +652 +653 +654 +655 +656 +657 +658 +659 +660 +661 +662 +663 +664 +665 +666 +667 +668 +669 +670 +671 +672 +673 +674 +675 +676 +677 +678 +679 +680 +681 +682 +683 +684 +685 +686 +687 +688 +689 +690 +691 +692 +693 +694 +695 +696 +697 +698 +699 +700 +701 +702 +703 +704 +705 +706 +707 +708 +709 +710 +711 +712 +713 +714 +715 +716 +717 +718 +719 +720 +721 +722 +723 +724 +725 +726 +727 +728 +729 +730 +731 +732 +733 +734 +735 +736 +737 +738 +739 +740 +741 +742 +743 +744 +745 +746 +747 +748 +749 +750 +751 +752 +753 +754 +755 +756 +757 +758 +759 +760 +761 +762 +763 +764 +765 +766 +767 +768 +769 +770 +771 +772 +773 +774 +775 +776 +777 +778 +779 +780 +781 +782 +783 +784 +785 +786 +787 +788 +789 +790 +791 +792 +793 +794 +795 +796 +797 +798 +799 +800 +801 +802 +803 +804 +805 +806 +807 +808 +809 +810 +811 +812 +813 +814 +815 +816 +817 +818 +819 +820 +821 +822 +823 +824 +825 +826 +827 +828 +829 +830 +831 +832 +833 +834 +835 +836 +837 +838 +839 +840 +841 +842 +843 +844 +845 +846 +847 +848 +849 +850 +851 +852 +853 +854 +855 +856 +857 +858 +859 +860 +861 +862 +863 +864 +865 +866 +867 +868 +869 +870 +871 +872 +873 +874 +875 +876 +877 +878 +879 +880 +881 +882 +883 +884 +885 +886 +887 +888 +889 +890 +891 +892 +893 +894 +895 +896 +897 +898 +899 +900 +901 +902 +903 +904 +905 +906 +907 +908 +909 +910 +911 +912 +913 +914 +915 +916 +917 +918 +919 +920 +921 +922 +923 +924 +925 +926 +927 +928 +929 +930 +931 +932 +933 +934 +935 +936 +937 +938 +939 +940 +941 +942 +943 +944 +945 +946 +947 +948 +949 +950 +951 +952 +953 +954 +955 +956 +957 +958 +959 +960 +961 +962 +963 +964 +965 +966 +967 +968 +969 +970 +971 +972 +973 +974 +975 +976 +977 +978 +979 +980 +981 +982 +983 +984 +985 +986 +987 +988 +989 +990 +991 +992 +993 +994 +995 +996 +997 +998 +999 +1000 +1001 +1002 +1003 +1004 +1005 +1006 +1007 +1008 +1009 +1010 +1011 +1012 +1013 +1014 +1015 +1016 +1017 +1018 +1019 +1020 +1021 +1022 +1023 +1024 +1025 +1026 +1027 +1028 +1029 +1030 +1031 +1032 +1033 +1034 +1035 +1036 +1037 +1038 +1039 +1040 +1041 +1042 +1043 +1044 +1045 +1046 +1047 +1048 +1049 +1050 +1051 +1052 +1053 +1054 +1055 +1056 +1057 +1058 +1059 +1060 +1061 +1062 +1063 +1064 +1065 +1066 +1067 +1068 +1069 +1070 +1071 +1072 +1073 +1074 +1075 +1076 +1077 +1078 +1079 +1080 +1081 +1082 +1083 +1084 +1085 +1086 +1087 +1088 +1089 +1090 +1091 +1092 +1093 +1094 +1095 +1096 +
//! Stabilizer hardware configuration
+//!
+//! This file contains all of the hardware-specific configuration of Stabilizer.
+use core::sync::atomic::{self, AtomicBool, Ordering};
+use core::{fmt::Write, ptr, slice};
+use stm32h7xx_hal::{
+ self as hal,
+ ethernet::{self, PHY},
+ gpio::Speed,
+ prelude::*,
+};
+
+use smoltcp_nal::smoltcp;
+
+use super::{
+ adc, afe, cpu_temp_sensor::CpuTempSensor, dac, delay, design_parameters,
+ eeprom, input_stamper::InputStamper, pounder,
+ pounder::dds_output::DdsOutput, serial_terminal::SerialTerminal,
+ shared_adc::SharedAdc, timers, DigitalInput0, DigitalInput1,
+ EemDigitalInput0, EemDigitalInput1, EemDigitalOutput0, EemDigitalOutput1,
+ EthernetPhy, NetworkStack, SystemTimer, Systick, UsbBus, AFE0, AFE1,
+};
+
+const NUM_TCP_SOCKETS: usize = 4;
+const NUM_UDP_SOCKETS: usize = 1;
+const NUM_SOCKETS: usize = NUM_UDP_SOCKETS + NUM_TCP_SOCKETS;
+
+pub struct NetStorage {
+ pub ip_addrs: [smoltcp::wire::IpCidr; 1],
+
+ // Note: There is an additional socket set item required for the DHCP and DNS sockets
+ // respectively.
+ pub sockets: [smoltcp::iface::SocketStorage<'static>; NUM_SOCKETS + 2],
+ pub tcp_socket_storage: [TcpSocketStorage; NUM_TCP_SOCKETS],
+ pub udp_socket_storage: [UdpSocketStorage; NUM_UDP_SOCKETS],
+ pub dns_storage: [Option<smoltcp::socket::dns::DnsQuery>; 1],
+}
+
+#[derive(Copy, Clone)]
+pub struct UdpSocketStorage {
+ rx_storage: [u8; 1024],
+ tx_storage: [u8; 2048],
+ tx_metadata: [smoltcp::storage::PacketMetadata<
+ smoltcp::socket::udp::UdpMetadata,
+ >; 10],
+ rx_metadata: [smoltcp::storage::PacketMetadata<
+ smoltcp::socket::udp::UdpMetadata,
+ >; 10],
+}
+
+impl UdpSocketStorage {
+ const fn new() -> Self {
+ Self {
+ rx_storage: [0; 1024],
+ tx_storage: [0; 2048],
+ tx_metadata: [smoltcp::storage::PacketMetadata::EMPTY; 10],
+ rx_metadata: [smoltcp::storage::PacketMetadata::EMPTY; 10],
+ }
+ }
+}
+
+#[derive(Copy, Clone)]
+pub struct TcpSocketStorage {
+ rx_storage: [u8; 1024],
+ tx_storage: [u8; 1024],
+}
+
+impl TcpSocketStorage {
+ const fn new() -> Self {
+ Self {
+ rx_storage: [0; 1024],
+ tx_storage: [0; 1024],
+ }
+ }
+}
+
+impl Default for NetStorage {
+ fn default() -> Self {
+ NetStorage {
+ // Placeholder for the real IP address, which is initialized at runtime.
+ ip_addrs: [smoltcp::wire::IpCidr::Ipv6(
+ smoltcp::wire::Ipv6Cidr::SOLICITED_NODE_PREFIX,
+ )],
+ sockets: [smoltcp::iface::SocketStorage::EMPTY; NUM_SOCKETS + 2],
+ tcp_socket_storage: [TcpSocketStorage::new(); NUM_TCP_SOCKETS],
+ udp_socket_storage: [UdpSocketStorage::new(); NUM_UDP_SOCKETS],
+ dns_storage: [None; 1],
+ }
+ }
+}
+
+/// The available networking devices on Stabilizer.
+pub struct NetworkDevices {
+ pub stack: NetworkStack,
+ pub phy: EthernetPhy,
+ pub mac_address: smoltcp::wire::EthernetAddress,
+}
+
+/// The GPIO pins available on the EEM connector, if Pounder is not present.
+pub struct EemGpioDevices {
+ pub lvds4: EemDigitalInput0,
+ pub lvds5: EemDigitalInput1,
+ pub lvds6: EemDigitalOutput0,
+ pub lvds7: EemDigitalOutput1,
+}
+
+/// The available hardware interfaces on Stabilizer.
+pub struct StabilizerDevices {
+ pub systick: Systick,
+ pub temperature_sensor: CpuTempSensor,
+ pub afes: (AFE0, AFE1),
+ pub adcs: (adc::Adc0Input, adc::Adc1Input),
+ pub dacs: (dac::Dac0Output, dac::Dac1Output),
+ pub timestamper: InputStamper,
+ pub adc_dac_timer: timers::SamplingTimer,
+ pub timestamp_timer: timers::TimestampTimer,
+ pub net: NetworkDevices,
+ pub digital_inputs: (DigitalInput0, DigitalInput1),
+ pub eem_gpio: EemGpioDevices,
+ pub usb_serial: SerialTerminal,
+}
+
+/// The available Pounder-specific hardware interfaces.
+pub struct PounderDevices {
+ pub pounder: pounder::PounderDevices,
+ pub dds_output: DdsOutput,
+
+ #[cfg(not(feature = "pounder_v1_0"))]
+ pub timestamper: pounder::timestamp::Timestamper,
+}
+
+#[link_section = ".sram3.eth"]
+/// Static storage for the ethernet DMA descriptor ring.
+static mut DES_RING: ethernet::DesRing<
+ { super::TX_DESRING_CNT },
+ { super::RX_DESRING_CNT },
+> = ethernet::DesRing::new();
+
+/// Setup ITCM and load its code from flash.
+///
+/// For portability and maintainability this is implemented in Rust.
+/// Since this is implemented in Rust the compiler may assume that bss and data are set
+/// up already. There is no easy way to ensure this implementation will never need bss
+/// or data. Hence we can't safely run this as the cortex-m-rt `pre_init` hook before
+/// bss/data is setup.
+///
+/// Calling (through IRQ or directly) any code in ITCM before having called
+/// this method is undefined.
+fn load_itcm() {
+ extern "C" {
+ static mut __sitcm: u32;
+ static mut __eitcm: u32;
+ static mut __siitcm: u32;
+ }
+ // NOTE(unsafe): Assuming the address symbols from the linker as well as
+ // the source instruction data are all valid, this is safe as it only
+ // copies linker-prepared data to where the code expects it to be.
+ // Calling it multiple times is safe as well.
+
+ unsafe {
+ // ITCM is enabled on reset on our CPU but might not be on others.
+ // Keep for completeness.
+ const ITCMCR: *mut u32 = 0xE000_EF90usize as _;
+ ptr::write_volatile(ITCMCR, ptr::read_volatile(ITCMCR) | 1);
+
+ // Ensure ITCM is enabled before loading.
+ atomic::fence(Ordering::SeqCst);
+
+ let len =
+ (&__eitcm as *const u32).offset_from(&__sitcm as *const _) as usize;
+ let dst = slice::from_raw_parts_mut(&mut __sitcm as *mut _, len);
+ let src = slice::from_raw_parts(&__siitcm as *const _, len);
+ // Load code into ITCM.
+ dst.copy_from_slice(src);
+ }
+
+ // Ensure ITCM is loaded before potentially executing any instructions from it.
+ atomic::fence(Ordering::SeqCst);
+ cortex_m::asm::dsb();
+ cortex_m::asm::isb();
+}
+
+/// Configure the stabilizer hardware for operation.
+///
+/// # Note
+/// Refer to [design_parameters::TIMER_FREQUENCY] to determine the frequency of the sampling timer.
+///
+/// # Args
+/// * `core` - The cortex-m peripherals.
+/// * `device` - The microcontroller peripherals to be configured.
+/// * `clock` - A `SystemTimer` implementing `Clock`.
+/// * `batch_size` - The size of each ADC/DAC batch.
+/// * `sample_ticks` - The number of timer ticks between each sample.
+///
+/// # Returns
+/// (stabilizer, pounder) where `stabilizer` is a `StabilizerDevices` structure containing all
+/// stabilizer hardware interfaces in a disabled state. `pounder` is an `Option` containing
+/// `Some(devices)` if pounder is detected, where `devices` is a `PounderDevices` structure
+/// containing all of the pounder hardware interfaces in a disabled state.
+pub fn setup(
+ mut core: stm32h7xx_hal::stm32::CorePeripherals,
+ device: stm32h7xx_hal::stm32::Peripherals,
+ clock: SystemTimer,
+ batch_size: usize,
+ sample_ticks: u32,
+) -> (StabilizerDevices, Option<PounderDevices>) {
+ // Set up RTT logging
+ {
+ // Enable debug during WFE/WFI-induced sleep
+ device.DBGMCU.cr.modify(|_, w| w.dbgsleep_d1().set_bit());
+
+ // Set up RTT channel to use for `rprintln!()` as "best effort".
+ // This removes a critical section around the logging and thus allows
+ // high-prio tasks to always interrupt at low latency.
+ // It comes at a cost:
+ // If a high-priority tasks preempts while we are logging something,
+ // and if we then also want to log from within that high-preiority task,
+ // the high-prio log message will be lost.
+
+ let channels = rtt_target::rtt_init_default!();
+ // Note(unsafe): The closure we pass does not establish a critical section
+ // as demanded but it does ensure synchronization and implements a lock.
+ unsafe {
+ rtt_target::set_print_channel_cs(
+ channels.up.0,
+ &((|arg, f| {
+ static LOCKED: AtomicBool = AtomicBool::new(false);
+ if LOCKED.compare_exchange_weak(
+ false,
+ true,
+ Ordering::Acquire,
+ Ordering::Relaxed,
+ ) == Ok(false)
+ {
+ f(arg);
+ LOCKED.store(false, Ordering::Release);
+ }
+ }) as rtt_target::CriticalSectionFunc),
+ );
+ }
+
+ static LOGGER: rtt_logger::RTTLogger =
+ rtt_logger::RTTLogger::new(log::LevelFilter::Info);
+ log::set_logger(&LOGGER)
+ .map(|()| log::set_max_level(log::LevelFilter::Trace))
+ .unwrap();
+ log::info!("Starting");
+ }
+
+ let pwr = device.PWR.constrain();
+ let vos = pwr.freeze();
+
+ // Enable SRAM3 for the ethernet descriptor ring.
+ device.RCC.ahb2enr.modify(|_, w| w.sram3en().set_bit());
+
+ // Clear reset flags.
+ device.RCC.rsr.write(|w| w.rmvf().set_bit());
+
+ // Select the PLLs for SPI.
+ device
+ .RCC
+ .d2ccip1r
+ .modify(|_, w| w.spi123sel().pll2_p().spi45sel().pll2_q());
+
+ device.RCC.d1ccipr.modify(|_, w| w.qspisel().rcc_hclk3());
+
+ device.RCC.d3ccipr.modify(|_, w| w.adcsel().per());
+
+ let rcc = device.RCC.constrain();
+ let mut ccdr = rcc
+ .use_hse(8.MHz())
+ .sysclk(design_parameters::SYSCLK.convert())
+ .hclk(200.MHz())
+ .per_ck(64.MHz()) // fixed frequency HSI, only used for internal ADC. This is not the "peripheral" clock for timers and others.
+ .pll2_p_ck(100.MHz())
+ .pll2_q_ck(100.MHz())
+ .freeze(vos, &device.SYSCFG);
+
+ // Set up USB clocks.
+ ccdr.clocks.hsi48_ck().unwrap();
+ ccdr.peripheral
+ .kernel_usb_clk_mux(stm32h7xx_hal::rcc::rec::UsbClkSel::Hsi48);
+
+ // Before being able to call any code in ITCM, load that code from flash.
+ load_itcm();
+
+ let systick = Systick::new(core.SYST, ccdr.clocks.sysclk().to_Hz());
+
+ // After ITCM loading.
+ core.SCB.enable_icache();
+
+ let mut delay = delay::AsmDelay::new(ccdr.clocks.c_ck().to_Hz());
+
+ let gpioa = device.GPIOA.split(ccdr.peripheral.GPIOA);
+ let gpiob = device.GPIOB.split(ccdr.peripheral.GPIOB);
+ let gpioc = device.GPIOC.split(ccdr.peripheral.GPIOC);
+ let gpiod = device.GPIOD.split(ccdr.peripheral.GPIOD);
+ let gpioe = device.GPIOE.split(ccdr.peripheral.GPIOE);
+ let gpiof = device.GPIOF.split(ccdr.peripheral.GPIOF);
+ let mut gpiog = device.GPIOG.split(ccdr.peripheral.GPIOG);
+
+ let dma_streams =
+ hal::dma::dma::StreamsTuple::new(device.DMA1, ccdr.peripheral.DMA1);
+
+ // Verify that batch period does not exceed RTIC Monotonic timer period.
+ assert!(
+ (batch_size as u32 * sample_ticks) as f32
+ * design_parameters::TIMER_PERIOD
+ * (super::MONOTONIC_FREQUENCY as f32)
+ < 1.
+ );
+
+ // Configure timer 2 to trigger conversions for the ADC
+ let mut sampling_timer = {
+ // The timer frequency is manually adjusted below, so the 1KHz setting here is a
+ // dont-care.
+ let mut timer2 =
+ device
+ .TIM2
+ .timer(1.kHz(), ccdr.peripheral.TIM2, &ccdr.clocks);
+
+ // Configure the timer to count at the designed tick rate. We will manually set the
+ // period below.
+ timer2.pause();
+ timer2.set_tick_freq(design_parameters::TIMER_FREQUENCY.convert());
+
+ let mut sampling_timer = timers::SamplingTimer::new(timer2);
+ sampling_timer.set_period_ticks(sample_ticks - 1);
+
+ // The sampling timer is used as the master timer for the shadow-sampling timer. Thus,
+ // it generates a trigger whenever it is enabled.
+
+ sampling_timer
+ };
+
+ let mut shadow_sampling_timer = {
+ // The timer frequency is manually adjusted below, so the 1KHz setting here is a
+ // dont-care.
+ let mut timer3 =
+ device
+ .TIM3
+ .timer(1.kHz(), ccdr.peripheral.TIM3, &ccdr.clocks);
+
+ // Configure the timer to count at the designed tick rate. We will manually set the
+ // period below.
+ timer3.pause();
+ timer3.reset_counter();
+ timer3.set_tick_freq(design_parameters::TIMER_FREQUENCY.convert());
+
+ let mut shadow_sampling_timer =
+ timers::ShadowSamplingTimer::new(timer3);
+ shadow_sampling_timer.set_period_ticks(sample_ticks as u16 - 1);
+
+ // The shadow sampling timer is a slave-mode timer to the sampling timer. It should
+ // always be in-sync - thus, we configure it to operate in slave mode using "Trigger
+ // mode".
+ // For TIM3, TIM2 can be made the internal trigger connection using ITR1. Thus, the
+ // SamplingTimer start now gates the start of the ShadowSamplingTimer.
+ shadow_sampling_timer.set_slave_mode(
+ timers::TriggerSource::Trigger1,
+ timers::SlaveMode::Trigger,
+ );
+
+ shadow_sampling_timer
+ };
+
+ let sampling_timer_channels = sampling_timer.channels();
+ let shadow_sampling_timer_channels = shadow_sampling_timer.channels();
+
+ let mut timestamp_timer = {
+ // The timer frequency is manually adjusted below, so the 1KHz setting here is a
+ // dont-care.
+ let mut timer5 =
+ device
+ .TIM5
+ .timer(1.kHz(), ccdr.peripheral.TIM5, &ccdr.clocks);
+
+ // Configure the timer to count at the designed tick rate. We will manually set the
+ // period below.
+ timer5.pause();
+ timer5.set_tick_freq(design_parameters::TIMER_FREQUENCY.convert());
+
+ // The timestamp timer runs at the counter cycle period as the sampling timers.
+ // To accomodate this, we manually set the prescaler identical to the sample
+ // timer, but use maximum overflow period.
+ let mut timer = timers::TimestampTimer::new(timer5);
+
+ // TODO: Check hardware synchronization of timestamping and the sampling timers
+ // for phase shift determinism.
+
+ timer.set_period_ticks(u32::MAX);
+
+ timer
+ };
+
+ let timestamp_timer_channels = timestamp_timer.channels();
+
+ // Configure the SPI interfaces to the ADCs and DACs.
+ let adcs = {
+ let adc0 = {
+ let miso = gpiob.pb14.into_alternate().speed(Speed::VeryHigh);
+ let sck = gpiob.pb10.into_alternate().speed(Speed::VeryHigh);
+ let nss = gpiob.pb9.into_alternate().speed(Speed::VeryHigh);
+
+ let config = hal::spi::Config::new(hal::spi::Mode {
+ polarity: hal::spi::Polarity::IdleHigh,
+ phase: hal::spi::Phase::CaptureOnSecondTransition,
+ })
+ .hardware_cs(hal::spi::HardwareCS {
+ mode: hal::spi::HardwareCSMode::WordTransaction,
+ assertion_delay: design_parameters::ADC_SETUP_TIME,
+ polarity: hal::spi::Polarity::IdleHigh,
+ })
+ .communication_mode(hal::spi::CommunicationMode::Receiver);
+
+ let spi: hal::spi::Spi<_, _, u16> = device.SPI2.spi(
+ (sck, miso, hal::spi::NoMosi, nss),
+ config,
+ design_parameters::ADC_DAC_SCK_MAX.convert(),
+ ccdr.peripheral.SPI2,
+ &ccdr.clocks,
+ );
+
+ adc::Adc0Input::new(
+ spi,
+ dma_streams.0,
+ dma_streams.1,
+ dma_streams.2,
+ sampling_timer_channels.ch1,
+ shadow_sampling_timer_channels.ch1,
+ batch_size,
+ )
+ };
+
+ let adc1 = {
+ let miso = gpiob.pb4.into_alternate().speed(Speed::VeryHigh);
+ let sck = gpioc.pc10.into_alternate().speed(Speed::VeryHigh);
+ let nss = gpioa.pa15.into_alternate().speed(Speed::VeryHigh);
+
+ let config = hal::spi::Config::new(hal::spi::Mode {
+ polarity: hal::spi::Polarity::IdleHigh,
+ phase: hal::spi::Phase::CaptureOnSecondTransition,
+ })
+ .hardware_cs(hal::spi::HardwareCS {
+ mode: hal::spi::HardwareCSMode::WordTransaction,
+ assertion_delay: design_parameters::ADC_SETUP_TIME,
+ polarity: hal::spi::Polarity::IdleHigh,
+ })
+ .communication_mode(hal::spi::CommunicationMode::Receiver);
+
+ let spi: hal::spi::Spi<_, _, u16> = device.SPI3.spi(
+ (sck, miso, hal::spi::NoMosi, nss),
+ config,
+ design_parameters::ADC_DAC_SCK_MAX.convert(),
+ ccdr.peripheral.SPI3,
+ &ccdr.clocks,
+ );
+
+ adc::Adc1Input::new(
+ spi,
+ dma_streams.3,
+ dma_streams.4,
+ dma_streams.5,
+ sampling_timer_channels.ch2,
+ shadow_sampling_timer_channels.ch2,
+ batch_size,
+ )
+ };
+
+ (adc0, adc1)
+ };
+
+ let dacs = {
+ let mut dac_clr_n = gpioe.pe12.into_push_pull_output();
+ dac_clr_n.set_high();
+
+ let dac0_spi = {
+ let miso = gpioe.pe5.into_alternate().speed(Speed::VeryHigh);
+ let sck = gpioe.pe2.into_alternate().speed(Speed::VeryHigh);
+ let nss = gpioe.pe4.into_alternate().speed(Speed::VeryHigh);
+
+ let config = hal::spi::Config::new(hal::spi::Mode {
+ polarity: hal::spi::Polarity::IdleHigh,
+ phase: hal::spi::Phase::CaptureOnSecondTransition,
+ })
+ .hardware_cs(hal::spi::HardwareCS {
+ mode: hal::spi::HardwareCSMode::WordTransaction,
+ assertion_delay: 0.0,
+ polarity: hal::spi::Polarity::IdleHigh,
+ })
+ .communication_mode(hal::spi::CommunicationMode::Transmitter)
+ .swap_mosi_miso();
+
+ device.SPI4.spi(
+ (sck, miso, hal::spi::NoMosi, nss),
+ config,
+ design_parameters::ADC_DAC_SCK_MAX.convert(),
+ ccdr.peripheral.SPI4,
+ &ccdr.clocks,
+ )
+ };
+
+ let dac1_spi = {
+ let miso = gpiof.pf8.into_alternate().speed(Speed::VeryHigh);
+ let sck = gpiof.pf7.into_alternate().speed(Speed::VeryHigh);
+ let nss = gpiof.pf6.into_alternate().speed(Speed::VeryHigh);
+
+ let config = hal::spi::Config::new(hal::spi::Mode {
+ polarity: hal::spi::Polarity::IdleHigh,
+ phase: hal::spi::Phase::CaptureOnSecondTransition,
+ })
+ .hardware_cs(hal::spi::HardwareCS {
+ mode: hal::spi::HardwareCSMode::WordTransaction,
+ assertion_delay: 0.0,
+ polarity: hal::spi::Polarity::IdleHigh,
+ })
+ .communication_mode(hal::spi::CommunicationMode::Transmitter)
+ .swap_mosi_miso();
+
+ device.SPI5.spi(
+ (sck, miso, hal::spi::NoMosi, nss),
+ config,
+ design_parameters::ADC_DAC_SCK_MAX.convert(),
+ ccdr.peripheral.SPI5,
+ &ccdr.clocks,
+ )
+ };
+
+ let dac0 = dac::Dac0Output::new(
+ dac0_spi,
+ dma_streams.6,
+ sampling_timer_channels.ch3,
+ batch_size,
+ );
+ let dac1 = dac::Dac1Output::new(
+ dac1_spi,
+ dma_streams.7,
+ sampling_timer_channels.ch4,
+ batch_size,
+ );
+
+ dac_clr_n.set_low();
+ // dac0_ldac_n
+ gpioe.pe11.into_push_pull_output().set_low();
+ // dac1_ldac_n
+ gpioe.pe15.into_push_pull_output().set_low();
+ dac_clr_n.set_high();
+
+ (dac0, dac1)
+ };
+
+ let afes = {
+ // AFE_PWR_ON on hardware revision v1.3.2
+ gpioe.pe1.into_push_pull_output().set_high();
+
+ let afe0 = {
+ let a0_pin = gpiof.pf2.into_push_pull_output();
+ let a1_pin = gpiof.pf5.into_push_pull_output();
+ afe::ProgrammableGainAmplifier::new(a0_pin, a1_pin)
+ };
+
+ let afe1 = {
+ let a0_pin = gpiod.pd14.into_push_pull_output();
+ let a1_pin = gpiod.pd15.into_push_pull_output();
+ afe::ProgrammableGainAmplifier::new(a0_pin, a1_pin)
+ };
+
+ (afe0, afe1)
+ };
+
+ let input_stamper = {
+ let trigger = gpioa.pa3.into_alternate();
+ InputStamper::new(trigger, timestamp_timer_channels.ch4)
+ };
+
+ let digital_inputs = {
+ let di0 = gpiog.pg9.into_floating_input();
+ let di1 = gpioc.pc15.into_floating_input();
+ (di0, di1)
+ };
+
+ let mut eeprom_i2c = {
+ let sda = gpiof.pf0.into_alternate().set_open_drain();
+ let scl = gpiof.pf1.into_alternate().set_open_drain();
+ device.I2C2.i2c(
+ (scl, sda),
+ 100.kHz(),
+ ccdr.peripheral.I2C2,
+ &ccdr.clocks,
+ )
+ };
+
+ let mac_addr = smoltcp::wire::EthernetAddress(eeprom::read_eui48(
+ &mut eeprom_i2c,
+ &mut delay,
+ ));
+ log::info!("EUI48: {}", mac_addr);
+
+ let network_devices = {
+ let ethernet_pins = {
+ // Reset the PHY before configuring pins.
+ let mut eth_phy_nrst = gpioe.pe3.into_push_pull_output();
+ eth_phy_nrst.set_low();
+ delay.delay_us(200u8);
+ eth_phy_nrst.set_high();
+
+ let ref_clk = gpioa.pa1.into_alternate().speed(Speed::VeryHigh);
+ let mdio = gpioa.pa2.into_alternate().speed(Speed::VeryHigh);
+ let mdc = gpioc.pc1.into_alternate().speed(Speed::VeryHigh);
+ let crs_dv = gpioa.pa7.into_alternate().speed(Speed::VeryHigh);
+ let rxd0 = gpioc.pc4.into_alternate().speed(Speed::VeryHigh);
+ let rxd1 = gpioc.pc5.into_alternate().speed(Speed::VeryHigh);
+ let tx_en = gpiob.pb11.into_alternate().speed(Speed::VeryHigh);
+ let txd0 = gpiob.pb12.into_alternate().speed(Speed::VeryHigh);
+ let txd1 = gpiog.pg14.into_alternate().speed(Speed::VeryHigh);
+
+ (ref_clk, mdio, mdc, crs_dv, rxd0, rxd1, tx_en, txd0, txd1)
+ };
+
+ // Configure the ethernet controller
+ let (mut eth_dma, eth_mac) = ethernet::new(
+ device.ETHERNET_MAC,
+ device.ETHERNET_MTL,
+ device.ETHERNET_DMA,
+ ethernet_pins,
+ // Note(unsafe): We only call this function once to take ownership of the
+ // descriptor ring.
+ unsafe { &mut DES_RING },
+ mac_addr,
+ ccdr.peripheral.ETH1MAC,
+ &ccdr.clocks,
+ );
+
+ // Reset and initialize the ethernet phy.
+ let mut lan8742a =
+ ethernet::phy::LAN8742A::new(eth_mac.set_phy_addr(0));
+ lan8742a.phy_reset();
+ lan8742a.phy_init();
+
+ unsafe { ethernet::enable_interrupt() };
+
+ // Configure IP address according to DHCP socket availability
+ let ip_addrs: smoltcp::wire::IpAddress = option_env!("STATIC_IP")
+ .unwrap_or("0.0.0.0")
+ .parse()
+ .unwrap();
+
+ let random_seed = {
+ let mut rng =
+ device.RNG.constrain(ccdr.peripheral.RNG, &ccdr.clocks);
+ let mut data = [0u8; 8];
+ rng.fill(&mut data).unwrap();
+ data
+ };
+
+ // Note(unwrap): The hardware configuration function is only allowed to be called once.
+ // Unwrapping is intended to panic if called again to prevent re-use of global memory.
+ let store =
+ cortex_m::singleton!(: NetStorage = NetStorage::default()).unwrap();
+
+ store.ip_addrs[0] = smoltcp::wire::IpCidr::new(ip_addrs, 24);
+
+ let mut ethernet_config = smoltcp::iface::Config::new(
+ smoltcp::wire::HardwareAddress::Ethernet(mac_addr),
+ );
+ ethernet_config.random_seed = u64::from_be_bytes(random_seed);
+
+ let mut interface = smoltcp::iface::Interface::new(
+ ethernet_config,
+ &mut eth_dma,
+ smoltcp::time::Instant::ZERO,
+ );
+
+ interface
+ .routes_mut()
+ .add_default_ipv4_route(smoltcp::wire::Ipv4Address::UNSPECIFIED)
+ .unwrap();
+
+ interface.update_ip_addrs(|ref mut addrs| {
+ if !ip_addrs.is_unspecified() {
+ addrs
+ .push(smoltcp::wire::IpCidr::new(ip_addrs, 24))
+ .unwrap();
+ }
+ });
+
+ let mut sockets =
+ smoltcp::iface::SocketSet::new(&mut store.sockets[..]);
+ for storage in store.tcp_socket_storage[..].iter_mut() {
+ let tcp_socket = {
+ let rx_buffer = smoltcp::socket::tcp::SocketBuffer::new(
+ &mut storage.rx_storage[..],
+ );
+ let tx_buffer = smoltcp::socket::tcp::SocketBuffer::new(
+ &mut storage.tx_storage[..],
+ );
+
+ smoltcp::socket::tcp::Socket::new(rx_buffer, tx_buffer)
+ };
+
+ sockets.add(tcp_socket);
+ }
+
+ if ip_addrs.is_unspecified() {
+ sockets.add(smoltcp::socket::dhcpv4::Socket::new());
+ }
+
+ sockets.add(smoltcp::socket::dns::Socket::new(
+ &[],
+ &mut store.dns_storage[..],
+ ));
+
+ for storage in store.udp_socket_storage[..].iter_mut() {
+ let udp_socket = {
+ let rx_buffer = smoltcp::socket::udp::PacketBuffer::new(
+ &mut storage.rx_metadata[..],
+ &mut storage.rx_storage[..],
+ );
+ let tx_buffer = smoltcp::socket::udp::PacketBuffer::new(
+ &mut storage.tx_metadata[..],
+ &mut storage.tx_storage[..],
+ );
+
+ smoltcp::socket::udp::Socket::new(rx_buffer, tx_buffer)
+ };
+
+ sockets.add(udp_socket);
+ }
+
+ let mut stack =
+ smoltcp_nal::NetworkStack::new(interface, eth_dma, sockets, clock);
+
+ stack.seed_random_port(&random_seed);
+
+ NetworkDevices {
+ stack,
+ phy: lan8742a,
+ mac_address: mac_addr,
+ }
+ };
+
+ let mut fp_led_0 = gpiod.pd5.into_push_pull_output();
+ let mut fp_led_1 = gpiod.pd6.into_push_pull_output();
+ let mut fp_led_2 = gpiog.pg4.into_push_pull_output();
+ let mut fp_led_3 = gpiod.pd12.into_push_pull_output();
+
+ fp_led_0.set_low();
+ fp_led_1.set_low();
+ fp_led_2.set_low();
+ fp_led_3.set_low();
+
+ let (adc1, adc2, adc3) = {
+ let (mut adc1, mut adc2) = hal::adc::adc12(
+ device.ADC1,
+ device.ADC2,
+ stm32h7xx_hal::time::Hertz::MHz(25),
+ &mut delay,
+ ccdr.peripheral.ADC12,
+ &ccdr.clocks,
+ );
+ let mut adc3 = hal::adc::Adc::adc3(
+ device.ADC3,
+ stm32h7xx_hal::time::Hertz::MHz(25),
+ &mut delay,
+ ccdr.peripheral.ADC3,
+ &ccdr.clocks,
+ );
+
+ adc1.set_sample_time(hal::adc::AdcSampleTime::T_810);
+ adc1.set_resolution(hal::adc::Resolution::SixteenBit);
+ adc1.calibrate();
+ adc2.set_sample_time(hal::adc::AdcSampleTime::T_810);
+ adc2.set_resolution(hal::adc::Resolution::SixteenBit);
+ adc2.calibrate();
+ adc3.set_sample_time(hal::adc::AdcSampleTime::T_810);
+ adc3.set_resolution(hal::adc::Resolution::SixteenBit);
+ adc3.calibrate();
+
+ hal::adc::Temperature::new().enable(&adc3);
+
+ let adc1 = adc1.enable();
+ let adc2 = adc2.enable();
+ let adc3 = adc3.enable();
+
+ (
+ // The ADCs must live as global, mutable singletons so that we can hand out references
+ // to the internal ADC. If they were instead to live within e.g. StabilizerDevices,
+ // they would not yet live in 'static memory, which means that we could not hand out
+ // references during initialization, since those references would be invalidated when
+ // we move StabilizerDevices into the late RTIC resources.
+ cortex_m::singleton!(: SharedAdc<hal::stm32::ADC1> = SharedAdc::new(adc1.slope() as f32, adc1)).unwrap(),
+ cortex_m::singleton!(: SharedAdc<hal::stm32::ADC2> = SharedAdc::new(adc2.slope() as f32, adc2)).unwrap(),
+ cortex_m::singleton!(: SharedAdc<hal::stm32::ADC3> = SharedAdc::new(adc3.slope() as f32, adc3)).unwrap(),
+ )
+ };
+
+ // Measure the Pounder PGOOD output to detect if pounder is present on Stabilizer.
+ let pounder_pgood = gpiob.pb13.into_pull_down_input();
+ delay.delay_ms(2u8);
+ let pounder = if pounder_pgood.is_high() {
+ log::info!("Found Pounder");
+
+ let i2c1 = {
+ let sda = gpiob.pb7.into_alternate().set_open_drain();
+ let scl = gpiob.pb8.into_alternate().set_open_drain();
+ let i2c1 = device.I2C1.i2c(
+ (scl, sda),
+ 400.kHz(),
+ ccdr.peripheral.I2C1,
+ &ccdr.clocks,
+ );
+
+ shared_bus::new_atomic_check!(hal::i2c::I2c<hal::stm32::I2C1> = i2c1).unwrap()
+ };
+
+ let io_expander =
+ mcp230xx::Mcp230xx::new_default(i2c1.acquire_i2c()).unwrap();
+
+ let temp_sensor =
+ lm75::Lm75::new(i2c1.acquire_i2c(), lm75::Address::default());
+
+ let spi = {
+ let mosi = gpiod.pd7.into_alternate();
+ let miso = gpioa.pa6.into_alternate();
+ let sck = gpiog.pg11.into_alternate();
+
+ let config = hal::spi::Config::new(hal::spi::Mode {
+ polarity: hal::spi::Polarity::IdleHigh,
+ phase: hal::spi::Phase::CaptureOnSecondTransition,
+ });
+
+ // The maximum frequency of this SPI must be limited due to capacitance on the MISO
+ // line causing a long RC decay.
+ device.SPI1.spi(
+ (sck, miso, mosi),
+ config,
+ 5.MHz(),
+ ccdr.peripheral.SPI1,
+ &ccdr.clocks,
+ )
+ };
+
+ let pwr0 = adc1.create_channel(gpiof.pf11.into_analog());
+ let pwr1 = adc2.create_channel(gpiof.pf14.into_analog());
+ let aux_adc0 = adc3.create_channel(gpiof.pf3.into_analog());
+ let aux_adc1 = adc3.create_channel(gpiof.pf4.into_analog());
+
+ let pounder_devices = pounder::PounderDevices::new(
+ temp_sensor,
+ io_expander,
+ spi,
+ pwr0,
+ pwr1,
+ aux_adc0,
+ aux_adc1,
+ )
+ .unwrap();
+
+ let ad9959 = {
+ let qspi_interface = {
+ // Instantiate the QUADSPI pins and peripheral interface.
+ let qspi_pins = {
+ let _ncs =
+ gpioc.pc11.into_alternate::<9>().speed(Speed::VeryHigh);
+
+ let clk = gpiob.pb2.into_alternate().speed(Speed::VeryHigh);
+ let io0 = gpioe.pe7.into_alternate().speed(Speed::VeryHigh);
+ let io1 = gpioe.pe8.into_alternate().speed(Speed::VeryHigh);
+ let io2 = gpioe.pe9.into_alternate().speed(Speed::VeryHigh);
+ let io3 =
+ gpioe.pe10.into_alternate().speed(Speed::VeryHigh);
+
+ (clk, io0, io1, io2, io3)
+ };
+
+ let qspi = device.QUADSPI.bank2(
+ qspi_pins,
+ design_parameters::POUNDER_QSPI_FREQUENCY.convert(),
+ &ccdr.clocks,
+ ccdr.peripheral.QSPI,
+ );
+
+ pounder::QspiInterface::new(qspi).unwrap()
+ };
+
+ #[cfg(not(feature = "pounder_v1_0"))]
+ let reset_pin = gpiog.pg6.into_push_pull_output();
+ #[cfg(feature = "pounder_v1_0")]
+ let reset_pin = gpioa.pa0.into_push_pull_output();
+
+ let mut io_update = gpiog.pg7.into_push_pull_output();
+
+ // Delay to allow the pounder DDS reference clock to fully start up. The exact startup
+ // time is not specified, but bench testing indicates it usually comes up within
+ // 200-300uS. We do a larger delay to ensure that it comes up and is stable before
+ // using it.
+ delay.delay_ms(10u32);
+
+ let mut ad9959 = ad9959::Ad9959::new(
+ qspi_interface,
+ reset_pin,
+ &mut io_update,
+ &mut delay,
+ ad9959::Mode::FourBitSerial,
+ design_parameters::DDS_REF_CLK.to_Hz() as f32,
+ design_parameters::DDS_MULTIPLIER,
+ )
+ .unwrap();
+
+ ad9959.self_test().unwrap();
+
+ // Return IO_Update
+ gpiog.pg7 = io_update.into_analog();
+
+ ad9959
+ };
+
+ let dds_output = {
+ let io_update_trigger = {
+ let _io_update =
+ gpiog.pg7.into_alternate::<2>().speed(Speed::VeryHigh);
+
+ // Configure the IO_Update signal for the DDS.
+ let mut hrtimer = pounder::hrtimer::HighResTimerE::new(
+ device.HRTIM_TIME,
+ device.HRTIM_MASTER,
+ device.HRTIM_COMMON,
+ ccdr.clocks,
+ ccdr.peripheral.HRTIM,
+ );
+
+ // IO_Update occurs after a fixed delay from the QSPI write. Note that the timer
+ // is triggered after the QSPI write, which can take approximately 120nS, so
+ // there is additional margin.
+ hrtimer.configure_single_shot(
+ pounder::hrtimer::Channel::Two,
+ design_parameters::POUNDER_IO_UPDATE_DELAY,
+ design_parameters::POUNDER_IO_UPDATE_DURATION,
+ );
+
+ // Ensure that we have enough time for an IO-update every batch.
+ let sample_frequency = {
+ design_parameters::TIMER_FREQUENCY.to_Hz() as f32
+ / sample_ticks as f32
+ };
+
+ let sample_period = 1.0 / sample_frequency;
+ assert!(
+ sample_period * batch_size as f32
+ > design_parameters::POUNDER_IO_UPDATE_DELAY
+ );
+
+ hrtimer
+ };
+
+ let (qspi, config) = ad9959.freeze();
+ DdsOutput::new(qspi, io_update_trigger, config)
+ };
+
+ #[cfg(not(feature = "pounder_v1_0"))]
+ let pounder_stamper = {
+ log::info!("Assuming Pounder v1.1 or later");
+ let etr_pin = gpioa.pa0.into_alternate();
+
+ // The frequency in the constructor is dont-care, as we will modify the period + clock
+ // source manually below.
+ let tim8 =
+ device
+ .TIM8
+ .timer(1.kHz(), ccdr.peripheral.TIM8, &ccdr.clocks);
+ let mut timestamp_timer = timers::PounderTimestampTimer::new(tim8);
+
+ // Pounder is configured to generate a 500MHz reference clock, so a 125MHz sync-clock is
+ // output. As a result, dividing the 125MHz sync-clk provides a 31.25MHz tick rate for
+ // the timestamp timer. 31.25MHz corresponds with a 32ns tick rate.
+ // This is less than fCK_INT/3 of the timer as required for oversampling the trigger.
+ timestamp_timer.set_external_clock(timers::Prescaler::Div4);
+ timestamp_timer.start();
+
+ // Set the timer to wrap at the u16 boundary to meet the PLL periodicity.
+ // Scale and wrap before or after the PLL.
+ timestamp_timer.set_period_ticks(u16::MAX);
+ let tim8_channels = timestamp_timer.channels();
+
+ pounder::timestamp::Timestamper::new(
+ timestamp_timer,
+ tim8_channels.ch1,
+ &mut sampling_timer,
+ etr_pin,
+ batch_size,
+ )
+ };
+
+ Some(PounderDevices {
+ pounder: pounder_devices,
+ dds_output,
+
+ #[cfg(not(feature = "pounder_v1_0"))]
+ timestamper: pounder_stamper,
+ })
+ } else {
+ None
+ };
+
+ let eem_gpio = EemGpioDevices {
+ lvds4: gpiod.pd1.into_floating_input(),
+ lvds5: gpiod.pd2.into_floating_input(),
+ lvds6: gpiod.pd3.into_push_pull_output(),
+ lvds7: gpiod.pd4.into_push_pull_output(),
+ };
+
+ let (usb_device, usb_serial) = {
+ let usb_bus = cortex_m::singleton!(: Option<usb_device::bus::UsbBusAllocator<UsbBus>> = None).unwrap();
+ let endpoint_memory =
+ cortex_m::singleton!(: [u32; 1024] = [0; 1024]).unwrap();
+
+ //let usb_id = gpioa.pa10.into_alternate::<8>();
+ let usb_n = gpioa.pa11.into_alternate();
+ let usb_p = gpioa.pa12.into_alternate();
+
+ let usb = stm32h7xx_hal::usb_hs::USB2::new(
+ device.OTG2_HS_GLOBAL,
+ device.OTG2_HS_DEVICE,
+ device.OTG2_HS_PWRCLK,
+ usb_n,
+ usb_p,
+ ccdr.peripheral.USB2OTG,
+ &ccdr.clocks,
+ );
+
+ // Generate a device serial number from the MAC address.
+ let serial_number =
+ cortex_m::singleton!(: Option<heapless::String<17>> = None)
+ .unwrap();
+ {
+ let mut serial_string: heapless::String<17> =
+ heapless::String::new();
+ let octets = mac_addr.0;
+
+ write!(
+ serial_string,
+ "{:02x}-{:02x}-{:02x}-{:02x}-{:02x}-{:02x}",
+ octets[0],
+ octets[1],
+ octets[2],
+ octets[3],
+ octets[4],
+ octets[5]
+ )
+ .unwrap();
+ serial_number.replace(serial_string);
+ }
+
+ usb_bus.replace(stm32h7xx_hal::usb_hs::UsbBus::new(
+ usb,
+ &mut endpoint_memory[..],
+ ));
+
+ let serial = usbd_serial::SerialPort::new(usb_bus.as_ref().unwrap());
+ let usb_device = usb_device::device::UsbDeviceBuilder::new(
+ usb_bus.as_ref().unwrap(),
+ usb_device::device::UsbVidPid(0x1209, 0x392F),
+ )
+ .manufacturer("ARTIQ/Sinara")
+ .product("Stabilizer")
+ .serial_number(serial_number.as_ref().unwrap())
+ .device_class(usbd_serial::USB_CLASS_CDC)
+ .build();
+
+ (usb_device, serial)
+ };
+
+ let stabilizer = StabilizerDevices {
+ systick,
+ afes,
+ adcs,
+ dacs,
+ temperature_sensor: CpuTempSensor::new(
+ adc3.create_channel(hal::adc::Temperature::new()),
+ ),
+ timestamper: input_stamper,
+ net: network_devices,
+ adc_dac_timer: sampling_timer,
+ timestamp_timer,
+ digital_inputs,
+ eem_gpio,
+ usb_serial: SerialTerminal::new(usb_device, usb_serial),
+ };
+
+ // info!("Version {} {}", build_info::PKG_VERSION, build_info::GIT_VERSION.unwrap());
+ // info!("Built on {}", build_info::BUILT_TIME_UTC);
+ // info!("{} {}", build_info::RUSTC_VERSION, build_info::TARGET);
+ log::info!("setup() complete");
+
+ (stabilizer, pounder)
+}
+
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 +
/// Shared Internal ADC Support
+///
+/// # Description
+/// This module provides an abstraction to share ownership of a single ADC peripheral with multiple
+/// ADC channels attached to it.
+///
+/// The design of this module mimics that of [`shared-bus`].
+///
+/// First, the shared ADC is created with the use of a macro, which places the ADC peripheral into
+/// a mutable, static (singleton) location. Then, individual channels are created by passing in the
+/// associated ADC input pin to the [SharedAdc::create_channel()] function to generate an
+/// [AdcChannel]. The [AdcChannel]'s ownership can then be moved to any required drivers.
+///
+/// ## Synchronization
+/// If the multiple priorities utilize the ADC that results in resource pre-emption, pre-emption is
+/// protected against through the use of an atomic bool. Attempting to utilize the ADC from a
+/// higher priority level while it is in use at a lower level will result in a [AdcError::InUse].
+use embedded_hal::adc::{Channel, OneShot};
+use stm32h7xx_hal as hal;
+
+#[derive(Debug, Copy, Clone)]
+pub enum AdcError {
+ /// Indicates that the ADC is already in use
+ InUse,
+}
+
+/// A single channel on an ADC peripheral.
+pub struct AdcChannel<'a, Adc, PIN> {
+ pin: PIN,
+ slope: f32,
+ mutex: &'a spin::Mutex<hal::adc::Adc<Adc, hal::adc::Enabled>>,
+}
+
+impl<'a, Adc, PIN> AdcChannel<'a, Adc, PIN>
+where
+ PIN: Channel<Adc, ID = u8>,
+ hal::adc::Adc<Adc, hal::adc::Enabled>: OneShot<Adc, u32, PIN>,
+ <hal::adc::Adc<Adc, hal::adc::Enabled> as OneShot<Adc, u32, PIN>>::Error:
+ core::fmt::Debug,
+{
+ /// Read the ADC channel and normalize the result.
+ ///
+ /// # Returns
+ /// The normalized ADC measurement as a ratio of full-scale.
+ pub fn read_normalized(&mut self) -> Result<f32, AdcError> {
+ self.read_raw().map(|code| code as f32 / self.slope)
+ }
+
+ /// Read the raw ADC sample for the channel.
+ ///
+ /// # Returns
+ /// The raw ADC code measured on the channel.
+ pub fn read_raw(&mut self) -> Result<u32, AdcError> {
+ let mut adc = self.mutex.try_lock().ok_or(AdcError::InUse)?;
+ Ok(adc.read(&mut self.pin).unwrap())
+ }
+}
+
+/// An ADC peripheral that can provide ownership of individual channels for sharing between
+/// drivers.
+pub struct SharedAdc<Adc> {
+ mutex: spin::Mutex<hal::adc::Adc<Adc, hal::adc::Enabled>>,
+ slope: f32,
+}
+
+impl<Adc> SharedAdc<Adc> {
+ /// Construct a new shared ADC driver.
+ ///
+ /// # Args
+ /// * `slope` - The slope of the ADC conversion transfer function.
+ /// * `adc` - The ADC peripheral to share.
+ pub fn new(slope: f32, adc: hal::adc::Adc<Adc, hal::adc::Enabled>) -> Self {
+ Self {
+ slope,
+ mutex: spin::Mutex::new(adc),
+ }
+ }
+
+ /// Allocate an ADC channel for usage.
+ ///
+ /// # Args
+ /// * `pin` - The ADC input associated with the desired ADC channel. Often, this is a GPIO pin.
+ ///
+ /// # Returns
+ /// An instantiated [AdcChannel] whose ownership can be transferred to other drivers.
+ pub fn create_channel<PIN: Channel<Adc, ID = u8>>(
+ &self,
+ pin: PIN,
+ ) -> AdcChannel<'_, Adc, PIN> {
+ AdcChannel {
+ pin,
+ slope: self.slope,
+ mutex: &self.mutex,
+ }
+ }
+}
+
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 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +
use miniconf::Tree;
+use rand_core::{RngCore, SeedableRng};
+use rand_xorshift::XorShiftRng;
+use serde::{Deserialize, Serialize};
+
+/// Types of signals that can be generated.
+#[derive(Copy, Clone, Debug, Deserialize, Serialize)]
+pub enum Signal {
+ Cosine,
+ Square,
+ Triangle,
+ WhiteNoise,
+}
+
+/// Basic configuration for a generated signal.
+///
+/// # Miniconf Tree
+/// `{"signal": <signal>, "frequency", 1000.0, "symmetry": 0.5, "amplitude": 1.0}`
+///
+/// Where `<signal>` 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)]
+pub struct BasicConfig {
+ /// The signal type that should be generated. See [Signal] variants.
+ pub signal: Signal,
+
+ /// The frequency of the generated signal in Hertz.
+ pub frequency: f32,
+
+ /// 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: f32,
+
+ /// The amplitude of the output signal in volts.
+ pub amplitude: f32,
+
+ /// The phase of the output signal in turns.
+ pub phase: f32,
+}
+
+impl Default for BasicConfig {
+ fn default() -> Self {
+ Self {
+ frequency: 1.0e3,
+ symmetry: 0.5,
+ signal: Signal::Cosine,
+ amplitude: 0.0,
+ phase: 0.0,
+ }
+ }
+}
+
+/// 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,
+ /// The provided symmetry is out of range.
+ InvalidSymmetry,
+ /// The provided frequency is out of range.
+ InvalidFrequency,
+}
+
+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<Config, Error> {
+ let symmetry_complement = 1.0 - self.symmetry;
+ // Validate symmetry
+ if self.symmetry < 0.0 || symmetry_complement < 0.0 {
+ return Err(Error::InvalidSymmetry);
+ }
+
+ 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);
+ }
+
+ // 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
+ } else {
+ NYQUIST
+ } as i32,
+ if symmetry_complement * NYQUIST > ftw {
+ ftw / symmetry_complement
+ } 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,
+ }
+ }
+}
+
+#[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
+ }
+ }
+
+ /// 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;
+ }
+}
+
+impl core::iter::Iterator for SignalGenerator {
+ type Item = i16;
+
+ /// Get the next value in the generator sequence.
+ fn next(&mut self) -> Option<i16> {
+ 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)
+ }
+ }
+ 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 _)
+ }
+}
+
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 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +
//! The sampling timer is used for managing ADC sampling and external reference timestamping.
+use super::hal;
+use num_enum::TryFromPrimitive;
+
+use hal::stm32::{
+ // TIM1 and TIM8 have identical registers.
+ tim1 as __tim8,
+ tim2 as __tim2,
+ // TIM2 and TIM5 have identical registers.
+ tim2 as __tim5,
+ tim3 as __tim3,
+};
+
+/// The event that should generate an external trigger from the peripheral.
+#[allow(dead_code)]
+pub enum TriggerGenerator {
+ Reset = 0b000,
+ Enable = 0b001,
+ Update = 0b010,
+ ComparePulse = 0b011,
+ Ch1Compare = 0b100,
+ Ch2Compare = 0b101,
+ Ch3Compare = 0b110,
+ Ch4Compare = 0b111,
+}
+
+/// Selects the trigger source for the timer peripheral.
+#[allow(dead_code)]
+pub enum TriggerSource {
+ Trigger0 = 0,
+ Trigger1 = 0b01,
+ Trigger2 = 0b10,
+ Trigger3 = 0b11,
+}
+
+/// Prescalers for externally-supplied reference clocks.
+#[allow(dead_code)]
+#[derive(TryFromPrimitive)]
+#[repr(u8)]
+pub enum Prescaler {
+ Div1 = 0b00,
+ Div2 = 0b01,
+ Div4 = 0b10,
+ Div8 = 0b11,
+}
+
+/// Optional slave operation modes of a timer.
+#[allow(dead_code)]
+pub enum SlaveMode {
+ Disabled = 0,
+ Trigger = 0b0110,
+}
+
+/// Optional input capture preconditioning filter configurations.
+#[allow(dead_code)]
+pub enum InputFilter {
+ Div1N1 = 0b0000,
+ Div1N8 = 0b0011,
+}
+
+macro_rules! timer_channels {
+ ($name:ident, $TY:ident, $size:ty) => {
+ paste::paste! {
+
+ /// The timer used for managing ADC sampling.
+ pub struct $name {
+ timer: hal::timer::Timer<hal::stm32::[< $TY >]>,
+ channels: Option<[< $TY:lower >]::Channels>,
+ update_event: Option<[< $TY:lower >]::UpdateEvent>,
+ }
+
+ impl $name {
+ /// Construct the sampling timer.
+ #[allow(dead_code)]
+ pub fn new(mut timer: hal::timer::Timer<hal::stm32::[< $TY>]>) -> Self {
+ timer.pause();
+
+ Self {
+ timer,
+ // Note(unsafe): Once these channels are taken, we guarantee that we do not
+ // modify any of the underlying timer channel registers, as ownership of the
+ // channels is now provided through the associated channel structures. We
+ // additionally guarantee this can only be called once because there is only
+ // one Timer2 and this resource takes ownership of it once instantiated.
+ channels: unsafe { Some([< $TY:lower >]::Channels::new()) },
+ update_event: unsafe { Some([< $TY:lower >]::UpdateEvent::new()) },
+ }
+ }
+
+ /// Get the timer capture/compare channels.
+ #[allow(dead_code)]
+ pub fn channels(&mut self) -> [< $TY:lower >]::Channels {
+ self.channels.take().unwrap()
+ }
+
+ /// Get the timer update event.
+ #[allow(dead_code)]
+ pub fn update_event(&mut self) -> [< $TY:lower >]::UpdateEvent {
+ self.update_event.take().unwrap()
+ }
+
+ /// Get the period of the timer.
+ #[allow(dead_code)]
+ pub fn get_period(&self) -> $size {
+ let regs = unsafe { &*hal::stm32::$TY::ptr() };
+ regs.arr.read().arr().bits()
+ }
+
+ /// Manually set the period of the timer.
+ #[allow(dead_code)]
+ pub fn set_period_ticks(&mut self, period: $size) {
+ let regs = unsafe { &*hal::stm32::$TY::ptr() };
+ regs.arr.write(|w| w.arr().bits(period));
+
+ // Force the new period to take effect immediately.
+ self.timer.apply_freq();
+ }
+
+ /// Clock the timer from an external source.
+ ///
+ /// # Note:
+ /// * Currently, only an external source applied to ETR is supported.
+ ///
+ /// # Args
+ /// * `prescaler` - The prescaler to use for the external source.
+ #[allow(dead_code)]
+ pub fn set_external_clock(&mut self, prescaler: Prescaler) {
+ let regs = unsafe { &*hal::stm32::$TY::ptr() };
+ regs.smcr.modify(|_, w| w.etps().bits(prescaler as u8).ece().set_bit());
+
+ // Clear any other prescaler configuration.
+ regs.psc.write(|w| w.psc().bits(0));
+ }
+
+ /// Start the timer.
+ #[allow(dead_code)]
+ pub fn start(&mut self) {
+ // Force a refresh of the frequency settings.
+ self.timer.apply_freq();
+ self.timer.reset_counter();
+
+ self.timer.resume();
+ }
+
+ /// Configure the timer peripheral to generate a trigger based on the provided
+ /// source.
+ #[allow(dead_code)]
+ pub fn generate_trigger(&mut self, source: TriggerGenerator) {
+ let regs = unsafe { &*hal::stm32::$TY::ptr() };
+ // Note(unsafe) The TriggerGenerator enumeration is specified such that this is
+ // always in range.
+ regs.cr2.modify(|_, w| w.mms().bits(source as u8));
+
+ }
+
+ /// Select a trigger source for the timer peripheral.
+ #[allow(dead_code)]
+ pub fn set_trigger_source(&mut self, source: TriggerSource) {
+ let regs = unsafe { &*hal::stm32::$TY::ptr() };
+ // Note(unsafe) The TriggerSource enumeration is specified such that this is
+ // always in range.
+ regs.smcr.modify(|_, w| unsafe { w.ts().bits(source as u8) } );
+ }
+
+ #[allow(dead_code)]
+ pub fn set_slave_mode(&mut self, source: TriggerSource, mode: SlaveMode) {
+ let regs = unsafe { &*hal::stm32::$TY::ptr() };
+ // Note(unsafe) The TriggerSource and SlaveMode enumerations are specified such
+ // that they are always in range.
+ regs.smcr.modify(|_, w| unsafe { w.sms().bits(mode as u8).ts().bits(source as u8) } );
+ }
+ }
+
+ pub mod [< $TY:lower >] {
+ use stm32h7xx_hal as hal;
+ use hal::dma::{traits::TargetAddress, PeripheralToMemory, dma::DMAReq};
+ use hal::stm32::$TY;
+
+ pub struct UpdateEvent {}
+
+ impl UpdateEvent {
+ /// Create a new update event
+ ///
+ /// # Safety
+ /// This is only safe to call once.
+ #[allow(dead_code)]
+ pub unsafe fn new() -> Self {
+ Self {}
+ }
+
+ /// Enable DMA requests upon timer updates.
+ #[allow(dead_code)]
+ pub fn listen_dma(&self) {
+ // Note(unsafe): We perform only atomic operations on the timer registers.
+ let regs = unsafe { &*<$TY>::ptr() };
+ regs.dier.modify(|_, w| w.ude().set_bit());
+ }
+
+ /// Trigger a DMA request manually
+ #[allow(dead_code)]
+ pub fn trigger(&self) {
+ let regs = unsafe { &*<$TY>::ptr() };
+ regs.egr.write(|w| w.ug().set_bit());
+ }
+ }
+
+ /// The channels representing the timer.
+ pub struct Channels {
+ pub ch1: Channel1,
+ pub ch2: Channel2,
+ pub ch3: Channel3,
+ pub ch4: Channel4,
+ }
+
+ impl Channels {
+ /// Construct a new set of channels.
+ ///
+ /// # Safety
+ /// This is only safe to call once.
+ #[allow(dead_code)]
+ pub unsafe fn new() -> Self {
+ Self {
+ ch1: Channel1::new(),
+ ch2: Channel2::new(),
+ ch3: Channel3::new(),
+ ch4: Channel4::new(),
+ }
+ }
+ }
+
+ timer_channels!(1, $TY, ccmr1, $size);
+ timer_channels!(2, $TY, ccmr1, $size);
+ timer_channels!(3, $TY, ccmr2, $size);
+ timer_channels!(4, $TY, ccmr2, $size);
+ }
+ }
+ };
+
+ ($index:expr, $TY:ty, $ccmrx:expr, $size:ty) => {
+ paste::paste! {
+ pub use super::[< __ $TY:lower >]::[< $ccmrx _input >]::[< CC $index S_A>] as [< CaptureSource $index >];
+
+ /// A capture/compare channel of the timer.
+ pub struct [< Channel $index >] {}
+
+ /// A capture channel of the timer.
+ pub struct [< Channel $index InputCapture>] {}
+
+ impl [< Channel $index >] {
+ /// Construct a new timer channel.
+ ///
+ /// Note(unsafe): This function must only be called once. Once constructed, the
+ /// constructee guarantees to never modify the timer channel.
+ #[allow(dead_code)]
+ unsafe fn new() -> Self {
+ Self {}
+ }
+
+ /// Allow the channel to generate DMA requests.
+ #[allow(dead_code)]
+ pub fn listen_dma(&self) {
+ let regs = unsafe { &*<$TY>::ptr() };
+ regs.dier.modify(|_, w| w.[< cc $index de >]().set_bit());
+ }
+
+ /// Operate the channel as an output-compare.
+ ///
+ /// # Args
+ /// * `value` - The value to compare the sampling timer's counter against.
+ #[allow(dead_code)]
+ pub fn to_output_compare(&self, value: $size) {
+ let regs = unsafe { &*<$TY>::ptr() };
+ let arr = regs.arr.read().bits() as $size;
+ assert!(value <= arr);
+ regs.ccr[$index - 1].write(|w| w.ccr().bits(value));
+ regs.[< $ccmrx _output >]()
+ .modify(|_, w| unsafe { w.[< cc $index s >]().bits(0) });
+ }
+
+ /// Operate the channel in input-capture mode.
+ ///
+ /// # Args
+ /// * `input` - The input source for the input capture event.
+ #[allow(dead_code)]
+ pub fn into_input_capture(self, input: [< CaptureSource $index >]) -> [< Channel $index InputCapture >]{
+ let regs = unsafe { &*<$TY>::ptr() };
+
+ regs.[< $ccmrx _input >]().modify(|_, w| w.[< cc $index s>]().variant(input));
+
+ [< Channel $index InputCapture >] {}
+ }
+ }
+
+ impl [< Channel $index InputCapture >] {
+ /// Get the latest capture from the channel.
+ #[allow(dead_code)]
+ pub fn latest_capture(&mut self) -> Result<Option<$size>, Option<$size>> {
+ // Note(unsafe): This channel owns all access to the specific timer channel.
+ // Only atomic operations on completed on the timer registers.
+ let regs = unsafe { &*<$TY>::ptr() };
+
+ if regs.sr.read().[< cc $index if >]().bit_is_set() {
+ // Read the capture value. Reading the captured value clears the flag in the
+ // status register automatically.
+ let result = regs.ccr[$index - 1].read().ccr().bits();
+
+ // Read SR again to check for a potential over-capture. Return an error in
+ // that case.
+ let sr = regs.sr.read();
+ if sr.[< cc $index of >]().bit_is_set() {
+ // NOTE(unsafe) write-back is safe
+ regs.sr.write(|w| unsafe { w.bits(sr.bits()) }.[< cc $index of >]().clear_bit());
+ Err(Some(result))
+ } else {
+ Ok(Some(result))
+ }
+ } else {
+ Ok(None)
+ }
+ }
+
+ /// Allow the channel to generate DMA requests.
+ #[allow(dead_code)]
+ pub fn listen_dma(&self) {
+ // Note(unsafe): This channel owns all access to the specific timer channel.
+ // Only atomic operations on completed on the timer registers.
+ let regs = unsafe { &*<$TY>::ptr() };
+ regs.dier.modify(|_, w| w.[< cc $index de >]().set_bit());
+ }
+
+ /// Enable the input capture to begin capturing timer values.
+ #[allow(dead_code)]
+ pub fn enable(&mut self) {
+ // Read the latest input capture to clear any pending data in the register.
+ let _ = self.latest_capture();
+
+ // Note(unsafe): This channel owns all access to the specific timer channel.
+ // Only atomic operations on completed on the timer registers.
+ let regs = unsafe { &*<$TY>::ptr() };
+ regs.ccer.modify(|_, w| w.[< cc $index e >]().set_bit());
+ }
+
+ /// Check if an over-capture event has occurred.
+ #[allow(dead_code)]
+ pub fn check_overcapture(&self) -> bool {
+ // Note(unsafe): This channel owns all access to the specific timer channel.
+ // Only atomic operations on completed on the timer registers.
+ let regs = unsafe { &*<$TY>::ptr() };
+ regs.sr.read().[< cc $index of >]().bit_is_set()
+ }
+
+ /// Configure the input capture input pre-filter.
+ ///
+ /// # Args
+ /// * `filter` - The desired input filter stage configuration. Defaults to disabled.
+ #[allow(dead_code)]
+ pub fn configure_filter(&mut self, filter: super::InputFilter) {
+ // Note(unsafe): This channel owns all access to the specific timer channel.
+ // Only atomic operations on completed on the timer registers.
+ let regs = unsafe { &*<$TY>::ptr() };
+ regs.[< $ccmrx _input >]().modify(|_, w| w.[< ic $index f >]().bits(filter as u8));
+ }
+
+ /// Configure the input capture prescaler.
+ ///
+ /// # Args
+ /// * `psc` - Prescaler exponent.
+ #[allow(dead_code)]
+ pub fn configure_prescaler(&mut self, prescaler: super::Prescaler) {
+ // Note(unsafe): This channel owns all access to the specific timer channel.
+ // Only atomic operations on completed on the timer registers.
+ let regs = unsafe { &*<$TY>::ptr() };
+ // Note(unsafe): Enum values are all valid.
+ #[allow(unused_unsafe)]
+ regs.[< $ccmrx _input >]().modify(|_, w| unsafe {
+ w.[< ic $index psc >]().bits(prescaler as u8)});
+ }
+ }
+
+ // Note(unsafe): This manually implements DMA support for input-capture channels. This
+ // is safe as it is only completed once per channel and each DMA request is allocated to
+ // each channel as the owner.
+ unsafe impl TargetAddress<PeripheralToMemory> for [< Channel $index InputCapture >] {
+ type MemSize = $size;
+
+ const REQUEST_LINE: Option<u8> = Some(DMAReq::[< $TY:camel Ch $index >]as u8);
+
+ fn address(&self) -> usize {
+ let regs = unsafe { &*<$TY>::ptr() };
+ ®s.ccr[$index - 1] as *const _ as usize
+ }
+ }
+ }
+ };
+}
+
+timer_channels!(SamplingTimer, TIM2, u32);
+timer_channels!(ShadowSamplingTimer, TIM3, u16);
+
+timer_channels!(TimestampTimer, TIM5, u32);
+timer_channels!(PounderTimestampTimer, TIM8, u16);
+
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 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +
//! Stabilizer data stream capabilities
+//!
+//! # Design
+//! Data streamining utilizes UDP packets to send live data streams at high throughput.
+//! Packets are always sent in a best-effort fashion, and data may be dropped.
+//!
+//! Stabilizer organizes livestreamed data into batches within a "Frame" that will be sent as a UDP
+//! packet. Each frame consits of a header followed by sequential batch serializations. The packet
+//! header is constant for all streaming capabilities, but the serialization format after the header
+//! is application-defined.
+//!
+//! ## Frame Header
+//! The header consists of the following, all in little-endian.
+//!
+//! * **Magic word 0x057B** (u16): a constant to identify Stabilizer streaming data.
+//! * **Format Code** (u8): a unique ID that indicates the serialization format of each batch of data
+//! in the frame. Refer to [StreamFormat] for further information.
+//! * **Batch Count** (u8): the number of batches of data.
+//! * **Sequence Number** (u32): an the sequence number of the first batch in the frame.
+//! This can be used to determine if and how many stream batches are lost.
+//!
+//! # Example
+//! A sample Python script is available in `scripts/stream_throughput.py` to demonstrate reception
+//! of livestreamed data.
+use core::mem::MaybeUninit;
+use heapless::{
+ pool::{Box, Init, Pool, Uninit},
+ spsc::{Consumer, Producer, Queue},
+};
+use num_enum::IntoPrimitive;
+use serde::{Deserialize, Serialize};
+use smoltcp_nal::embedded_nal::{IpAddr, Ipv4Addr, SocketAddr, UdpClientStack};
+
+use super::NetworkReference;
+
+// Magic first bytes indicating a UDP frame of straming data
+const MAGIC: u16 = 0x057B;
+
+// The size of the header, calculated in words.
+// The header has a 16-bit magic word, an 8-bit format, 8-bit batch-size, and 32-bit sequence
+// number, which corresponds to 8 bytes.
+const HEADER_SIZE: usize = 8;
+
+// The number of frames that can be buffered.
+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;
+
+// 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.
+const FRAME_QUEUE_SIZE: usize = FRAME_COUNT * 2;
+
+// Static storage used for a heapless::Pool of frame buffers.
+static mut FRAME_DATA: [u8; core::mem::size_of::<u8>()
+ * FRAME_SIZE
+ * FRAME_COUNT] = [0; core::mem::size_of::<u8>() * FRAME_SIZE * FRAME_COUNT];
+
+type Frame = [MaybeUninit<u8>; FRAME_SIZE];
+
+/// Represents the destination for the UDP stream to send data to.
+///
+/// # Miniconf
+/// `{"ip": <addr>, "port": <port>}`
+///
+/// * `<addr>` is an array of 4 bytes. E.g. `[192, 168, 0, 1]`
+/// * `<port>` is any unsigned 16-bit value.
+///
+/// ## Example
+/// `{"ip": [192, 168,0, 1], "port": 1111}`
+#[derive(Copy, Clone, Debug, Serialize, Deserialize, Default)]
+pub struct StreamTarget {
+ pub ip: [u8; 4],
+ pub port: u16,
+}
+
+/// Specifies the format of streamed data
+#[repr(u8)]
+#[derive(Debug, Copy, Clone, PartialEq, Eq, IntoPrimitive)]
+pub enum StreamFormat {
+ /// Reserved, unused format specifier.
+ Unknown = 0,
+
+ /// Streamed data contains ADC0, ADC1, DAC0, and DAC1 sequentially in little-endian format.
+ ///
+ /// # Example
+ /// With a batch size of 2, the serialization would take the following form:
+ /// ```
+ /// <ADC0[0]> <ADC0[1]> <ADC1[0]> <ADC1[1]> <DAC0[0]> <DAC0[1]> <DAC1[0]> <DAC1[1]>
+ /// ```
+ AdcDacData = 1,
+
+ /// Streamed data in FLS (fiber length stabilization) format. See the FLS application for
+ /// detailed definition.
+ Fls = 2,
+}
+
+impl From<StreamTarget> for SocketAddr {
+ fn from(target: StreamTarget) -> SocketAddr {
+ SocketAddr::new(
+ IpAddr::V4(Ipv4Addr::new(
+ target.ip[0],
+ target.ip[1],
+ target.ip[2],
+ target.ip[3],
+ )),
+ target.port,
+ )
+ }
+}
+
+/// Configure streaming on a device.
+///
+/// # Args
+/// * `stack` - A reference to the shared network stack.
+///
+/// # Returns
+/// (generator, stream) where `generator` can be used to enqueue "batches" for transmission. The
+/// `stream` is the logically consumer (UDP transmitter) of the enqueued data.
+pub fn setup_streaming(
+ stack: NetworkReference,
+) -> (FrameGenerator, DataStream) {
+ // The queue needs to be at least as large as the frame count to ensure that every allocated
+ // frame can potentially be enqueued for transmission.
+ let queue =
+ cortex_m::singleton!(: Queue<StreamFrame, FRAME_QUEUE_SIZE> = Queue::new())
+ .unwrap();
+ let (producer, consumer) = queue.split();
+
+ let frame_pool = cortex_m::singleton!(: Pool<Frame> = Pool::new()).unwrap();
+
+ // Note(unsafe): We guarantee that FRAME_DATA is only accessed once in this function.
+ let memory = unsafe { &mut FRAME_DATA };
+ frame_pool.grow(memory);
+
+ let generator = FrameGenerator::new(producer, frame_pool);
+
+ let stream = DataStream::new(stack, consumer, frame_pool);
+
+ (generator, stream)
+}
+
+#[derive(Debug)]
+struct StreamFrame {
+ buffer: Box<Frame, Init>,
+ offset: usize,
+ batches: u8,
+}
+
+impl StreamFrame {
+ pub fn new(
+ buffer: Box<Frame, Uninit>,
+ format_id: u8,
+ sequence_number: u32,
+ ) -> Self {
+ let mut buffer = buffer.init([MaybeUninit::uninit(); FRAME_SIZE]);
+
+ for (byte, buf) in MAGIC
+ .to_le_bytes()
+ .iter()
+ .chain(&[format_id, 0])
+ .chain(sequence_number.to_le_bytes().iter())
+ .zip(buffer.iter_mut())
+ {
+ buf.write(*byte);
+ }
+
+ Self {
+ buffer,
+ offset: HEADER_SIZE,
+ batches: 0,
+ }
+ }
+
+ pub fn add_batch<F>(&mut self, mut f: F) -> usize
+ where
+ F: FnMut(&mut [MaybeUninit<u8>]) -> usize,
+ {
+ let len = f(&mut self.buffer[self.offset..]);
+ self.offset += len;
+ self.batches += 1;
+ len
+ }
+
+ pub fn is_full(&self, len: usize) -> bool {
+ self.offset + len > self.buffer.len()
+ }
+
+ pub fn finish(&mut self) -> &[MaybeUninit<u8>] {
+ self.buffer[3].write(self.batches);
+ &self.buffer[..self.offset]
+ }
+}
+
+/// The data generator for a stream.
+pub struct FrameGenerator {
+ queue: Producer<'static, StreamFrame, FRAME_QUEUE_SIZE>,
+ pool: &'static Pool<Frame>,
+ current_frame: Option<StreamFrame>,
+ sequence_number: u32,
+ format: u8,
+}
+
+impl FrameGenerator {
+ fn new(
+ queue: Producer<'static, StreamFrame, FRAME_QUEUE_SIZE>,
+ pool: &'static Pool<Frame>,
+ ) -> Self {
+ Self {
+ queue,
+ pool,
+ format: StreamFormat::Unknown.into(),
+ current_frame: None,
+ sequence_number: 0,
+ }
+ }
+
+ /// Configure the format of the stream.
+ ///
+ /// # Note:
+ /// This function shall only be called once upon initializing streaming
+ ///
+ /// # Args
+ /// * `format` - The desired format of the stream.
+ #[doc(hidden)]
+ pub(crate) fn configure(&mut self, format: impl Into<u8>) {
+ self.format = format.into();
+ }
+
+ /// Add a batch to the current stream frame.
+ ///
+ /// # Args
+ /// * `f` - A closure that will be provided the buffer to write batch data into.
+ /// Returns the number of bytes written.
+ pub fn add<F>(&mut self, f: F)
+ where
+ F: FnMut(&mut [MaybeUninit<u8>]) -> usize,
+ {
+ let sequence_number = self.sequence_number;
+ self.sequence_number = self.sequence_number.wrapping_add(1);
+
+ if self.current_frame.is_none() {
+ if let Some(buffer) = self.pool.alloc() {
+ self.current_frame.replace(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();
+
+ let len = current_frame.add_batch(f);
+
+ 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();
+ }
+ }
+}
+
+/// The "consumer" portion of the data stream.
+///
+/// # Note
+/// This is responsible for consuming data and sending it over UDP.
+pub struct DataStream {
+ stack: NetworkReference,
+ socket: Option<<NetworkReference as UdpClientStack>::UdpSocket>,
+ queue: Consumer<'static, StreamFrame, FRAME_QUEUE_SIZE>,
+ frame_pool: &'static Pool<Frame>,
+ remote: SocketAddr,
+}
+
+impl DataStream {
+ /// Construct a new data streamer.
+ ///
+ /// # Args
+ /// * `stack` - A reference to the shared network stack.
+ /// * `consumer` - The read side of the queue containing data to transmit.
+ /// * `frame_pool` - The Pool to return stream frame objects into.
+ fn new(
+ stack: NetworkReference,
+ consumer: Consumer<'static, StreamFrame, FRAME_QUEUE_SIZE>,
+ frame_pool: &'static Pool<Frame>,
+ ) -> Self {
+ Self {
+ stack,
+ socket: None,
+ remote: StreamTarget::default().into(),
+ queue: consumer,
+ frame_pool,
+ }
+ }
+
+ fn close(&mut self) {
+ if let Some(socket) = self.socket.take() {
+ log::info!("Closing stream");
+ // Note(unwrap): We guarantee that the socket is available above.
+ self.stack.close(socket).unwrap();
+ }
+ }
+
+ // Open new socket.
+ fn open(&mut self) -> Result<(), ()> {
+ // If there is already a socket of if remote address is unspecified,
+ // do not open a new socket.
+ if self.socket.is_some() || self.remote.ip().is_unspecified() {
+ return Err(());
+ }
+
+ log::info!("Opening stream");
+
+ let mut socket = self.stack.socket().or(Err(()))?;
+
+ // Note(unwrap): We only connect with a new socket, so it is guaranteed to not already be
+ // bound.
+ self.stack.connect(&mut socket, self.remote).unwrap();
+
+ self.socket.replace(socket);
+
+ Ok(())
+ }
+
+ /// Configure the remote endpoint of the stream.
+ ///
+ /// # Args
+ /// * `remote` - The destination to send stream data to.
+ pub fn set_remote(&mut self, remote: SocketAddr) {
+ // Close socket to be reopened if the remote has changed.
+ if remote != self.remote {
+ self.close();
+ }
+ self.remote = remote;
+ }
+
+ /// Process any data for transmission.
+ pub fn process(&mut self) {
+ match self.socket.as_mut() {
+ None => {
+ // If there's no socket available, try to connect to our remote.
+ if self.open().is_ok() {
+ // If we just successfully opened the socket, flush old data from queue.
+ while let Some(frame) = self.queue.dequeue() {
+ self.frame_pool.free(frame.buffer);
+ }
+ }
+ }
+ Some(handle) => {
+ if let Some(mut frame) = self.queue.dequeue() {
+ // Transmit the frame and return it to the pool.
+ let buf = frame.finish();
+ let data = unsafe {
+ core::slice::from_raw_parts(
+ buf.as_ptr() as *const u8,
+ core::mem::size_of_val(buf),
+ )
+ };
+ self.stack.send(handle, data).ok();
+ self.frame_pool.free(frame.buffer)
+ }
+ }
+ }
+ }
+}
+
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 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +
//! Stabilizer network management module
+//!
+//! # Design
+//! The stabilizer network architecture supports numerous layers to permit transmission of
+//! telemetry (via MQTT), configuration of run-time settings (via MQTT + Miniconf), and live data
+//! streaming over raw UDP/TCP sockets. This module encompasses the main processing routines
+//! related to Stabilizer networking operations.
+pub use heapless;
+pub use miniconf;
+pub use serde;
+
+pub mod data_stream;
+pub mod network_processor;
+pub mod telemetry;
+
+use crate::hardware::{EthernetPhy, NetworkManager, NetworkStack, SystemTimer};
+use data_stream::{DataStream, FrameGenerator};
+use network_processor::NetworkProcessor;
+use telemetry::TelemetryClient;
+
+use core::fmt::Write;
+use heapless::String;
+use miniconf::JsonCoreSlash;
+use serde::Serialize;
+use smoltcp_nal::embedded_nal::SocketAddr;
+
+pub type NetworkReference =
+ smoltcp_nal::shared::NetworkStackProxy<'static, NetworkStack>;
+
+pub struct MqttStorage {
+ telemetry: [u8; 2048],
+ settings: [u8; 1024],
+}
+
+impl Default for MqttStorage {
+ fn default() -> Self {
+ Self {
+ telemetry: [0u8; 2048],
+ settings: [0u8; 1024],
+ }
+ }
+}
+
+pub enum UpdateState {
+ NoChange,
+ Updated,
+}
+
+pub enum NetworkState {
+ SettingsChanged(String<128>),
+ Updated,
+ NoChange,
+}
+
+/// A structure of Stabilizer's default network users.
+pub struct NetworkUsers<S, T, const Y: usize>
+where
+ for<'de> S: Default + JsonCoreSlash<'de, Y> + Clone,
+ T: Serialize,
+{
+ pub miniconf: miniconf::MqttClient<
+ 'static,
+ S,
+ NetworkReference,
+ SystemTimer,
+ miniconf::minimq::broker::NamedBroker<NetworkReference>,
+ Y,
+ >,
+ pub processor: NetworkProcessor,
+ stream: DataStream,
+ generator: Option<FrameGenerator>,
+ pub telemetry: TelemetryClient<T>,
+}
+
+impl<S, T, const Y: usize> NetworkUsers<S, T, Y>
+where
+ for<'de> S: Default + JsonCoreSlash<'de, Y> + Clone,
+ T: Serialize,
+{
+ /// Construct Stabilizer's default network users.
+ ///
+ /// # Args
+ /// * `stack` - The network stack that will be used to share with all network users.
+ /// * `phy` - The ethernet PHY connecting the network.
+ /// * `clock` - A `SystemTimer` implementing `Clock`.
+ /// * `app` - The name of the application.
+ /// * `mac` - The MAC address of the network.
+ /// * `broker` - The domain name of the MQTT broker to use.
+ ///
+ /// # Returns
+ /// A new struct of network users.
+ pub fn new(
+ stack: NetworkStack,
+ phy: EthernetPhy,
+ clock: SystemTimer,
+ app: &str,
+ mac: smoltcp_nal::smoltcp::wire::EthernetAddress,
+ broker: &str,
+ ) -> Self {
+ let stack_manager =
+ cortex_m::singleton!(: NetworkManager = NetworkManager::new(stack))
+ .unwrap();
+
+ let processor =
+ NetworkProcessor::new(stack_manager.acquire_stack(), phy);
+
+ let prefix = get_device_prefix(app, mac);
+
+ let store =
+ cortex_m::singleton!(: MqttStorage = MqttStorage::default())
+ .unwrap();
+
+ let named_broker = miniconf::minimq::broker::NamedBroker::new(
+ broker,
+ stack_manager.acquire_stack(),
+ )
+ .unwrap();
+ let settings = miniconf::MqttClient::new(
+ stack_manager.acquire_stack(),
+ &prefix,
+ clock,
+ S::default(),
+ miniconf::minimq::ConfigBuilder::new(
+ named_broker,
+ &mut store.settings,
+ )
+ .client_id(&get_client_id(app, "settings", mac))
+ .unwrap(),
+ )
+ .unwrap();
+
+ let named_broker = minimq::broker::NamedBroker::new(
+ broker,
+ stack_manager.acquire_stack(),
+ )
+ .unwrap();
+ let mqtt = minimq::Minimq::new(
+ stack_manager.acquire_stack(),
+ clock,
+ minimq::ConfigBuilder::new(named_broker, &mut store.telemetry)
+ // The telemetry client doesn't receive any messages except MQTT control packets.
+ // As such, we don't need much of the buffer for RX.
+ .rx_buffer(minimq::config::BufferConfig::Maximum(100))
+ .client_id(&get_client_id(app, "tlm", mac))
+ .unwrap(),
+ );
+
+ let telemetry = TelemetryClient::new(mqtt, &prefix);
+
+ let (generator, stream) =
+ data_stream::setup_streaming(stack_manager.acquire_stack());
+
+ NetworkUsers {
+ miniconf: settings,
+ processor,
+ telemetry,
+ stream,
+ generator: Some(generator),
+ }
+ }
+
+ /// Enable live data streaming.
+ ///
+ /// # Args
+ /// * `format` - A unique u8 code indicating the format of the data.
+ pub fn configure_streaming(
+ &mut self,
+ format: impl Into<u8>,
+ ) -> FrameGenerator {
+ let mut generator = self.generator.take().unwrap();
+ generator.configure(format);
+ generator
+ }
+
+ /// Direct the stream to the provided remote target.
+ ///
+ /// # Args
+ /// * `remote` - The destination for the streamed data.
+ pub fn direct_stream(&mut self, remote: SocketAddr) {
+ if self.generator.is_none() {
+ self.stream.set_remote(remote);
+ }
+ }
+
+ /// Update and process all of the network users state.
+ ///
+ /// # Returns
+ /// An indication if any of the network users indicated a state change.
+ /// The SettingsChanged option contains the path of the settings that changed.
+ pub fn update(&mut self) -> NetworkState {
+ // Update the MQTT clients.
+ self.telemetry.update();
+
+ // Update the data stream.
+ if self.generator.is_none() {
+ self.stream.process();
+ }
+
+ // Poll for incoming data.
+ let poll_result = match self.processor.update() {
+ UpdateState::NoChange => NetworkState::NoChange,
+ UpdateState::Updated => NetworkState::Updated,
+ };
+
+ // `settings_path` has to be at least as large as `miniconf::mqtt_client::MAX_TOPIC_LENGTH`.
+ let mut settings_path: String<128> = String::new();
+ match self.miniconf.handled_update(|path, old, new| {
+ settings_path = path.into();
+ *old = new.clone();
+ Result::<(), &'static str>::Ok(())
+ }) {
+ Ok(true) => NetworkState::SettingsChanged(settings_path),
+ _ => poll_result,
+ }
+ }
+}
+
+/// Get an MQTT client ID for a client.
+///
+/// # Args
+/// * `app` - The name of the application
+/// * `client` - The unique tag of the client
+/// * `mac` - The MAC address of the device.
+///
+/// # Returns
+/// A client ID that may be used for MQTT client identification.
+fn get_client_id(
+ app: &str,
+ client: &str,
+ mac: smoltcp_nal::smoltcp::wire::EthernetAddress,
+) -> String<64> {
+ let mut identifier = String::new();
+ write!(&mut identifier, "{app}-{mac}-{client}").unwrap();
+ identifier
+}
+
+/// Get the MQTT prefix of a device.
+///
+/// # Args
+/// * `app` - The name of the application that is executing.
+/// * `mac` - The ethernet MAC address of the device.
+///
+/// # Returns
+/// The MQTT prefix used for this device.
+pub fn get_device_prefix(
+ app: &str,
+ mac: smoltcp_nal::smoltcp::wire::EthernetAddress,
+) -> String<128> {
+ // Note(unwrap): The mac address + binary name must be short enough to fit into this string. If
+ // they are defined too long, this will panic and the device will fail to boot.
+ let mut prefix: String<128> = String::new();
+ write!(&mut prefix, "dt/sinara/{app}/{mac}").unwrap();
+
+ prefix
+}
+
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 +
//! Task to process network hardware.
+//!
+//! # Design
+//! The network processir is a small taks to regularly process incoming data over ethernet, handle
+//! the ethernet PHY state, and reset the network as appropriate.
+use super::{NetworkReference, UpdateState};
+use crate::hardware::EthernetPhy;
+
+/// Processor for managing network hardware.
+pub struct NetworkProcessor {
+ pub stack: NetworkReference,
+ phy: EthernetPhy,
+ network_was_reset: bool,
+}
+
+impl NetworkProcessor {
+ /// Construct a new network processor.
+ ///
+ /// # Args
+ /// * `stack` - A reference to the shared network stack
+ /// * `phy` - The ethernet PHY used for the network.
+ ///
+ /// # Returns
+ /// The newly constructed processor.
+ pub fn new(stack: NetworkReference, phy: EthernetPhy) -> Self {
+ Self {
+ stack,
+ phy,
+ network_was_reset: false,
+ }
+ }
+
+ /// Handle ethernet link connection status.
+ ///
+ /// # Note
+ /// This may take non-trivial amounts of time to communicate with the PHY. As such, this should
+ /// only be called as often as necessary (e.g. once per second or so).
+ pub fn handle_link(&mut self) {
+ // If the PHY indicates there's no more ethernet link, reset the DHCP server in the network
+ // stack.
+ let link_up = self.phy.poll_link();
+ match (link_up, self.network_was_reset) {
+ (true, true) => {
+ log::warn!("Network link UP");
+ self.network_was_reset = false;
+ }
+ // Only reset the network stack once per link reconnection. This prevents us from
+ // sending an excessive number of DHCP requests.
+ (false, false) => {
+ log::warn!("Network link DOWN");
+ self.network_was_reset = true;
+ self.stack.lock(|stack| stack.handle_link_reset());
+ }
+ _ => {}
+ };
+ }
+
+ /// Process and update the state of the network.
+ ///
+ /// # Note
+ /// This function should be called regularly before other network tasks to update the state of
+ /// all relevant network sockets.
+ ///
+ /// # Returns
+ /// An update state corresponding with any changes in the underlying network.
+ pub fn update(&mut self) -> UpdateState {
+ match self.stack.lock(|stack| stack.poll()) {
+ Ok(true) => UpdateState::Updated,
+ Ok(false) => UpdateState::NoChange,
+ Err(_) => UpdateState::Updated,
+ }
+ }
+}
+
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 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +
//! Stabilizer Telemetry Capabilities
+//!
+//! # Design
+//! Telemetry is reported regularly using an MQTT client. All telemetry is reported in SI units
+//! using standard JSON format.
+//!
+//! In order to report ADC/DAC codes generated during the DSP routines, a telemetry buffer is
+//! employed to track the latest codes. Converting these codes to SI units would result in
+//! repetitive and unnecessary calculations within the DSP routine, slowing it down and limiting
+//! sampling frequency. Instead, the raw codes are stored and the telemetry is generated as
+//! required immediately before transmission. This ensures that any slower computation required
+//! for unit conversion can be off-loaded to lower priority tasks.
+use heapless::{String, Vec};
+use serde::Serialize;
+
+use super::NetworkReference;
+use crate::hardware::{adc::AdcCode, afe::Gain, dac::DacCode, SystemTimer};
+
+/// The telemetry client for reporting telemetry data over MQTT.
+pub struct TelemetryClient<T: Serialize> {
+ mqtt: minimq::Minimq<
+ 'static,
+ NetworkReference,
+ SystemTimer,
+ minimq::broker::NamedBroker<NetworkReference>,
+ >,
+ telemetry_topic: String<128>,
+ _telemetry: core::marker::PhantomData<T>,
+}
+
+/// The telemetry buffer is used for storing sample values during execution.
+///
+/// # Note
+/// These values can be converted to SI units immediately before reporting to save processing time.
+/// This allows for the DSP process to continually update the values without incurring significant
+/// run-time overhead during conversion to SI units.
+#[derive(Copy, Clone)]
+pub struct TelemetryBuffer {
+ /// The latest input sample on ADC0/ADC1.
+ pub adcs: [AdcCode; 2],
+ /// The latest output code on DAC0/DAC1.
+ pub dacs: [DacCode; 2],
+ /// The latest digital input states during processing.
+ pub digital_inputs: [bool; 2],
+}
+
+/// The telemetry structure is data that is ultimately reported as telemetry over MQTT.
+///
+/// # Note
+/// This structure should be generated on-demand by the buffer when required to minimize conversion
+/// overhead.
+#[derive(Serialize)]
+pub struct Telemetry {
+ /// Most recent input voltage measurement.
+ pub adcs: [f32; 2],
+
+ /// Most recent output voltage.
+ pub dacs: [f32; 2],
+
+ /// Most recent digital input assertion state.
+ pub digital_inputs: [bool; 2],
+
+ /// The CPU temperature in degrees Celsius.
+ pub cpu_temp: f32,
+}
+
+impl Default for TelemetryBuffer {
+ fn default() -> Self {
+ Self {
+ adcs: [AdcCode(0), AdcCode(0)],
+ dacs: [DacCode(0), DacCode(0)],
+ digital_inputs: [false, false],
+ }
+ }
+}
+
+impl TelemetryBuffer {
+ /// Convert the telemetry buffer to finalized, SI-unit telemetry for reporting.
+ ///
+ /// # Args
+ /// * `afe0` - The current AFE configuration for channel 0.
+ /// * `afe1` - The current AFE configuration for channel 1.
+ /// * `cpu_temp` - The current CPU temperature.
+ ///
+ /// # Returns
+ /// The finalized telemetry structure that can be serialized and reported.
+ pub fn finalize(self, afe0: Gain, afe1: Gain, cpu_temp: f32) -> Telemetry {
+ let in0_volts = Into::<f32>::into(self.adcs[0]) / afe0.as_multiplier();
+ let in1_volts = Into::<f32>::into(self.adcs[1]) / afe1.as_multiplier();
+
+ Telemetry {
+ cpu_temp,
+ adcs: [in0_volts, in1_volts],
+ dacs: [self.dacs[0].into(), self.dacs[1].into()],
+ digital_inputs: self.digital_inputs,
+ }
+ }
+}
+
+impl<T: Serialize> TelemetryClient<T> {
+ /// Construct a new telemetry client.
+ ///
+ /// # Args
+ /// * `mqtt` - The MQTT client
+ /// * `prefix` - The device prefix to use for MQTT telemetry reporting.
+ ///
+ /// # Returns
+ /// A new telemetry client.
+ pub fn new(
+ mqtt: minimq::Minimq<
+ 'static,
+ NetworkReference,
+ SystemTimer,
+ minimq::broker::NamedBroker<NetworkReference>,
+ >,
+ prefix: &str,
+ ) -> Self {
+ let mut telemetry_topic: String<128> = String::from(prefix);
+ telemetry_topic.push_str("/telemetry").unwrap();
+
+ Self {
+ mqtt,
+ telemetry_topic,
+ _telemetry: core::marker::PhantomData,
+ }
+ }
+
+ /// Publish telemetry over MQTT
+ ///
+ /// # Note
+ /// Telemetry is reported in a "best-effort" fashion. Failure to transmit telemetry will cause
+ /// it to be silently dropped.
+ ///
+ /// # Args
+ /// * `telemetry` - The telemetry to report
+ pub fn publish(&mut self, telemetry: &T) {
+ let telemetry: Vec<u8, 512> =
+ serde_json_core::to_vec(telemetry).unwrap();
+ self.mqtt
+ .client()
+ .publish(
+ minimq::Publication::<&[u8]>::new(&telemetry)
+ .topic(&self.telemetry_topic)
+ .finish()
+ .unwrap(),
+ )
+ .map_err(|e| log::error!("Telemetry publishing error: {:?}", e))
+ .ok();
+ }
+
+ /// Update the telemetry client
+ ///
+ /// # Note
+ /// This function is provided to force the underlying MQTT state machine to process incoming
+ /// and outgoing messages. Without this, the client will never connect to the broker. This
+ /// should be called regularly.
+ pub fn update(&mut self) {
+ match self.mqtt.poll(|_client, _topic, _message, _properties| {}) {
+ Err(minimq::Error::Network(
+ smoltcp_nal::NetworkError::TcpConnectionFailure(
+ smoltcp_nal::smoltcp::socket::tcp::ConnectError::Unaddressable
+ ),
+ )) => {}
+
+ Err(error) => log::info!("Unexpected error: {:?}", error),
+ _ => {}
+ }
+ }
+}
+
Stabilizer ADC management interface
+Stabilizer ADCs are connected to the MCU via a simplex, SPI-compatible interface. The ADCs +require a setup conversion time after asserting the CSn (convert) signal to generate the ADC +code from the sampled level. Once the setup time has elapsed, the ADC data is clocked out of +MISO. The internal setup time is managed by the SPI peripheral via a CSn setup time parameter +during SPI configuration, which allows offloading the management of the setup time to hardware.
+Because of the SPI-compatibility of the ADCs, a single SPI peripheral + DMA is used to automate +the collection of multiple ADC samples without requiring processing by the CPU, which reduces +overhead and provides the CPU with more time for processing-intensive tasks, like DSP.
+The automation of sample collection utilizes three DMA streams, the SPI peripheral, and two +timer compare channel for each ADC. One timer comparison channel is configured to generate a +comparison event every time the timer is equal to a specific value. Each comparison then +generates a DMA transfer event to write into the SPI CR1 register to initiate the transfer. +This allows the SPI interface to periodically read a single sample. The other timer comparison +channel is configured to generate a comparison event slightly before the first (~10 timer +cycles). This channel triggers a separate DMA stream to clear the EOT flag within the SPI +peripheral. The EOT flag must be cleared after each transfer or the SPI peripheral will not +properly complete the single conversion. Thus, by using two DMA streams and timer comparison +channels, the SPI can regularly acquire ADC samples.
+In order to collect the acquired ADC samples into a RAM buffer, a final DMA transfer is +configured to read from the SPI RX FIFO into RAM. The request for this transfer is connected to +the SPI RX data signal, so the SPI peripheral will request to move data into RAM whenever it is +available. When enough samples have been collected, a transfer-complete interrupt is generated +and the ADC samples are available for processing.
+After a complete transfer of a batch of samples, the inactive buffer is available to the +user for processing. The processing must complete before the DMA transfer of the next batch +completes.
+Because the DMA data collection is automated via timer count comparisons and DMA transfers, the +ADCs can be initialized and configured, but will not begin sampling the external ADCs until the +sampling timer is enabled. As such, the sampling timer should be enabled after all +initialization has completed and immediately before the embedded processing loop begins.
+The ADCs collect a group of N samples, which is referred to as a batch. The size of the batch +is configured by the user at compile-time to allow for a custom-tailored implementation. Larger +batch sizes generally provide for lower overhead and more processing time per sample, but come +at the expense of increased input -> output latency.
+While there are two ADCs, only a single ADC is configured to generate transfer-complete +interrupts. This is done because it is assumed that the ADCs will always be sampled +simultaneously. If only a single ADC is used, it must always be ADC0, as ADC1 will not generate +transfer-complete interrupts.
+There is a very small amount of latency between sampling of ADCs due to bus matrix priority. As +such, one of the ADCs will be sampled marginally earlier before the other because the DMA +requests are generated simultaneously. This can be avoided by providing a known offset to the +sample DMA requests, which can be completed by setting e.g. ADC0’s comparison to a counter +value of 0 and ADC1’s comparison to a counter value of 1.
+In this implementation, double buffer mode DMA transfers are used because the SPI RX FIFOs +have finite depth, FIFO access is slower than AXISRAM access, and because the single +buffer mode DMA disable/enable and buffer update sequence is slow.
+pub struct Adc0Input { /* private fields */ }
Represents data associated with ADC.
+Construct the ADC input channel.
+spi
- The SPI interface used to communicate with the ADC.trigger_stream
- The DMA stream used to trigger each ADC transfer by
+writing a word into the SPI TX FIFO.data_stream
- The DMA stream used to read samples received over SPI into a data buffer.clear_stream
- The DMA stream used to clear the EOT flag in the SPI peripheral.trigger_channel
- The ADC sampling timer output compare channel for read triggers.clear_channel
- The shadow sampling timer output compare channel used for
+clearing the SPI EOT flag.Wait for the transfer of the currently active buffer to complete, +then call a function on the now inactive buffer and acknowledge the +transfer complete flag.
+NOTE(unsafe): Memory safety and access ordering is not guaranteed +(see the HAL DMA docs).
+pub struct Adc1Input { /* private fields */ }
Represents data associated with ADC.
+Construct the ADC input channel.
+spi
- The SPI interface used to communicate with the ADC.trigger_stream
- The DMA stream used to trigger each ADC transfer by
+writing a word into the SPI TX FIFO.data_stream
- The DMA stream used to read samples received over SPI into a data buffer.clear_stream
- The DMA stream used to clear the EOT flag in the SPI peripheral.trigger_channel
- The ADC sampling timer output compare channel for read triggers.clear_channel
- The shadow sampling timer output compare channel used for
+clearing the SPI EOT flag.Wait for the transfer of the currently active buffer to complete, +then call a function on the now inactive buffer and acknowledge the +transfer complete flag.
+NOTE(unsafe): Memory safety and access ordering is not guaranteed +(see the HAL DMA docs).
+pub struct AdcCode(pub u16);
A type representing an ADC sample.
+0: u16
#[repr(u8)]pub enum Gain {
+ G1,
+ G2,
+ G5,
+ G10,
+}
pub struct ProgrammableGainAmplifier<A0, A1> { /* private fields */ }
A programmable gain amplifier that allows for setting the gain via GPIO.
+pub const MONOTONIC_FREQUENCY: u32 = 1_000;
System timer (RTIC Monotonic) tick frequency
+STM32 Temperature Sensor Driver
+This file provides an API for measuring the internal STM32 temperature sensor. This temperature +sensor measures the silicon junction temperature (Tj) and is connected via an internal ADC.
+pub struct CpuTempSensor { /* private fields */ }
A driver to access the CPU temeprature sensor.
+Construct the temperature sensor.
+sensor
- The ADC channel of the integrated temperature sensor.Get the temperature of the CPU in degrees Celsius.
+Stabilizer DAC management interface
+Stabilizer DACs are connected to the MCU via a simplex, SPI-compatible interface. Each DAC +accepts a 16-bit output code.
+In order to maximize CPU processing time, the DAC code updates are offloaded to hardware using +a timer compare channel, DMA stream, and the DAC SPI interface.
+The timer comparison channel is configured to generate a DMA request whenever the comparison +occurs. Thus, whenever a comparison happens, a single DAC code can be written to the output. By +configuring a DMA stream for a number of successive DAC codes, hardware can regularly update +the DAC without requiring the CPU.
+In order to ensure alignment between the ADC sample batches and DAC output code batches, a DAC +output batch is always exactly 3 batches after the ADC batch that generated it.
+The DMA transfer for the DAC output codes utilizes a double-buffer mode to avoid losing any +transfer events generated by the timer (for example, when 2 update cycles occur before the DMA +transfer completion is handled). In this mode, by the time DMA swaps buffers, there is always a valid buffer in the +“next-transfer” double-buffer location for the DMA transfer. Once a transfer completes, +software then has exactly one batch duration to fill the next buffer before its +transfer begins. If software does not meet this deadline, old data will be repeatedly generated +on the output and output will be shifted by one batch.
+For some applications, it may be desirable to generate a single DAC code from multiple ADC +samples. In order to maintain timing characteristics between ADC samples and DAC code outputs, +applications are required to generate one DAC code for each ADC sample. To accomodate mapping +multiple inputs to a single output, the output code can be repeated a number of times in the +output buffer corresponding with the number of input samples that were used to generate it.
+There is a very small amount of latency between updating the two DACs due to bus matrix +priority. As such, one of the DACs will be updated marginally earlier before the other because +the DMA requests are generated simultaneously. This can be avoided by providing a known offset +to other DMA requests, which can be completed by setting e.g. DAC0’s comparison to a +counter value of 2 and DAC1’s comparison to a counter value of 3. This will have the effect of +generating the DAC updates with a known latency of 1 timer tick to each other and prevent the +DMAs from racing for the bus. As implemented, the DMA channels utilize natural priority of the +DMA channels to arbitrate which transfer occurs first.
+While double-buffered mode is used for DMA to avoid lost DAC-update events, there is no check +for re-use of a previously provided DAC output buffer. It is assumed that the DMA request is +served promptly after the transfer completes.
+pub struct Dac0Output { /* private fields */ }
Represents data associated with DAC.
+Construct the DAC output channel.
+spi
- The SPI interface used to communicate with the ADC.stream
- The DMA stream used to write DAC codes over SPI.trigger_channel
- The sampling timer output compare channel for update triggers.Wait for the transfer of the currently active buffer to complete, +then call a function on the now inactive buffer and acknowledge the +transfer complete flag.
+NOTE(unsafe): Memory safety and access ordering is not guaranteed +(see the HAL DMA docs).
+pub struct Dac1Output { /* private fields */ }
Represents data associated with DAC.
+Construct the DAC output channel.
+spi
- The SPI interface used to communicate with the ADC.stream
- The DMA stream used to write DAC codes over SPI.trigger_channel
- The sampling timer output compare channel for update triggers.Wait for the transfer of the currently active buffer to complete, +then call a function on the now inactive buffer and acknowledge the +transfer complete flag.
+NOTE(unsafe): Memory safety and access ordering is not guaranteed +(see the HAL DMA docs).
+pub struct DacCode(pub u16);
Custom type for referencing DAC output codes. +The internal integer is the raw code written to the DAC output register.
+0: u16
Basic blocking delay
+This module provides a basic asm-based blocking delay.
+This implementation takes into account the Cortex-M7 CPU pipeline architecture to ensure delays +are at least as long as specified.
+pub struct AsmDelay { /* private fields */ }
A basic delay implementation.
+pub const ADC_DAC_SCK_MAX: MegaHertz;
The maximum DAC/ADC serial clock line frequency. This is a hardware limit.
+pub const ADC_SETUP_TIME: f32 = 220e-9;
The ADC setup time is the number of seconds after the CSn line goes low before the serial clock +may begin. This is used for performing the internal ADC conversion.
+pub const DDS_MULTIPLIER: u8 = 5;
The multiplier used for the DDS reference clock PLL.
+pub const DDS_REF_CLK: MegaHertz;
The DDS reference clock frequency in MHz.
+pub const DDS_SYNC_CLK_DIV: u8 = 4;
The divider from the DDS system clock to the SYNC_CLK output (sync-clk is always 1/4 of sysclk).
+pub const DDS_SYSTEM_CLK: MegaHertz;
The DDS system clock frequency after the internal PLL multiplication.
+pub const MAX_SAMPLE_BUFFER_SIZE: usize = 32;
The maximum ADC/DAC sample processing buffer size.
+pub const POUNDER_IO_UPDATE_DELAY: f32 = 1_300e-9;
The delay after initiating a QSPI transfer before asserting the IO_Update for the pounder DDS.
+pub const POUNDER_IO_UPDATE_DURATION: f32 = 50e-9;
The duration to assert IO_Update for the pounder DDS.
+pub const POUNDER_QSPI_FREQUENCY: MegaHertz;
The QSPI frequency for communicating with the pounder DDS.
+pub const SYSCLK: MegaHertz;
The system clock, used in various timer calculations
+pub const TIMER_FREQUENCY: MegaHertz;
The optimal counting frequency of the hardware timers used for timestamping and sampling.
+pub const TIMER_PERIOD: f32 = _; // 9.99999993E-9f32
pub type SampleBuffer = [u16; 32];
Module for all hardware-specific setup of Stabilizer
+pub use embedded_hal;
pub use stm32h7xx_hal as hal;
Digital Input 0 (DI0) reference clock timestamper
+This module provides a means of timestamping the rising edges of an external reference clock on +the DI0 with a timer value from TIM5.
+An input capture channel is configured on DI0 and fed into TIM5’s capture channel 4. TIM5 is +then run in a free-running mode with a configured tick rate (PSC) and maximum count value +(ARR). Whenever an edge on DI0 triggers, the current TIM5 counter value is captured and +recorded as a timestamp. This timestamp can be either directly read from the timer channel or +can be collected asynchronously via DMA collection.
+To prevent silently discarding timestamps, the TIM5 input capture over-capture flag is +continually checked. Any over-capture event (which indicates an overwritten timestamp) then +triggers a panic to indicate the dropped timestamp so that design parameters can be adjusted.
+It appears that DMA transfers can take a significant amount of time to disable (400ns) if they +are being prematurely stopped (such is the case here). As such, for a sample batch size of 1, +this can take up a significant amount of the total available processing time for the samples. +This module checks for any captured timestamps from the timer capture channel manually. In +this mode, the maximum input clock frequency supported is dependant on the sampling rate and +batch size.
+This module only supports DI0 for timestamping due to trigger constraints on the DIx pins. If +timestamping is desired in DI1, a separate timer + capture channel will be necessary.
+pub struct InputStamper { /* private fields */ }
The timestamper for DI0 reference clock inputs.
+Construct the DI0 input timestamper.
+trigger
- The capture trigger input pin.Get the latest timestamp that has occurred.
+This function must be called at least as often as timestamps arrive.
+If an over-capture event occurs, this function will clear the overflow,
+and return a new timestamp of unknown recency an Err()
.
+Note that this indicates at least one timestamp was inadvertently dropped.
pub trait AttenuatorInterface {
+ // Required methods
+ fn reset_attenuators(&mut self) -> Result<(), Error>;
+ fn latch_attenuator(&mut self, channel: Channel) -> Result<(), Error>;
+ fn transfer_attenuators(
+ &mut self,
+ channels: &mut [u8; 4]
+ ) -> Result<(), Error>;
+
+ // Provided methods
+ fn set_attenuation(
+ &mut self,
+ channel: Channel,
+ attenuation: f32
+ ) -> Result<f32, Error> { ... }
+ fn get_attenuation(&mut self, channel: Channel) -> Result<f32, Error> { ... }
+}
Provide an interface for managing digital attenuators on Pounder hardware.
+Note: The digital attenuators do not allow read-back of attenuation. To circumvent this, this +driver maintains the attenuation code in both the shift register as well as the latched output +register of the attenuators. This allows the “active” attenuation code to be read back by +reading the shfit register. The downside of this approach is that any read is destructive, so a +read-writeback approach is employed.
+Set the attenuation of a single channel.
+Args:
+channel
- The pounder channel to configure the attenuation of.attenuation
- The desired attenuation of the channel in dB. This has a resolution of
+0.5dB.The DdsOutput is used as an output stream to the pounder DDS.
+The DDS stream interface is a means of quickly updating pounder DDS (direct digital synthesis) +outputs of the AD9959 DDS chip. The DDS communicates via a quad-SPI interface and a single +IO-update output pin.
+In order to update the DDS interface, the frequency tuning word, amplitude control word, and +the phase offset word for a channel can be modified to change the frequency, amplitude, or +phase on any of the 4 available output channels. Changes do not propagate to DDS outputs until +the IO-update pin is toggled high to activate the new configurations. This allows multiple +channels or parameters to be updated and then effects can take place simultaneously.
+In this implementation, the phase, frequency, or amplitude can be updated for any single +collection of outputs simultaneously. This is done by serializing the register writes to the +DDS into a single buffer of data and then writing the data over QSPI to the DDS.
+In order to minimize software overhead, data is written directly into the QSPI output FIFO. In +order to accomplish this most efficiently, serialized data is written as 32-bit words to +minimize the number of bus cycles necessary to write to the peripheral FIFO. A consequence of +this is that additional unneeded register writes may be appended to align a transfer to 32-bit +word sizes.
+In order to pulse the IO-update signal, the high-resolution timer output is used. The timer is +configured to assert the IO-update signal after a predefined delay and then de-assert the +signal after a predefined assertion duration. This allows for the actual QSPI transfer and +IO-update toggle to be completed asynchronously to the rest of software processing - that is, +software can schedule the DDS updates and then continue data processing. DDS updates then take +place in the future when the IO-update is toggled by hardware.
+The QSPI output FIFO is used as an intermediate buffer for holding pending QSPI writes. Because +of this, the implementation only supports up to 16 serialized bytes (the QSPI FIFO is 8 32-bit +words, or 32 bytes, wide) in a single update.
+There is currently no synchronization between completion of the QSPI data write and the +IO-update signal. It is currently assumed that the QSPI transfer will always complete within a +predefined delay (the pre-programmed IO-update timer delay).
+In the future, it would be possible to utilize a DMA transfer to complete the QSPI transfer. +Once the QSPI transfer completed, this could trigger the IO-update timer to start to +asynchronously complete IO-update automatically. This would allow for arbitrary profile sizes +and ensure that IO-update was in-sync with the QSPI transfer.
+Currently, serialization is performed on each processing cycle. If there is a +compile-time-known register update sequence needed for the application, the serialization +process can be done once and then register values can be written into a pre-computed serialized +buffer to avoid the software overhead of much of the serialization process.
+pub struct DdsOutput { /* private fields */ }
The DDS profile update stream.
+Construct a new DDS output stream.
+It is assumed that the QSPI stream and the IO_Update trigger timer have been configured in a +way such that the profile has sufficient time to be written before the IO_Update signal is +generated.
+qspi
- The QSPI interface to the run the stream on.io_update_trigger
- The HighResTimerE used to generate IO_Update pulses.config
- The frozen DDS configuration.Get a builder for serializing a Pounder DDS profile.
+pub struct ProfileBuilder<'a> { /* private fields */ }
A temporary builder for serializing and writing profiles.
+Update a number of channels with the provided configuration
+channels
- A list of channels to apply the configuration to.ftw
- If provided, indicates a frequency tuning word for the channels.pow
- If provided, indicates a phase offset word for the channels.acr
- If provided, indicates the amplitude control register for the channels. The
+24-bits of the ACR should be stored in the last 3 LSB.pub enum Channel {
+ In0,
+ Out0,
+ In1,
+ Out1,
+}
The numerical value (discriminant) of the Channel enum is the index in the attenuator shift +register as well as the attenuator latch enable signal index on the GPIO extender.
+pub enum Error {
+ Spi,
+ I2c,
+ Qspi(QspiError),
+ Bounds,
+ InvalidAddress,
+ InvalidChannel,
+ Adc,
+ InvalidState,
+}
pub enum GpioPin {
+Show 13 variants
Led4Green,
+ Led5Red,
+ Led6Green,
+ Led7Red,
+ Led8Green,
+ Led9Red,
+ AttLe0,
+ AttLe1,
+ AttLe2,
+ AttLe3,
+ AttRstN,
+ OscEnN,
+ ExtClkSel,
+}
pub enum Channel {
+ One,
+ Two,
+}
A HRTimer output channel.
+pub struct HighResTimerE { /* private fields */ }
The high resolution timer. Currently, only Timer E is supported.
+Construct a new high resolution timer for generating IO_update signals.
+Configure the timer to operate in single-shot mode.
+This will configure the timer to generate a single pulse on an output channel. The timer
+will only count up once and must be trigger()
’d after / configured.
The output will be asserted from set_offset
to set_offset
+ set_duration
in the count.
channel
- The timer output channel to configure.set_duration
- The duration that the output should be asserted for.set_offset
- The first time at which the output should be asserted.pub trait PowerMeasurementInterface {
+ // Required method
+ fn sample_converter(&mut self, channel: Channel) -> Result<f32, Error>;
+
+ // Provided method
+ fn measure_power(&mut self, channel: Channel) -> Result<f32, Error> { ... }
+}
Provide an interface to measure RF input power in dBm.
+pub struct ChannelState {
+ pub parameters: DdsChannelState,
+ pub attenuation: f32,
+}
parameters: DdsChannelState
§attenuation: f32
source
. Read morepub struct DdsChannelState {
+ pub phase_offset: f32,
+ pub frequency: f32,
+ pub amplitude: f32,
+ pub enabled: bool,
+}
phase_offset: f32
§frequency: f32
§amplitude: f32
§enabled: bool
source
. Read morepub struct DdsClockConfig {
+ pub multiplier: u8,
+ pub reference_clock: f32,
+ pub external_clock: bool,
+}
multiplier: u8
§reference_clock: f32
§external_clock: bool
source
. Read morepub struct InputChannelState {
+ pub attenuation: f32,
+ pub power: f32,
+ pub mixer: DdsChannelState,
+}
attenuation: f32
§power: f32
§mixer: DdsChannelState
source
. Read morepub struct OutputChannelState {
+ pub attenuation: f32,
+ pub channel: DdsChannelState,
+}
attenuation: f32
§channel: DdsChannelState
source
. Read morepub struct PounderDevices {
+ pub lm75: Lm75<I2c1Proxy, Lm75>,
+ /* private fields */
+}
A structure containing implementation for Pounder hardware.
+lm75: Lm75<I2c1Proxy, Lm75>
Construct and initialize pounder-specific hardware.
+Args:
+lm75
- The temperature sensor on Pounder.mcp23017
- The GPIO expander on Pounder.attenuator_spi
- A SPI interface to control digital attenuators.pwr0
- The ADC channel to measure the IN0 input power.pwr1
- The ADC channel to measure the IN1 input power.aux_adc0
- The ADC channel to measure the ADC0 auxiliary input.aux_adc1
- The ADC channel to measure the ADC1 auxiliary input.Sample one of the two auxiliary ADC channels associated with the respective RF input channel.
+Reset all of the attenuators to a power-on default state.
+Latch a configuration into a digital attenuator.
+Args:
+channel
- The attenuator channel to latch.Read the raw attenuation codes stored in the attenuator shift registers.
+Args:
+channels
- A 4 byte slice to be shifted into the
+attenuators and to contain the data shifted out.pub struct QspiInterface {
+ pub qspi: Qspi<QUADSPI>,
+ /* private fields */
+}
A structure for the QSPI interface for the DDS.
+qspi: Qspi<QUADSPI>
Configure the operations mode of the interface.
+Args:
+mode
- The newly desired operational mode.Write data over QSPI to the DDS.
+Args:
+addr
- The address to write over QSPI to the DDS.data
- The data to write.ADC sample timestamper using external Pounder reference clock.
+The pounder timestamper utilizes the pounder SYNC_CLK output as a fast external reference clock +for recording a timestamp for each of the ADC samples.
+To accomplish this, a timer peripheral is configured to be driven by an external clock input. +Due to the limitations of clock frequencies allowed by the timer peripheral, the SYNC_CLK input +is divided by 4. This clock then clocks the timer peripheral in a free-running mode with an ARR +(max count register value) configured to overflow once per ADC sample batch.
+Once the timer is configured, an input capture is configured to record the timer count +register. The input capture is configured to utilize an internal trigger for the input capture. +The internal trigger is selected such that when a sample is generated on ADC0, the input +capture is simultaneously triggered. That trigger is prescaled (its rate is divided) by the +batch size. This results in the input capture triggering identically to when the ADC samples +the last sample of the batch. That sample is then available for processing by the user.
+pub struct Timestamper { /* private fields */ }
Software unit to timestamp stabilizer ADC samples using an external pounder reference clock.
+Construct the pounder sample timestamper.
+timestamp_timer
- The timer peripheral used for capturing timestamps from.capture_channel
- The input capture channel for collecting timestamps.sampling_timer
- The stabilizer ADC sampling timer._clock_input
- The input pin for the external clock from Pounder.batch_size
- The number of samples in each batch.The new pounder timestamper in an operational state.
+Update the period of the underlying timestamp timer.
+pub struct OutputBuffer { /* private fields */ }
pub struct SerialTerminal { /* private fields */ }
pub fn setup(
+ core: CorePeripherals,
+ device: Peripherals,
+ clock: SystemTimer,
+ batch_size: usize,
+ sample_ticks: u32
+) -> (StabilizerDevices, Option<PounderDevices>)
Configure the stabilizer hardware for operation.
+Refer to design_parameters::TIMER_FREQUENCY to determine the frequency of the sampling timer.
+core
- The cortex-m peripherals.device
- The microcontroller peripherals to be configured.clock
- A SystemTimer
implementing Clock
.batch_size
- The size of each ADC/DAC batch.sample_ticks
- The number of timer ticks between each sample.(stabilizer, pounder) where stabilizer
is a StabilizerDevices
structure containing all
+stabilizer hardware interfaces in a disabled state. pounder
is an Option
containing
+Some(devices)
if pounder is detected, where devices
is a PounderDevices
structure
+containing all of the pounder hardware interfaces in a disabled state.
Stabilizer hardware configuration
+This file contains all of the hardware-specific configuration of Stabilizer.
+pub struct EemGpioDevices {
+ pub lvds4: EemDigitalInput0,
+ pub lvds5: EemDigitalInput1,
+ pub lvds6: EemDigitalOutput0,
+ pub lvds7: EemDigitalOutput1,
+}
The GPIO pins available on the EEM connector, if Pounder is not present.
+lvds4: EemDigitalInput0
§lvds5: EemDigitalInput1
§lvds6: EemDigitalOutput0
§lvds7: EemDigitalOutput1
pub struct NetStorage {
+ pub ip_addrs: [IpCidr; 1],
+ pub sockets: [SocketStorage<'static>; 7],
+ pub tcp_socket_storage: [TcpSocketStorage; 4],
+ pub udp_socket_storage: [UdpSocketStorage; 1],
+ pub dns_storage: [Option<DnsQuery>; 1],
+}
ip_addrs: [IpCidr; 1]
§sockets: [SocketStorage<'static>; 7]
§tcp_socket_storage: [TcpSocketStorage; 4]
§udp_socket_storage: [UdpSocketStorage; 1]
§dns_storage: [Option<DnsQuery>; 1]
pub struct NetworkDevices {
+ pub stack: NetworkStack,
+ pub phy: EthernetPhy,
+ pub mac_address: EthernetAddress,
+}
The available networking devices on Stabilizer.
+stack: NetworkStack
§phy: EthernetPhy
§mac_address: EthernetAddress
pub struct PounderDevices {
+ pub pounder: PounderDevices,
+ pub dds_output: DdsOutput,
+ pub timestamper: Timestamper,
+}
The available Pounder-specific hardware interfaces.
+pounder: PounderDevices
§dds_output: DdsOutput
§timestamper: Timestamper
pub struct StabilizerDevices {
+ pub systick: Systick,
+ pub temperature_sensor: CpuTempSensor,
+ pub afes: (AFE0, AFE1),
+ pub adcs: (Adc0Input, Adc1Input),
+ pub dacs: (Dac0Output, Dac1Output),
+ pub timestamper: InputStamper,
+ pub adc_dac_timer: SamplingTimer,
+ pub timestamp_timer: TimestampTimer,
+ pub net: NetworkDevices,
+ pub digital_inputs: (DigitalInput0, DigitalInput1),
+ pub eem_gpio: EemGpioDevices,
+ pub usb_serial: SerialTerminal,
+}
The available hardware interfaces on Stabilizer.
+systick: Systick
§temperature_sensor: CpuTempSensor
§afes: (AFE0, AFE1)
§adcs: (Adc0Input, Adc1Input)
§dacs: (Dac0Output, Dac1Output)
§timestamper: InputStamper
§adc_dac_timer: SamplingTimer
§timestamp_timer: TimestampTimer
§net: NetworkDevices
§digital_inputs: (DigitalInput0, DigitalInput1)
§eem_gpio: EemGpioDevices
§usb_serial: SerialTerminal
pub struct TcpSocketStorage { /* private fields */ }
source
. Read morepub struct UdpSocketStorage { /* private fields */ }
source
. Read morepub enum AdcError {
+ InUse,
+}
Indicates that the ADC is already in use
+pub struct AdcChannel<'a, Adc, PIN> { /* private fields */ }
A single channel on an ADC peripheral.
+pub struct SharedAdc<Adc> { /* private fields */ }
An ADC peripheral that can provide ownership of individual channels for sharing between +drivers.
+Construct a new shared ADC driver.
+slope
- The slope of the ADC conversion transfer function.adc
- The ADC peripheral to share.Allocate an ADC channel for usage.
+pin
- The ADC input associated with the desired ADC channel. Often, this is a GPIO pin.An instantiated AdcChannel whose ownership can be transferred to other drivers.
+pub enum Error {
+ InvalidAmplitude,
+ InvalidSymmetry,
+ InvalidFrequency,
+}
Represents the errors that can occur when attempting to configure the signal generator.
+The provided amplitude is out-of-range.
+The provided symmetry is out of range.
+The provided frequency is out of range.
+pub enum Signal {
+ Cosine,
+ Square,
+ Triangle,
+ WhiteNoise,
+}
Types of signals that can be generated.
+pub struct BasicConfig {
+ pub signal: Signal,
+ pub frequency: f32,
+ pub symmetry: f32,
+ pub amplitude: f32,
+ pub phase: f32,
+}
Basic configuration for a generated signal.
+{"signal": <signal>, "frequency", 1000.0, "symmetry": 0.5, "amplitude": 1.0}
Where <signal>
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.
signal: Signal
The signal type that should be generated. See Signal variants.
+frequency: f32
The frequency of the generated signal in Hertz.
+symmetry: f32
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.
+amplitude: f32
The amplitude of the output signal in volts.
+phase: f32
The phase of the output signal in turns.
+source
. Read moreindices
. Read morepub struct Config {
+ pub signal: Signal,
+ pub amplitude: i16,
+ pub phase_increment: [i32; 2],
+ pub phase_offset: i32,
+}
signal: Signal
The type of signal being generated
+amplitude: i16
The full-scale output code of the signal
+phase_increment: [i32; 2]
The frequency tuning word of the signal. Phase is incremented by this amount
+phase_offset: i32
The phase offset
+pub struct SignalGenerator { /* private fields */ }
Update waveform generation settings.
+Clear the phase accumulator.
+iter_next_chunk
)N
values. Read moreiter_advance_by
)n
elements. Read moren
th element of the iterator. Read moreiter_intersperse
)separator
between adjacent
+items of the original iterator. Read moreiter_intersperse
)separator
+between adjacent items of the original iterator. Read moren
elements. Read moren
elements, or fewer
+if the underlying iterator ends sooner. Read moreiter_map_windows
)f
for each contiguous window of size N
over
+self
and returns an iterator over the outputs of f
. Like slice::windows()
,
+the windows during mapping overlap as well. Read moreiter_collect_into
)iter_is_partitioned
)true
precede all those that return false
. Read moreiterator_try_reduce
)try_find
)iter_array_chunks
)N
elements of the iterator at a time. Read moreiter_order_by
)Iterator
with those
+of another with respect to the specified comparison function. Read morePartialOrd
elements of
+this Iterator
with those of another. The comparison works like short-circuit
+evaluation, returning a result without comparing the remaining elements.
+As soon as an order can be determined, the evaluation stops and a result is returned. Read moreiter_order_by
)Iterator
with those
+of another with respect to the specified comparison function. Read moreiter_order_by
)Iterator
are lexicographically
+less than those of another. Read moreIterator
are lexicographically
+less or equal to those of another. Read moreIterator
are lexicographically
+greater than those of another. Read moreIterator
are lexicographically
+greater than or equal to those of another. Read moreis_sorted
)is_sorted
)is_sorted
)pub enum InputFilter {
+ Div1N1,
+ Div1N8,
+}
Optional input capture preconditioning filter configurations.
+#[repr(u8)]pub enum Prescaler {
+ Div1,
+ Div2,
+ Div4,
+ Div8,
+}
Prescalers for externally-supplied reference clocks.
+pub enum SlaveMode {
+ Disabled,
+ Trigger,
+}
Optional slave operation modes of a timer.
+pub enum TriggerGenerator {
+ Reset,
+ Enable,
+ Update,
+ ComparePulse,
+ Ch1Compare,
+ Ch2Compare,
+ Ch3Compare,
+ Ch4Compare,
+}
The event that should generate an external trigger from the peripheral.
+pub enum TriggerSource {
+ Trigger0,
+ Trigger1,
+ Trigger2,
+ Trigger3,
+}
Selects the trigger source for the timer peripheral.
+The sampling timer is used for managing ADC sampling and external reference timestamping.
+pub struct PounderTimestampTimer { /* private fields */ }
The timer used for managing ADC sampling.
+Get the timer update event.
+Get the period of the timer.
+Manually set the period of the timer.
+Configure the timer peripheral to generate a trigger based on the provided +source.
+Select a trigger source for the timer peripheral.
+pub struct SamplingTimer { /* private fields */ }
The timer used for managing ADC sampling.
+Get the timer update event.
+Get the period of the timer.
+Manually set the period of the timer.
+Configure the timer peripheral to generate a trigger based on the provided +source.
+Select a trigger source for the timer peripheral.
+pub struct ShadowSamplingTimer { /* private fields */ }
The timer used for managing ADC sampling.
+Get the timer update event.
+Get the period of the timer.
+Manually set the period of the timer.
+Configure the timer peripheral to generate a trigger based on the provided +source.
+Select a trigger source for the timer peripheral.
+pub struct TimestampTimer { /* private fields */ }
The timer used for managing ADC sampling.
+Get the timer update event.
+Get the period of the timer.
+Manually set the period of the timer.
+Configure the timer peripheral to generate a trigger based on the provided +source.
+Select a trigger source for the timer peripheral.
+#[repr(u8)]pub enum CaptureSource1 {
+ Ti1,
+ Ti2,
+ Trc,
+}
Capture/Compare 1 selection
+Value on reset: 0
+1: CC1 channel is configured as input, IC1 is mapped on TI1
+2: CC1 channel is configured as input, IC1 is mapped on TI2
+3: CC1 channel is configured as input, IC1 is mapped on TRC
+#[repr(u8)]pub enum CaptureSource2 {
+ Ti2,
+ Ti1,
+ Trc,
+}
Capture/Compare 2 selection
+Value on reset: 0
+1: CC2 channel is configured as input, IC2 is mapped on TI2
+2: CC2 channel is configured as input, IC2 is mapped on TI1
+3: CC2 channel is configured as input, IC2 is mapped on TRC
+#[repr(u8)]pub enum CaptureSource3 {
+ Ti3,
+ Ti4,
+ Trc,
+}
Capture/compare 3 selection
+Value on reset: 0
+1: CC3 channel is configured as input, IC3 is mapped on TI3
+2: CC3 channel is configured as input, IC3 is mapped on TI4
+3: CC3 channel is configured as input, IC3 is mapped on TRC
+#[repr(u8)]pub enum CaptureSource4 {
+ Ti4,
+ Ti3,
+ Trc,
+}
Capture/Compare 4 selection
+Value on reset: 0
+1: CC4 channel is configured as input, IC4 is mapped on TI4
+2: CC4 channel is configured as input, IC4 is mapped on TI3
+3: CC4 channel is configured as input, IC4 is mapped on TRC
+pub struct Channel1 {}
A capture/compare channel of the timer.
+Allow the channel to generate DMA requests.
+Operate the channel as an output-compare.
+value
- The value to compare the sampling timer’s counter against.Operate the channel in input-capture mode.
+input
- The input source for the input capture event.pub struct Channel1InputCapture {}
A capture channel of the timer.
+Get the latest capture from the channel.
+Allow the channel to generate DMA requests.
+Check if an over-capture event has occurred.
+Configure the input capture input pre-filter.
+filter
- The desired input filter stage configuration. Defaults to disabled.pub struct Channel2 {}
A capture/compare channel of the timer.
+Allow the channel to generate DMA requests.
+Operate the channel as an output-compare.
+value
- The value to compare the sampling timer’s counter against.Operate the channel in input-capture mode.
+input
- The input source for the input capture event.pub struct Channel2InputCapture {}
A capture channel of the timer.
+Get the latest capture from the channel.
+Allow the channel to generate DMA requests.
+Check if an over-capture event has occurred.
+Configure the input capture input pre-filter.
+filter
- The desired input filter stage configuration. Defaults to disabled.pub struct Channel3 {}
A capture/compare channel of the timer.
+Allow the channel to generate DMA requests.
+Operate the channel as an output-compare.
+value
- The value to compare the sampling timer’s counter against.Operate the channel in input-capture mode.
+input
- The input source for the input capture event.pub struct Channel3InputCapture {}
A capture channel of the timer.
+Get the latest capture from the channel.
+Allow the channel to generate DMA requests.
+Check if an over-capture event has occurred.
+Configure the input capture input pre-filter.
+filter
- The desired input filter stage configuration. Defaults to disabled.pub struct Channel4 {}
A capture/compare channel of the timer.
+Allow the channel to generate DMA requests.
+Operate the channel as an output-compare.
+value
- The value to compare the sampling timer’s counter against.Operate the channel in input-capture mode.
+input
- The input source for the input capture event.pub struct Channel4InputCapture {}
A capture channel of the timer.
+Get the latest capture from the channel.
+Allow the channel to generate DMA requests.
+Check if an over-capture event has occurred.
+Configure the input capture input pre-filter.
+filter
- The desired input filter stage configuration. Defaults to disabled.pub struct Channels {
+ pub ch1: Channel1,
+ pub ch2: Channel2,
+ pub ch3: Channel3,
+ pub ch4: Channel4,
+}
The channels representing the timer.
+ch1: Channel1
§ch2: Channel2
§ch3: Channel3
§ch4: Channel4
pub struct UpdateEvent {}
#[repr(u8)]pub enum CaptureSource1 {
+ Ti1,
+ Ti2,
+ Trc,
+}
Capture/Compare 1 selection
+Value on reset: 0
+1: CC1 channel is configured as input, IC1 is mapped on TI1
+2: CC1 channel is configured as input, IC1 is mapped on TI2
+3: CC1 channel is configured as input, IC1 is mapped on TRC
+#[repr(u8)]pub enum CaptureSource2 {
+ Ti2,
+ Ti1,
+ Trc,
+}
Capture/Compare 2 selection
+Value on reset: 0
+1: CC2 channel is configured as input, IC2 is mapped on TI2
+2: CC2 channel is configured as input, IC2 is mapped on TI1
+3: CC2 channel is configured as input, IC2 is mapped on TRC
+#[repr(u8)]pub enum CaptureSource3 {
+ Ti3,
+ Ti4,
+ Trc,
+}
Capture/compare 3 selection
+Value on reset: 0
+1: CC3 channel is configured as input, IC3 is mapped on TI3
+2: CC3 channel is configured as input, IC3 is mapped on TI4
+3: CC3 channel is configured as input, IC3 is mapped on TRC
+#[repr(u8)]pub enum CaptureSource4 {
+ Ti4,
+ Ti3,
+ Trc,
+}
Capture/Compare 4 selection
+Value on reset: 0
+1: CC4 channel is configured as input, IC4 is mapped on TI4
+2: CC4 channel is configured as input, IC4 is mapped on TI3
+3: CC4 channel is configured as input, IC4 is mapped on TRC
+pub struct Channel1 {}
A capture/compare channel of the timer.
+Allow the channel to generate DMA requests.
+Operate the channel as an output-compare.
+value
- The value to compare the sampling timer’s counter against.Operate the channel in input-capture mode.
+input
- The input source for the input capture event.pub struct Channel1InputCapture {}
A capture channel of the timer.
+Get the latest capture from the channel.
+Allow the channel to generate DMA requests.
+Check if an over-capture event has occurred.
+Configure the input capture input pre-filter.
+filter
- The desired input filter stage configuration. Defaults to disabled.pub struct Channel2 {}
A capture/compare channel of the timer.
+Allow the channel to generate DMA requests.
+Operate the channel as an output-compare.
+value
- The value to compare the sampling timer’s counter against.Operate the channel in input-capture mode.
+input
- The input source for the input capture event.pub struct Channel2InputCapture {}
A capture channel of the timer.
+Get the latest capture from the channel.
+Allow the channel to generate DMA requests.
+Check if an over-capture event has occurred.
+Configure the input capture input pre-filter.
+filter
- The desired input filter stage configuration. Defaults to disabled.pub struct Channel3 {}
A capture/compare channel of the timer.
+Allow the channel to generate DMA requests.
+Operate the channel as an output-compare.
+value
- The value to compare the sampling timer’s counter against.Operate the channel in input-capture mode.
+input
- The input source for the input capture event.pub struct Channel3InputCapture {}
A capture channel of the timer.
+Get the latest capture from the channel.
+Allow the channel to generate DMA requests.
+Check if an over-capture event has occurred.
+Configure the input capture input pre-filter.
+filter
- The desired input filter stage configuration. Defaults to disabled.pub struct Channel4 {}
A capture/compare channel of the timer.
+Allow the channel to generate DMA requests.
+Operate the channel as an output-compare.
+value
- The value to compare the sampling timer’s counter against.Operate the channel in input-capture mode.
+input
- The input source for the input capture event.pub struct Channel4InputCapture {}
A capture channel of the timer.
+Get the latest capture from the channel.
+Allow the channel to generate DMA requests.
+Check if an over-capture event has occurred.
+Configure the input capture input pre-filter.
+filter
- The desired input filter stage configuration. Defaults to disabled.pub struct Channels {
+ pub ch1: Channel1,
+ pub ch2: Channel2,
+ pub ch3: Channel3,
+ pub ch4: Channel4,
+}
The channels representing the timer.
+ch1: Channel1
§ch2: Channel2
§ch3: Channel3
§ch4: Channel4
pub struct UpdateEvent {}
#[repr(u8)]pub enum CaptureSource1 {
+ Ti1,
+ Ti2,
+ Trc,
+}
Capture/Compare 1 selection
+Value on reset: 0
+1: CC1 channel is configured as input, IC1 is mapped on TI1
+2: CC1 channel is configured as input, IC1 is mapped on TI2
+3: CC1 channel is configured as input, IC1 is mapped on TRC
+#[repr(u8)]pub enum CaptureSource2 {
+ Ti2,
+ Ti1,
+ Trc,
+}
Capture/Compare 2 selection
+Value on reset: 0
+1: CC2 channel is configured as input, IC2 is mapped on TI2
+2: CC2 channel is configured as input, IC2 is mapped on TI1
+3: CC2 channel is configured as input, IC2 is mapped on TRC
+#[repr(u8)]pub enum CaptureSource3 {
+ Ti3,
+ Ti4,
+ Trc,
+}
Capture/compare 3 selection
+Value on reset: 0
+1: CC3 channel is configured as input, IC3 is mapped on TI3
+2: CC3 channel is configured as input, IC3 is mapped on TI4
+3: CC3 channel is configured as input, IC3 is mapped on TRC
+#[repr(u8)]pub enum CaptureSource4 {
+ Ti4,
+ Ti3,
+ Trc,
+}
Capture/Compare 4 selection
+Value on reset: 0
+1: CC4 channel is configured as input, IC4 is mapped on TI4
+2: CC4 channel is configured as input, IC4 is mapped on TI3
+3: CC4 channel is configured as input, IC4 is mapped on TRC
+pub struct Channel1 {}
A capture/compare channel of the timer.
+Allow the channel to generate DMA requests.
+Operate the channel as an output-compare.
+value
- The value to compare the sampling timer’s counter against.Operate the channel in input-capture mode.
+input
- The input source for the input capture event.pub struct Channel1InputCapture {}
A capture channel of the timer.
+Get the latest capture from the channel.
+Allow the channel to generate DMA requests.
+Check if an over-capture event has occurred.
+Configure the input capture input pre-filter.
+filter
- The desired input filter stage configuration. Defaults to disabled.pub struct Channel2 {}
A capture/compare channel of the timer.
+Allow the channel to generate DMA requests.
+Operate the channel as an output-compare.
+value
- The value to compare the sampling timer’s counter against.Operate the channel in input-capture mode.
+input
- The input source for the input capture event.pub struct Channel2InputCapture {}
A capture channel of the timer.
+Get the latest capture from the channel.
+Allow the channel to generate DMA requests.
+Check if an over-capture event has occurred.
+Configure the input capture input pre-filter.
+filter
- The desired input filter stage configuration. Defaults to disabled.pub struct Channel3 {}
A capture/compare channel of the timer.
+Allow the channel to generate DMA requests.
+Operate the channel as an output-compare.
+value
- The value to compare the sampling timer’s counter against.Operate the channel in input-capture mode.
+input
- The input source for the input capture event.pub struct Channel3InputCapture {}
A capture channel of the timer.
+Get the latest capture from the channel.
+Allow the channel to generate DMA requests.
+Check if an over-capture event has occurred.
+Configure the input capture input pre-filter.
+filter
- The desired input filter stage configuration. Defaults to disabled.pub struct Channel4 {}
A capture/compare channel of the timer.
+Allow the channel to generate DMA requests.
+Operate the channel as an output-compare.
+value
- The value to compare the sampling timer’s counter against.Operate the channel in input-capture mode.
+input
- The input source for the input capture event.pub struct Channel4InputCapture {}
A capture channel of the timer.
+Get the latest capture from the channel.
+Allow the channel to generate DMA requests.
+Check if an over-capture event has occurred.
+Configure the input capture input pre-filter.
+filter
- The desired input filter stage configuration. Defaults to disabled.pub struct Channels {
+ pub ch1: Channel1,
+ pub ch2: Channel2,
+ pub ch3: Channel3,
+ pub ch4: Channel4,
+}
The channels representing the timer.
+ch1: Channel1
§ch2: Channel2
§ch3: Channel3
§ch4: Channel4
pub struct UpdateEvent {}
#[repr(u8)]pub enum CaptureSource1 {
+ Ti1,
+ Ti2,
+ Trc,
+}
Capture/Compare 1 selection
+Value on reset: 0
+1: CC1 channel is configured as input, IC1 is mapped on TI1
+2: CC1 channel is configured as input, IC1 is mapped on TI2
+3: CC1 channel is configured as input, IC1 is mapped on TRC
+#[repr(u8)]pub enum CaptureSource2 {
+ Ti2,
+ Ti1,
+ Trc,
+}
Capture/Compare 2 selection
+Value on reset: 0
+1: CC2 channel is configured as input, IC2 is mapped on TI2
+2: CC2 channel is configured as input, IC2 is mapped on TI1
+3: CC2 channel is configured as input, IC2 is mapped on TRC
+#[repr(u8)]pub enum CaptureSource3 {
+ Ti3,
+ Ti4,
+ Trc,
+}
Capture/compare 3 selection
+Value on reset: 0
+1: CC3 channel is configured as input, IC3 is mapped on TI3
+2: CC3 channel is configured as input, IC3 is mapped on TI4
+3: CC3 channel is configured as input, IC3 is mapped on TRC
+#[repr(u8)]pub enum CaptureSource4 {
+ Ti4,
+ Ti3,
+ Trc,
+}
Capture/Compare 4 selection
+Value on reset: 0
+1: CC4 channel is configured as input, IC4 is mapped on TI4
+2: CC4 channel is configured as input, IC4 is mapped on TI3
+3: CC4 channel is configured as input, IC4 is mapped on TRC
+pub struct Channel1 {}
A capture/compare channel of the timer.
+Allow the channel to generate DMA requests.
+Operate the channel as an output-compare.
+value
- The value to compare the sampling timer’s counter against.Operate the channel in input-capture mode.
+input
- The input source for the input capture event.pub struct Channel1InputCapture {}
A capture channel of the timer.
+Get the latest capture from the channel.
+Allow the channel to generate DMA requests.
+Check if an over-capture event has occurred.
+Configure the input capture input pre-filter.
+filter
- The desired input filter stage configuration. Defaults to disabled.pub struct Channel2 {}
A capture/compare channel of the timer.
+Allow the channel to generate DMA requests.
+Operate the channel as an output-compare.
+value
- The value to compare the sampling timer’s counter against.Operate the channel in input-capture mode.
+input
- The input source for the input capture event.pub struct Channel2InputCapture {}
A capture channel of the timer.
+Get the latest capture from the channel.
+Allow the channel to generate DMA requests.
+Check if an over-capture event has occurred.
+Configure the input capture input pre-filter.
+filter
- The desired input filter stage configuration. Defaults to disabled.pub struct Channel3 {}
A capture/compare channel of the timer.
+Allow the channel to generate DMA requests.
+Operate the channel as an output-compare.
+value
- The value to compare the sampling timer’s counter against.Operate the channel in input-capture mode.
+input
- The input source for the input capture event.pub struct Channel3InputCapture {}
A capture channel of the timer.
+Get the latest capture from the channel.
+Allow the channel to generate DMA requests.
+Check if an over-capture event has occurred.
+Configure the input capture input pre-filter.
+filter
- The desired input filter stage configuration. Defaults to disabled.pub struct Channel4 {}
A capture/compare channel of the timer.
+Allow the channel to generate DMA requests.
+Operate the channel as an output-compare.
+value
- The value to compare the sampling timer’s counter against.Operate the channel in input-capture mode.
+input
- The input source for the input capture event.pub struct Channel4InputCapture {}
A capture channel of the timer.
+Get the latest capture from the channel.
+Allow the channel to generate DMA requests.
+Check if an over-capture event has occurred.
+Configure the input capture input pre-filter.
+filter
- The desired input filter stage configuration. Defaults to disabled.pub struct Channels {
+ pub ch1: Channel1,
+ pub ch2: Channel2,
+ pub ch3: Channel3,
+ pub ch4: Channel4,
+}
The channels representing the timer.
+ch1: Channel1
§ch2: Channel2
§ch3: Channel3
§ch4: Channel4
pub struct UpdateEvent {}
pub type AFE0 = ProgrammableGainAmplifier<PF2<Output<PushPull>>, PF5<Output<PushPull>>>;
pub type AFE1 = ProgrammableGainAmplifier<PD14<Output<PushPull>>, PD15<Output<PushPull>>>;
pub type DigitalInput0 = PG9<Input>;
pub type DigitalInput1 = PC15<Input>;
pub type EemDigitalInput0 = PD1<Input>;
pub type EemDigitalInput1 = PD2<Input>;
pub type EemDigitalOutput0 = PD3<Output>;
pub type EemDigitalOutput1 = PD4<Output>;
pub type EthernetPhy = LAN8742A<EthernetMAC>;
pub type I2c1 = I2c<I2C1>;
pub type I2c1Proxy = I2cProxy<'static, AtomicCheckMutex<I2c1>>;
pub type NetworkManager = NetworkManager<'static, EthernetDMA<TX_DESRING_CNT, RX_DESRING_CNT>, SystemTimer>;
pub type NetworkStack = NetworkStack<'static, EthernetDMA<TX_DESRING_CNT, RX_DESRING_CNT>, SystemTimer>;
pub type SystemTimer = MonoClock<u32, MONOTONIC_FREQUENCY>;
pub type Systick = Systick<MONOTONIC_FREQUENCY>;
pub type UsbBus = UsbBus<USB2>;
#[repr(u8)]pub enum StreamFormat {
+ Unknown,
+ AdcDacData,
+ Fls,
+}
Specifies the format of streamed data
+Reserved, unused format specifier.
+Streamed data contains ADC0, ADC1, DAC0, and DAC1 sequentially in little-endian format.
+With a batch size of 2, the serialization would take the following form:
+ +<ADC0[0]> <ADC0[1]> <ADC1[0]> <ADC1[1]> <DAC0[0]> <DAC0[1]> <DAC1[0]> <DAC1[1]>
Streamed data in FLS (fiber length stabilization) format. See the FLS application for +detailed definition.
+source
. Read moreself
and other
values to be equal, and is used
+by ==
.pub fn setup_streaming(stack: NetworkReference) -> (FrameGenerator, DataStream)
Stabilizer data stream capabilities
+Data streamining utilizes UDP packets to send live data streams at high throughput. +Packets are always sent in a best-effort fashion, and data may be dropped.
+Stabilizer organizes livestreamed data into batches within a “Frame” that will be sent as a UDP +packet. Each frame consits of a header followed by sequential batch serializations. The packet +header is constant for all streaming capabilities, but the serialization format after the header +is application-defined.
+The header consists of the following, all in little-endian.
+A sample Python script is available in scripts/stream_throughput.py
to demonstrate reception
+of livestreamed data.
pub struct DataStream { /* private fields */ }
The “consumer” portion of the data stream.
+This is responsible for consuming data and sending it over UDP.
+Configure the remote endpoint of the stream.
+remote
- The destination to send stream data to.pub struct FrameGenerator { /* private fields */ }
The data generator for a stream.
+pub struct StreamTarget {
+ pub ip: [u8; 4],
+ pub port: u16,
+}
ip: [u8; 4]
§port: u16
source
. Read morepub enum NetworkState {
+ SettingsChanged(String<128>),
+ Updated,
+ NoChange,
+}
pub enum UpdateState {
+ NoChange,
+ Updated,
+}
pub fn get_device_prefix(app: &str, mac: EthernetAddress) -> String<128>
Stabilizer network management module
+The stabilizer network architecture supports numerous layers to permit transmission of +telemetry (via MQTT), configuration of run-time settings (via MQTT + Miniconf), and live data +streaming over raw UDP/TCP sockets. This module encompasses the main processing routines +related to Stabilizer networking operations.
+Task to process network hardware.
+The network processir is a small taks to regularly process incoming data over ethernet, handle +the ethernet PHY state, and reset the network as appropriate.
+pub struct NetworkProcessor {
+ pub stack: NetworkReference,
+ /* private fields */
+}
Processor for managing network hardware.
+stack: NetworkReference
Handle ethernet link connection status.
+This may take non-trivial amounts of time to communicate with the PHY. As such, this should +only be called as often as necessary (e.g. once per second or so).
+pub struct MqttStorage { /* private fields */ }
pub struct NetworkUsers<S, T, const Y: usize>where
+ for<'de> S: Default + JsonCoreSlash<'de, Y> + Clone,
+ T: Serialize,{
+ pub miniconf: MqttClient<'static, S, NetworkReference, SystemTimer, NamedBroker<NetworkReference>, Y>,
+ pub processor: NetworkProcessor,
+ pub telemetry: TelemetryClient<T>,
+ /* private fields */
+}
A structure of Stabilizer’s default network users.
+miniconf: MqttClient<'static, S, NetworkReference, SystemTimer, NamedBroker<NetworkReference>, Y>
§processor: NetworkProcessor
§telemetry: TelemetryClient<T>
Construct Stabilizer’s default network users.
+stack
- The network stack that will be used to share with all network users.phy
- The ethernet PHY connecting the network.clock
- A SystemTimer
implementing Clock
.app
- The name of the application.mac
- The MAC address of the network.broker
- The domain name of the MQTT broker to use.A new struct of network users.
+Enable live data streaming.
+format
- A unique u8 code indicating the format of the data.Direct the stream to the provided remote target.
+remote
- The destination for the streamed data.Update and process all of the network users state.
+An indication if any of the network users indicated a state change. +The SettingsChanged option contains the path of the settings that changed.
+Stabilizer Telemetry Capabilities
+Telemetry is reported regularly using an MQTT client. All telemetry is reported in SI units +using standard JSON format.
+In order to report ADC/DAC codes generated during the DSP routines, a telemetry buffer is +employed to track the latest codes. Converting these codes to SI units would result in +repetitive and unnecessary calculations within the DSP routine, slowing it down and limiting +sampling frequency. Instead, the raw codes are stored and the telemetry is generated as +required immediately before transmission. This ensures that any slower computation required +for unit conversion can be off-loaded to lower priority tasks.
+pub struct Telemetry {
+ pub adcs: [f32; 2],
+ pub dacs: [f32; 2],
+ pub digital_inputs: [bool; 2],
+ pub cpu_temp: f32,
+}
The telemetry structure is data that is ultimately reported as telemetry over MQTT.
+This structure should be generated on-demand by the buffer when required to minimize conversion +overhead.
+adcs: [f32; 2]
Most recent input voltage measurement.
+dacs: [f32; 2]
Most recent output voltage.
+digital_inputs: [bool; 2]
Most recent digital input assertion state.
+cpu_temp: f32
The CPU temperature in degrees Celsius.
+pub struct TelemetryBuffer {
+ pub adcs: [AdcCode; 2],
+ pub dacs: [DacCode; 2],
+ pub digital_inputs: [bool; 2],
+}
The telemetry buffer is used for storing sample values during execution.
+These values can be converted to SI units immediately before reporting to save processing time. +This allows for the DSP process to continually update the values without incurring significant +run-time overhead during conversion to SI units.
+adcs: [AdcCode; 2]
The latest input sample on ADC0/ADC1.
+dacs: [DacCode; 2]
The latest output code on DAC0/DAC1.
+digital_inputs: [bool; 2]
The latest digital input states during processing.
+Convert the telemetry buffer to finalized, SI-unit telemetry for reporting.
+afe0
- The current AFE configuration for channel 0.afe1
- The current AFE configuration for channel 1.cpu_temp
- The current CPU temperature.The finalized telemetry structure that can be serialized and reported.
+source
. Read morepub struct TelemetryClient<T: Serialize> { /* private fields */ }
The telemetry client for reporting telemetry data over MQTT.
+pub type NetworkReference = NetworkStackProxy<'static, NetworkStack>;
fn:
) to \
+ restrict the search to a given item kind.","Accepted kinds are: fn
, mod
, struct
, \
+ enum
, trait
, type
, macro
, \
+ and const
.","Search functions by type signature (e.g., vec -> usize
or \
+ -> vec
or String, enum:Cow -> bool
)","You can look for items with an exact name by putting double quotes around \
+ your request: \"string\"
","Look for functions that accept or return \
+ slices and \
+ arrays by writing \
+ square brackets (e.g., -> [u8]
or [] -> Option
)","Look for items inside another one by searching for a path: vec::Vec
",].map(x=>""+x+"
").join("");const div_infos=document.createElement("div");addClass(div_infos,"infos");div_infos.innerHTML="${value.replaceAll(" ", " ")}
`}else{error[index]=value}});output+=`Stabilizer is a flexible tool designed for quantum physics experiments. Fundamentally, Stabilizer +samples up two two analog input signals, performs digital signal processing internally, and then +generates up to two output signals.
+Stabilizer firmware supports run-time configuration of the internal signal processing algorithms, +which allows for a wide variety of experimental uses, such as digital filter design or +implementation of digital lockin schemes.
+This documentation is intended to bring a user up to speed on using Stabilizer and the firmware +provided by QUARTIQ and contributors.
+The Stabilizer hardware is managed via a separate repository. +Some information about the hardware is gathered in the Stabilizer wiki. More detailed data, measurements, discussions, and tests have been posted in the Stabilizer issue tracker.
+ +Stabilizer can be extended and coupled with a mezzanine board. One such mezzanine is the DDS upconversion/downconversion frontend Pounder. The Pounder hardware is managed via a separate repository, again with wiki and issue tracker.
+This firmware offers a library of hardware and software functionality targeting the use of the Stabilizer hardware in various digital signal processing applications commonly occurring in Quantum Technology.
+It provides abstractions over the fast analog inputs and outputs, time stamping, Pounder DDS interfaces and a collection of tailored and optimized digital signal processing algorithms (IIR, FIR, Lockin, PLL, reciprocal PLL, Unwrapper, Lowpass, Cosine-Sine, Atan2) in the idsp
crate.
+An application, which is the compiled firmware running on the device, can compose and configure these hardware and software components to implement different use cases.
+Several applications are provided by default.
The following documentation links contain the application-specific settings and telemetry +information.
+Application | Description |
---|---|
dual-iir | Two channel biquad IIR filter |
lockin | Lockin amplifier support various various reference sources |
The Stabilizer library docs contain documentation for common components used in all Stabilizer +applications.
+The Stabilizer library documentation is available here.
+ +