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.
+Convert from underlying bit representation, unless that +representation contains bits that do not correspond to a flag.
+Convert from underlying bit representation, dropping any bits +that do not correspond to flags.
+Convert from underlying bit representation, preserving all +bits (even those not corresponding to a defined flag).
+Get the value for a flag from its stringified name.
+Names are case-sensitive, so must correspond exactly to +the identifier given to the flag.
+Returns true
if there are flags common to both self
and other
.
Returns true
if all of the flags in other
are contained within self
.
Inserts the specified flags in-place.
+This method is equivalent to union
.
Removes the specified flags in-place.
+This method is equivalent to difference
.
Toggles the specified flags in-place.
+This method is equivalent to symmetric_difference
.
Inserts or removes the specified flags depending on the passed value.
+Returns the intersection between the flags in self
and
+other
.
Calculating self
bitwise and (&
) other, including
+any bits that don’t correspond to a defined flag.
Returns the union of between the flags in self
and other
.
Calculates self
bitwise or (|
) other
, including
+any bits that don’t correspond to a defined flag.
Returns the difference between the flags in self
and other
.
Calculates self
bitwise and (&!
) the bitwise negation of other
,
+including any bits that don’t correspond to a defined flag.
This method is not equivalent to a & !b
when there are bits set that
+don’t correspond to a defined flag. The !
operator will unset any
+bits that don’t correspond to a flag, so they’ll always be unset by a &! b
,
+but respected by a.difference(b)
.
Returns the symmetric difference between the flags
+in self
and other
.
Calculates self
bitwise exclusive or (^
) other
,
+including any bits that don’t correspond to a defined flag.
Returns the complement of this set of flags.
+Calculates the bitwise negation (!
) of self
,
+unsetting any bits that don’t correspond to a defined flag.
Disables all flags disabled in the set.
+Adds the set of flags.
+Toggles the set of flags.
+extend_one
)extend_one
)true
if there are flags common to both self
and other
.true
if all of the flags in other
are contained within self
.self
and other
.self
and other
.self
and other
.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]
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>,
+}
Shared resources idle
has access to
network: network_that_needs_to_be_locked<'a>
Resource proxy resource network
. 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 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 {
+ network: NetworkUsers<Settings, Telemetry>,
+ settings: Settings,
+ telemetry: TelemetryBuffer,
+ signal_generator: [SignalGenerator; 2],
+}
RTIC shared resource struct
+network: NetworkUsers<Settings, Telemetry>
§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>,
+}
Shared resources idle
has access to
network: network_that_needs_to_be_locked<'a>
Resource proxy resource network
. 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
+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
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: Array<[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: Array<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: Array<[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: Array<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]Redirecting 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..ab1ae56c39 --- /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/fn.abs.html b/firmware/idsp/fn.abs.html new file mode 100644 index 0000000000..29e2d30f50 --- /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 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/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..8e76197a59 --- /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":["iir","iir_int"],"struct":["Accu","Complex","Lockin","Lowpass","PLL","RPLL","Unwrapper"],"trait":["ComplexExt","MulScaled"]}; \ 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..2030d46fb1 --- /dev/null +++ b/firmware/idsp/struct.Accu.html @@ -0,0 +1,194 @@ +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_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 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*=
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<const N: usize> { /* private fields */ }
pub struct Lowpass<const N: usize> { /* private fields */ }
Arbitrary order, high dynamic range, wide coefficient range, +lowpass filter implementation. DC gain is 1.
+Type argument N is the filter order.
+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.shift_frequency
: Frequency error scaling. The frequency gain per update is
+1/(1 << shift_frequency)
.shift_phase
: Phase error scaling. The phase gain is 1/(1 << shift_phase)
+per update. A good value is typically shift_frequency - 1
.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..8a0f0cb201 --- /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 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..2390be55e4 --- /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..119d646760 --- /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 RPLL"],["impl<T: Clone> Clone for IIR<T>"],["impl<const N: usize> Clone for Lockin<N>"],["impl Clone for PLL"],["impl<T: Clone> Clone for Unwrapper<T>"],["impl<const N: usize> Clone for Lowpass<N>"],["impl<T: Clone> Clone for Accu<T>"],["impl Clone for IIR"]], +"lockin":[["impl Clone for Settings"],["impl Clone for Conf"],["impl Clone for LockinMode"]], +"miniconf":[["impl Clone for IterError"],["impl Clone for Metadata"],["impl Clone for Error"],["impl<M: Clone + ?Sized, const L: usize, const TS: usize> Clone for MiniconfIter<M, L, TS>"],["impl<T: Clone> Clone for Option<T>"],["impl<T: Clone, const N: usize> Clone for Array<T, N>"]], +"stabilizer":[["impl Clone for ChannelState"],["impl Clone for StreamFormat"],["impl Clone for InputChannelState"],["impl Clone for Gain"],["impl Clone for TcpSocketStorage"],["impl Clone for Config"],["impl Clone for GpioPin"],["impl Clone for AdcCode"],["impl Clone for DdsClockConfig"],["impl Clone for DdsChannelState"],["impl Clone for OutputChannelState"],["impl Clone for StreamTarget"],["impl Clone for Channel"],["impl Clone for AdcError"],["impl Clone for DacCode"],["impl Clone for Error"],["impl Clone for Signal"],["impl Clone for BasicConfig"],["impl Clone for UdpSocketStorage"],["impl Clone for Error"],["impl Clone for TelemetryBuffer"]] +};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..b02f4ec9e8 --- /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 Eq for Error"],["impl<T: Eq> Eq for Option<T>"],["impl<T: Eq, const N: usize> Eq for Array<T, N>"],["impl Eq for Metadata"],["impl Eq for IterError"],["impl<M: Eq + ?Sized, const L: usize, const TS: usize> Eq for MiniconfIter<M, L, TS>"]], +"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.Ord.js b/firmware/implementors/core/cmp/trait.Ord.js new file mode 100644 index 0000000000..0ba2277790 --- /dev/null +++ b/firmware/implementors/core/cmp/trait.Ord.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"miniconf":[["impl<T: Ord> Ord for Option<T>"],["impl<T: Ord, const N: usize> Ord for Array<T, N>"]] +};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..b1a4183be5 --- /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<Error> for Error"],["impl<T: PartialEq> PartialEq<Option<T>> for Option<T>"],["impl<M: PartialEq + ?Sized, const L: usize, const TS: usize> PartialEq<MiniconfIter<M, L, TS>> for MiniconfIter<M, L, TS>"],["impl<T: PartialEq, const N: usize> PartialEq<Array<T, N>> for Array<T, N>"],["impl PartialEq<Metadata> for Metadata"],["impl PartialEq<IterError> for IterError"]], +"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/cmp/trait.PartialOrd.js b/firmware/implementors/core/cmp/trait.PartialOrd.js new file mode 100644 index 0000000000..4060633f0c --- /dev/null +++ b/firmware/implementors/core/cmp/trait.PartialOrd.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"miniconf":[["impl<T: PartialOrd, const N: usize> PartialOrd<Array<T, N>> for Array<T, N>"],["impl<T: PartialOrd> PartialOrd<Option<T>> for Option<T>"]] +};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.AsMut.js b/firmware/implementors/core/convert/trait.AsMut.js new file mode 100644 index 0000000000..e226758844 --- /dev/null +++ b/firmware/implementors/core/convert/trait.AsMut.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"miniconf":[["impl<T> AsMut<Option<T>> for Option<T>"],["impl<T, const N: usize> AsMut<[T; N]> for Array<T, N>"]] +};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.AsRef.js b/firmware/implementors/core/convert/trait.AsRef.js new file mode 100644 index 0000000000..cc8cf05ac0 --- /dev/null +++ b/firmware/implementors/core/convert/trait.AsRef.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"miniconf":[["impl<T, const N: usize> AsRef<[T; N]> for Array<T, N>"],["impl<T> AsRef<Option<T>> for Option<T>"]] +};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..4f3d2443ee --- /dev/null +++ b/firmware/implementors/core/convert/trait.From.js @@ -0,0 +1,4 @@ +(function() {var implementors = { +"miniconf":[["impl<T> From<Option<T>> for Option<T>"],["impl From<Error> for u8"],["impl From<Error> for Error"],["impl<T> From<Option<T>> for Option<T>"],["impl<T, const N: usize> From<[T; N]> for Array<T, N>"],["impl<T, const N: usize> From<Array<T, N>> for [T; N]"],["impl From<Error> for Error"]], +"stabilizer":[["impl From<i16> for DacCode"],["impl From<Channel> for GpioPin"],["impl From<DacCode> for i16"],["impl From<XspiError> for Error"],["impl From<i16> for AdcCode"],["impl From<AdcCode> for f32"],["impl From<StreamFormat> for u8"],["impl From<u16> for DacCode"],["impl From<DacCode> for f32"],["impl From<AdcCode> for u16"],["impl From<AdcCode> for i16"],["impl From<GpioPin> for Mcp23017"],["impl From<StreamTarget> for SocketAddr"],["impl From<Channel> for Channel"],["impl From<u16> 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/convert/trait.TryFrom.js b/firmware/implementors/core/convert/trait.TryFrom.js new file mode 100644 index 0000000000..80719a01c3 --- /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<u8> for Gain"],["impl TryFrom<f32> 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/default/trait.Default.js b/firmware/implementors/core/default/trait.Default.js new file mode 100644 index 0000000000..4bb8d4c915 --- /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<T: Default> Default for IIR<T>"],["impl Default for IIR"],["impl Default for PLL"],["impl<T: Default> Default for Unwrapper<T>"],["impl Default for RPLL"],["impl<T: Default> Default for Accu<T>"],["impl<const N: usize> Default for Lowpass<N>"],["impl<const N: usize> Default for Lockin<N>"]], +"lockin":[["impl Default for Settings"]], +"miniconf":[["impl<T: Default> Default for Option<T>"],["impl<M: ?Sized, const L: usize, const TS: usize> Default for MiniconfIter<M, L, TS>"],["impl<T: Default + Copy, const N: usize> Default for Array<T, N>"],["impl Default for Metadata"]], +"stabilizer":[["impl Default for StreamTarget"],["impl Default for TelemetryBuffer"],["impl Default for Config"],["impl Default for BasicConfig"],["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..8829e573fe --- /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..242bbd682a --- /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 Debug for IIR"],["impl<T: Debug> Debug for IIR<T>"],["impl<T: Debug> Debug for Accu<T>"]], +"lockin":[["impl Debug for Settings"],["impl Debug for LockinMode"],["impl Debug for Conf"]], +"miniconf":[["impl<T: Debug, const N: usize> Debug for Array<T, N>"],["impl Debug for Error"],["impl Debug for Metadata"],["impl<M: Debug + ?Sized, const L: usize, const TS: usize> Debug for MiniconfIter<M, L, TS>"],["impl<T: Debug> Debug for Option<T>"],["impl Debug for IterError"]], +"stabilizer":[["impl Debug for StreamTarget"],["impl Debug for Signal"],["impl Debug for GpioPin"],["impl Debug for Error"],["impl Debug for AdcError"],["impl Debug for Config"],["impl Debug for SignalGenerator"],["impl Debug for ChannelState"],["impl Debug for Error"],["impl Debug for DdsChannelState"],["impl Debug for InputChannelState"],["impl Debug for Gain"],["impl Debug for BasicConfig"],["impl Debug for DdsClockConfig"],["impl Debug for OutputChannelState"],["impl Debug for StreamFormat"],["impl Debug 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.LowerHex.js b/firmware/implementors/core/fmt/trait.LowerHex.js new file mode 100644 index 0000000000..afd2a047ec --- /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..6eb921280c --- /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..934fa12c27 --- /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/hash/trait.Hash.js b/firmware/implementors/core/hash/trait.Hash.js new file mode 100644 index 0000000000..9ea72e66ef --- /dev/null +++ b/firmware/implementors/core/hash/trait.Hash.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"miniconf":[["impl<T: Hash, const N: usize> Hash for Array<T, N>"],["impl<T: Hash> Hash for Option<T>"]] +};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..cb33bfb421 --- /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..5bddebfbda --- /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..64afbf6ecf --- /dev/null +++ b/firmware/implementors/core/iter/traits/collect/trait.IntoIterator.js @@ -0,0 +1,4 @@ +(function() {var implementors = { +"ad9959":[["impl IntoIterator for Channel"]], +"miniconf":[["impl<'a, T, const N: usize> IntoIterator for &'a mut Array<T, N>"],["impl<'a, T, const N: usize> IntoIterator for &'a Array<T, N>"],["impl<T, const N: usize> IntoIterator for Array<T, N>"]] +};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..24f1ac2533 --- /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<M: Miniconf + ?Sized, const L: usize, const TS: usize> Iterator for MiniconfIter<M, L, TS>"]], +"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/marker/trait.Copy.js b/firmware/implementors/core/marker/trait.Copy.js new file mode 100644 index 0000000000..3530187524 --- /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 Copy for IIR"],["impl<const N: usize> Copy for Lowpass<N>"],["impl Copy for RPLL"],["impl<T: Copy> Copy for Accu<T>"],["impl<T: Copy> Copy for IIR<T>"],["impl<const N: usize> Copy for Lockin<N>"],["impl<T: Copy> Copy for Unwrapper<T>"],["impl Copy for PLL"]], +"lockin":[["impl Copy for Settings"],["impl Copy for Conf"],["impl Copy for LockinMode"]], +"miniconf":[["impl<M: Copy + ?Sized, const L: usize, const TS: usize> Copy for MiniconfIter<M, L, TS>"],["impl Copy for IterError"],["impl<T: Copy, const N: usize> Copy for Array<T, N>"],["impl Copy for Error"],["impl<T: Copy> Copy for Option<T>"],["impl Copy for Metadata"]], +"stabilizer":[["impl Copy for DdsClockConfig"],["impl Copy for DdsChannelState"],["impl Copy for InputChannelState"],["impl Copy for AdcError"],["impl Copy for StreamTarget"],["impl Copy for ChannelState"],["impl Copy for Error"],["impl Copy for DacCode"],["impl Copy for Channel"],["impl Copy for UdpSocketStorage"],["impl Copy for Error"],["impl Copy for Config"],["impl Copy for BasicConfig"],["impl Copy for StreamFormat"],["impl Copy for TcpSocketStorage"],["impl Copy for TelemetryBuffer"],["impl Copy for AdcCode"],["impl Copy for OutputChannelState"],["impl Copy for Gain"],["impl Copy for GpioPin"],["impl Copy 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/marker/trait.Freeze.js b/firmware/implementors/core/marker/trait.Freeze.js new file mode 100644 index 0000000000..58dd653330 --- /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_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<T> Freeze for IIR<T>where\n T: Freeze,",1,["idsp::iir::IIR"]],["impl Freeze for IIR",1,["idsp::iir_int::IIR"]],["impl<const N: usize> Freeze for Lockin<N>",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"]]], +"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_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<T, const N: usize> Freeze for Array<T, N>where\n T: Freeze,",1,["miniconf::array::Array"]],["impl<M: ?Sized, const L: usize, const TS: usize> Freeze for MiniconfIter<M, L, TS>",1,["miniconf::iter::MiniconfIter"]],["impl<T> Freeze for Option<T>where\n T: Freeze,",1,["miniconf::option::Option"]],["impl<Settings, Stack, Clock, const MESSAGE_SIZE: usize> Freeze for MqttClient<Settings, Stack, Clock, MESSAGE_SIZE>where\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"]],["impl Freeze for Error",1,["miniconf::Error"]],["impl Freeze for IterError",1,["miniconf::IterError"]],["impl Freeze for Metadata",1,["miniconf::Metadata"]]], +"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 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 UpdateState",1,["stabilizer::net::UpdateState"]],["impl Freeze for NetworkState",1,["stabilizer::net::NetworkState"]],["impl<S, T> Freeze for NetworkUsers<S, T>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..75ed99991a --- /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_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<T> Send for IIR<T>where\n T: Send,",1,["idsp::iir::IIR"]],["impl Send for IIR",1,["idsp::iir_int::IIR"]],["impl<const N: usize> Send for Lockin<N>",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"]]], +"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_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<T, const N: usize> Send for Array<T, N>where\n T: Send,",1,["miniconf::array::Array"]],["impl<M: ?Sized, const L: usize, const TS: usize> Send for MiniconfIter<M, L, TS>where\n M: Send,",1,["miniconf::iter::MiniconfIter"]],["impl<T> Send for Option<T>where\n T: Send,",1,["miniconf::option::Option"]],["impl<Settings, Stack, Clock, const MESSAGE_SIZE: usize> Send for MqttClient<Settings, Stack, Clock, MESSAGE_SIZE>where\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"]],["impl Send for Error",1,["miniconf::Error"]],["impl Send for IterError",1,["miniconf::IterError"]],["impl Send for Metadata",1,["miniconf::Metadata"]]], +"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 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 UpdateState",1,["stabilizer::net::UpdateState"]],["impl Send for NetworkState",1,["stabilizer::net::NetworkState"]],["impl<S, T> Send for NetworkUsers<S, T>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..db7f014345 --- /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<T, const N: usize> StructuralEq for Array<T, N>"],["impl StructuralEq for Metadata"],["impl StructuralEq for Error"],["impl StructuralEq for IterError"],["impl<M: ?Sized, const L: usize, const TS: usize> StructuralEq for MiniconfIter<M, L, TS>"],["impl<T> StructuralEq for Option<T>"]], +"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..8193e21c5b --- /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<T> StructuralPartialEq for Option<T>"],["impl<T, const N: usize> StructuralPartialEq for Array<T, N>"],["impl StructuralPartialEq for Error"],["impl StructuralPartialEq for IterError"],["impl StructuralPartialEq for Metadata"],["impl<M: ?Sized, const L: usize, const TS: usize> StructuralPartialEq for MiniconfIter<M, L, TS>"]], +"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..06937f6be1 --- /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_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<T> Sync for IIR<T>where\n T: Sync,",1,["idsp::iir::IIR"]],["impl Sync for IIR",1,["idsp::iir_int::IIR"]],["impl<const N: usize> Sync for Lockin<N>",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"]]], +"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_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<T, const N: usize> Sync for Array<T, N>where\n T: Sync,",1,["miniconf::array::Array"]],["impl<M: ?Sized, const L: usize, const TS: usize> Sync for MiniconfIter<M, L, TS>where\n M: Sync,",1,["miniconf::iter::MiniconfIter"]],["impl<T> Sync for Option<T>where\n T: Sync,",1,["miniconf::option::Option"]],["impl<Settings, Stack, Clock, const MESSAGE_SIZE: usize> Sync for MqttClient<Settings, Stack, Clock, MESSAGE_SIZE>where\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"]],["impl Sync for Error",1,["miniconf::Error"]],["impl Sync for IterError",1,["miniconf::IterError"]],["impl Sync for Metadata",1,["miniconf::Metadata"]]], +"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 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 UpdateState",1,["stabilizer::net::UpdateState"]],["impl Sync for NetworkState",1,["stabilizer::net::NetworkState"]],["impl<S, T> !Sync for NetworkUsers<S, T>",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..c8853ab26a --- /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_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<T> Unpin for IIR<T>where\n T: Unpin,",1,["idsp::iir::IIR"]],["impl Unpin for IIR",1,["idsp::iir_int::IIR"]],["impl<const N: usize> Unpin for Lockin<N>",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"]]], +"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_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<T, const N: usize> Unpin for Array<T, N>where\n T: Unpin,",1,["miniconf::array::Array"]],["impl<M: ?Sized, const L: usize, const TS: usize> Unpin for MiniconfIter<M, L, TS>where\n M: Unpin,",1,["miniconf::iter::MiniconfIter"]],["impl<T> Unpin for Option<T>where\n T: Unpin,",1,["miniconf::option::Option"]],["impl<Settings, Stack, Clock, const MESSAGE_SIZE: usize> Unpin for MqttClient<Settings, Stack, Clock, MESSAGE_SIZE>where\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"]],["impl Unpin for Error",1,["miniconf::Error"]],["impl Unpin for IterError",1,["miniconf::IterError"]],["impl Unpin for Metadata",1,["miniconf::Metadata"]]], +"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 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 UpdateState",1,["stabilizer::net::UpdateState"]],["impl Unpin for NetworkState",1,["stabilizer::net::NetworkState"]],["impl<S, T> Unpin for NetworkUsers<S, T>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..f93d7cc4c9 --- /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..a6fa2cd352 --- /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..a5c0fac0b2 --- /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..164099d960 --- /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..9e8ab36135 --- /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..a87ce50c5c --- /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..6fe82e1c2c --- /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..03f7b85422 --- /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..2767a4876e --- /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/ops/deref/trait.Deref.js b/firmware/implementors/core/ops/deref/trait.Deref.js new file mode 100644 index 0000000000..21749c8c6f --- /dev/null +++ b/firmware/implementors/core/ops/deref/trait.Deref.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"miniconf":[["impl<T, const N: usize> Deref for Array<T, N>"],["impl<T> Deref for Option<T>"]] +};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/deref/trait.DerefMut.js b/firmware/implementors/core/ops/deref/trait.DerefMut.js new file mode 100644 index 0000000000..6133a4774f --- /dev/null +++ b/firmware/implementors/core/ops/deref/trait.DerefMut.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"miniconf":[["impl<T, const N: usize> DerefMut for Array<T, N>"],["impl<T> DerefMut for Option<T>"]] +};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..4323d3c58c --- /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_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<T> RefUnwindSafe for IIR<T>where\n T: RefUnwindSafe,",1,["idsp::iir::IIR"]],["impl RefUnwindSafe for IIR",1,["idsp::iir_int::IIR"]],["impl<const N: usize> RefUnwindSafe for Lockin<N>",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"]]], +"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_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<T, const N: usize> RefUnwindSafe for Array<T, N>where\n T: RefUnwindSafe,",1,["miniconf::array::Array"]],["impl<M: ?Sized, const L: usize, const TS: usize> RefUnwindSafe for MiniconfIter<M, L, TS>where\n M: RefUnwindSafe,",1,["miniconf::iter::MiniconfIter"]],["impl<T> RefUnwindSafe for Option<T>where\n T: RefUnwindSafe,",1,["miniconf::option::Option"]],["impl<Settings, Stack, Clock, const MESSAGE_SIZE: usize> RefUnwindSafe for MqttClient<Settings, Stack, Clock, MESSAGE_SIZE>where\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"]],["impl RefUnwindSafe for Error",1,["miniconf::Error"]],["impl RefUnwindSafe for IterError",1,["miniconf::IterError"]],["impl RefUnwindSafe for Metadata",1,["miniconf::Metadata"]]], +"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 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 UpdateState",1,["stabilizer::net::UpdateState"]],["impl RefUnwindSafe for NetworkState",1,["stabilizer::net::NetworkState"]],["impl<S, T> !RefUnwindSafe for NetworkUsers<S, T>",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..2908f01872 --- /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_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<T> UnwindSafe for IIR<T>where\n T: UnwindSafe,",1,["idsp::iir::IIR"]],["impl UnwindSafe for IIR",1,["idsp::iir_int::IIR"]],["impl<const N: usize> UnwindSafe for Lockin<N>",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"]]], +"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_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<T, const N: usize> UnwindSafe for Array<T, N>where\n T: UnwindSafe,",1,["miniconf::array::Array"]],["impl<M: ?Sized, const L: usize, const TS: usize> UnwindSafe for MiniconfIter<M, L, TS>where\n M: UnwindSafe,",1,["miniconf::iter::MiniconfIter"]],["impl<T> UnwindSafe for Option<T>where\n T: UnwindSafe,",1,["miniconf::option::Option"]],["impl<Settings, Stack, Clock, const MESSAGE_SIZE: usize> UnwindSafe for MqttClient<Settings, Stack, Clock, MESSAGE_SIZE>where\n Clock: UnwindSafe,\n Settings: UnwindSafe,\n Stack: UnwindSafe,\n <Clock as Clock>::T: UnwindSafe,\n <Stack as TcpClientStack>::TcpSocket: UnwindSafe,",1,["miniconf::mqtt_client::MqttClient"]],["impl UnwindSafe for Error",1,["miniconf::Error"]],["impl UnwindSafe for IterError",1,["miniconf::IterError"]],["impl UnwindSafe for Metadata",1,["miniconf::Metadata"]]], +"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 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 UpdateState",1,["stabilizer::net::UpdateState"]],["impl UnwindSafe for NetworkState",1,["stabilizer::net::NetworkState"]],["impl<S, T> !UnwindSafe for NetworkUsers<S, T>",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..7bacfb05c3 --- /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..282d4b439d --- /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/miniconf/trait.Miniconf.js b/firmware/implementors/miniconf/trait.Miniconf.js new file mode 100644 index 0000000000..d6d8e45ab0 --- /dev/null +++ b/firmware/implementors/miniconf/trait.Miniconf.js @@ -0,0 +1,6 @@ +(function() {var implementors = { +"dual_iir":[["impl Miniconf for Settings"]], +"lockin":[["impl Miniconf for Settings"]], +"miniconf":[], +"stabilizer":[["impl Miniconf 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/trait.Peekable.js b/firmware/implementors/miniconf/trait.Peekable.js new file mode 100644 index 0000000000..9275b6060f --- /dev/null +++ b/firmware/implementors/miniconf/trait.Peekable.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/mutex_trait/trait.Mutex.js b/firmware/implementors/mutex_trait/trait.Mutex.js new file mode 100644 index 0000000000..d66f2c3566 --- /dev/null +++ b/firmware/implementors/mutex_trait/trait.Mutex.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"stabilizer":[["impl Mutex for Adc0Input"],["impl Mutex for Adc1Input"],["impl Mutex for Dac0Output"],["impl Mutex for Dac1Output"]] +};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..baa224c681 --- /dev/null +++ b/firmware/implementors/num_enum/trait.TryFromPrimitive.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"stabilizer":[["impl TryFromPrimitive for Prescaler"],["impl TryFromPrimitive 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/serde/de/trait.Deserialize.js b/firmware/implementors/serde/de/trait.Deserialize.js new file mode 100644 index 0000000000..7d551a6309 --- /dev/null +++ b/firmware/implementors/serde/de/trait.Deserialize.js @@ -0,0 +1,6 @@ +(function() {var implementors = { +"idsp":[["impl<'de> Deserialize<'de> for IIR"],["impl<'de, T> Deserialize<'de> for Unwrapper<T>where\n T: Deserialize<'de>,"],["impl<'de> Deserialize<'de> for PLL"],["impl<'de, T> Deserialize<'de> for IIR<T>where\n T: Deserialize<'de>,"]], +"lockin":[["impl<'de> Deserialize<'de> for LockinMode"],["impl<'de> Deserialize<'de> for Conf"]], +"miniconf":[["impl<'de, T> Deserialize<'de> for Option<T>where\n T: Deserialize<'de>,"]], +"stabilizer":[["impl<'de> Deserialize<'de> for ChannelState"],["impl<'de> Deserialize<'de> for Gain"],["impl<'de> Deserialize<'de> for Signal"],["impl<'de> Deserialize<'de> for OutputChannelState"],["impl<'de> Deserialize<'de> for DdsChannelState"],["impl<'de> Deserialize<'de> for DdsClockConfig"],["impl<'de> Deserialize<'de> for StreamTarget"],["impl<'de> Deserialize<'de> for InputChannelState"]] +};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..1180159e55 --- /dev/null +++ b/firmware/implementors/serde/ser/trait.Serialize.js @@ -0,0 +1,6 @@ +(function() {var implementors = { +"idsp":[["impl Serialize for PLL"],["impl Serialize for IIR"],["impl<T> Serialize for IIR<T>where\n T: Serialize,"],["impl<T> Serialize for Unwrapper<T>where\n T: Serialize,"]], +"lockin":[["impl Serialize for Conf"],["impl Serialize for LockinMode"]], +"miniconf":[["impl<T> Serialize for Option<T>where\n T: Serialize,"]], +"stabilizer":[["impl Serialize for DdsClockConfig"],["impl Serialize for StreamTarget"],["impl Serialize for Gain"],["impl Serialize for Telemetry"],["impl Serialize for Signal"],["impl Serialize for InputChannelState"],["impl Serialize for DdsChannelState"],["impl Serialize for ChannelState"],["impl Serialize for OutputChannelState"]] +};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..8f9f5d7676 --- /dev/null +++ b/firmware/implementors/stm32h7xx_hal/dma/traits/trait.TargetAddress.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"stabilizer":[["impl TargetAddress<PeripheralToMemory> for Channel1InputCapture"],["impl TargetAddress<PeripheralToMemory> for Channel3InputCapture"],["impl TargetAddress<PeripheralToMemory> for Channel2InputCapture"],["impl TargetAddress<PeripheralToMemory> for Channel2InputCapture"],["impl TargetAddress<PeripheralToMemory> for Channel4InputCapture"],["impl TargetAddress<PeripheralToMemory> for Channel4InputCapture"],["impl TargetAddress<PeripheralToMemory> for Channel1InputCapture"],["impl TargetAddress<PeripheralToMemory> for Channel3InputCapture"],["impl TargetAddress<PeripheralToMemory> for Channel4InputCapture"],["impl TargetAddress<PeripheralToMemory> for Channel4InputCapture"],["impl TargetAddress<PeripheralToMemory> for Channel1InputCapture"],["impl TargetAddress<PeripheralToMemory> for Channel2InputCapture"],["impl TargetAddress<PeripheralToMemory> for Channel2InputCapture"],["impl TargetAddress<PeripheralToMemory> for Channel3InputCapture"],["impl TargetAddress<PeripheralToMemory> for Channel1InputCapture"],["impl TargetAddress<PeripheralToMemory> for Channel3InputCapture"]] +};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..5939844db0 --- /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]
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>,
+}
Shared resources idle
has access to
network: network_that_needs_to_be_locked<'a>
Resource proxy resource network
. 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 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<4>,
+ 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<4>
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<4>,
+ 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<4>
§signal_generator: SignalGenerator
§generator: FrameGenerator
§cpu_temp_sensor: CpuTempSensor
struct Shared {
+ network: NetworkUsers<Settings, Telemetry>,
+ settings: Settings,
+ telemetry: TelemetryBuffer,
+}
RTIC shared resource struct
+network: NetworkUsers<Settings, Telemetry>
§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>,
+}
Shared resources idle
has access to
network: network_that_needs_to_be_locked<'a>
Resource proxy resource network
. 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<4>,
+ 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<4>
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
+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
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_tc: u32,
+ 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_tc: u32
Specifies the lockin time constant.
+lockin_tc
The lockin low-pass time constant as an unsigned byte (0-255).
+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
Redirecting to ../../miniconf/struct.Array.html...
+ + + \ No newline at end of file diff --git a/firmware/miniconf/derive.Miniconf.html b/firmware/miniconf/derive.Miniconf.html new file mode 100644 index 0000000000..f0617a4973 --- /dev/null +++ b/firmware/miniconf/derive.Miniconf.html @@ -0,0 +1,27 @@ +#[derive(Miniconf)]
+{
+ // Attributes available to this derive:
+ #[miniconf]
+}
+
Derive the Miniconf trait for custom types.
+Each field of the struct will be recursively used to construct a unique path for all elements.
+All paths are similar to file-system paths with variable names separated by forward +slashes.
+For arrays, the array index is treated as a unique identifier. That is, to access the first
+element of array data
, the path would be data/0
.
#[derive(Miniconf)]
+struct Nested {
+ #[miniconf(defer)]
+ data: [u32; 2],
+}
+#[derive(Miniconf)]
+struct Settings {
+ // Accessed with path `nested/data/0` or `nested/data/1`
+ #[miniconf(defer)]
+ nested: Nested,
+
+ // Accessed with path `external`
+ external: bool,
+}
#[non_exhaustive]
+pub enum Error {
+ PathNotFound,
+ PathTooLong,
+ PathTooShort,
+ Deserialization(Error),
+ Serialization(Error),
+ BadIndex,
+ PathAbsent,
+}
Errors that can occur when using the Miniconf API.
+The provided path wasn’t found in the structure.
+Double check the provided path to verify that it’s valid.
+The provided path was valid, but there was trailing data at the end.
+Check the end of the path and remove any excess characters.
+The provided path was valid, but did not specify a value fully.
+Double check the ending and add the remainder of the path.
+The value provided for configuration could not be deserialized into the proper type.
+Check that the serialized data is valid JSON and of the correct type.
+The value provided could not be serialized.
+Check that the buffer had sufficient space.
+When indexing into an array, the index provided was out of bounds.
+Check array indices to ensure that bounds for all paths are respected.
+The path does not exist at runtime.
+This is the case if a deferred core::option::Option or Option
+is None
at runtime. PathAbsent
takes precedence over PathNotFound
+if the path is simultaneously masked by a Option::None
at runtime but
+would still be non-existent if it weren’t.
#[non_exhaustive]
+pub enum IterError {
+ PathDepth,
+ PathLength,
+}
Errors that occur during iteration over topic paths.
+The provided state vector is not long enough.
+The provided topic length is not long enough.
+Miniconf enables lightweight (no_std
) partial serialization (retrieval) and deserialization
+(updates, modification) within a hierarchical namespace by path. The namespace is backed by
+structs and arrays of serializable types.
Miniconf can be used as a very simple and flexible backend for run-time settings management in embedded devices +over any transport. It was originally designed to work with JSON (serde_json_core) +payloads over MQTT (minimq) and provides a comlete MQTT settings management +client and a Python reference implementation to ineract with it.
+use miniconf::{Error, Miniconf};
+use serde::{Deserialize, Serialize};
+
+#[derive(Deserialize, Serialize, Copy, Clone, Default)]
+enum Either {
+ #[default]
+ Bad,
+ Good,
+}
+
+#[derive(Deserialize, Serialize, Copy, Clone, Default, Miniconf)]
+struct Inner {
+ a: i32,
+ b: i32,
+}
+
+#[derive(Miniconf, Default)]
+struct Settings {
+ // Atomic updtes by field name
+ foo: bool,
+ enum_: Either,
+ struct_: Inner,
+ array: [i32; 2],
+ option: Option<i32>,
+
+ // Exposing elements of containers
+ // ... by field name
+ #[miniconf(defer)]
+ struct_defer: Inner,
+ // ... or by index
+ #[miniconf(defer)]
+ array_defer: [i32; 2],
+ // ... or deferring to two levels (index and then inner field name)
+ #[miniconf(defer)]
+ array_miniconf: miniconf::Array<Inner, 2>,
+
+ // Hiding paths by setting the Option to `None` at runtime
+ #[miniconf(defer)]
+ option_defer: Option<i32>,
+ // Hiding a path and deferring to the inner
+ #[miniconf(defer)]
+ option_miniconf: miniconf::Option<Inner>,
+ // Hiding elements of an Array of Miniconf items
+ #[miniconf(defer)]
+ array_option_miniconf: miniconf::Array<miniconf::Option<Inner>, 2>,
+}
+
+let mut settings = Settings::default();
+let mut buf = [0; 64];
+
+// Atomic updates by field name
+settings.set("foo", b"true")?;
+assert_eq!(settings.foo, true);
+settings.set("enum_", br#""Good""#)?;
+settings.set("struct_", br#"{"a": 3, "b": 3}"#)?;
+settings.set("array", b"[6, 6]")?;
+settings.set("option", b"12")?;
+settings.set("option", b"null")?;
+
+// Deep access by field name in a struct
+settings.set("struct_defer/a", b"4")?;
+// ... or by index in an array
+settings.set("array_defer/0", b"7")?;
+// ... or by index and then struct field name
+settings.set("array_miniconf/1/b", b"11")?;
+
+// If a deferred Option is `None` it is hidden at runtime and can't be accessed
+settings.option_defer = None;
+assert_eq!(settings.set("option_defer", b"13"), Err(Error::PathAbsent));
+settings.option_defer = Some(0);
+settings.set("option_defer", b"13")?;
+settings.option_miniconf = Some(Inner::default()).into();
+settings.set("option_miniconf/a", b"14")?;
+settings.array_option_miniconf[1] = Some(Inner::default()).into();
+settings.set("array_option_miniconf/1/a", b"15")?;
+
+// Serializing elements by path
+let len = settings.get("struct_", &mut buf)?;
+assert_eq!(&buf[..len], br#"{"a":3,"b":3}"#);
+
+// Iterating over and serializing all paths
+for path in Settings::iter_paths::<3, 32>().unwrap() {
+ let ret = settings.get(&path, &mut buf);
+
+ // Some settings are still `None` and thus their paths are expected to be absent
+ assert!(matches!(ret, Ok(_) | Err(Error::PathAbsent)));
+}
+
There is an MQTT-based client that implements settings management over the MQTT +protocol with JSON payloads. A Python reference library is provided that +interfaces with it.
+# Discover the complete 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 with named fields, Miniconf offers a derive macro to automatically +assign a unique path to each item in the namespace of the struct. +The macro implements the Miniconf trait that exposes access to serialized field values through their path. +All types supported by [serde_json_core] can be used as fields.
+Elements of homogeneous core::arrays are similarly accessed through their numeric indices.
+Structs, arrays, and Options can then be cascaded to construct a multi-level namespace.
+Namespace depth and access to individual elements instead of the atomic updates
+is configured at compile (derive) time using the #[miniconf(defer)]
attribute.
+Option
is used with #[miniconf(defer)]
to support paths that may be absent (masked) at
+runtime.
While the Miniconf implementations for core::array and core::option::Option by provide +atomic access to their respective inner element(s), Array and +Option have alternative Miniconf implementations that expose deep access +into the inner element(s) through their respective inner Miniconf implementations.
+The path hierarchy separator is the slash /
.
Values are serialized into and deserialized from JSON.
+Miniconf is designed to be protocol-agnostic. Any means that can receive key-value input from +some external source can be used to modify values by path.
+Deferred (non-atomic) access to inner elements of some types is not yet supported. This includes:
+mqtt-client
Enabled the MQTT client feature. See the example in MqttClient.pub use heapless;
pub use serde;
pub use serde_json_core;
pub use minimq;
pub use minimq::embedded_time;
Miniconf
implementation.Option
that exposes its value through their Miniconf
implementation.Redirecting to ../../miniconf/struct.MiniconfIter.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/option/struct.Option.html b/firmware/miniconf/option/struct.Option.html new file mode 100644 index 0000000000..0fa892af0a --- /dev/null +++ b/firmware/miniconf/option/struct.Option.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../miniconf/struct.Option.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..b893d2cca7 --- /dev/null +++ b/firmware/miniconf/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"derive":["Miniconf"],"enum":["Error","IterError"],"struct":["Array","Metadata","MiniconfIter","MqttClient","Option"],"trait":["Miniconf","Peekable"]}; \ No newline at end of file diff --git a/firmware/miniconf/struct.Array.html b/firmware/miniconf/struct.Array.html new file mode 100644 index 0000000000..2db743cd36 --- /dev/null +++ b/firmware/miniconf/struct.Array.html @@ -0,0 +1,176 @@ +#[repr(transparent)]pub struct Array<T, const N: usize>(_);
An array that exposes each element through their Miniconf
implementation.
Miniconf supports homogeneous arrays of items contained in structures using two forms. For the
+miniconf::Array
, each item of the array is accessed as a Miniconf
tree.
For standard arrays of [T; N]
form, by default the entire array is accessed as one atomic
+value. By adding the #[miniconf(defer)]
attribute, each index of the array is is instead accessed as
+one atomic value (i.e. a single Miniconf item).
The type you should use depends on what data is contained in your array. If your array contains
+Miniconf
items, you can (and often want to) use Array
and the #[miniconf(defer)]
attribute.
+However, if each element in your list is individually configurable as a single value (e.g. a list
+of u32
), then you must use a standard [T; N]
array but you may optionally
+#[miniconf(defer)]
access to individual indices.
An Array
can be constructed using From<[T; N]>
/Into<miniconf::Array>
+and the contained value can be accessed through Deref
/DerefMut
.
Returns a slice containing the entire array. Equivalent to &s[..]
.
Returns a mutable slice containing the entire array. Equivalent to
+&mut s[..]
.
array_methods
)Borrows each element and returns an array of references with the same
+size as self
.
#![feature(array_methods)]
+
+let floats = [3.1, 2.7, -1.0];
+let float_refs: [&f64; 3] = floats.each_ref();
+assert_eq!(float_refs, [&3.1, &2.7, &-1.0]);
This method is particularly useful if combined with other methods, like
+map
. This way, you can avoid moving the original
+array if its elements are not Copy
.
#![feature(array_methods)]
+
+let strings = ["Ferris".to_string(), "♥".to_string(), "Rust".to_string()];
+let is_ascii = strings.each_ref().map(|s| s.is_ascii());
+assert_eq!(is_ascii, [true, false, true]);
+
+// We can still access the original array: it has not been moved.
+assert_eq!(strings.len(), 3);
array_methods
)Borrows each element mutably and returns an array of mutable references
+with the same size as self
.
#![feature(array_methods)]
+
+let mut floats = [3.1, 2.7, -1.0];
+let float_refs: [&mut f64; 3] = floats.each_mut();
+*float_refs[0] = 0.0;
+assert_eq!(float_refs, [&mut 0.0, &mut 2.7, &mut -1.0]);
+assert_eq!(floats, [0.0, 2.7, -1.0]);
split_array
)Divides one array reference into two at an index.
+The first will contain all indices from [0, M)
(excluding
+the index M
itself) and the second will contain all
+indices from [M, N)
(excluding the index N
itself).
Panics if M > N
.
#![feature(split_array)]
+
+let v = [1, 2, 3, 4, 5, 6];
+
+{
+ let (left, right) = v.split_array_ref::<0>();
+ assert_eq!(left, &[]);
+ assert_eq!(right, &[1, 2, 3, 4, 5, 6]);
+}
+
+{
+ let (left, right) = v.split_array_ref::<2>();
+ assert_eq!(left, &[1, 2]);
+ assert_eq!(right, &[3, 4, 5, 6]);
+}
+
+{
+ let (left, right) = v.split_array_ref::<6>();
+ assert_eq!(left, &[1, 2, 3, 4, 5, 6]);
+ assert_eq!(right, &[]);
+}
split_array
)Divides one mutable array reference into two at an index.
+The first will contain all indices from [0, M)
(excluding
+the index M
itself) and the second will contain all
+indices from [M, N)
(excluding the index N
itself).
Panics if M > N
.
#![feature(split_array)]
+
+let mut v = [1, 0, 3, 0, 5, 6];
+let (left, right) = v.split_array_mut::<2>();
+assert_eq!(left, &mut [1, 0][..]);
+assert_eq!(right, &mut [3, 0, 5, 6]);
+left[1] = 2;
+right[1] = 4;
+assert_eq!(v, [1, 2, 3, 4, 5, 6]);
split_array
)Divides one array reference into two at an index from the end.
+The first will contain all indices from [0, N - M)
(excluding
+the index N - M
itself) and the second will contain all
+indices from [N - M, N)
(excluding the index N
itself).
Panics if M > N
.
#![feature(split_array)]
+
+let v = [1, 2, 3, 4, 5, 6];
+
+{
+ let (left, right) = v.rsplit_array_ref::<0>();
+ assert_eq!(left, &[1, 2, 3, 4, 5, 6]);
+ assert_eq!(right, &[]);
+}
+
+{
+ let (left, right) = v.rsplit_array_ref::<2>();
+ assert_eq!(left, &[1, 2, 3, 4]);
+ assert_eq!(right, &[5, 6]);
+}
+
+{
+ let (left, right) = v.rsplit_array_ref::<6>();
+ assert_eq!(left, &[]);
+ assert_eq!(right, &[1, 2, 3, 4, 5, 6]);
+}
split_array
)Divides one mutable array reference into two at an index from the end.
+The first will contain all indices from [0, N - M)
(excluding
+the index N - M
itself) and the second will contain all
+indices from [N - M, N)
(excluding the index N
itself).
Panics if M > N
.
#![feature(split_array)]
+
+let mut v = [1, 0, 3, 0, 5, 6];
+let (left, right) = v.rsplit_array_mut::<4>();
+assert_eq!(left, &mut [1, 0]);
+assert_eq!(right, &mut [3, 0, 5, 6][..]);
+left[1] = 2;
+right[1] = 4;
+assert_eq!(v, [1, 2, 3, 4, 5, 6]);
self
and other
) and is used by the <=
+operator. Read more#[non_exhaustive]pub struct Metadata {
+ pub max_length: usize,
+ pub max_depth: usize,
+ pub count: usize,
+}
Metadata about a Miniconf 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.
+max_depth: usize
The maximum path depth.
+count: usize
The number of paths.
+pub struct MiniconfIter<M: ?Sized, const L: usize, const TS: usize> { /* private fields */ }
An iterator over the paths in a Miniconf namespace.
+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_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
)self
and other
values to be equal, and is used
+by ==
.pub struct MqttClient<Settings, Stack, Clock, const MESSAGE_SIZE: usize>where
+ Settings: Miniconf + Clone,
+ Stack: TcpClientStack,
+ Clock: Clock,{ /* private fields */ }
MQTT settings interface.
+The MQTT client places the Miniconf 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 128 byte length and maximum depth of 8. +Keepalive interval and re-publication timeout are fixed to 60 and 2 seconds respectively.
+use miniconf::{MqttClient, Miniconf};
+
+#[derive(Miniconf, Clone, Default)]
+struct Settings {
+ foo: bool,
+}
+
+let mut client: MqttClient<Settings, _, _, 256> = MqttClient::new(
+ std_embedded_nal::Stack::default(),
+ "", // client_id auto-assign
+ "quartiq/application/12345", // prefix
+ "127.0.0.1".parse().unwrap(),
+ std_embedded_time::StandardClock::default(),
+ Settings::default(),
+)
+.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.client_id
- The ID of the MQTT client. May be an empty string for auto-assigning.prefix
- The MQTT device prefix to use for this device.broker
- The IP address of the MQTT broker to use.clock
- The clock for managing the MQTT connection.settings
- The initial settings values.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.
+#[repr(transparent)]pub struct Option<T>(_);
An Option
that exposes its value through their Miniconf
implementation.
Miniconf supports optional values in two forms.
+In both forms, the Option
may be marked with #[miniconf(defer)]
+and be None
at run-time. This makes the corresponding part of the namespace inaccessible
+at run-time. It will still be iterated over by Miniconf::iter_paths()
but cannot be
+get()
or set()
using the Miniconf
API.
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.
+The first form is the miniconf::Option
type which optionally exposes its
+interior Miniconf
value as a sub-tree. An miniconf::Option
should usually be
+#[miniconf(defer)]
.
Miniconf also allows for the normal usage of Rust core::option::Option
types. In this case,
+the Option
can be used to atomically access the content within. If marked with #[miniconf(defer)]
+and None
at runtime, it is inaccessible through Miniconf
. Otherwise, JSON null
corresponds to
+None
as usual.
An miniconf::Option
can be constructed using From<core::option::Option>
/Into<miniconf::Option>
+and the contained value can be accessed through Deref
/DerefMut
.
Converts from &Option<T>
to Option<&T>
.
Calculates the length of an Option<String>
as an Option<usize>
+without moving the String
. The map
method takes the self
argument by value,
+consuming the original, so this technique uses as_ref
to first take an Option
to a
+reference to the value inside the original.
let text: Option<String> = Some("Hello, world!".to_string());
+// First, cast `Option<String>` to `Option<&String>` with `as_ref`,
+// then consume *that* with `map`, leaving `text` on the stack.
+let text_length: Option<usize> = text.as_ref().map(|s| s.len());
+println!("still can print text: {text:?}");
Converts from &mut Option<T>
to Option<&mut T>
.
let mut x = Some(2);
+match x.as_mut() {
+ Some(v) => *v = 42,
+ None => {},
+}
+assert_eq!(x, Some(42));
option_as_slice
)Returns a slice of the contained value, if any. If this is None
, an
+empty slice is returned. This can be useful to have a single type of
+iterator over an Option
or slice.
Note: Should you have an Option<&T>
and wish to get a slice of T
,
+you can unpack it via opt.map_or(&[], std::slice::from_ref)
.
#![feature(option_as_slice)]
+
+assert_eq!(
+ [Some(1234).as_slice(), None.as_slice()],
+ [&[1234][..], &[][..]],
+);
The inverse of this function is (discounting
+borrowing) [_]::first
:
#![feature(option_as_slice)]
+
+for i in [Some(1234_u16), None] {
+ assert_eq!(i.as_ref(), i.as_slice().first());
+}
option_as_slice
)Returns a mutable slice of the contained value, if any. If this is
+None
, an empty slice is returned. This can be useful to have a
+single type of iterator over an Option
or slice.
Note: Should you have an Option<&mut T>
instead of a
+&mut Option<T>
, which this method takes, you can obtain a mutable
+slice via opt.map_or(&mut [], std::slice::from_mut)
.
#![feature(option_as_slice)]
+
+assert_eq!(
+ [Some(1234).as_mut_slice(), None.as_mut_slice()],
+ [&mut [1234][..], &mut [][..]],
+);
The result is a mutable slice of zero or one items that points into
+our original Option
:
#![feature(option_as_slice)]
+
+let mut x = Some(1234);
+x.as_mut_slice()[0] += 1;
+assert_eq!(x, Some(1235));
The inverse of this method (discounting borrowing)
+is [_]::first_mut
:
#![feature(option_as_slice)]
+
+assert_eq!(Some(123).as_mut_slice().first_mut(), Some(&mut 123))
Converts from Option<T>
(or &Option<T>
) to Option<&T::Target>
.
Leaves the original Option in-place, creating a new one with a reference
+to the original one, additionally coercing the contents via Deref
.
let x: Option<String> = Some("hey".to_owned());
+assert_eq!(x.as_deref(), Some("hey"));
+
+let x: Option<String> = None;
+assert_eq!(x.as_deref(), None);
Converts from Option<T>
(or &mut Option<T>
) to Option<&mut T::Target>
.
Leaves the original Option
in-place, creating a new one containing a mutable reference to
+the inner type’s Deref::Target
type.
let mut x: Option<String> = Some("hey".to_owned());
+assert_eq!(x.as_deref_mut().map(|x| {
+ x.make_ascii_uppercase();
+ x
+}), Some("HEY".to_owned().as_mut_str()));
Returns an iterator over the possibly contained value.
+let x = Some(4);
+assert_eq!(x.iter().next(), Some(&4));
+
+let x: Option<u32> = None;
+assert_eq!(x.iter().next(), None);
Returns a mutable iterator over the possibly contained value.
+let mut x = Some(4);
+match x.iter_mut().next() {
+ Some(v) => *v = 42,
+ None => {},
+}
+assert_eq!(x, Some(42));
+
+let mut x: Option<u32> = None;
+assert_eq!(x.iter_mut().next(), None);
Inserts value
into the option, then returns a mutable reference to it.
If the option already contains a value, the old value is dropped.
+See also Option::get_or_insert
, which doesn’t update the value if
+the option already contains Some
.
let mut opt = None;
+let val = opt.insert(1);
+assert_eq!(*val, 1);
+assert_eq!(opt.unwrap(), 1);
+let val = opt.insert(2);
+assert_eq!(*val, 2);
+*val = 3;
+assert_eq!(opt.unwrap(), 3);
Inserts value
into the option if it is None
, then
+returns a mutable reference to the contained value.
See also Option::insert
, which updates the value even if
+the option already contains Some
.
let mut x = None;
+
+{
+ let y: &mut u32 = x.get_or_insert(5);
+ assert_eq!(y, &5);
+
+ *y = 7;
+}
+
+assert_eq!(x, Some(7));
option_get_or_insert_default
)Inserts the default value into the option if it is None
, then
+returns a mutable reference to the contained value.
#![feature(option_get_or_insert_default)]
+
+let mut x = None;
+
+{
+ let y: &mut u32 = x.get_or_insert_default();
+ assert_eq!(y, &0);
+
+ *y = 7;
+}
+
+assert_eq!(x, Some(7));
Replaces the actual value in the option by the value given in parameter,
+returning the old value if present,
+leaving a Some
in its place without deinitializing either one.
let mut x = Some(2);
+let old = x.replace(5);
+assert_eq!(x, Some(5));
+assert_eq!(old, Some(2));
+
+let mut x = None;
+let old = x.replace(3);
+assert_eq!(x, Some(3));
+assert_eq!(old, None);
self
and other
) and is used by the <=
+operator. Read morepub trait Miniconf {
+ // Required methods
+ fn set_path<'a, P: Peekable<Item = &'a str>>(
+ &mut self,
+ path_parts: &'a mut P,
+ value: &[u8]
+ ) -> Result<usize, Error>;
+ fn get_path<'a, P: Peekable<Item = &'a str>>(
+ &self,
+ path_parts: &'a mut P,
+ value: &mut [u8]
+ ) -> Result<usize, Error>;
+ fn next_path<const TS: usize>(
+ state: &mut [usize],
+ path: &mut String<TS>
+ ) -> Result<bool, IterError>;
+ fn metadata() -> Metadata;
+
+ // Provided methods
+ fn set(&mut self, path: &str, data: &[u8]) -> Result<usize, Error> { ... }
+ fn get(&self, path: &str, data: &mut [u8]) -> Result<usize, Error> { ... }
+ fn iter_paths<const L: usize, const TS: usize>(
+
+ ) -> Result<MiniconfIter<Self, L, TS>, IterError> { ... }
+ fn unchecked_iter_paths<const L: usize, const TS: usize>(
+ count: Option<usize>
+ ) -> MiniconfIter<Self, L, TS> ⓘ { ... }
+}
Trait exposing serialization/deserialization of elements by path.
+Get the next path in the namespace.
+This is usually not called directly but through a MiniconfIter returned by Miniconf::iter_paths.
+state
: A state array indicating the path to be retrieved.
+A zeroed vector indicates the first path. The vector is advanced
+such that the next element will be retrieved when called again.
+The array needs to be at least as long as the maximum path depth.path
: A string to write the path into.A bool
indicating a valid path was written to path
from the given state
.
+If false
, path
is invalid and there are no more paths within self
at and
+beyond state
.
+May return IterError
indicating insufficient state
or path
size.
Create an iterator of all possible paths.
+This is a depth-first walk. +The iterator will walk all paths, even those that may be absent at run-time (see Option). +The iterator has an exact and trusted Iterator::size_hint.
+L
- The maximum depth of the path, i.e. number of separators plus 1.TS
- The maximum length of the path in bytes.A MiniconfIter of paths or an IterError if L
or TS
are insufficient.
Create an iterator of all possible paths.
+This is a depth-first walk. +It will return all paths, even those that may be absent at run-time.
+This does not check that the path size or state vector are large enough. If they are not, +panics may be generated internally by the library.
+count
: Optional iterator length if known.L
- The maximum depth of the path, i.e. number of separators plus 1.TS
- The maximum length of the path in bytes.true
if all of the flags in other
are contained …","Returns the difference between the flags in self
and other
.","Returns an empty set of flags.","","","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 underlying bit representation, unless that …","","Convert from underlying bit representation, preserving all …","Convert from underlying bit representation, dropping any …","","Get the value for a flag from its stringified name.","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.","Inserts the specified flags in-place.","Returns the intersection between the flags in self
and …","Returns true
if there are flags common to both self
and …","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 true
if all flags are currently set.","Returns true
if no flags are currently stored.","Iterate over enabled flag values.","Iterate over enabled flag values with their stringified …","Construct and initialize the DDS.","Construct a new serializer.","Returns the complement of this set of flags.","","Removes the specified flags in-place.","Perform a self-test of the communication interface.","Inserts or removes the specified flags depending on the …","Configure the amplitude of a specified channel.","Configure the frequency of a specified channel.","Configure the phase of a specified channel.","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self
…","Toggles the specified flags in-place.","","","","","","","","","","","","","","","","","","","Returns the union of between the flags in self
and other
.","Update a number of channels with the requested profile.",""],"i":[23,1,0,10,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,0,10,0,11,23,1,23,23,3,10,0,10,23,0,1,10,0,23,0,3,3,1,1,3,1,1,1,1,1,1,1,1,1,12,23,7,3,1,10,12,23,7,3,1,10,3,1,11,1,1,1,3,1,7,1,1,1,1,10,12,12,23,7,3,1,10,1,1,1,1,1,1,12,12,12,12,12,1,1,1,12,23,7,3,1,10,1,1,1,1,1,12,7,1,11,1,12,1,12,12,12,1,1,1,1,12,23,7,3,1,10,12,23,7,3,1,10,12,23,7,3,1,10,1,7,11],"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],[[1,8],9],[[1,8],9],[[1,8],9],[[1,8],9],[[10,8],9],[[[12,[11]]]],[[]],[[]],[[]],[[]],[[]],[[]],[2,[[13,[1]]]],[2,1],[2,1],[2,1],[6,1],[14,[[13,[1]]]],[[[12,[11]],1],[[4,[15,10]]]],[[[12,[11]],1],[[4,[15,10]]]],[[[12,[11]],1],[[4,[15,10]]]],[[[12,[11]]],15],[[[12,[11]]],[[4,[2,10]]]],[[1,1]],[[1,1],1],[[1,1],5],[[]],[[]],[[]],[[]],[[]],[[]],[1],[1,5],[1,5],[1,[[16,[1]]]],[1,[[17,[1]]]],[[11,18,18,[19,[2]],3,15,2],[[4,[[12,[11]],10]]]],[3,7],[1,1],[2,4],[[1,1]],[[[12,[11]]],[[4,[5,10]]]],[[1,1,5]],[[[12,[11]],1,15],[[4,[15,10]]]],[[[12,[11]],1,15],[[4,[15,10]]]],[[[12,[11]],1,15],[[4,[15,10]]]],[[1,1],1],[[1,1]],[[1,1],1],[[1,1]],[[],4],[[],4],[[],4],[[],4],[[],4],[[],4],[[],4],[[],4],[[],4],[[],4],[[],4],[[],4],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[1,1],1],[[7,1,[13,[21]],[13,[22]],[13,[21]]]],[2,4]],"c":[],"p":[[3,"Channel"],[15,"u8"],[4,"Mode"],[4,"Result"],[15,"bool"],[8,"IntoIterator"],[3,"ProfileSerializer"],[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,"u32"],[15,"u16"],[4,"Register"]]},\
+"dual_iir":{"doc":"Dual IIR","t":"RRRRRRDMMALLLLLMLLMLLLLMMMLLLRRRRFFFDGRRRRRRRRFFFFDFCDDDDFFFDDDDDDDDDFFFDDDDDDFFFDDDFFFMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMMMMMMMAFAFLLLLLLLLLLLLLLLLLLLLLLLLMMAFMMAFLLLLLLLLLLLLLLLLLLLLLLLLMMMMAMMMMMAFMMMMMMAFMMMMMAMMMAFAFAFAFAFAFMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLDDDCMMFCCDDMMDDMMMACFDDDMMMMMMMMMMDDDCMMMMMMFCCDDDDDDCMMFCCDDDCMMMMMFCCM","n":["BATCH_SIZE","IIR_CASCADE_LENGTH","SAMPLE_PERIOD","SAMPLE_TICKS","SAMPLE_TICKS_LOG2","SCALE","Settings","afe","allow_hold","app","borrow","borrow_mut","clone","default","fmt","force_hold","from","get_path","iir_ch","into","metadata","next_path","set_path","signal_generator","stream_target","telemetry_period","try_from","try_into","type_id","CEILING","CEILING","CEILING","CEILING","DCMI","DMA1_STR4","ETH","Local","Monotonic","N","PRIORITY","PRIORITY","PRIORITY","PRIORITY","PRIORITY","PRIORITY","PRIORITY","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","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_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","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","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_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_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","Context","Context","SharedResources","SpawnHandle","network","shared","spawn","spawn_after","spawn_at","Context","SharedResources","network","shared","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"],"q":[[0,"dual_iir"],[29,"dual_iir::app"],[324,"dual_iir::app::eth"],[325,"dual_iir::app::ethernet_link"],[333,"dual_iir::app::idle"],[337,"dual_iir::app::init"],[342,"dual_iir::app::monotonics"],[344,"dual_iir::app::monotonics::Monotonic"],[345,"dual_iir::app::process"],[358,"dual_iir::app::settings_update"],[371,"dual_iir::app::spi2"],[372,"dual_iir::app::spi3"],[373,"dual_iir::app::spi4"],[374,"dual_iir::app::spi5"],[375,"dual_iir::app::start"],[383,"dual_iir::app::telemetry"]],"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.","","","","Priority ceiling","Priority ceiling","Priority ceiling","Priority ceiling","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","","","","The priority of this interrupt handler","","","","","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","","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.","","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)
.","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","","","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 …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","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","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 …"],"i":[0,0,0,0,0,0,0,1,1,0,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,0,0,0,0,0,0,0,26,27,26,28,29,26,30,17,31,15,27,32,18,13,20,21,22,23,33,24,28,34,19,35,36,25,37,14,29,26,30,17,31,15,27,32,18,13,20,21,22,23,33,24,28,34,19,35,36,25,37,14,17,26,35,17,26,27,17,26,27,0,0,0,0,29,26,30,17,31,15,27,32,18,13,20,21,22,23,33,24,28,34,19,35,36,25,37,14,26,27,0,0,26,27,0,0,29,26,30,17,31,15,27,32,18,13,20,21,22,23,33,24,28,34,19,35,36,25,37,14,18,24,19,25,0,29,31,34,36,37,0,0,26,33,29,32,34,36,0,0,15,18,19,25,14,0,29,32,34,0,0,0,0,0,0,0,0,0,0,0,0,29,32,36,29,26,30,17,31,15,27,32,18,13,20,21,22,23,33,24,28,34,19,35,36,25,37,14,29,26,30,17,31,15,27,32,18,13,20,21,22,23,33,24,28,34,19,35,36,25,37,14,29,26,30,17,31,15,27,32,18,13,20,21,22,23,33,24,28,34,19,35,36,25,37,14,0,0,0,0,37,14,0,0,0,0,0,31,15,0,0,17,17,17,0,0,0,0,0,0,27,27,27,27,27,18,32,18,32,32,0,0,0,0,28,19,34,34,19,34,0,0,0,0,0,0,0,0,0,0,24,33,0,0,0,0,0,0,0,35,25,36,36,25,0,0,0,36],"f":[0,0,0,0,0,0,0,0,0,0,[[]],[[]],[1,1],[[],1],[[1,2],3],0,[[]],[[1,4],[[7,[5,6]]]],0,[[]],[[],8],[9,[[7,[10,11]]]],[[1,4],[[7,[5,6]]]],0,0,0,[[],7],[[],7],[[],12],0,0,0,0,[[]],[[]],[[]],0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],0,[[]],0,0,0,0,0,[[],[[7,[0]]]],[[],[[7,[0]]]],[[],7],0,0,0,0,0,0,0,0,0,[[],[[7,[0]]]],[[],[[7,[0]]]],[[],7],0,0,0,0,0,0,[[],[[7,[0]]]],[[],[[7,[0]]]],[[],7],0,0,0,[[],[[7,[0]]]],[[],[[7,[0]]]],[[],7],0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,0,0,0,0,0,0,[13],0,[14],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,[15,16],0,0,0,[17],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,0,0,0,0,0,0,0,[18],0,0,0,0,0,0,0,[19],0,0,0,0,0,0,0,0,0,0,[20],0,[21],0,[22],0,[23],0,[24],0,[25],0,0,0,[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],0,0,0,0,0,0,[[],7],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,[[],7],0,0,0,0,0,0,0,0,0,0,0,[[],7],0,0,0,0,0,0,0,0,0,0,0,[[],7],0,0,0],"c":[],"p":[[3,"Settings"],[3,"Formatter"],[6,"Result"],[8,"Peekable"],[15,"usize"],[4,"Error"],[4,"Result"],[3,"Metadata"],[3,"String"],[15,"bool"],[4,"IterError"],[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,"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_ethernet_linkSharedResources"]]},\
+"idsp":{"doc":"","t":"DDIDDIDDDFKLLLLLLLLLLLLLKLLFLLLLLLLLLLLLLLLLLLLLLLFFLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKLLLLLLLLLLLLLLLLLLLAAMLLLLLLLLLLLLLLLLLLKLFFLLLLLLLLLLLLLLLLKLLLLLLLLLLLLFLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMLLLLLLLLLLLLKLFKLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLDGMLLLLLLLLLLLLLLLLLLMMMDSGMLLLLLLLLLLLLLMMM","n":["Accu","Complex","ComplexExt","Lockin","Lowpass","MulScaled","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_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","clone","clone","clone","conj","copysign","cossin","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_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","hash","i","iir","iir_int","im","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","output","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_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_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","unscale","update","update","update","update","update","update_iq","wraps","zero","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"],[285,"idsp::iir"],[309,"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 …","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.","","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 …","","","","","","","","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)
.","","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 RPLL instance.","Create a new Complex","","Returns the square of the norm (since T
doesn’t …","","Return the current filter output","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
.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Divides self
by the scalar t
.","Update the lockin with a sample taken at a given phase.","Update the filter with a new sample.","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","","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,0,0,0,0,0,73,5,5,5,5,5,5,5,5,5,5,5,5,5,73,5,5,0,12,13,14,15,16,17,5,12,13,14,15,16,17,5,12,13,14,15,16,17,5,5,0,0,12,13,14,15,16,17,5,15,17,5,5,5,5,5,5,5,5,5,5,5,5,5,12,5,12,5,5,5,5,5,5,5,5,15,16,12,13,14,15,16,17,5,5,5,5,73,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,0,0,5,12,13,14,15,16,17,5,12,5,5,5,5,5,5,5,5,5,5,73,5,0,0,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,74,5,5,5,5,5,12,16,5,12,5,5,14,0,15,16,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,5,5,5,5,5,5,5,5,5,5,5,73,5,0,73,5,5,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,5,12,13,14,15,16,17,5,12,13,14,15,16,17,5,12,13,14,15,16,17,5,5,13,14,15,16,17,13,17,5,0,0,67,67,67,67,67,67,67,67,67,67,67,67,67,67,67,67,67,67,67,67,67,67,0,71,0,71,71,71,71,71,71,71,71,71,71,71,71,71,71,71,71,71],"f":[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]]]],[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]]]],[0,[7,8]]]],[[[5,[[0,[7,8]]]],[5,[[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]]]],[0,[7,9]]]],[[[5,[[0,[7,9]]]],[5,[[0,[7,9]]]]]],[[]],[[[5,[4]]],4],[[[5,[[11,[10]]]]],10],[[4,4],4],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[12,[7]]],[[12,[7]]]],[13,13],[14,14],[15,15],[16,16],[[[17,[7]]],[[17,[7]]]],[[[5,[7]]],[[5,[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],[[],15],[[],16],[[],[[17,[2]]]],[[],[[5,[2]]]],[18,[[19,[15]]]],[18,[[19,[[17,[20]]]]]],[18,[[19,[[5,[[0,[20,8,7]]]]]]]],[[[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]]]],[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,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]]]],[[[12,[21]],[12,[21]]],22],[[[5,[[21,[[21,[[21,[21]]]]]]]],[5,[[21,[[21,[[21,[21]]]]]]]]],22],[[[12,[23]],24],25],[[[5,[[0,[26,8,[1,[[0,[26,8,[1,[[0,[26,8,[1,[[0,[26,8,1,7]]]],7]]]],7]]]],7]]]],24],[[19,[27]]]],[[[5,[[0,[28,8,[1,[[0,[28,8,[1,[[0,[28,8,[1,[[0,[28,8,1,7]]]],7]]]],7]]]],7]]]],24],[[19,[27]]]],[[[5,[[0,[29,8,[1,[[0,[29,8,[1,[[0,[29,8,[1,[[0,[29,8,1,7]]]],7]]]],7]]]],7]]]],24],[[19,[27]]]],[[[5,[[0,[30,8,[1,[[0,[30,8,[1,[[0,[30,8,[1,[[0,[30,8,1,7]]]],7]]]],7]]]],7]]]],24],[[19,[27]]]],[[[5,[[0,[31,8,[1,[[0,[31,8,[1,[[0,[31,8,[1,[[0,[31,8,1,7]]]],7]]]],7]]]],7]]]],24],[[19,[27]]]],[[[5,[[0,[32,8,[1,[[0,[32,8,[1,[[0,[32,8,[1,[[0,[32,8,1,7]]]],7]]]],7]]]],7]]]],24],[[19,[27]]]],[[[5,[[0,[33,8,[1,[[0,[33,8,[1,[[0,[33,8,[1,[[0,[33,8,1,7]]]],7]]]],7]]]],7]]]],24],[[19,[27]]]],[[[5,[23]],24],[[19,[27]]]],[15,4],[16,6],[[]],[[]],[[]],[[]],[[]],[[]],[[[0,[7,8]]],[[5,[[0,[7,8]]]]]],[[[0,[7,8]]],[[5,[[0,[7,8]]]]]],[34,[[36,[[5,[[0,[35,8]]]]]]]],[[]],[[]],[4,[[5,[4]]]],[37,[[36,[[5,[[0,[38,8]]]]]]]],[39,[[36,[[5,[[0,[38,8]]]]]]]],[40,[[36,[[5,[[0,[38,8]]]]]]]],[41,[[36,[[5,[[0,[38,8]]]]]]]],[4,[[36,[[5,[[0,[38,8]]]]]]]],[42,[[36,[[5,[[0,[38,8]]]]]]]],[43,[[36,[[5,[[0,[38,8]]]]]]]],[44,[[36,[[5,[[0,[38,8]]]]]]]],[45,[[19,[[5,[[0,[46,8,7]]]]]]]],[[45,6],[[19,[[5,[[0,[8,7]]]]]]]],[47,[[36,[[5,[[0,[38,8]]]]]]]],[48,[[36,[[5,[[0,[38,8]]]]]]]],[6,[[36,[[5,[[0,[38,8]]]]]]]],[49,[[36,[[5,[[0,[38,8]]]]]]]],[50,[[36,[[5,[[0,[38,8]]]]]]]],[51,[[36,[[5,[[0,[38,8]]]]]]]],[[[5,[52]],53]],[[],[[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,[54]]],22],[[[5,[54]]],22],[[[5,[54]]],22],[[[5,[54]]],22],[[[5,[[0,[7,8]]]]],22],[[[5,[[0,[7,8]]]]],22],[[[5,[[0,[7,55]]]]],[[0,[7,55]]]],[[]],[[[5,[4]]],4],[[[0,[56,57,10]]],[[0,[56,57,10]]]],[[4,6],4],[[[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]]]],[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,[58,[[0,[7,8,[58,[[0,[7,8,[58,[[0,[7,8,58]],[0,[7,8,58]]]]]],[0,[7,8,[58,[[0,[7,8,58]],[0,[7,8,58]]]]]]]]]],[0,[7,8,[58,[[0,[7,8,[58,[[0,[7,8,58]],[0,[7,8,58]]]]]],[0,[7,8,[58,[[0,[7,8,58]],[0,[7,8,58]]]]]]]]]]]]]]]],[5,[[0,[7,8,[58,[[0,[7,8,[58,[[0,[7,8,[58,[[0,[7,8,58]],[0,[7,8,58]]]]]],[0,[7,8,[58,[[0,[7,8,58]],[0,[7,8,58]]]]]]]]]],[0,[7,8,[58,[[0,[7,8,[58,[[0,[7,8,58]],[0,[7,8,58]]]]]],[0,[7,8,[58,[[0,[7,8,58]],[0,[7,8,58]]]]]]]]]]]]]]]],[5,[[0,[7,8,[58,[[0,[7,8,[58,[[0,[7,8,[58,[[0,[7,8,58]],[0,[7,8,58]]]]]],[0,[7,8,[58,[[0,[7,8,58]],[0,[7,8,58]]]]]]]]]],[0,[7,8,[58,[[0,[7,8,[58,[[0,[7,8,58]],[0,[7,8,58]]]]]],[0,[7,8,[58,[[0,[7,8,58]],[0,[7,8,58]]]]]]]]]]]]]]]]],[[5,[[0,[7,8,[58,[[0,[7,8,[58,[[0,[7,8,[58,[[0,[7,8,58]],[0,[7,8,58]]]]]],[0,[7,8,[58,[[0,[7,8,58]],[0,[7,8,58]]]]]]]]]],[0,[7,8,[58,[[0,[7,8,[58,[[0,[7,8,58]],[0,[7,8,58]]]]]],[0,[7,8,[58,[[0,[7,8,58]],[0,[7,8,58]]]]]]]]]]]]]]]]]],[[[5,[[0,[7,8,[58,[[0,[7,8,[58,[[0,[7,8,[58,[[0,[7,8,58]],[0,[7,8,58]]]]]],[0,[7,8,[58,[[0,[7,8,58]],[0,[7,8,58]]]]]]]]]],[0,[7,8,[58,[[0,[7,8,[58,[[0,[7,8,58]],[0,[7,8,58]]]]]],[0,[7,8,[58,[[0,[7,8,58]],[0,[7,8,58]]]]]]]]]]]]]]]],[5,[[0,[7,8,[58,[[0,[7,8,[58,[[0,[7,8,[58,[[0,[7,8,58]],[0,[7,8,58]]]]]],[0,[7,8,[58,[[0,[7,8,58]],[0,[7,8,58]]]]]]]]]],[0,[7,8,[58,[[0,[7,8,[58,[[0,[7,8,58]],[0,[7,8,58]]]]]],[0,[7,8,[58,[[0,[7,8,58]],[0,[7,8,58]]]]]]]]]]]]]]]],[5,[[0,[7,8,[58,[[0,[7,8,[58,[[0,[7,8,[58,[[0,[7,8,58]],[0,[7,8,58]]]]]],[0,[7,8,[58,[[0,[7,8,58]],[0,[7,8,58]]]]]]]]]],[0,[7,8,[58,[[0,[7,8,[58,[[0,[7,8,58]],[0,[7,8,58]]]]]],[0,[7,8,[58,[[0,[7,8,58]],[0,[7,8,58]]]]]]]]]]]]]]]]],[[5,[[0,[7,8,[58,[[0,[7,8,[58,[[0,[7,8,[58,[[0,[7,8,58]],[0,[7,8,58]]]]]],[0,[7,8,[58,[[0,[7,8,58]],[0,[7,8,58]]]]]]]]]],[0,[7,8,[58,[[0,[7,8,[58,[[0,[7,8,58]],[0,[7,8,58]]]]]],[0,[7,8,[58,[[0,[7,8,58]],[0,[7,8,58]]]]]]]]]]]]]]]]]],[[[5,[[0,[7,9,[59,[[0,[7,9,[59,[[0,[7,9,[59,[[0,[7,9,59]],[0,[7,9,59]]]]]],[0,[7,9,[59,[[0,[7,9,59]],[0,[7,9,59]]]]]]]]]],[0,[7,9,[59,[[0,[7,9,[59,[[0,[7,9,59]],[0,[7,9,59]]]]]],[0,[7,9,[59,[[0,[7,9,59]],[0,[7,9,59]]]]]]]]]]]]]]]],[5,[[0,[7,9,[59,[[0,[7,9,[59,[[0,[7,9,[59,[[0,[7,9,59]],[0,[7,9,59]]]]]],[0,[7,9,[59,[[0,[7,9,59]],[0,[7,9,59]]]]]]]]]],[0,[7,9,[59,[[0,[7,9,[59,[[0,[7,9,59]],[0,[7,9,59]]]]]],[0,[7,9,[59,[[0,[7,9,59]],[0,[7,9,59]]]]]]]]]]]]]]]],[5,[[0,[7,9,[59,[[0,[7,9,[59,[[0,[7,9,[59,[[0,[7,9,59]],[0,[7,9,59]]]]]],[0,[7,9,[59,[[0,[7,9,59]],[0,[7,9,59]]]]]]]]]],[0,[7,9,[59,[[0,[7,9,[59,[[0,[7,9,59]],[0,[7,9,59]]]]]],[0,[7,9,[59,[[0,[7,9,59]],[0,[7,9,59]]]]]]]]]]]]]]]]]],[[[5,[[0,[7,9,[59,[[0,[7,9,[59,[[0,[7,9,[59,[[0,[7,9,59]],[0,[7,9,59]]]]]],[0,[7,9,[59,[[0,[7,9,59]],[0,[7,9,59]]]]]]]]]],[0,[7,9,[59,[[0,[7,9,[59,[[0,[7,9,59]],[0,[7,9,59]]]]]],[0,[7,9,[59,[[0,[7,9,59]],[0,[7,9,59]]]]]]]]]]]]]]]],[5,[[0,[7,9,[59,[[0,[7,9,[59,[[0,[7,9,[59,[[0,[7,9,59]],[0,[7,9,59]]]]]],[0,[7,9,[59,[[0,[7,9,59]],[0,[7,9,59]]]]]]]]]],[0,[7,9,[59,[[0,[7,9,[59,[[0,[7,9,59]],[0,[7,9,59]]]]]],[0,[7,9,[59,[[0,[7,9,59]],[0,[7,9,59]]]]]]]]]]]]]]]],[5,[[0,[7,9,[59,[[0,[7,9,[59,[[0,[7,9,[59,[[0,[7,9,59]],[0,[7,9,59]]]]]],[0,[7,9,[59,[[0,[7,9,59]],[0,[7,9,59]]]]]]]]]],[0,[7,9,[59,[[0,[7,9,[59,[[0,[7,9,59]],[0,[7,9,59]]]]]],[0,[7,9,[59,[[0,[7,9,59]],[0,[7,9,59]]]]]]]]]]]]]]]]]],[[[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]],4],[[5,[4]]]],[[[5,[4]],[5,[4]]],[[5,[4]]]],[[[5,[4]],41],[[5,[4]]]],[[[5,[[0,[7,8,3]]]]]],[[[5,[[0,[7,8,3]]]]]],[[],12],[6,16],[[],5],[[[12,[[0,[60,10]]]]],[[36,[[0,[60,10]]]]]],[[[5,[[0,[7,8]]]]],[[0,[7,8]]]],[[],[[5,[[0,[7,8]]]]]],[14,4],[[[0,[61,62,1]],[0,[61,62,1]]]],[15,4],[16,4],[[[17,[[0,[61,62,1,10]]]]],[[0,[61,62,1,10]]]],[[[5,[[0,[7,8,3]]]],42]],[[[5,[[0,[7,8]]]],47]],[[[5,[[0,[7,8]]]],48]],[[[5,[[0,[7,8,3]]]],41]],[[[5,[[0,[7,8,3]]]],44]],[[[5,[[0,[7,8,3]]]],43]],[[[5,[[0,[7,8,3]]]],42]],[[[5,[[0,[7,8]]]],49]],[[[5,[[0,[7,8,3]]]],41]],[[[5,[[0,[7,8,3]]]],44]],[[[5,[[0,[7,8,3]]]],40]],[[[5,[[0,[7,8,3]]]],4]],[[[5,[[0,[7,8]]]],47]],[[[5,[[0,[7,8,3]]]],4]],[[[5,[[0,[7,8]]]],6]],[[[5,[[0,[7,8]]]],48]],[[[5,[[0,[7,8]]]],51]],[[[5,[[0,[7,8,3]]]],40]],[[[5,[[0,[7,8]]]],6]],[[[5,[[0,[7,8]]]],49]],[[[5,[[0,[7,8]]]],51]],[[[5,[[0,[7,8]]]],50]],[[[5,[[0,[7,8]]]],50]],[[[5,[[0,[7,8,3]]]],43]],[[[5,[[0,[7,8,3]]]],4],[[5,[[0,[7,8,3]]]]]],[[[5,[[0,[7,8]]]],6],[[5,[[0,[7,8]]]]]],[63,[[5,[[0,[8,7]]]]]],[63,[[5,[[0,[8,7]]]]]],0,[[[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]]]],[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,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]]]],[[4,4,6],4],[[]],[[[5,[4]],[5,[4]]],[[5,[4]]]],[[[5,[[0,[7,8]]]],[0,[7,8]]],[[5,[[0,[7,8]]]]]],[[15,64],19],[[[17,[65]],64],19],[[[5,[65]],64],19],[[[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,8]]]],[0,[7,8]]]],[[[5,[[0,[7,8]]]],[5,[[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]]]]]],[63,[[5,[[0,[8,7]]]]]],[63,[[5,[[0,[8,7]]]]]],[[[5,[[0,[34,8]]]]],[[36,[37]]]],[[[5,[[0,[34,8]]]]],[[36,[39]]]],[[[5,[[0,[34,8]]]]],[[36,[40]]]],[[[5,[[0,[34,8]]]]],[[36,[41]]]],[[[5,[[0,[34,8]]]]],[[36,[4]]]],[[[5,[[0,[34,8]]]]],[[36,[42]]]],[[[5,[[0,[34,8]]]]],[[36,[43]]]],[[[5,[[0,[34,8]]]]],[[36,[44]]]],[[[5,[[0,[34,8]]]]],[[36,[47]]]],[[[5,[[0,[34,8]]]]],[[36,[48]]]],[[[5,[[0,[34,8]]]]],[[36,[6]]]],[[[5,[[0,[34,8]]]]],[[36,[49]]]],[[[5,[[0,[34,8]]]]],[[36,[50]]]],[[[5,[[0,[34,8]]]]],[[36,[51]]]],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],66],[[],66],[[],66],[[],66],[[],66],[[],66],[[],66],[[[5,[[0,[7,8]]]],[0,[7,8]]],[[5,[[0,[7,8]]]]]],[[13,4,4,6],[[5,[4]]]],[[14,4,6],4],[[15,[36,[4]],6,6]],[[16,[36,[4]],6,6]],[[[17,[[0,[61,62,1,10]]]],[0,[61,62,1,10]]]],[[13,4,[5,[4]],6],[[5,[4]]]],[[[17,[[0,[61,62,1,10]]]]],4],[[],[[5,[[0,[7,8]]]]]],0,0,0,[[]],[[]],[[[67,[7]]],[[67,[7]]]],[[],[[67,[2]]]],[18,[[19,[[67,[20]]]]]],[[[67,[23]],24],25],[[]],[[[67,[[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,69]]]]]]]]]]]]]]]]],[[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,69]]]]]]]]]]]]]]]]]]]],[[[67,[[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,69]]]]]]]]]]]]]]]]],[[19,[[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,69]]]]]]]]]]]]]],45]]]],[[]],[[[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,69]]]]]]]]]]]]]]]]]],[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,69]]]]]]]]]]]]]]]]]],[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,69]]]]]]]]]]]]]]]]]]],[[67,[[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,69]]]]]]]]]]]]]]]]]],[[[67,[65]],64],19],[[[67,[[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,69]]]]]]]]]]]]]]]],[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,69]]]]]]]]]]]]]]]]]],[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,69]]]]]]]]]]]]]]]]]],[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,69]]]]]]]]]]]]]]]]]]],[[19,[45]]]],[[[67,[[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,69]]]]]]]]]]]]]]]],[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,69]]]]]]]]]]]]]]]]]]]],[[],19],[[],19],[[],66],[[[67,[[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,69]]]]]]]]]]]]]]]],[70,[[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,69]]]]]]]]]]]]]]]],[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,69]]]]]]]]]]]]]]]]]],22],[[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,[69,[[0,[68,2,69]]]]]]]]]]]]]]]]]]]],0,0,0,0,0,0,0,[[]],[[]],[71,71],[[],71],[18,[[19,[71]]]],[[71,24],25],[[]],[[]],[[71,64],19],[[],19],[[],19],[[],66],[[71,72,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,"Lockin"],[3,"Lowpass"],[3,"PLL"],[3,"RPLL"],[3,"Unwrapper"],[8,"Deserializer"],[4,"Result"],[8,"Deserialize"],[8,"PartialEq"],[15,"bool"],[8,"Debug"],[3,"Formatter"],[6,"Result"],[8,"Binary"],[3,"Error"],[8,"LowerHex"],[8,"UpperExp"],[8,"Display"],[8,"UpperHex"],[8,"Octal"],[8,"LowerExp"],[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,"Hash"],[8,"Hasher"],[8,"FloatCore"],[8,"Signed"],[8,"Add"],[8,"Mul"],[8,"MulAdd"],[8,"MulAddAssign"],[8,"WrappingAdd"],[8,"WrappingSub"],[8,"Zero"],[8,"Iterator"],[8,"Serializer"],[8,"Serialize"],[3,"TypeId"],[3,"IIR"],[8,"Float"],[8,"Sum"],[6,"Vec5"],[3,"IIR"],[6,"Vec5"],[8,"ComplexExt"],[8,"MulScaled"]]},\
+"lockin":{"doc":"Lockin","t":"RRENNNENNNNNNRRDMALLLLLLLLLLLLLLLLLLLLLLLMMMMLLMMLLLMMLLLLLLLLLRRRFFFDGRRRRDFCDDDDFFFDDDDDDDDDFFFDDFFFDDDFFFMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMMMMMMMAFAFLLLLLLLLLLLLLLLLLLLLMMAFAFLLLLLLLLLLLLLLLLLLLLMMMMMMAMMMMMMMAFMMMMMMAFMMMMMAMMAFAFMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLDDDCMMFCCDDMMDDMMMACFDDDMMMMMMMMMMMDDDCMMMMMFCCDDCMMFCCDDDCMMMMMMFCCM","n":["BATCH_SIZE","BATCH_SIZE_LOG2","Conf","External","InPhase","Internal","LockinMode","LogPower","Magnitude","Modulation","Phase","Quadrature","ReferenceFrequency","SAMPLE_TICKS","SAMPLE_TICKS_LOG2","Settings","afe","app","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","default","deserialize","deserialize","eq","fmt","fmt","fmt","from","from","from","get_path","into","into","into","lockin_harmonic","lockin_mode","lockin_phase","lockin_tc","metadata","next_path","output_conf","pll_tc","serialize","serialize","set_path","stream_target","telemetry_period","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","CEILING","CEILING","CEILING","DCMI","DMA1_STR4","ETH","Local","Monotonic","N","PRIORITY","PRIORITY","PRIORITY","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","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_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","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","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_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_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","Context","Context","SharedResources","SpawnHandle","network","shared","spawn","spawn_after","spawn_at","Context","SharedResources","network","shared","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"],"q":[[0,"lockin"],[63,"lockin::app"],[312,"lockin::app::eth"],[313,"lockin::app::ethernet_link"],[321,"lockin::app::idle"],[325,"lockin::app::init"],[330,"lockin::app::monotonics"],[332,"lockin::app::monotonics::Monotonic"],[333,"lockin::app::process"],[347,"lockin::app::settings_update"],[359,"lockin::app::start"],[367,"lockin::app::telemetry"]],"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 operational mode of the lockin.","Specifies the LO phase offset.","Specifies the lockin time constant.","","","Specifies DAC output mode.","Specifis the PLL time constant.","","","","Specifies the target for data livestreaming.","Specifies the telemetry output period in seconds.","","","","","","","","","","Priority ceiling","Priority ceiling","Priority ceiling","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","","","","The priority of this interrupt handler","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","","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.","","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)
.","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","","","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
","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","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","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 …"],"i":[0,0,0,2,1,2,0,1,1,1,1,1,1,0,0,0,3,0,1,2,3,1,2,3,1,2,3,3,1,2,2,1,2,3,1,2,3,3,1,2,3,3,3,3,3,3,3,3,3,1,2,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,0,0,26,27,26,28,29,26,30,21,31,19,27,32,22,17,33,24,28,34,23,35,36,25,37,18,29,26,30,21,31,19,27,32,22,17,33,24,28,34,23,35,36,25,37,18,21,26,35,21,26,27,21,26,35,0,0,0,0,29,26,30,21,31,19,27,32,22,17,33,24,28,34,23,35,36,25,37,18,26,27,0,0,0,0,29,26,30,21,31,19,27,32,22,17,33,24,28,34,23,35,36,25,37,18,22,24,23,25,26,27,0,29,31,34,36,37,26,27,0,0,26,33,29,32,34,36,0,0,19,22,23,25,18,0,26,27,0,0,0,0,29,32,36,26,27,29,26,30,21,31,19,27,32,22,17,33,24,28,34,23,35,36,25,37,18,29,26,30,21,31,19,27,32,22,17,33,24,28,34,23,35,36,25,37,18,29,26,30,21,31,19,27,32,22,17,33,24,28,34,23,35,36,25,37,18,0,0,0,0,37,18,0,0,0,0,0,31,19,0,0,21,21,21,0,0,0,0,0,0,27,27,27,22,27,27,32,22,27,32,27,0,0,0,0,28,23,34,34,23,0,0,0,0,0,0,24,33,0,0,0,0,0,0,0,35,35,25,36,36,25,0,0,0,36],"f":[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]]]],[[2,2],6],[[1,7],8],[[2,7],8],[[3,7],8],[[]],[[]],[[]],[[3,9],[[5,[10,11]]]],[[]],[[]],[[]],0,0,0,0,[[],12],[13,[[5,[6,14]]]],0,0,[[1,15],5],[[2,15],5],[[3,9],[[5,[10,11]]]],0,0,[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],16],[[],16],[[],16],0,0,0,[[]],[[]],[[]],0,0,0,0,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,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,0,0,0,0,0,0,[17],0,[18],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,[19,20],0,[21],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[22],0,0,0,0,0,0,0,[23],0,0,0,0,0,0,0,0,0,[24],0,[25],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],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],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,[[],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],"c":[],"p":[[4,"Conf"],[4,"LockinMode"],[3,"Settings"],[8,"Deserializer"],[4,"Result"],[15,"bool"],[3,"Formatter"],[6,"Result"],[8,"Peekable"],[15,"usize"],[4,"Error"],[3,"Metadata"],[3,"String"],[4,"IterError"],[8,"Serializer"],[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,"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_ethernet_linkSharedResources"]]},\
+"miniconf":{"doc":"Miniconf","t":"DNNEEDIYDDDNNNNNNINLLLLLLLLLLLLLLLLLLLLLLLLLLMLLLLLLLLLCLLLLLLLLLLLLLLLLLLLLLLLLLKLLLLLCLLLLLLLLLLLLMMKLLCLLLKLLLLKCCLLKLLLLLLLLLLLLLLLLLLLLLLLLLLL","n":["Array","BadIndex","Deserialization","Error","IterError","Metadata","Miniconf","Miniconf","MiniconfIter","MqttClient","Option","PathAbsent","PathDepth","PathLength","PathNotFound","PathTooLong","PathTooShort","Peekable","Serialization","as_mut","as_mut","as_ref","as_ref","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","clone","clone","cmp","cmp","count","default","default","default","default","deref","deref","deref_mut","deref_mut","deserialize","embedded_time","eq","eq","eq","eq","eq","eq","fmt","fmt","fmt","fmt","fmt","fmt","force_republish","from","from","from","from","from","from","from","from","from","from","from","get","get_path","get_path","get_path","handled_update","hash","hash","heapless","into","into","into","into","into","into","into","into_iter","into_iter","into_iter","into_iter","iter_paths","max_depth","max_length","metadata","metadata","metadata","minimq","new","new","next","next_path","next_path","next_path","partial_cmp","partial_cmp","peek","serde","serde_json_core","serialize","set","set_path","set_path","set_path","settings","size_hint","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","unchecked_iter_paths","update"],"q":[[0,"miniconf"]],"d":["An array that exposes each element through their Miniconf
…","When indexing into an array, the index provided was out of …","The value provided for configuration could not be …","Errors that can occur when using the Miniconf API.","Errors that occur during iteration over topic paths.","Metadata about a Miniconf namespace.","Trait exposing serialization/deserialization of elements …","Derive the Miniconf trait for custom types.","An iterator over the paths in a Miniconf namespace.","MQTT settings interface.","An Option
that exposes its value through their Miniconf
…","The path does not exist at runtime.","The provided state vector is not long enough.","The provided topic length is not long enough.","The provided path wasn’t found in the structure.","The provided path was valid, but there was trailing data …","The provided path was valid, but did not specify a value …","Helper trait for core::iter::Peekable.","The value provided could not be serialized.","","","","","","","","","","","","","","","","","","","","","","","","","","","The number of paths.","","","","","","","","","","","","","","","","","","","","","","","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.","Returns the argument unchanged.","Returns the argument unchanged.","Retrieve a serialized value by path.","Serialize an element by path.","","","Update the MQTT interface and service the network. Pass …","","","","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)
.","","","","","Create an iterator of all possible paths.","The maximum path depth.","The maximum length of a path.","Get metadata about the paths in the namespace.","","","","Construct a new MQTT settings interface.","","","Get the next path in the namespace.","","","","","","","","","Update an element by path.","Deserialize an element by path.","","","Get the current settings from miniconf.","","","","","","","","","","","","","","","","","","","","","","","Create an iterator of all possible paths.","Update the settings from the network stack without any …"],"i":[0,7,7,0,0,0,0,0,0,0,0,7,8,8,7,7,7,0,7,1,2,1,2,25,1,6,2,7,8,9,25,1,6,2,7,8,9,1,6,2,7,8,9,1,2,9,1,6,2,9,1,2,1,2,2,0,1,6,2,7,8,9,1,6,2,7,8,9,25,25,1,1,6,2,2,7,7,7,8,9,22,22,1,2,25,1,2,0,25,1,6,2,7,8,9,1,1,1,6,22,9,9,22,1,2,0,25,6,6,22,1,2,1,2,30,0,0,2,22,22,1,2,25,6,25,1,6,2,7,8,9,25,1,6,2,7,8,9,25,1,6,2,7,8,9,22,25],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[1],[2,3],[1],[2,3],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[1,[4]]],[[1,[4]]]],[[[6,[[0,[4,5]]]]],[[6,[[0,[4,5]]]]]],[[[2,[4]]],[[2,[4]]]],[7,7],[8,8],[9,9],[[[1,[10]],[1,[10]]],11],[[[2,[10]],[2,[10]]],11],0,[[],[[1,[[0,[12,13]]]]]],[[],[[6,[5]]]],[[],[[2,[12]]]],[[],9],[1],[2],[1],[2],[14,[[16,[[2,[15]]]]]],0,[[[1,[17]],[1,[17]]],18],[[[6,[[0,[17,5]]]],[6,[[0,[17,5]]]]],18],[[[2,[17]],[2,[17]]],18],[[7,7],18],[[8,8],18],[[9,9],18],[[[1,[19]],20],21],[[[6,[[0,[19,5]]]],20],21],[[[2,[19]],20],21],[[7,20],21],[[8,20],21],[[9,20],21],[[[25,[[0,[22,4]],23,[0,[24,4]]]]]],[[]],[[]],[[],1],[[]],[[]],[3,2],[26,7],[27,7],[[]],[[]],[[]],[28,[[16,[29,7]]]],[30,[[16,[29,7]]]],[[[1,[22]],30],[[16,[29,7]]]],[[[2,[22]],30],[[16,[29,7]]]],[[[25,[[0,[22,4]],23,[0,[24,4]]]],31],[[16,[18,32]]]],[[[1,[33]],34]],[[[2,[33]],34]],0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1],[1],[1],[[]],[[],[[16,[6,8]]]],0,0,[[],9],[[],9],[[],9],0,[[23,28,28,35,[0,[24,4]],[0,[22,4]]],[[16,[[25,[[0,[22,4]],23,[0,[24,4]]]],32]]]],[[[3,[29]]],[[6,[5]]]],[[[6,[[0,[22,5]]]]],3],[36,[[16,[18,8]]]],[36,[[16,[18,8]]]],[36,[[16,[18,8]]]],[[[1,[37]],[1,[37]]],[[3,[11]]]],[[[2,[37]],[2,[37]]],[[3,[11]]]],[[],3],0,0,[[[2,[38]],39],16],[28,[[16,[29,7]]]],[30,[[16,[29,7]]]],[[[1,[22]],30],[[16,[29,7]]]],[[[2,[22]],30],[[16,[29,7]]]],[[[25,[[0,[22,4]],23,[0,[24,4]]]]],[[0,[22,4]]]],[[[6,[[0,[22,5]]]]]],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],40],[[],40],[[],40],[[],40],[[],40],[[],40],[[],40],[[[3,[29]]],6],[[[25,[[0,[22,4]],23,[0,[24,4]]]]],[[16,[18,32]]]]],"c":[],"p":[[3,"Array"],[3,"Option"],[4,"Option"],[8,"Clone"],[8,"Sized"],[3,"MiniconfIter"],[4,"Error"],[4,"IterError"],[3,"Metadata"],[8,"Ord"],[4,"Ordering"],[8,"Default"],[8,"Copy"],[8,"Deserializer"],[8,"Deserialize"],[4,"Result"],[8,"PartialEq"],[15,"bool"],[8,"Debug"],[3,"Formatter"],[6,"Result"],[8,"Miniconf"],[8,"TcpClientStack"],[8,"Clock"],[3,"MqttClient"],[4,"Error"],[4,"Error"],[15,"str"],[15,"usize"],[8,"Peekable"],[8,"FnMut"],[4,"Error"],[8,"Hash"],[8,"Hasher"],[4,"IpAddr"],[3,"String"],[8,"PartialOrd"],[8,"Serialize"],[8,"Serializer"],[3,"TypeId"]]},\
+"stabilizer":{"doc":"","t":"AAGGGGGGGGGGGRGGGGAAAAAACCAAAAAADDDLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNEDLLLLLLLLLLLLLLLLLLLLLLLLDLLLLLLLLLDDDSSSLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLDLLLLLLLLLLRRRRRRRRRRRGRRDLLLLLLLLLLNNNNNNNEDDDENENNNDNNNNNNNNNNNNDDNDNMMMMALLLLLLLLLLLLLLLLLLLLMLLLLLLLLLALLLLLMMLLLLLLLLLMLLLLLLLLLLLLALLLLLLLLLLLLMMMLLLMMMLMLMLALLLLLLLLLLALLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLILKKLKDDLLLLLLLLLLLLLLLLLLLEDNNLLLLLLLLLLLLLLLLLILKDLLLLLLLLLLLDDDDDDDMMMLLLLLLLLLLLLLLLLMMLMMLLLLLLLLLLLLLLMMMMMMMMMFMMMMMMMMLLLLLLLLLLLLLLLLLLLLLMDENDLLLLLLLLLLLLLLLLLLLLLLLLLLLDDNENNNEDNNNMMLLLLLLLLLLLLLLLLLLLLLLLMLLLLLLLLLLLLLLLLMMMLLMMMLLLLLLLLLLLLLLLLLNNNNNNNNNNNNNEDENDDEDNNNNNEENLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLAAAALLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLEEEEDDDDDDDDDNNNNNNNNNNNNDLLLLLLLLLLLLLLLLLLLLLLLLMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLEEEEDDDDDDDDDNNNNNNNNNNNNDLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLEEEEDDDDDDDDDNNNNNNNNNNNNDLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLEEEEDDDDDDDDDNNNNNNNNNNNNDLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLRGEDNNNENNLLLLLLLALLLLFCLLLCMALMCAMLLLLLLLLLLNDNDEDNLLLLLLLLLLLLLLLLLLLLLLLLMMLLLFLLLLLLLLLLLLDLLLLLLMLLLLDDDMMLLLLLLLMMMLMMLLLLLLLLLLLLLLLLLLLL","n":["hardware","net","AFE0","AFE1","DigitalInput0","DigitalInput1","EemDigitalInput0","EemDigitalInput1","EemDigitalOutput0","EemDigitalOutput1","EthernetPhy","I2c1","I2c1Proxy","MONOTONIC_FREQUENCY","NetworkManager","NetworkStack","SystemTimer","Systick","adc","afe","cpu_temp_sensor","dac","delay","design_parameters","embedded_hal","hal","input_stamper","pounder","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","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","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","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","fmt","fmt","fmt","fmt","fmt","frequency","from","from","from","from","from","get_path","into","into","into","into","into","into_iter","metadata","new","next","next_path","phase","phase_increment","phase_offset","serialize","set_path","signal","signal","symmetry","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","DEFAULT_MQTT_BROKER","NetworkReference","NetworkState","NetworkUsers","NoChange","NoChange","SettingsChanged","UpdateState","Updated","Updated","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","configure_streaming","data_stream","direct_stream","from","from","from","get_device_prefix","heapless","into","into","into","miniconf","miniconf","network_processor","new","processor","serde","telemetry","telemetry","try_from","try_from","try_from","try_into","try_into","try_into","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"],[32,"stabilizer::hardware::adc"],[68,"stabilizer::hardware::afe"],[98,"stabilizer::hardware::cpu_temp_sensor"],[108,"stabilizer::hardware::dac"],[147,"stabilizer::hardware::delay"],[158,"stabilizer::hardware::design_parameters"],[172,"stabilizer::hardware::input_stamper"],[183,"stabilizer::hardware::pounder"],[354,"stabilizer::hardware::pounder::attenuators"],[360,"stabilizer::hardware::pounder::dds_output"],[381,"stabilizer::hardware::pounder::hrtimer"],[402,"stabilizer::hardware::pounder::rf_power"],[405,"stabilizer::hardware::pounder::timestamp"],[417,"stabilizer::hardware::setup"],[502,"stabilizer::hardware::shared_adc"],[533,"stabilizer::hardware::signal_generator"],[612,"stabilizer::hardware::timers"],[750,"stabilizer::hardware::timers::tim2"],[894,"stabilizer::hardware::timers::tim3"],[1078,"stabilizer::hardware::timers::tim5"],[1262,"stabilizer::hardware::timers::tim8"],[1446,"stabilizer::net"],[1491,"stabilizer::net::data_stream"],[1540,"stabilizer::net::network_processor"],[1552,"stabilizer::net::telemetry"]],"d":["","","","","","","","","","","","","","System timer (RTIC Monotonic) tick frequency","","","","","","","STM32 Temperature Sensor Driver","","Basic blocking delay","","","! Module for all hardware-specific setup of Stabilizer","","","","","","","Represents data associated with ADC.","Represents data associated with ADC.","A type representing an ADC sample.","","","","","","","","Construct an ADC code from the stabilizer-defined code …","Returns the argument unchanged.","Construct an ADC code from a provided binary …","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 …","","","","","","","","","","","Returns the argument unchanged.","Encode signed 16-bit values into DAC offset binary for a …","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.","","","","","","","","","","","","","","","","","","","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)
.","","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 …","","","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.","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","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","The default MQTT broker IP address if unspecified.","","","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.","Get the MQTT prefix of a device.","! Stabilizer network management module ! ! # Design ! The …","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","","","","Construct Stabilizer’s default network users.","","","","","","","","","","","","","","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,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,202,202,202,202,202,0,0,87,88,87,88,87,87,88,87,88,87,87,88,87,88,87,88,88,87,88,0,0,91,91,91,89,91,89,89,91,89,91,89,89,89,91,89,91,89,91,89,0,203,203,0,97,97,97,97,97,97,97,97,97,97,97,0,0,0,0,0,0,0,204,204,204,104,205,206,204,207,102,103,104,205,206,204,207,102,103,102,103,204,207,104,204,204,104,205,206,204,207,102,103,104,205,206,204,207,102,103,104,206,206,206,206,205,204,205,207,0,104,205,204,104,204,204,204,207,104,205,206,204,207,102,103,104,205,206,204,207,102,103,104,205,206,204,207,102,103,104,0,0,37,0,40,108,37,40,108,37,37,108,37,40,108,37,40,108,37,108,40,40,40,108,37,40,108,37,40,108,37,0,0,113,0,115,115,115,0,0,113,113,113,114,116,113,114,115,116,112,113,114,115,116,112,112,113,114,115,116,114,116,113,113,114,115,116,112,114,113,114,115,116,112,114,113,114,115,116,112,112,114,112,112,114,114,116,116,113,114,114,116,114,113,114,115,116,112,113,114,115,116,112,114,113,114,115,116,112,112,128,128,128,128,128,136,134,145,145,134,134,134,128,0,0,0,128,0,0,0,0,136,135,135,135,135,0,0,128,128,135,136,145,134,100,123,125,98,128,135,136,145,134,100,123,125,98,100,123,125,98,128,135,136,145,134,100,123,125,98,100,123,125,98,100,123,125,98,128,135,136,145,134,100,123,125,98,100,123,125,98,100,123,125,98,100,123,125,98,100,123,125,98,100,123,125,98,100,123,125,98,0,0,0,0,128,135,136,145,134,134,100,123,125,98,134,128,135,136,145,134,100,123,125,98,128,135,136,145,134,100,123,125,98,100,123,125,98,0,0,0,0,0,0,0,0,0,0,0,0,0,147,146,147,146,149,148,149,148,147,146,149,148,0,141,142,143,144,137,122,14,141,21,142,46,143,49,144,137,122,14,141,21,142,46,143,49,144,122,122,122,122,141,142,143,144,141,142,143,144,141,142,143,144,141,142,143,144,137,122,14,141,21,142,46,143,49,144,137,122,14,141,21,142,46,143,49,144,14,21,46,49,141,142,143,144,137,14,141,21,142,46,143,49,144,137,122,14,21,46,49,137,137,122,14,141,21,142,46,143,49,144,137,122,14,141,21,142,46,143,49,144,137,122,14,141,21,142,46,143,49,144,0,0,0,0,0,0,0,0,0,0,0,0,0,154,155,154,155,156,157,156,157,154,155,156,157,0,150,151,152,153,138,124,15,150,22,151,159,152,160,153,154,155,156,157,138,124,15,150,22,151,159,152,160,153,154,155,156,157,124,124,124,124,150,151,152,153,154,155,156,157,150,151,152,153,150,151,152,153,150,151,152,153,154,155,156,157,154,155,156,157,138,124,15,150,22,151,159,152,160,153,154,155,156,157,138,124,15,150,22,151,159,152,160,153,154,155,156,157,15,22,159,160,150,151,152,153,138,15,150,22,151,159,152,160,153,138,124,15,22,159,160,138,138,124,15,150,22,151,159,152,160,153,154,155,156,157,138,124,15,150,22,151,159,152,160,153,154,155,156,157,138,124,15,150,22,151,159,152,160,153,154,155,156,157,0,0,0,0,0,0,0,0,0,0,0,0,0,147,146,147,146,149,148,149,148,147,146,149,148,0,161,162,163,164,139,126,165,161,166,162,167,163,57,164,147,146,149,148,139,126,165,161,166,162,167,163,57,164,147,146,149,148,126,126,126,126,161,162,163,164,147,146,149,148,161,162,163,164,161,162,163,164,161,162,163,164,147,146,149,148,147,146,149,148,139,126,165,161,166,162,167,163,57,164,147,146,149,148,139,126,165,161,166,162,167,163,57,164,147,146,149,148,165,166,167,57,161,162,163,164,139,165,161,166,162,167,163,57,164,139,126,165,166,167,57,139,139,126,165,161,166,162,167,163,57,164,147,146,149,148,139,126,165,161,166,162,167,163,57,164,147,146,149,148,139,126,165,161,166,162,167,163,57,164,147,146,149,148,0,0,0,0,0,0,0,0,0,0,0,0,0,172,173,172,173,174,175,174,175,172,173,174,175,0,168,169,170,171,140,127,99,168,176,169,177,170,178,171,172,173,174,175,140,127,99,168,176,169,177,170,178,171,172,173,174,175,127,127,127,127,168,169,170,171,172,173,174,175,168,169,170,171,168,169,170,171,168,169,170,171,172,173,174,175,172,173,174,175,140,127,99,168,176,169,177,170,178,171,172,173,174,175,140,127,99,168,176,169,177,170,178,171,172,173,174,175,99,176,177,178,168,169,170,171,140,99,168,176,169,177,170,178,171,140,127,99,176,177,178,140,140,127,99,168,176,169,177,170,178,171,172,173,174,175,140,127,99,168,176,169,177,170,178,171,172,173,174,175,140,127,99,168,176,169,177,170,178,171,172,173,174,175,0,0,0,0,198,191,191,0,198,191,198,191,183,198,191,183,183,0,183,198,191,183,0,0,198,191,183,0,183,0,183,183,0,0,183,198,191,183,198,191,183,198,191,183,183,194,0,194,0,0,0,194,184,184,195,193,194,184,195,193,194,193,194,193,193,194,193,194,184,195,193,194,184,195,193,194,193,193,195,193,195,0,184,195,193,194,184,195,193,194,184,195,193,194,0,197,197,197,197,197,197,197,197,197,197,197,0,0,0,199,200,201,199,200,201,199,200,199,200,199,200,199,199,200,199,201,199,200,201,199,200,201,201,200,201,199,200,201,199,200,201,199,200,201],"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,[[]],[[]],[[]],[[]],[[]],[[]],[1,1],[2,1],[[]],[3,1],[[]],[[]],[[]],[[]],[[]],[[4,5]],[[6,5]],[[[9,[7,8,3]],[11,[10]],[12,[10]],[13,[10]],14,15,16],4],[[[9,[17,8,3]],[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],[[]],[2,41],[3,41],[[]],[[]],[[]],[[]],[[]],[[42,5]],[[43,5]],[[[9,[44,8,3]],[45,[10]],46,16],42],[[[9,[47,8,3]],[48,[10]],49,16],43],[42],[43],[23,[[24,[41]]]],[[],24],[[],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],[[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,85],[[24,[59]]]],[[69,58,86],[[24,[59]]]],[66,[[24,[59]]]],0,[69,[[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],[[24,[59]]]],0,[60,[[24,[23,59]]]],[60,[[24,[59]]]],[[],[[24,[59]]]],[[60,23],[[24,[23,59]]]],[[],[[24,[59]]]],0,0,[[]],[[]],[[]],[[]],[87,88],[[]],[[]],[[]],[[]],[[66,89,67],87],[[],24],[[],24],[[],24],[[],24],[[],25],[[],25],[[88,90,[54,[51]],[54,[3]],[54,[51]]],88],[87],[88],0,0,0,0,[[]],[[]],[[]],[[]],[[89,91,23,23]],[[]],[[]],[[]],[[]],[[92,93,94,95,96],89],[89],[[],24],[[],24],[[],24],[[],24],[[],25],[[],25],0,[60,[[24,[23,59]]]],[60,[[24,[23,59]]]],0,[[]],[[]],[[]],[[]],[97,[[24,[[54,[3]],[54,[3]]]]]],[[98,99,100,[101,[55]],16],97],[97],[[],24],[[],24],[[],25],[[97,3]],0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[102,102],[103,103],0,0,[[],104],0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,0,0,0,0,0,[[105,106,107,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,[[]],[[]],[[]],[[]],[[]],[[]],[37,37],[[108,109],[[40,[109]]]],[[37,29],30],[[]],[[]],[[]],[[]],[[]],[[]],[[23,[111,[110]]],108],[[[40,[109]]],[[24,[23,37]]]],[[[40,[109]]],[[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,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[112],[113,113],[114,114],[115,115],[116,116],[[],114],[[],116],[28,[[24,[113]]]],[[113,29],30],[[114,29],30],[[115,29],30],[[116,29],30],[[112,29],30],0,[[]],[[]],[[]],[[]],[[]],[[114,117],[[24,[16,118]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[],119],[116,112],[112,[[54,[2]]]],[120,[[24,[85,121]]]],0,0,0,[[113,33],24],[[114,117],[[24,[16,118]]]],0,0,0,[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[114,23,23],[[24,[116,115]]]],[[],25],[[],25],[[],25],[[],25],[[],25],[[112,116]],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,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[100,122],[123,124],[125,126],[98,127],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[100,128]],[[123,128]],[[125,128]],[[98,128]],[100,51],[123,3],[125,51],[98,3],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[130,[129]]],100],[[[130,[131]]],123],[[[130,[132]]],125],[[[130,[133]]],98],[[100,134]],[[123,134]],[[125,134]],[[98,134]],[[100,51]],[[123,3]],[[125,51]],[[98,3]],[[100,135,136]],[[123,135,136]],[[125,135,136]],[[98,135,136]],[[100,135]],[[123,135]],[[125,135]],[[98,135]],[100],[123],[125],[98],0,0,0,0,[[],24],[[],24],[[],24],[[],24],[[],24],[34,[[24,[134,[35,[134]]]]]],[[],24],[[],24],[[],24],[[],24],[[],[[24,[134,[35,[134]]]]]],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],[[],25],[100,137],[123,138],[125,139],[98,140],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,[141,16],[142,16],[143,16],[144,16],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,[141,85],[142,85],[143,85],[144,85],[[141,145]],[[142,145]],[[143,145]],[[144,145]],[[141,134]],[[142,134]],[[143,134]],[[144,134]],[141],[142],[143],[144],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[14,146],141],[[21,147],142],[[46,148],143],[[49,149],144],[141,[[24,[[54,[51]],[54,[51]]]]]],[142,[[24,[[54,[51]],[54,[51]]]]]],[143,[[24,[[54,[51]],[54,[51]]]]]],[144,[[24,[[54,[51]],[54,[51]]]]]],[137],[14],[141],[21],[142],[46],[143],[49],[144],[[],137],[[],122],[[14,51]],[[21,51]],[[46,51]],[[49,51]],[137],[[],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,[150,16],[151,16],[152,16],[153,16],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,[150,85],[151,85],[152,85],[153,85],[154,154],[155,155],[156,156],[157,157],[[150,145]],[[151,145]],[[152,145]],[[153,145]],[[150,134]],[[151,134]],[[152,134]],[[153,134]],[150],[151],[152],[153],[[154,154],85],[[155,155],85],[[156,156],85],[[157,157],85],[[154,29],[[24,[158]]]],[[155,29],[[24,[158]]]],[[156,29],[[24,[158]]]],[[157,29],[[24,[158]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[15,155],150],[[22,154],151],[[159,157],152],[[160,156],153],[150,[[24,[[54,[3]],[54,[3]]]]]],[151,[[24,[[54,[3]],[54,[3]]]]]],[152,[[24,[[54,[3]],[54,[3]]]]]],[153,[[24,[[54,[3]],[54,[3]]]]]],[138],[15],[150],[22],[151],[159],[152],[160],[153],[[],138],[[],124],[[15,3]],[[22,3]],[[159,3]],[[160,3]],[138],[[],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,[161,16],[162,16],[163,16],[164,16],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,[161,85],[162,85],[163,85],[164,85],[147,147],[146,146],[149,149],[148,148],[[161,145]],[[162,145]],[[163,145]],[[164,145]],[[161,134]],[[162,134]],[[163,134]],[[164,134]],[161],[162],[163],[164],[[147,147],85],[[146,146],85],[[149,149],85],[[148,148],85],[[147,29],[[24,[158]]]],[[146,29],[[24,[158]]]],[[149,29],[[24,[158]]]],[[148,29],[[24,[158]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[165,146],161],[[166,147],162],[[167,148],163],[[57,149],164],[161,[[24,[[54,[51]],[54,[51]]]]]],[162,[[24,[[54,[51]],[54,[51]]]]]],[163,[[24,[[54,[51]],[54,[51]]]]]],[164,[[24,[[54,[51]],[54,[51]]]]]],[139],[165],[161],[166],[162],[167],[163],[57],[164],[[],139],[[],126],[[165,51]],[[166,51]],[[167,51]],[[57,51]],[139],[[],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,[168,16],[169,16],[170,16],[171,16],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,[168,85],[169,85],[170,85],[171,85],[172,172],[173,173],[174,174],[175,175],[[168,145]],[[169,145]],[[170,145]],[[171,145]],[[168,134]],[[169,134]],[[170,134]],[[171,134]],[168],[169],[170],[171],[[172,172],85],[[173,173],85],[[174,174],85],[[175,175],85],[[172,29],[[24,[158]]]],[[173,29],[[24,[158]]]],[[174,29],[[24,[158]]]],[[175,29],[[24,[158]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[99,173],168],[[176,172],169],[[177,175],170],[[178,174],171],[168,[[24,[[54,[3]],[54,[3]]]]]],[169,[[24,[[54,[3]],[54,[3]]]]]],[170,[[24,[[54,[3]],[54,[3]]]]]],[171,[[24,[[54,[3]],[54,[3]]]]]],[140],[99],[168],[176],[169],[177],[170],[178],[171],[[],140],[[],127],[[99,3]],[[176,3]],[[177,3]],[[178,3]],[140],[[],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,[[]],[[]],[[]],[[]],[[]],[[]],[[[183,[[0,[179,180,181]],182]],[52,[34]],34],184],0,[[[183,[[0,[179,180,181]],182]],185]],[[]],[[]],[[]],[[186,187],120],0,[[]],[[]],[[]],0,0,0,[[188,189,107,186,187,190],[[183,[[0,[179,180,181]],182]]]],0,0,0,0,[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],25],[[],25],[[],25],[[[183,[[0,[179,180,181]],182]]],191],0,0,0,0,0,0,0,[[184,192]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[193,193],[194,194],[[],193],[28,[[24,[193]]]],[[194,194],85],[[193,29],30],[[194,29],30],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,[195],[[193,33],24],[[195,185]],[196],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],25],[[],25],[[],25],[[],25],0,[[]],[[]],[[]],[197],[[]],[[196,189],197],0,[[],24],[[],24],[[],25],[197,198],0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[199,199],0,0,0,[[],199],0,0,[[199,27,27,23],200],[[]],[[]],[[]],[[]],[[]],[[]],[[196,107,186,186,190],[[201,[182]]]],[[[201,[182]],182]],[[200,33],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],25],[[],25],[[],25],[[[201,[182]]]]],"c":[],"p":[[3,"AdcCode"],[15,"i16"],[15,"u16"],[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,"bool"],[4,"Level"],[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"],[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,"Peekable"],[4,"Error"],[3,"Metadata"],[3,"String"],[4,"IterError"],[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,"Miniconf"],[8,"Clone"],[8,"Serialize"],[3,"NetworkUsers"],[3,"FrameGenerator"],[4,"SocketAddr"],[15,"str"],[3,"Address"],[6,"NetworkStack"],[6,"EthernetPhy"],[4,"IpAddr"],[4,"NetworkState"],[8,"FnMut"],[3,"StreamTarget"],[4,"StreamFormat"],[3,"DataStream"],[6,"NetworkReference"],[3,"NetworkProcessor"],[4,"UpdateState"],[3,"TelemetryBuffer"],[3,"Telemetry"],[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..72f9b3c13d
--- /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 +
//! # 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,
+ signal_generator::{self, SignalGenerator},
+ timers::SamplingTimer,
+ DigitalInput0, DigitalInput1, SystemTimer, Systick, AFE0, AFE1,
+ },
+ net::{
+ data_stream::{FrameGenerator, StreamFormat, StreamTarget},
+ miniconf::Miniconf,
+ 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, Miniconf)]
+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.
+ #[miniconf(defer)]
+ 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]
+ #[miniconf(defer)]
+ iir_ch: miniconf::Array<[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]
+ #[miniconf(defer)]
+ signal_generator: miniconf::Array<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]
+ .into(),
+
+ // 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]
+ .into(),
+
+ 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 {
+ network: NetworkUsers<Settings, Telemetry>,
+
+ 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("10.34.16.1")
+ .parse()
+ .unwrap(),
+ );
+
+ let generator = network
+ .configure_streaming(StreamFormat::AdcDacData, BATCH_SIZE as _);
+
+ let settings = Settings::default();
+
+ let shared = Shared {
+ 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();
+ 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>()
+ / core::mem::size_of::<MaybeUninit<u8>>();
+ generator.add::<_, { N * 4 }>(|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)
+ }
+ });
+ // 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])]
+ 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 => 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=[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,
+ 0x0fbdb026,
+ -0x25b32e58,
+ 0x43b34e3c,
+ -0x3bc82700,
+ ];
+ 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 + (1 << 30)) >> 31) as i32,
+ im: ((b * c + a * d + (1 << 30)) >> 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 + (1 << 30)) >> 31) as i32,
+ im: ((other as i64 * self.im as i64 + (1 << 30)) >> 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 +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);
+ }
+}
+
#![cfg_attr(not(test), no_std)]
+
+mod tools;
+pub use tools::*;
+mod atan2;
+pub use atan2::*;
+mod accu;
+pub use accu::*;
+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::*;
+
+#[cfg(test)]
+pub mod testing;
+
use super::{Complex, ComplexExt, Lowpass, MulScaled};
+
+#[derive(Copy, Clone, Default)]
+pub struct Lockin<const N: usize> {
+ state: [Lowpass<N>; 2],
+}
+
+impl<const N: usize> Lockin<N> {
+ /// 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: u32) -> 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: u32) -> 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 +
/// Arbitrary order, high dynamic range, wide coefficient range,
+/// lowpass filter implementation. DC gain is 1.
+///
+/// Type argument N is the filter order.
+#[derive(Copy, Clone)]
+pub struct Lowpass<const N: usize> {
+ // IIR state storage
+ y: [i32; N],
+}
+
+impl<const N: usize> Default for Lowpass<N> {
+ fn default() -> Self {
+ Lowpass { y: [0i32; N] }
+ }
+}
+
+impl<const N: usize> Lowpass<N> {
+ /// Update the filter with a new sample.
+ ///
+ /// # Args
+ /// * `x`: Input data. Needs 1 bit headroom but will saturate cleanly beyond that.
+ /// * `k`: Log2 time constant, 1..=31.
+ ///
+ /// # Return
+ /// Filtered output y.
+ pub fn update(&mut self, x: i32, k: u32) -> i32 {
+ debug_assert!(k & 31 == k);
+ // This is an unrolled and optimized first-order IIR loop
+ // that works for all possible time constants.
+ // Note T-DF-I and the zeros at Nyquist.
+ let mut x = x;
+ for y in self.y.iter_mut() {
+ let dy = x.saturating_sub(*y) >> k;
+ *y += dy;
+ x = *y - (dy >> 1);
+ }
+ x.saturating_add((N as i32) << (k - 1).max(0))
+ }
+
+ /// Return the current filter output
+ pub fn output(&self) -> i32 {
+ self.y[N - 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 +
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: i32,
+ // filtered output phase
+ y: i32,
+}
+
+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.
+ /// * `shift_frequency`: Frequency error scaling. The frequency gain per update is
+ /// `1/(1 << shift_frequency)`.
+ /// * `shift_phase`: Phase error scaling. The phase gain is `1/(1 << shift_phase)`
+ /// per update. A good value is typically `shift_frequency - 1`.
+ ///
+ /// Returns:
+ /// A tuple of instantaneous phase and frequency estimates.
+ pub fn update(&mut self, x: Option<i32>, shift_frequency: u32, shift_phase: u32) -> (i32, i32) {
+ debug_assert!((1..=30).contains(&shift_frequency));
+ debug_assert!((1..=30).contains(&shift_phase));
+ if let Some(x) = x {
+ let df = (1i32 << (shift_frequency - 1))
+ .wrapping_add(x)
+ .wrapping_sub(self.x)
+ .wrapping_sub(self.f)
+ >> shift_frequency;
+ self.x = x;
+ self.f = self.f.wrapping_add(df);
+ let f = self.f.wrapping_sub(df >> 1);
+ self.y = self.y.wrapping_add(f);
+ let dy = (1i32 << (shift_phase - 1))
+ .wrapping_add(x)
+ .wrapping_sub(self.y)
+ >> shift_phase;
+ self.y = self.y.wrapping_add(dy);
+ let y = self.y.wrapping_sub(dy >> 1);
+ (y, f.wrapping_add(dy))
+ } else {
+ self.x = self.x.wrapping_add(self.f);
+ self.y = self.y.wrapping_add(self.f);
+ (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) -> i32 {
+ self.f
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ #[test]
+ fn mini() {
+ let mut p = PLL::default();
+ let (y, f) = p.update(Some(0x10000), 8, 4);
+ assert_eq!(y, 0x87c);
+ assert_eq!(f, 0x1078);
+ }
+
+ #[test]
+ fn converge() {
+ let mut p = PLL::default();
+ let f0 = 0x71f63049_i32;
+ let shift = (10, 9);
+ let n = 31 << shift.0 + 2;
+ let mut x = 0i32;
+ for i in 0..n {
+ x = x.wrapping_add(f0);
+ let (y, f) = p.update(Some(x), shift.0, shift.1);
+ if i > n / 4 {
+ // The remaining error would be removed by dithering.
+ assert_eq!(f.wrapping_sub(f0).abs() <= 1 << 10, true);
+ }
+ if i > n / 2 {
+ // The remaining error would be removed by dithering.
+ assert_eq!(y.wrapping_sub(x).abs() < 1 << 18, 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 as i32);
+ (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 +
//! # 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, Complex, ComplexExt, Lockin, RPLL};
+
+use stabilizer::{
+ hardware::{
+ self,
+ adc::{Adc0Input, Adc1Input, AdcCode},
+ afe::Gain,
+ dac::{Dac0Output, Dac1Output, DacCode},
+ hal,
+ input_stamper::InputStamper,
+ signal_generator,
+ timers::SamplingTimer,
+ DigitalInput0, DigitalInput1, SystemTimer, Systick, AFE0, AFE1,
+ },
+ net::{
+ data_stream::{FrameGenerator, StreamFormat, StreamTarget},
+ miniconf::Miniconf,
+ 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, Miniconf)]
+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.
+ #[miniconf(defer)]
+ 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 time constant.
+ ///
+ /// # Path
+ /// `lockin_tc`
+ ///
+ /// # Value
+ /// The lockin low-pass time constant as an unsigned byte (0-255).
+ lockin_tc: u32,
+
+ /// 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.
+ #[miniconf(defer)]
+ 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_tc: 6, // lockin lowpass time constant
+ 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 {
+ network: NetworkUsers<Settings, Telemetry>,
+ 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<4>,
+ 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("10.34.16.1")
+ .parse()
+ .unwrap(),
+ );
+
+ let generator = network
+ .configure_streaming(StreamFormat::AdcDacData, BATCH_SIZE as _);
+
+ let shared = Shared {
+ network,
+ 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_tc)
+ })
+ // 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::<_, { N * 4 }>(|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)
+ }
+ });
+
+ // 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])]
+ 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 => 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=[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 +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 +
use super::{Error, IterError, Metadata, Miniconf, Peekable};
+use core::ops::{Deref, DerefMut};
+
+/// An array that exposes each element through their [`Miniconf`] implementation.
+///
+/// # Design
+///
+/// Miniconf supports homogeneous arrays of items contained in structures using two forms. For the
+/// [`miniconf::Array`](Array), each item of the array is accessed as a [`Miniconf`] tree.
+///
+/// For standard arrays of [`[T; N]`](array) form, by default the entire array is accessed as one atomic
+/// value. By adding the `#[miniconf(defer)]` attribute, each index of the array is is instead accessed as
+/// one atomic value (i.e. a single Miniconf item).
+///
+/// The type you should use depends on what data is contained in your array. If your array contains
+/// `Miniconf` items, you can (and often want to) use [`Array`] and the `#[miniconf(defer)]` attribute.
+/// However, if each element in your list is individually configurable as a single value (e.g. a list
+/// of `u32`), then you must use a standard [`[T; N]`](array) array but you may optionally
+/// `#[miniconf(defer)]` access to individual indices.
+///
+/// # Construction
+///
+/// An `Array` can be constructed using [`From<[T; N]>`](From)/[`Into<miniconf::Array>`]
+/// and the contained value can be accessed through [`Deref`]/[`DerefMut`].
+#[derive(Clone, Copy, PartialEq, Eq, Debug, PartialOrd, Ord, Hash)]
+#[repr(transparent)]
+pub struct Array<T, const N: usize>([T; N]);
+
+impl<T, const N: usize> Deref for Array<T, N> {
+ type Target = [T; N];
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl<T, const N: usize> DerefMut for Array<T, N> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.0
+ }
+}
+
+impl<T: Default + Copy, const N: usize> Default for Array<T, N> {
+ fn default() -> Self {
+ Self([T::default(); N])
+ }
+}
+
+impl<T, const N: usize> From<[T; N]> for Array<T, N> {
+ fn from(x: [T; N]) -> Self {
+ Self(x)
+ }
+}
+
+impl<T, const N: usize> AsRef<[T; N]> for Array<T, N> {
+ fn as_ref(&self) -> &[T; N] {
+ self
+ }
+}
+
+impl<T, const N: usize> AsMut<[T; N]> for Array<T, N> {
+ fn as_mut(&mut self) -> &mut [T; N] {
+ self
+ }
+}
+
+impl<T, const N: usize> IntoIterator for Array<T, N> {
+ type Item = T;
+ type IntoIter = <[T; N] as IntoIterator>::IntoIter;
+ fn into_iter(self) -> Self::IntoIter {
+ self.0.into_iter()
+ }
+}
+
+impl<'a, T, const N: usize> IntoIterator for &'a Array<T, N> {
+ type Item = <&'a [T; N] as IntoIterator>::Item;
+ type IntoIter = <&'a [T; N] as IntoIterator>::IntoIter;
+ fn into_iter(self) -> Self::IntoIter {
+ self.iter()
+ }
+}
+
+impl<'a, T, const N: usize> IntoIterator for &'a mut Array<T, N> {
+ type Item = <&'a mut [T; N] as IntoIterator>::Item;
+ type IntoIter = <&'a mut [T; N] as IntoIterator>::IntoIter;
+ fn into_iter(self) -> Self::IntoIter {
+ self.iter_mut()
+ }
+}
+
+impl<T, const N: usize> From<Array<T, N>> for [T; N] {
+ fn from(x: Array<T, N>) -> Self {
+ x.0
+ }
+}
+
+/// Returns the number of digits required to format an integer less than `x`.
+const fn digits(x: usize) -> usize {
+ let mut n = 10;
+ let mut num_digits = 1;
+
+ while x > n {
+ n *= 10;
+ num_digits += 1;
+ }
+ num_digits
+}
+
+impl<T: Miniconf, const N: usize> Miniconf for Array<T, N> {
+ fn set_path<'a, P: Peekable<Item = &'a str>>(
+ &mut self,
+ path_parts: &'a mut P,
+ value: &[u8],
+ ) -> Result<usize, Error> {
+ let i = self.0.index(path_parts.next())?;
+
+ self.0
+ .get_mut(i)
+ .ok_or(Error::BadIndex)?
+ .set_path(path_parts, value)
+ }
+
+ fn get_path<'a, P: Peekable<Item = &'a str>>(
+ &self,
+ path_parts: &'a mut P,
+ value: &mut [u8],
+ ) -> Result<usize, Error> {
+ let i = self.0.index(path_parts.next())?;
+
+ self.0
+ .get(i)
+ .ok_or(Error::BadIndex)?
+ .get_path(path_parts, value)
+ }
+
+ fn metadata() -> Metadata {
+ let mut meta = T::metadata();
+
+ // Unconditionally account for separator since we add it
+ // even if elements that are deferred to (`Options`)
+ // may have no further hierarchy to add and remove the separator again.
+ meta.max_length += digits(N) + 1;
+ meta.max_depth += 1;
+ meta.count *= N;
+
+ meta
+ }
+
+ fn next_path<const TS: usize>(
+ state: &mut [usize],
+ topic: &mut heapless::String<TS>,
+ ) -> Result<bool, IterError> {
+ let original_length = topic.len();
+
+ while *state.first().ok_or(IterError::PathDepth)? < N {
+ // Add the array index and separator to the topic name.
+ topic
+ .push_str(itoa::Buffer::new().format(state[0]))
+ .and_then(|_| topic.push('/'))
+ .map_err(|_| IterError::PathLength)?;
+
+ if T::next_path(&mut state[1..], topic)? {
+ return Ok(true);
+ }
+
+ // Strip off the previously prepended index, since we completed that element and need
+ // to instead check the next one.
+ topic.truncate(original_length);
+
+ state[0] += 1;
+ state[1..].fill(0);
+ }
+
+ Ok(false)
+ }
+}
+
+trait IndexLookup {
+ fn index(&self, next: Option<&str>) -> Result<usize, Error>;
+}
+
+impl<T, const N: usize> IndexLookup for [T; N] {
+ fn index(&self, next: Option<&str>) -> Result<usize, Error> {
+ let next = next.ok_or(Error::PathTooShort)?;
+
+ // Parse what should be the index value
+ next.parse().map_err(|_| Error::BadIndex)
+ }
+}
+
+impl<T: crate::Serialize + crate::DeserializeOwned, const N: usize> Miniconf for [T; N] {
+ fn set_path<'a, P: Peekable<Item = &'a str>>(
+ &mut self,
+ path_parts: &mut P,
+ value: &[u8],
+ ) -> Result<usize, Error> {
+ let i = self.index(path_parts.next())?;
+
+ if path_parts.peek().is_some() {
+ return Err(Error::PathTooLong);
+ }
+
+ let item = <[T]>::get_mut(self, i).ok_or(Error::BadIndex)?;
+ let (value, len) = serde_json_core::from_slice(value)?;
+ *item = value;
+ Ok(len)
+ }
+
+ fn get_path<'a, P: Peekable<Item = &'a str>>(
+ &self,
+ path_parts: &mut P,
+ value: &mut [u8],
+ ) -> Result<usize, Error> {
+ let i = self.index(path_parts.next())?;
+
+ if path_parts.peek().is_some() {
+ return Err(Error::PathTooLong);
+ }
+
+ let item = <[T]>::get(self, i).ok_or(Error::BadIndex)?;
+ Ok(serde_json_core::to_slice(item, value)?)
+ }
+
+ fn metadata() -> Metadata {
+ Metadata {
+ max_length: digits(N),
+ max_depth: 1,
+ count: N,
+ }
+ }
+
+ fn next_path<const TS: usize>(
+ state: &mut [usize],
+ path: &mut heapless::String<TS>,
+ ) -> Result<bool, IterError> {
+ if *state.first().ok_or(IterError::PathDepth)? < N {
+ // Add the array index to the topic name.
+ path.push_str(itoa::Buffer::new().format(state[0]))
+ .map_err(|_| IterError::PathLength)?;
+
+ state[0] += 1;
+ Ok(true)
+ } else {
+ Ok(false)
+ }
+ }
+}
+
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 +
use super::Miniconf;
+use core::marker::PhantomData;
+use heapless::String;
+
+/// An iterator over the paths in a Miniconf namespace.
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct MiniconfIter<M: ?Sized, const L: usize, const TS: usize> {
+ /// Zero-size marker field to allow being generic over M and gaining access to M.
+ marker: PhantomData<M>,
+
+ /// 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; L],
+
+ /// The remaining length of the iterator.
+ ///
+ /// It is used to provide an exact and trusted [Iterator::size_hint].
+ /// C.f. [core::iter::TrustedLen].
+ ///
+ /// It may be None to indicate unknown length.
+ count: Option<usize>,
+}
+
+impl<M: ?Sized, const L: usize, const TS: usize> Default for MiniconfIter<M, L, TS> {
+ fn default() -> Self {
+ MiniconfIter {
+ marker: PhantomData,
+ state: [0; L],
+ count: None,
+ }
+ }
+}
+
+impl<M: ?Sized, const L: usize, const TS: usize> MiniconfIter<M, L, TS> {
+ pub fn new(count: Option<usize>) -> Self {
+ Self {
+ count,
+ ..Default::default()
+ }
+ }
+}
+
+impl<M: Miniconf + ?Sized, const L: usize, const TS: usize> Iterator for MiniconfIter<M, L, TS> {
+ type Item = String<TS>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ let mut path = Self::Item::new();
+
+ if M::next_path(&mut self.state, &mut path).unwrap() {
+ self.count = self.count.map(|c| c - 1);
+ Some(path)
+ } else {
+ debug_assert_eq!(self.count.unwrap_or_default(), 0);
+ None
+ }
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ (self.count.unwrap_or_default(), self.count)
+ }
+}
+
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 +
#![no_std]
+#![doc = include_str!("../README.md")]
+
+mod array;
+mod iter;
+mod option;
+
+pub use array::Array;
+pub use iter::MiniconfIter;
+pub use miniconf_derive::Miniconf;
+pub use option::Option;
+
+#[cfg(feature = "mqtt-client")]
+mod mqtt_client;
+
+#[cfg(feature = "mqtt-client")]
+pub use mqtt_client::MqttClient;
+
+// Re-exports
+pub use heapless;
+pub use serde;
+pub use serde_json_core;
+
+#[cfg(feature = "mqtt-client")]
+pub use minimq;
+
+#[cfg(feature = "mqtt-client")]
+pub use minimq::embedded_time;
+
+#[doc(hidden)]
+pub use serde::{
+ de::{Deserialize, DeserializeOwned},
+ ser::Serialize,
+};
+
+/// Errors that can occur when using the [Miniconf] API.
+#[non_exhaustive]
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum Error {
+ /// The provided path wasn't found in the structure.
+ ///
+ /// Double check the provided path to verify that it's valid.
+ PathNotFound,
+
+ /// The provided path was valid, but there was trailing data at the end.
+ ///
+ /// Check the end of the path and remove any excess characters.
+ PathTooLong,
+
+ /// The provided path was valid, but did not specify a value fully.
+ ///
+ /// Double check the ending and add the remainder of the path.
+ PathTooShort,
+
+ /// The value provided for configuration could not be deserialized into the proper type.
+ ///
+ /// Check that the serialized data is valid JSON and of the correct type.
+ Deserialization(serde_json_core::de::Error),
+
+ /// The value provided could not be serialized.
+ ///
+ /// Check that the buffer had sufficient space.
+ Serialization(serde_json_core::ser::Error),
+
+ /// When indexing into an array, the index provided was out of bounds.
+ ///
+ /// Check array indices to ensure that bounds for all paths are respected.
+ BadIndex,
+
+ /// The path does not exist at runtime.
+ ///
+ /// This is the case if a deferred [core::option::Option] or [Option]
+ /// is `None` at runtime. `PathAbsent` takes precedence over `PathNotFound`
+ /// if the path is simultaneously masked by a `Option::None` at runtime but
+ /// would still be non-existent if it weren't.
+ PathAbsent,
+}
+
+/// Errors that occur during iteration over topic paths.
+#[non_exhaustive]
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+pub enum IterError {
+ /// The provided state vector is not long enough.
+ PathDepth,
+
+ /// The provided topic length is not long enough.
+ PathLength,
+}
+
+impl From<Error> for u8 {
+ fn from(err: Error) -> u8 {
+ match err {
+ Error::PathNotFound => 1,
+ Error::PathTooLong => 2,
+ Error::PathTooShort => 3,
+ Error::Deserialization(_) => 5,
+ Error::BadIndex => 6,
+ Error::Serialization(_) => 7,
+ Error::PathAbsent => 8,
+ }
+ }
+}
+
+impl From<serde_json_core::de::Error> for Error {
+ fn from(err: serde_json_core::de::Error) -> Error {
+ Error::Deserialization(err)
+ }
+}
+
+impl From<serde_json_core::ser::Error> for Error {
+ fn from(err: serde_json_core::ser::Error) -> Error {
+ Error::Serialization(err)
+ }
+}
+
+/// Metadata about a [Miniconf] namespace.
+#[non_exhaustive]
+#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
+pub struct Metadata {
+ /// The maximum length of a path.
+ pub max_length: usize,
+
+ /// The maximum path depth.
+ pub max_depth: usize,
+
+ /// The number of paths.
+ pub count: usize,
+}
+
+/// Helper trait for [core::iter::Peekable].
+pub trait Peekable: core::iter::Iterator {
+ fn peek(&mut self) -> core::option::Option<&Self::Item>;
+}
+
+impl<I: core::iter::Iterator> Peekable for core::iter::Peekable<I> {
+ fn peek(&mut self) -> core::option::Option<&Self::Item> {
+ core::iter::Peekable::peek(self)
+ }
+}
+
+/// Trait exposing serialization/deserialization of elements by path.
+pub trait Miniconf {
+ /// Update an element by path.
+ ///
+ /// # Args
+ /// * `path` - The path to the element with '/' as the separator.
+ /// * `data` - The serialized data making up the content.
+ ///
+ /// # Returns
+ /// The number of bytes consumed from `data` or an [Error].
+ fn set(&mut self, path: &str, data: &[u8]) -> Result<usize, Error> {
+ self.set_path(&mut path.split('/').peekable(), data)
+ }
+
+ /// Retrieve a serialized value by path.
+ ///
+ /// # Args
+ /// * `path` - The path to the element with '/' as the separator.
+ /// * `data` - The buffer to serialize the data into.
+ ///
+ /// # Returns
+ /// The number of bytes used in the `data` buffer or an [Error].
+ fn get(&self, path: &str, data: &mut [u8]) -> Result<usize, Error> {
+ self.get_path(&mut path.split('/').peekable(), data)
+ }
+
+ /// Create an iterator of all possible paths.
+ ///
+ /// This is a depth-first walk.
+ /// The iterator will walk all paths, even those that may be absent at run-time (see [Option]).
+ /// The iterator has an exact and trusted [Iterator::size_hint].
+ ///
+ /// # Template Arguments
+ /// * `L` - The maximum depth of the path, i.e. number of separators plus 1.
+ /// * `TS` - The maximum length of the path in bytes.
+ ///
+ /// # Returns
+ /// A [MiniconfIter] of paths or an [IterError] if `L` or `TS` are insufficient.
+ fn iter_paths<const L: usize, const TS: usize>(
+ ) -> Result<iter::MiniconfIter<Self, L, TS>, IterError> {
+ let meta = Self::metadata();
+
+ if TS < meta.max_length {
+ return Err(IterError::PathLength);
+ }
+
+ if L < meta.max_depth {
+ return Err(IterError::PathDepth);
+ }
+
+ Ok(Self::unchecked_iter_paths(Some(meta.count)))
+ }
+
+ /// Create an iterator of all possible paths.
+ ///
+ /// This is a depth-first walk.
+ /// It will return all paths, even those that may be absent at run-time.
+ ///
+ /// # Note
+ /// This does not check that the path size or state vector are large enough. If they are not,
+ /// panics may be generated internally by the library.
+ ///
+ /// # Args
+ /// * `count`: Optional iterator length if known.
+ ///
+ /// # Template Arguments
+ /// * `L` - The maximum depth of the path, i.e. number of separators plus 1.
+ /// * `TS` - The maximum length of the path in bytes.
+ fn unchecked_iter_paths<const L: usize, const TS: usize>(
+ count: core::option::Option<usize>,
+ ) -> iter::MiniconfIter<Self, L, TS> {
+ iter::MiniconfIter::new(count)
+ }
+
+ /// Deserialize an element by path.
+ ///
+ /// # Args
+ /// * `path_parts`: A `Peekable` `Iterator` identifying the element.
+ /// * `value`: A slice containing the data to be deserialized.
+ ///
+ /// # Returns
+ /// The number of bytes consumed from `value` or an `Error`.
+ fn set_path<'a, P: Peekable<Item = &'a str>>(
+ &mut self,
+ path_parts: &'a mut P,
+ value: &[u8],
+ ) -> Result<usize, Error>;
+
+ /// Serialize an element by path.
+ ///
+ /// # Args
+ /// * `path_parts`: A `Peekable` `Iterator` identifying the element.
+ /// * `value`: A slice for the value to be serialized into.
+ ///
+ /// # Returns
+ /// The number of bytes written to `value` or an `Error`.
+ fn get_path<'a, P: Peekable<Item = &'a str>>(
+ &self,
+ path_parts: &'a mut P,
+ value: &mut [u8],
+ ) -> Result<usize, Error>;
+
+ /// Get the next path in the namespace.
+ ///
+ /// This is usually not called directly but through a [MiniconfIter] returned by [Miniconf::iter_paths].
+ ///
+ /// # Args
+ /// * `state`: A state array indicating the path to be retrieved.
+ /// A zeroed vector indicates the first path. The vector is advanced
+ /// such that the next element will be retrieved when called again.
+ /// The array needs to be at least as long as the maximum path depth.
+ /// * `path`: A string to write the path into.
+ ///
+ /// # Returns
+ /// A `bool` indicating a valid path was written to `path` from the given `state`.
+ /// If `false`, `path` is invalid and there are no more paths within `self` at and
+ /// beyond `state`.
+ /// May return `IterError` indicating insufficient `state` or `path` size.
+ fn next_path<const TS: usize>(
+ state: &mut [usize],
+ path: &mut heapless::String<TS>,
+ ) -> Result<bool, IterError>;
+
+ /// Get metadata about the paths in the namespace.
+ fn metadata() -> Metadata;
+}
+
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 +
use serde_json_core::heapless::{String, Vec};
+
+use crate::Miniconf;
+use minimq::{
+ embedded_nal::{IpAddr, TcpClientStack},
+ embedded_time,
+ types::{SubscriptionOptions, TopicFilter},
+ Publication, QoS, Retain,
+};
+
+use core::fmt::Write;
+
+// The maximum topic length of any settings path.
+const MAX_TOPIC_LENGTH: usize = 128;
+
+// The keepalive interval to use for MQTT in seconds.
+const KEEPALIVE_INTERVAL_SECONDS: u16 = 60;
+
+// The maximum recursive depth of a settings structure.
+const MAX_RECURSION_DEPTH: usize = 8;
+
+// The delay after not receiving messages after initial connection that settings will be
+// republished.
+const REPUBLISH_TIMEOUT_SECONDS: u32 = 2;
+
+type MiniconfIter<M> = crate::MiniconfIter<M, MAX_RECURSION_DEPTH, MAX_TOPIC_LENGTH>;
+
+mod sm {
+ 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: super::Miniconf + ?Sized> {
+ clock: C,
+ timeout: Option<Instant<C>>,
+ pub republish_state: super::MiniconfIter<M>,
+ }
+
+ impl<C: embedded_time::Clock, M: super::Miniconf> Context<C, M> {
+ pub fn new(clock: C) -> Self {
+ Self {
+ clock,
+ timeout: None,
+ republish_state: Default::default(),
+ }
+ }
+
+ 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: super::Miniconf> StateMachineContext for Context<C, M> {
+ fn start_republish_timeout(&mut self) {
+ self.timeout.replace(
+ self.clock.try_now().unwrap() + super::REPUBLISH_TIMEOUT_SECONDS.seconds(),
+ );
+ }
+
+ fn start_republish(&mut self) {
+ self.republish_state = Default::default();
+ }
+ }
+}
+
+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.split_once('/') {
+ Some(("settings", path)) => {
+ if value.is_empty() {
+ Ok(Command::Get { path })
+ } else {
+ Ok(Command::Set { path, value })
+ }
+ }
+ _ => Err(()),
+ }
+ }
+ }
+}
+
+/// MQTT settings interface.
+///
+/// # Design
+/// The MQTT client places the [Miniconf] 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 128 byte length and maximum depth of 8.
+/// Keepalive interval and re-publication timeout are fixed to 60 and 2 seconds respectively.
+///
+/// # Example
+/// ```
+/// use miniconf::{MqttClient, Miniconf};
+///
+/// #[derive(Miniconf, Clone, Default)]
+/// struct Settings {
+/// foo: bool,
+/// }
+///
+/// let mut client: MqttClient<Settings, _, _, 256> = MqttClient::new(
+/// std_embedded_nal::Stack::default(),
+/// "", // client_id auto-assign
+/// "quartiq/application/12345", // prefix
+/// "127.0.0.1".parse().unwrap(),
+/// std_embedded_time::StandardClock::default(),
+/// Settings::default(),
+/// )
+/// .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<Settings, Stack, Clock, const MESSAGE_SIZE: usize>
+where
+ Settings: Miniconf + Clone,
+ Stack: TcpClientStack,
+ Clock: embedded_time::Clock,
+{
+ mqtt: minimq::Minimq<Stack, Clock, MESSAGE_SIZE, 1>,
+ settings: Settings,
+ state: sm::StateMachine<sm::Context<Clock, Settings>>,
+ prefix: String<MAX_TOPIC_LENGTH>,
+ listing_state: Option<MiniconfIter<Settings>>,
+ properties_cache: Option<Vec<u8, MESSAGE_SIZE>>,
+ pending_response: Option<Response<32>>,
+}
+
+impl<Settings, Stack, Clock, const MESSAGE_SIZE: usize>
+ MqttClient<Settings, Stack, Clock, MESSAGE_SIZE>
+where
+ Settings: Miniconf + Clone,
+ Stack: TcpClientStack,
+ Clock: embedded_time::Clock + Clone,
+{
+ /// Construct a new MQTT settings interface.
+ ///
+ /// # Args
+ /// * `stack` - The network stack to use for communication.
+ /// * `client_id` - The ID of the MQTT client. May be an empty string for auto-assigning.
+ /// * `prefix` - The MQTT device prefix to use for this device.
+ /// * `broker` - The IP address of the MQTT broker to use.
+ /// * `clock` - The clock for managing the MQTT connection.
+ /// * `settings` - The initial settings values.
+ pub fn new(
+ stack: Stack,
+ client_id: &str,
+ prefix: &str,
+ broker: IpAddr,
+ clock: Clock,
+ settings: Settings,
+ ) -> Result<Self, minimq::Error<Stack::Error>> {
+ let mut mqtt = minimq::Minimq::new(broker, client_id, stack, clock.clone())?;
+
+ // Note(unwrap): The client was just created, so it's valid to set a keepalive interval
+ // now, since we're not yet connected to the broker.
+ mqtt.client()
+ .set_keepalive_interval(KEEPALIVE_INTERVAL_SECONDS)?;
+
+ let prefix = String::from(prefix);
+
+ // Configure a will so that we can indicate whether or not we are connected.
+ let mut connection_topic = prefix.clone();
+ connection_topic.push_str("/alive").unwrap();
+ mqtt.client().set_will(
+ &connection_topic,
+ b"0",
+ QoS::AtMostOnce,
+ Retain::Retained,
+ &[],
+ )?;
+
+ assert!(
+ prefix.len() + "/settings/".len() + Settings::metadata().max_length <= MAX_TOPIC_LENGTH
+ );
+
+ Ok(Self {
+ mqtt,
+ state: sm::StateMachine::new(sm::Context::new(clock)),
+ settings,
+ prefix,
+ listing_state: None,
+ properties_cache: None,
+ pending_response: None,
+ })
+ }
+
+ fn handle_listing(&mut self) {
+ let Some(iter) = &mut self.listing_state else {
+ return;
+ };
+
+ let Some(props) = &self.properties_cache else {
+ return
+ };
+
+ let reply_props = minimq::types::Properties::DataBlock(props);
+
+ while self.mqtt.client().can_publish(QoS::AtLeastOnce) {
+ // Note(unwrap): Publishing should not fail because `can_publish()` was checked before
+ // attempting this publish.
+ let response: Response<MAX_TOPIC_LENGTH> = iter
+ .next()
+ .map(|path| Response::custom(ResponseCode::Continue, &path))
+ .unwrap_or_else(Response::ok);
+
+ let props = [minimq::Property::UserProperty(
+ minimq::types::Utf8String("code"),
+ minimq::types::Utf8String(response.code.as_ref()),
+ )];
+
+ self.mqtt
+ .client()
+ .publish(
+ // Note(unwrap): We already guaranteed that the reply properties have a response
+ // topic.
+ Publication::new(response.msg.as_bytes())
+ .reply(&reply_props)
+ .properties(&props)
+ .qos(QoS::AtLeastOnce)
+ .finish()
+ .unwrap(),
+ )
+ .unwrap();
+
+ // If we're done with listing, bail out of the loop.
+ if response.code != ResponseCode::Continue {
+ self.listing_state.take();
+ break;
+ }
+ }
+ }
+
+ fn handle_republish(&mut self) {
+ let mut data = [0; MESSAGE_SIZE];
+
+ 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
+ };
+
+ // Note: The topic may be absent at runtime (`miniconf::Option` or deferred `Option`).
+ let len = match self.settings.get(&topic, &mut data) {
+ Err(crate::Error::PathAbsent) => continue,
+ Ok(len) => len,
+ e => e.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.
+ self.mqtt
+ .client()
+ .publish(
+ Publication::new(&data[..len])
+ .topic(&prefixed_topic)
+ .finish()
+ .unwrap(),
+ )
+ .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: AsRef<str>,
+ {
+ 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();
+
+ self.handle_pending_response()?;
+
+ // All states must handle MQTT traffic.
+ self.handle_mqtt_traffic(handler)
+ }
+
+ fn handle_pending_response(&mut self) -> Result<(), minimq::Error<Stack::Error>> {
+ // Try to publish any pending response.
+ if !self.mqtt.client().can_publish(QoS::AtLeastOnce) {
+ return Ok(());
+ }
+
+ let Some(response) = self.pending_response.take() else {
+ return Ok(());
+ };
+
+ let Some(props) = self.properties_cache.as_ref() else {
+ return Ok(());
+ };
+
+ let reply_props = minimq::types::Properties::DataBlock(props);
+
+ let props = [minimq::Property::UserProperty(
+ minimq::types::Utf8String("code"),
+ minimq::types::Utf8String(response.code.as_ref()),
+ )];
+
+ let Ok(response) = minimq::Publication::new(response.msg.as_bytes())
+ .reply(&reply_props)
+ .properties(&props)
+ .qos(QoS::AtLeastOnce)
+ .finish() else {
+ return Ok(());
+ };
+
+ self.mqtt.client().publish(response)?;
+
+ Ok(())
+ }
+
+ 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: AsRef<str>,
+ {
+ 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;
+ };
+
+ if self.pending_response.is_some() {
+ log::warn!("Discarding command due to pending response");
+ return;
+ }
+
+ let minimq::types::Properties::DataBlock(binary_props) = properties else {
+ // Received properties are always serialized.
+ unreachable!();
+ };
+
+ let have_response_topic = properties
+ .into_iter()
+ .any(|prop| matches!(prop, Ok(minimq::Property::ResponseTopic(_))));
+
+ let response: Response<32> = match command {
+ Command::List => {
+ if self.listing_state.is_none() {
+ if have_response_topic {
+ // We only reply if there is a response topic to publish the list to.
+ // Note(unwrap): The vector is guaranteed to be as large as the largest MQTT
+ // message size, so the properties (which are a portion of the message) will
+ // always fit into it.
+ self.properties_cache
+ .replace(Vec::from_slice(binary_props).unwrap());
+ self.listing_state.replace(Default::default());
+ } else {
+ log::info!("Discarding `List` without `ResponseTopic`");
+ }
+ // Response sent with listing.
+ return;
+ } else {
+ Response::error("`List` already in progress")
+ }
+ }
+
+ Command::Get { path } => {
+ let mut data = [0u8; MESSAGE_SIZE];
+ match self.settings.get(path, &mut data) {
+ Err(err) => err.into(),
+ Ok(len) => {
+ let mut topic = self.prefix.clone();
+
+ // Note(unwrap): We check that the string will fit during
+ // construction.
+ topic
+ .push_str("/settings/")
+ .and_then(|_| topic.push_str(path))
+ .unwrap();
+
+ // Note(unwrap): This construction cannot fail because there's always a
+ // valid topic.
+ let message = minimq::Publication::new(&data[..len])
+ .reply(properties)
+ // Override the response topic with the path.
+ .topic(&topic)
+ .qos(QoS::AtLeastOnce)
+ .finish()
+ .unwrap();
+
+ if client.publish(message).is_err() {
+ Response::error("Can't publish `Get` response")
+ } else {
+ Response::ok()
+ }
+ }
+ }
+ }
+ Command::Set { path, value } => {
+ let mut new_settings = self.settings.clone();
+ match new_settings.set(path, value) {
+ Err(err) => err.into(),
+ Ok(_) => {
+ updated = true;
+ handler(path, &mut self.settings, &new_settings).into()
+ }
+ }
+ }
+ };
+
+ if have_response_topic {
+ let props = [minimq::Property::UserProperty(
+ minimq::types::Utf8String("code"),
+ minimq::types::Utf8String(response.code.as_ref()),
+ )];
+
+ let Ok(response_pub) = minimq::Publication::new(response.msg.as_bytes())
+ .reply(properties)
+ .properties(&props)
+ .qos(QoS::AtLeastOnce)
+ .finish() else {
+ log::warn!("Failed to build response `Pub`");
+ return;
+ };
+
+ // If we cannot publish the response yet (possibly because we just published something
+ // that hasn't completed yet), cache the response for future transmission.
+ if client.publish(response_pub).is_err() {
+ // Note(unwrap): The vector is guaranteed to be as large as the largest MQTT
+ // message size, so the properties (which are a portion of the message) will
+ // always fit into it.
+ self.properties_cache
+ .replace(Vec::from_slice(binary_props).unwrap());
+ self.pending_response.replace(response);
+ }
+ }
+ }) {
+ 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(PartialEq)]
+enum ResponseCode {
+ Ok,
+ Continue,
+ Error,
+}
+
+impl AsRef<str> for ResponseCode {
+ fn as_ref(&self) -> &str {
+ match self {
+ ResponseCode::Ok => "Ok",
+ ResponseCode::Continue => "Continue",
+ ResponseCode::Error => "Error",
+ }
+ }
+}
+
+/// The payload of the MQTT response message to a settings update request.
+struct Response<const N: usize> {
+ code: ResponseCode,
+ msg: String<N>,
+}
+
+impl<const N: usize> Response<N> {
+ pub fn ok() -> Self {
+ Self {
+ msg: String::from("OK"),
+ code: ResponseCode::Ok,
+ }
+ }
+
+ /// Generate a custom response with any response code.
+ ///
+ /// # Args
+ /// * `code` - The code to provide in the response.
+ /// * `msg` - The message to provide in the response.
+ pub fn custom(code: ResponseCode, message: &str) -> Self {
+ // Truncate the provided message to ensure it fits within the heapless String.
+ Self {
+ code,
+ msg: String::from(&message[..N.min(message.len())]),
+ }
+ }
+
+ /// Generate an error response
+ ///
+ /// # Args
+ /// * `message` - A message to provide in the response. Will be truncated to fit.
+ pub fn error(message: &str) -> Self {
+ Self::custom(ResponseCode::Error, message)
+ }
+}
+
+impl<T, E: AsRef<str>, const N: usize> From<Result<T, E>> for Response<N> {
+ fn from(result: Result<T, E>) -> Self {
+ match result {
+ Ok(_) => Response::ok(),
+
+ Err(error) => {
+ let mut msg = String::new();
+ if msg.push_str(error.as_ref()).is_err() {
+ msg = String::from("Configuration Error");
+ }
+
+ Self {
+ code: ResponseCode::Error,
+ msg,
+ }
+ }
+ }
+ }
+}
+
+impl<const N: usize> From<crate::Error> for Response<N> {
+ fn from(err: crate::Error) -> Self {
+ let mut msg = String::new();
+ if write!(&mut msg, "{:?}", err).is_err() {
+ msg = String::from("Configuration Error");
+ }
+
+ Self {
+ code: ResponseCode::Error,
+ msg,
+ }
+ }
+}
+
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 +
use super::{Error, IterError, Metadata, Miniconf, Peekable};
+use core::ops::{Deref, DerefMut};
+
+/// An `Option` that exposes its value through their [`Miniconf`] implementation.
+///
+/// # Design
+///
+/// Miniconf supports optional values in two forms.
+///
+/// In both forms, the `Option` may be marked with `#[miniconf(defer)]`
+/// and be `None` at run-time. This makes the corresponding part of the namespace inaccessible
+/// at run-time. It will still be iterated over by [`Miniconf::iter_paths()`] but cannot be
+/// `get()` or `set()` using the [`Miniconf`] API.
+///
+/// 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.
+///
+/// The first form is the [`miniconf::Option`](Option) type which optionally exposes its
+/// interior `Miniconf` value as a sub-tree. An [`miniconf::Option`](Option) should usually be
+/// `#[miniconf(defer)]`.
+///
+/// Miniconf also allows for the normal usage of Rust [`core::option::Option`] types. In this case,
+/// the `Option` can be used to atomically access the content within. If marked with `#[miniconf(defer)]`
+/// and `None` at runtime, it is inaccessible through `Miniconf`. Otherwise, JSON `null` corresponds to
+/// `None` as usual.
+///
+/// # Construction
+///
+/// An `miniconf::Option` can be constructed using [`From<core::option::Option>`]/[`Into<miniconf::Option>`]
+/// and the contained value can be accessed through [`Deref`]/[`DerefMut`].
+#[derive(
+ Clone,
+ Copy,
+ Default,
+ PartialEq,
+ Eq,
+ Debug,
+ PartialOrd,
+ Ord,
+ Hash,
+ serde::Serialize,
+ serde::Deserialize,
+)]
+#[repr(transparent)]
+pub struct Option<T>(core::option::Option<T>);
+
+impl<T> Deref for Option<T> {
+ type Target = core::option::Option<T>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl<T> DerefMut for Option<T> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.0
+ }
+}
+
+impl<T> AsRef<core::option::Option<T>> for Option<T> {
+ fn as_ref(&self) -> &core::option::Option<T> {
+ self
+ }
+}
+
+impl<T> AsMut<core::option::Option<T>> for Option<T> {
+ fn as_mut(&mut self) -> &mut core::option::Option<T> {
+ self
+ }
+}
+
+impl<T> From<core::option::Option<T>> for Option<T> {
+ fn from(x: core::option::Option<T>) -> Self {
+ Self(x)
+ }
+}
+
+impl<T> From<Option<T>> for core::option::Option<T> {
+ fn from(x: Option<T>) -> Self {
+ x.0
+ }
+}
+
+impl<T: Miniconf> Miniconf for Option<T> {
+ fn set_path<'a, P: Peekable<Item = &'a str>>(
+ &mut self,
+ path_parts: &'a mut P,
+ value: &[u8],
+ ) -> Result<usize, Error> {
+ if let Some(inner) = self.0.as_mut() {
+ inner.set_path(path_parts, value)
+ } else {
+ Err(Error::PathAbsent)
+ }
+ }
+
+ fn get_path<'a, P: Peekable<Item = &'a str>>(
+ &self,
+ path_parts: &'a mut P,
+ value: &mut [u8],
+ ) -> Result<usize, Error> {
+ if let Some(inner) = self.0.as_ref() {
+ inner.get_path(path_parts, value)
+ } else {
+ Err(Error::PathAbsent)
+ }
+ }
+
+ fn metadata() -> Metadata {
+ T::metadata()
+ }
+
+ fn next_path<const TS: usize>(
+ state: &mut [usize],
+ path: &mut heapless::String<TS>,
+ ) -> Result<bool, IterError> {
+ T::next_path(state, path)
+ }
+}
+
+impl<T: crate::Serialize + crate::DeserializeOwned> Miniconf for core::option::Option<T> {
+ fn set_path<'a, P: Peekable<Item = &'a str>>(
+ &mut self,
+ path_parts: &mut P,
+ value: &[u8],
+ ) -> Result<usize, Error> {
+ if path_parts.peek().is_some() {
+ return Err(Error::PathTooLong);
+ }
+
+ if self.is_none() {
+ return Err(Error::PathAbsent);
+ }
+
+ let (value, len) = serde_json_core::from_slice(value)?;
+ *self = Some(value);
+ Ok(len)
+ }
+
+ fn get_path<'a, P: Peekable<Item = &'a str>>(
+ &self,
+ path_parts: &mut P,
+ value: &mut [u8],
+ ) -> Result<usize, Error> {
+ if path_parts.peek().is_some() {
+ return Err(Error::PathTooLong);
+ }
+
+ let data = self.as_ref().ok_or(Error::PathAbsent)?;
+ Ok(serde_json_core::to_slice(data, value)?)
+ }
+
+ fn metadata() -> Metadata {
+ Metadata {
+ count: 1,
+ ..Default::default()
+ }
+ }
+
+ fn next_path<const TS: usize>(
+ state: &mut [usize],
+ path: &mut heapless::String<TS>,
+ ) -> Result<bool, IterError> {
+ if *state.first().ok_or(IterError::PathDepth)? == 0 {
+ state[0] += 1;
+
+ // Remove trailing slash added by a deferring container (array or struct).
+ if path.ends_with('/') {
+ path.pop();
+ }
+ Ok(true)
+ } else {
+ Ok(false)
+ }
+ }
+}
+
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 +
pub use embedded_hal;
+///! Module for all hardware-specific setup of Stabilizer
+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 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>>,
+>;
+
+// 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 +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 +
///! Stabilizer hardware configuration
+///!
+///! This file contains all of the hardware-specific configuration of Stabilizer.
+use core::sync::atomic::{self, AtomicBool, Ordering};
+use core::{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, shared_adc::SharedAdc, timers,
+ DigitalInput0, DigitalInput1, EemDigitalInput0, EemDigitalInput1,
+ EemDigitalOutput0, EemDigitalOutput1, EthernetPhy, NetworkStack,
+ SystemTimer, Systick, 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 socket.
+ pub sockets: [smoltcp::iface::SocketStorage<'static>; NUM_SOCKETS + 1],
+ pub tcp_socket_storage: [TcpSocketStorage; NUM_TCP_SOCKETS],
+ pub udp_socket_storage: [UdpSocketStorage; NUM_UDP_SOCKETS],
+}
+
+#[derive(Copy, Clone)]
+pub struct UdpSocketStorage {
+ rx_storage: [u8; 1024],
+ tx_storage: [u8; 2048],
+ tx_metadata:
+ [smoltcp::storage::PacketMetadata<smoltcp::wire::IpEndpoint>; 10],
+ rx_metadata:
+ [smoltcp::storage::PacketMetadata<smoltcp::wire::IpEndpoint>; 10],
+}
+
+impl UdpSocketStorage {
+ const fn new() -> Self {
+ Self {
+ rx_storage: [0; 1024],
+ tx_storage: [0; 2048],
+ tx_metadata: [smoltcp::storage::PacketMetadata::<
+ smoltcp::wire::IpEndpoint,
+ >::EMPTY; 10],
+ rx_metadata: [smoltcp::storage::PacketMetadata::<
+ smoltcp::wire::IpEndpoint,
+ >::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 + 1],
+ tcp_socket_storage: [TcpSocketStorage::new(); NUM_TCP_SOCKETS],
+ udp_socket_storage: [UdpSocketStorage::new(); NUM_UDP_SOCKETS],
+ }
+ }
+}
+
+/// 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,
+}
+
+/// 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 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);
+
+ // 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::default();
+ ethernet_config
+ .hardware_addr
+ .replace(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);
+
+ 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());
+ }
+
+ 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 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,
+ };
+
+ // 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::Miniconf;
+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
+/// `{"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, Miniconf)]
+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 +374 +375 +376 +
//! 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 Size** (u8): the number of samples in each batch 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 livestream frame in bytes.
+// Ensure the resulting ethernet frame is within the MTU:
+// 1500 MTU - 40 IP6 header - 20 UDP header
+const FRAME_SIZE: usize = 1500 - 40 - 20 - HEADER_SIZE;
+
+// 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,
+}
+
+impl StreamFrame {
+ pub fn new(
+ buffer: Box<Frame, Uninit>,
+ format_id: u8,
+ batch_size: u8,
+ sequence_number: u32,
+ ) -> Self {
+ let mut buffer = buffer.init([MaybeUninit::uninit(); FRAME_SIZE]);
+
+ for (offset, byte) in MAGIC
+ .to_le_bytes()
+ .iter()
+ .chain(&[format_id, batch_size])
+ .chain(sequence_number.to_le_bytes().iter())
+ .enumerate()
+ {
+ buffer[offset].write(*byte);
+ }
+
+ Self {
+ buffer,
+ offset: HEADER_SIZE,
+ }
+ }
+
+ pub fn add_batch<F, const T: usize>(&mut self, mut f: F)
+ where
+ F: FnMut(&mut [MaybeUninit<u8>]),
+ {
+ f(&mut self.buffer[self.offset..self.offset + T]);
+
+ self.offset += T;
+ }
+
+ pub fn is_full<const T: usize>(&self) -> bool {
+ self.offset + T > self.buffer.len()
+ }
+
+ pub fn finish(&self) -> &[MaybeUninit<u8>] {
+ &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,
+ batch_size: u8,
+}
+
+impl FrameGenerator {
+ fn new(
+ queue: Producer<'static, StreamFrame, FRAME_QUEUE_SIZE>,
+ pool: &'static Pool<Frame>,
+ ) -> Self {
+ Self {
+ queue,
+ pool,
+ batch_size: 0,
+ 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.
+ /// * `batch_size` - The number of samples in each data batch. See
+ /// [crate::hardware::design_parameters::SAMPLE_BUFFER_SIZE]
+ #[doc(hidden)]
+ pub(crate) fn configure(&mut self, format: impl Into<u8>, batch_size: u8) {
+ self.format = format.into();
+ self.batch_size = batch_size;
+ }
+
+ /// Add a batch to the current stream frame.
+ ///
+ /// # Args
+ /// * `f` - A closure that will be provided the buffer to write batch data into. The buffer will
+ /// be the size of the `T` template argument.
+ pub fn add<F, const T: usize>(&mut self, f: F)
+ where
+ F: FnMut(&mut [MaybeUninit<u8>]),
+ {
+ 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,
+ self.batch_size,
+ sequence_number,
+ ));
+ } else {
+ return;
+ }
+ }
+
+ // Note(unwrap): We ensure the frame is present above.
+ let current_frame = self.current_frame.as_mut().unwrap();
+
+ current_frame.add_batch::<_, T>(f);
+
+ if current_frame.is_full::<T>() {
+ // 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(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,
+ buf.len() * core::mem::size_of::<MaybeUninit<u8>>(),
+ )
+ };
+ 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 +
///! 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 minimq::embedded_nal::IpAddr;
+use network_processor::NetworkProcessor;
+use telemetry::TelemetryClient;
+
+use core::fmt::Write;
+use heapless::String;
+use miniconf::Miniconf;
+use serde::Serialize;
+use smoltcp_nal::embedded_nal::SocketAddr;
+
+pub type NetworkReference =
+ smoltcp_nal::shared::NetworkStackProxy<'static, NetworkStack>;
+
+/// The default MQTT broker IP address if unspecified.
+pub const DEFAULT_MQTT_BROKER: [u8; 4] = [10, 34, 16, 10];
+
+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: Default + Miniconf + Clone, T: Serialize> {
+ pub miniconf: miniconf::MqttClient<S, NetworkReference, SystemTimer, 512>,
+ pub processor: NetworkProcessor,
+ stream: DataStream,
+ generator: Option<FrameGenerator>,
+ pub telemetry: TelemetryClient<T>,
+}
+
+impl<S, T> NetworkUsers<S, T>
+where
+ S: Default + Miniconf + 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 IP address 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: IpAddr,
+ ) -> 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 settings = miniconf::MqttClient::new(
+ stack_manager.acquire_stack(),
+ &get_client_id(app, "settings", mac),
+ &prefix,
+ broker,
+ clock,
+ S::default(),
+ )
+ .unwrap();
+
+ let telemetry = TelemetryClient::new(
+ stack_manager.acquire_stack(),
+ clock,
+ &get_client_id(app, "tlm", mac),
+ &prefix,
+ broker,
+ );
+
+ 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>,
+ batch_size: u8,
+ ) -> FrameGenerator {
+ let mut generator = self.generator.take().unwrap();
+ generator.configure(format, batch_size);
+ 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};
+use minimq::embedded_nal::IpAddr;
+
+/// The telemetry client for reporting telemetry data over MQTT.
+pub struct TelemetryClient<T: Serialize> {
+ mqtt: minimq::Minimq<NetworkReference, SystemTimer, 1024, 1>,
+ 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
+ /// * `stack` - A reference to the (shared) underlying network stack.
+ /// * `clock` - A `SystemTimer` implementing `Clock`.
+ /// * `client_id` - The MQTT client ID of the telemetry client.
+ /// * `prefix` - The device prefix to use for MQTT telemetry reporting.
+ /// * `broker` - The IP address of the MQTT broker to use.
+ ///
+ /// # Returns
+ /// A new telemetry client.
+ pub fn new(
+ stack: NetworkReference,
+ clock: SystemTimer,
+ client_id: &str,
+ prefix: &str,
+ broker: IpAddr,
+ ) -> Self {
+ let mqtt =
+ minimq::Minimq::new(broker, client_id, stack, clock).unwrap();
+
+ let mut telemetry_topic: String<128> = String::from(prefix);
+ telemetry_topic.push_str("/telemetry").unwrap();
+
+ Self {
+ mqtt,
+ telemetry_topic,
+ _telemetry: core::marker::PhantomData::default(),
+ }
+ }
+
+ /// 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> =
+ miniconf::serde_json_core::to_vec(telemetry).unwrap();
+ self.mqtt
+ .client()
+ .publish(
+ minimq::Publication::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),
+ _ => {}
+ }
+ }
+}
+
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.
+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];
pub use embedded_hal;
pub use stm32h7xx_hal as hal;
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.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.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 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.
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>; 6],
+ pub tcp_socket_storage: [TcpSocketStorage; 4],
+ pub udp_socket_storage: [UdpSocketStorage; 1],
+}
ip_addrs: [IpCidr; 1]
§sockets: [SocketStorage<'static>; 6]
§tcp_socket_storage: [TcpSocketStorage; 4]
§udp_socket_storage: [UdpSocketStorage; 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,
+}
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
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 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_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.
+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.
+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
+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
+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
+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 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
+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
+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
+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 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
+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
+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
+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 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
+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
+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
+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 const DEFAULT_MQTT_BROKER: [u8; 4];
The default MQTT broker IP address if unspecified.
+#[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>
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 NetworkUsers<S: Default + Miniconf + Clone, T: Serialize> {
+ pub miniconf: MqttClient<S, NetworkReference, SystemTimer, 512>,
+ pub processor: NetworkProcessor,
+ pub telemetry: TelemetryClient<T>,
+ /* private fields */
+}
A structure of Stabilizer’s default network users.
+miniconf: MqttClient<S, NetworkReference, SystemTimer, 512>
§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 IP address 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.
+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.
+Construct a new telemetry client.
+stack
- A reference to the (shared) underlying network stack.clock
- A SystemTimer
implementing Clock
.client_id
- The MQTT client ID of the telemetry client.prefix
- The device prefix to use for MQTT telemetry reporting.broker
- The IP address of the MQTT broker to use.A new telemetry client.
+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 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}
`}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.
+ +